145 lines
8.1 KiB
TypeScript
145 lines
8.1 KiB
TypeScript
import React from 'react';
|
|
import { Download, X } from 'lucide-react';
|
|
import { AnimatePresence, motion } from 'motion/react';
|
|
import type { FlowDetailItem, FlowType } from '../api';
|
|
|
|
export const FLOW_META: Record<FlowType, { label: string; tone: string; chip: string }> = {
|
|
delivered: { label: '交车', tone: 'text-blue-600 bg-blue-50 border-blue-100', chip: 'bg-blue-50 text-blue-700 border-blue-100' },
|
|
returned: { label: '还车', tone: 'text-orange-600 bg-orange-50 border-orange-100', chip: 'bg-orange-50 text-orange-700 border-orange-100' },
|
|
replaced: { label: '替换', tone: 'text-violet-600 bg-violet-50 border-violet-100', chip: 'bg-violet-50 text-violet-700 border-violet-100' },
|
|
};
|
|
|
|
interface FlowDetailModalProps {
|
|
selectedFlow: { date: string; type: FlowType } | null;
|
|
selectedFlowDetails: FlowDetailItem[];
|
|
setSelectedFlow: React.Dispatch<React.SetStateAction<{ date: string; type: FlowType } | null>>;
|
|
exportFlowDetails: (rows?: FlowDetailItem[], title?: string) => void;
|
|
}
|
|
export function FlowDetailModal({
|
|
selectedFlow,
|
|
selectedFlowDetails,
|
|
setSelectedFlow,
|
|
exportFlowDetails,
|
|
}: FlowDetailModalProps) {
|
|
return (
|
|
<>
|
|
{/* Flow Detail Modal */}
|
|
<AnimatePresence>
|
|
{selectedFlow && (
|
|
<div className="fixed inset-0 z-[1000] flex items-end justify-center bg-slate-950/45 p-0 backdrop-blur-sm sm:items-center sm:p-4">
|
|
<motion.div
|
|
data-testid="asset-flow-detail-modal"
|
|
initial={{ opacity: 0, y: 28, scale: 0.98 }}
|
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
exit={{ opacity: 0, y: 20, scale: 0.98 }}
|
|
className="flex max-h-[88vh] w-full flex-col overflow-hidden rounded-t-3xl bg-white shadow-2xl sm:max-w-5xl sm:rounded-3xl"
|
|
>
|
|
<div className="border-b border-slate-100 bg-slate-950 px-4 py-4 text-white sm:px-5">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<div className="text-[11px] font-black uppercase tracking-wide text-slate-400">提交时间口径</div>
|
|
<h3 className="mt-1 text-lg font-black">
|
|
{selectedFlow.date} · {FLOW_META[selectedFlow.type].label}明细
|
|
</h3>
|
|
<div className="mt-1 text-[12px] font-bold text-slate-300">
|
|
共 {selectedFlowDetails.length} 条,包含车牌、流转时间、提交时间、部门、负责人和客户
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => setSelectedFlow(null)}
|
|
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white/10 text-white transition hover:bg-white/20"
|
|
>
|
|
<X size={18} />
|
|
</button>
|
|
</div>
|
|
<div className="mt-3 flex flex-wrap gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => exportFlowDetails(selectedFlowDetails, `${selectedFlow.date}-${FLOW_META[selectedFlow.type].label}明细`)}
|
|
disabled={selectedFlowDetails.length === 0}
|
|
className="inline-flex h-8 items-center gap-1 rounded-xl bg-white px-3 text-[11px] font-black text-slate-900 transition hover:bg-blue-50 disabled:cursor-not-allowed disabled:opacity-40"
|
|
>
|
|
<Download size={13} />
|
|
导出当前明细
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="overflow-auto bg-slate-50 p-3 sm:p-4">
|
|
<div className="hidden overflow-hidden rounded-2xl border border-slate-100 bg-white lg:block">
|
|
<table className="w-full table-fixed text-left">
|
|
<thead className="bg-slate-50 text-[11px] font-black text-slate-400">
|
|
<tr>
|
|
<th className="w-28 px-3 py-3">车牌</th>
|
|
<th className="w-40 px-3 py-3">{FLOW_META[selectedFlow.type].label}时间</th>
|
|
<th className="w-40 px-3 py-3">提交时间</th>
|
|
<th className="w-36 px-3 py-3">部门</th>
|
|
<th className="w-28 px-3 py-3">业务负责人</th>
|
|
<th className="px-3 py-3">客户</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100 text-[12px] font-bold text-slate-700">
|
|
{selectedFlowDetails.map((item) => (
|
|
<tr key={item.id} className="hover:bg-blue-50/40">
|
|
<td className="px-3 py-3 font-black text-slate-950">{item.plateNumber}</td>
|
|
<td className="px-3 py-3 text-slate-500">{item.eventTime || '-'}</td>
|
|
<td className="px-3 py-3 text-blue-600">{item.submitTime || '-'}</td>
|
|
<td className="px-3 py-3">{item.department || '-'}</td>
|
|
<td className="px-3 py-3">{item.manager || '-'}</td>
|
|
<td className="px-3 py-3">{item.customerName || '-'}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div className="space-y-2 lg:hidden">
|
|
{selectedFlowDetails.map((item) => (
|
|
<div key={item.id} className="rounded-2xl border border-slate-100 bg-white p-3 shadow-sm">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<div className="text-base font-black text-slate-950">{item.plateNumber}</div>
|
|
<div className={`mt-1 inline-flex rounded-full border px-2 py-0.5 text-[10px] font-black ${FLOW_META[item.type].chip}`}>
|
|
{item.typeLabel}
|
|
</div>
|
|
</div>
|
|
<div className="text-right text-[10px] font-bold text-slate-400">
|
|
<div>提交</div>
|
|
<div className="mt-0.5 text-[11px] text-blue-600">{item.submitTime?.slice(5, 16) || '-'}</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-3 grid grid-cols-2 gap-2 text-[11px]">
|
|
<div className="rounded-xl bg-slate-50 p-2">
|
|
<div className="font-black text-slate-400">{item.typeLabel}时间</div>
|
|
<div className="mt-1 font-bold text-slate-700">{item.eventTime || '-'}</div>
|
|
</div>
|
|
<div className="rounded-xl bg-slate-50 p-2">
|
|
<div className="font-black text-slate-400">负责人</div>
|
|
<div className="mt-1 font-bold text-slate-700">{item.manager || '-'}</div>
|
|
</div>
|
|
<div className="rounded-xl bg-slate-50 p-2">
|
|
<div className="font-black text-slate-400">部门</div>
|
|
<div className="mt-1 font-bold text-slate-700">{item.department || '-'}</div>
|
|
</div>
|
|
<div className="rounded-xl bg-slate-50 p-2">
|
|
<div className="font-black text-slate-400">客户</div>
|
|
<div className="mt-1 line-clamp-2 font-bold text-slate-700">{item.customerName || '-'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{selectedFlowDetails.length === 0 && (
|
|
<div className="rounded-2xl bg-white px-4 py-10 text-center text-sm font-bold text-slate-400">当前日期暂无明细</div>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
}
|