Different assessment targets have different end dates. Previously all
candidates used the current vehicle's daysLeft, causing wrong predictions.
Now each inventory vehicle computes its own daysLeft from its assessment
target's current_year_assessment_end_date. predictedAfterSwap uses the
candidate's own daysLeft instead of the current vehicle's.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Disable BYPASS_AUTH (was true, now false) — backend enforces JWT auth
- Scheduling suggestions filtered by department/manager permissions:
- full: see all suggestions
- department: see only own department's vehicles
- personal: see only own managed vehicles
- Candidate vehicles (inventory) remain fully visible to all
- Summary recalculated after permission filtering
- Consistent with mileage module permission model
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously: no jumpToken → direct access allowed (临时放行)
Now: no jumpToken → show "请从业务系统跳转访问" unauthorized page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only userId 1105261382487539712 and 1116631120763437056 can see the
scheduling tab. Other users see only assets + mileage modules.
- Add userId to frontend AuthState.user type
- App.tsx conditionally includes scheduling module based on user ID
- Backend already returns userId in auth exchange response
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove type badge, reason section — too verbose
- Two clean white cards connected by arrow (swap diagram)
- Result section: predicted mileage, target, conclusion badge
- Tighter spacing, no redundant labels
- Professional tone, no childish wording
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For rescue_hopeless (换走) scenario, completely rethought candidate logic:
Before: showed biggest-gap candidates (0 mileage) → pointless, customer can't
drive them to target
After: prioritize candidates where customer's remaining driving can push them
over the target line (canQualifyAfterSwap), sorted by smallest gap first
Example: customer drives 178km/day × 57 days = ~1万km remaining.
- 粤AGR6869 (缺口 1990km) → 换后 3.8万, 可达标 ✅ (shown first)
- 浙FF58720 (缺口 6万km) → 换后 1万, 远不达标 (no longer shown first)
Also updated reason text to explain the math:
"该客户剩余57天还能跑约1万km,足以帮缺口小的车冲线"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For rescue_hopeless (换走) scenario, filter out inventory candidates
where totalMileage/yearTarget >= 80%. These are already near target
and swapping them in adds no value.
Instead, prioritize candidates with biggest mileage gaps — they benefit
most from accumulating any mileage, even at a low-mileage customer.
Before: showed 粤AGR6869 (93% done, 缺口 1990) as "可达标" — pointless
After: shows 浙FF58720 (0% done, 缺口 60000) — genuinely needs mileage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Full-page skeleton on initial load: card placeholders + list row placeholders
- List skeleton on refresh: 6 rows with pulse animation
- Skeleton blocks match actual layout (color bar, plate, badges, info line)
- Uses Tailwind animate-pulse for smooth loading effect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Customer name in list items: truncate with max-w-[40%]
- Daily km and completion rate: flex-shrink-0 to stay on same line
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add department and manager fields to backend types and suggestions API
- Add department/manager to advanced filter panel
- Refine card colors: orange (换下) / blue (换走) / dark slate (全部)
- Selected card uses solid bg color, inactive uses gradient
- Batch pills use dark slate, confirm button uses dark slate
- Background changed to #F0F4F8 for subtle cool tone
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Filter icon in list header with active count badge
- Expandable filter panel: plate search, region select, vehicle type select, customer select
- FilterSelect component with search for long option lists
- Active filter tags shown as removable pills below header
- Temp/confirmed filter pattern (edit → confirm/cancel)
- Result count displayed when filters active
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Modal header: unified dark slate-800 with directional icon (↓ rescue, ↑ release)
- Modal click-outside to close
- Candidate metrics: table-style with bg-slate-50 + dividers, more scannable
- Send button: dark slate instead of blue (avoids color overload)
- Reason section: warm amber accent
- Consistent font sizing and spacing throughout
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Cards filter suggestions by type (已达标/无望达标/全部)
- Toggle: click active card again to reset to all
- Default: white bg + gray border; active: colored bg + ring
- Batch selector: dark pills instead of blue
- Refresh button moved into list header
- Reset type filter when switching batch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add currentYearMileage to SchedulingVehicleInfo (backend + frontend)
- Compute completionRate as currentYearMileage/yearTarget (year-based)
instead of using overall completion_rate from DB
- Display "本年已跑" instead of "累计" in detail modal
- Fix reason text to show year completion rate
Before: 累计 4.6万, 考核 3.0万, 完成率 12.1% (mismatched periods)
After: 本年已跑 8.3万, 考核 3.0万, 完成率 275% (consistent year-based)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Summary cards: white bg + color border, remove icons, more compact
- SuggestionList: replace badge stacking with compact 2-line layout,
use color bars for priority, fix completion rate format (0.16 → 16.4%)
- SuggestionDetail: bottom-sheet on mobile, compact inline metrics
instead of grid cards, reduce vertical space per candidate
- Follows ui-ux-pro-max Data-Dense Dashboard guidelines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Filter enriched vehicles to only include rent_status = '租赁' or '自营'.
Inventory candidates already filtered by truck_rent_status = 0 (在库).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
411 of 451 assessment vehicles had is_deleted=1 in tab_truck, causing type
classification to fall back to "其他" and miss all inventory matches. Fix:
- Remove is_deleted=0 filter from truck type query (assessment vehicles need type info regardless)
- Add inferTypeFromTargetName() fallback deriving type from target name when truck record is missing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- classifyVehicleType now parses dic_type.dic_name (e.g. "4.5吨冷链车") instead of raw model code
- Remove overly strict completionRate >= 0.8 filter for hopeless candidates
- Use vehicle's yearTarget as fallback when inventory has no assessment target
- Filter out suggestions with no candidates (not actionable)
- estimatedGain counts rescue_hopeless suggestions as potential gains
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>