diff --git a/src/modules/scheduling/SuggestionList.tsx b/src/modules/scheduling/SuggestionList.tsx index 620e943..caa9ee2 100644 --- a/src/modules/scheduling/SuggestionList.tsx +++ b/src/modules/scheduling/SuggestionList.tsx @@ -1,4 +1,5 @@ -import { ArrowRightLeft, ChevronRight } from 'lucide-react'; +import { useState, useMemo } from 'react'; +import { ArrowRightLeft, ChevronRight, ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'; import { motion } from 'motion/react'; import type { SchedulingSuggestion } from './types'; import Blur from '../../components/Blur'; @@ -12,7 +13,27 @@ function fmtRate(rate: number): string { return (rate * 100).toFixed(1) + '%'; } +type SortKey = 'default' | 'avgDaily' | 'completion'; +type SortDir = 'asc' | 'desc'; + export default function SuggestionList({ suggestions, onSelect }: Props) { + const [sortKey, setSortKey] = useState('default'); + const [sortDir, setSortDir] = useState('desc'); + + const toggleSort = (key: SortKey) => { + if (sortKey === key) { setSortDir(d => d === 'desc' ? 'asc' : 'desc'); } + else { setSortKey(key); setSortDir('desc'); } + }; + + const sorted = useMemo(() => { + if (sortKey === 'default') return suggestions; + return [...suggestions].sort((a, b) => { + const va = sortKey === 'avgDaily' ? a.currentVehicle.customerAvgDaily : a.currentVehicle.completionRate; + const vb = sortKey === 'avgDaily' ? b.currentVehicle.customerAvgDaily : b.currentVehicle.completionRate; + return sortDir === 'desc' ? vb - va : va - vb; + }); + }, [suggestions, sortKey, sortDir]); + if (suggestions.length === 0) { return (
@@ -23,55 +44,81 @@ export default function SuggestionList({ suggestions, onSelect }: Props) { } return ( -
- {suggestions.map((s, idx) => { - const isRescue = s.type === 'rescue_hopeless'; - const v = s.currentVehicle; +
+ {/* Sort controls */} +
+ + +
- return ( - onSelect(s)} - > - {/* Color bar */} -
+
+ {sorted.map((s, idx) => { + const isRescue = s.type === 'rescue_hopeless'; + const v = s.currentVehicle; - {/* Info */} -
-
- - {v.plateNumber} - - - {isRescue ? '里程低·换走' : '里程高·换下'} - - {v.vehicleType} - · - {v.region} + return ( + onSelect(s)} + > + {/* Color bar */} +
+ + {/* Info */} +
+
+ + {v.plateNumber} + + + {isRescue ? '里程低·换走' : '里程高·换下'} + + {v.vehicleType} + · + {v.region} +
+
+ {v.department && {v.department}} + {v.manager && {v.manager}} + {v.customer || '-'} + 日均 {Math.round(v.customerAvgDaily)} + 年度达标 = 1 ? 'text-emerald-600' : v.completionRate >= 0.5 ? 'text-amber-600' : 'text-rose-500'}`}>{fmtRate(v.completionRate)} +
-
- {v.department && {v.department}} - {v.manager && {v.manager}} - {v.customer || '-'} - 日均 {Math.round(v.customerAvgDaily)} - 完成 = 1 ? 'text-emerald-600' : v.completionRate >= 0.5 ? 'text-amber-600' : 'text-rose-500'}`}>{fmtRate(v.completionRate)} -
-
- {/* Right */} -
- 干预 - -
-
- ); - })} + {/* Right */} +
+ 干预 + +
+ + ); + })} +
); }