feat(scheduling): rename 完成→年度达标, add sort by 客户日均/年度达标 to list
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -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 { motion } from 'motion/react';
|
||||||
import type { SchedulingSuggestion } from './types';
|
import type { SchedulingSuggestion } from './types';
|
||||||
import Blur from '../../components/Blur';
|
import Blur from '../../components/Blur';
|
||||||
@@ -12,7 +13,27 @@ function fmtRate(rate: number): string {
|
|||||||
return (rate * 100).toFixed(1) + '%';
|
return (rate * 100).toFixed(1) + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SortKey = 'default' | 'avgDaily' | 'completion';
|
||||||
|
type SortDir = 'asc' | 'desc';
|
||||||
|
|
||||||
export default function SuggestionList({ suggestions, onSelect }: Props) {
|
export default function SuggestionList({ suggestions, onSelect }: Props) {
|
||||||
|
const [sortKey, setSortKey] = useState<SortKey>('default');
|
||||||
|
const [sortDir, setSortDir] = useState<SortDir>('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) {
|
if (suggestions.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="py-16 text-center">
|
<div className="py-16 text-center">
|
||||||
@@ -23,8 +44,33 @@ export default function SuggestionList({ suggestions, onSelect }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Sort controls */}
|
||||||
|
<div className="px-4 py-2 border-b border-slate-50 flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleSort('avgDaily')}
|
||||||
|
className={`text-[10px] px-2 py-1 rounded-lg border flex items-center gap-1 cursor-pointer transition-colors ${
|
||||||
|
sortKey === 'avgDaily' ? 'border-blue-300 bg-blue-50 text-blue-700' : 'border-slate-200 text-slate-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
客户日均
|
||||||
|
{sortKey === 'avgDaily' && (sortDir === 'desc' ? <ArrowDown size={10} /> : <ArrowUp size={10} />)}
|
||||||
|
{sortKey !== 'avgDaily' && <ArrowUpDown size={10} />}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => toggleSort('completion')}
|
||||||
|
className={`text-[10px] px-2 py-1 rounded-lg border flex items-center gap-1 cursor-pointer transition-colors ${
|
||||||
|
sortKey === 'completion' ? 'border-blue-300 bg-blue-50 text-blue-700' : 'border-slate-200 text-slate-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
年度达标
|
||||||
|
{sortKey === 'completion' && (sortDir === 'desc' ? <ArrowDown size={10} /> : <ArrowUp size={10} />)}
|
||||||
|
{sortKey !== 'completion' && <ArrowUpDown size={10} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="divide-y divide-slate-50">
|
<div className="divide-y divide-slate-50">
|
||||||
{suggestions.map((s, idx) => {
|
{sorted.map((s, idx) => {
|
||||||
const isRescue = s.type === 'rescue_hopeless';
|
const isRescue = s.type === 'rescue_hopeless';
|
||||||
const v = s.currentVehicle;
|
const v = s.currentVehicle;
|
||||||
|
|
||||||
@@ -60,7 +106,7 @@ export default function SuggestionList({ suggestions, onSelect }: Props) {
|
|||||||
{v.manager && <span className="flex-shrink-0 text-slate-500">{v.manager}</span>}
|
{v.manager && <span className="flex-shrink-0 text-slate-500">{v.manager}</span>}
|
||||||
<span className="truncate max-w-[35%] flex-shrink"><Blur>{v.customer || '-'}</Blur></span>
|
<span className="truncate max-w-[35%] flex-shrink"><Blur>{v.customer || '-'}</Blur></span>
|
||||||
<span className="flex-shrink-0">日均 <span className="text-slate-600 font-medium">{Math.round(v.customerAvgDaily)}</span></span>
|
<span className="flex-shrink-0">日均 <span className="text-slate-600 font-medium">{Math.round(v.customerAvgDaily)}</span></span>
|
||||||
<span className="flex-shrink-0">完成 <span className={`font-medium ${v.completionRate >= 1 ? 'text-emerald-600' : v.completionRate >= 0.5 ? 'text-amber-600' : 'text-rose-500'}`}>{fmtRate(v.completionRate)}</span></span>
|
<span className="flex-shrink-0">年度达标 <span className={`font-medium ${v.completionRate >= 1 ? 'text-emerald-600' : v.completionRate >= 0.5 ? 'text-amber-600' : 'text-rose-500'}`}>{fmtRate(v.completionRate)}</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -73,5 +119,6 @@ export default function SuggestionList({ suggestions, onSelect }: Props) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user