import { useCallback, useEffect, useState, useMemo } from "react"; import { zhCN } from "date-fns/locale"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { Skeleton } from "@/components/ui/skeleton"; import { CalendarDays, Download } from "lucide-react"; import { fetchFlowStats, type FlowStatsResponse, type FlowDetailItem, type FlowDailyPoint, } from "../api"; import { getRecentFlowRange, getWeeklyFlowRange, formatLocalDate, selectFlowDetails, type FlowSelection, } from "../model"; import { Failure, useMobile } from "./shared"; import { DataGrid, type Column } from "./DataGrid"; import { exportToXlsx } from "./export"; import { Chart } from "./Chart"; export function FlowStatistics({ subject }: { subject: string | null }) { const [range, setRange] = useState(getRecentFlowRange); const [calendarOpen, setCalendarOpen] = useState(false); const [draft, setDraft] = useState<{ from: Date | undefined; to?: Date }>({ from: new Date(`${range.start}T00:00:00`), to: new Date(`${range.end}T00:00:00`), }); const [data, setData] = useState(null); const [error, setError] = useState(""); const [retry, setRetry] = useState(0); const [selection, setSelection] = useState(null); const mobile = useMobile(); useEffect(() => { let active = true; setData(null); setError(""); fetchFlowStats({ ...range, subject }) .then((v) => { if (active) setData(v); }) .catch((e) => { if (active) setError(String(e.message || e)); }); return () => { active = false; }; }, [range, subject, retry]); const details = data && selection ? selectFlowDetails(data, selection) : []; const columns: Column[] = [ { key: "plateNumber", label: "车牌", private: true }, { key: "typeLabel", label: "类型" }, { key: "date", label: "业务日期" }, { key: "eventTime", label: "运维签章时间", width: 180 }, { key: "submitTime", label: "建单时间", width: 180 }, { key: "department", label: "部门" }, { key: "manager", label: "负责人", private: true }, { key: "customerName", label: "客户", private: true, width: 180 }, ]; const exportRows = (rows: FlowDetailItem[], filename: string) => exportToXlsx(rows, columns, filename, "交还车明细"); const option = useMemo( () => ({ color: ["var(--blue)", "var(--orange)", "var(--indigo)"], textStyle: { color: "var(--label-secondary)" }, tooltip: { trigger: "axis" }, legend: { data: ["交车", "还车", "替换交车"], textStyle: { color: "var(--label-secondary)" }, }, grid: { left: 40, right: 20, bottom: 40, top: 40 }, xAxis: { type: "category", data: data?.daily.map((d) => d.date.slice(5)) || [], axisLabel: { color: "var(--label-secondary)" }, axisLine: { lineStyle: { color: "var(--separator)" } }, }, yAxis: { type: "value", minInterval: 1, axisLabel: { color: "var(--label-secondary)" }, splitLine: { lineStyle: { color: "var(--separator)" } }, }, series: ["delivered", "returned", "replaced"].map((key, i) => ({ name: ["交车", "还车", "替换交车"][i], type: "line", data: data?.daily.map((d) => d[key as "delivered"]) || [], })), }), [data], ); return ( 交还车统计(车次) 按运维签章时间统计;交车含替换交车,合计 = 交车 + 还车。
{ setCalendarOpen(v); if (v) setDraft({ from: new Date(`${range.start}T00:00:00`), to: new Date(`${range.end}T00:00:00`), }); }} > setDraft(v || { from: undefined })} numberOfMonths={mobile ? 1 : 2} disabled={{ after: new Date() }} />
{error ? ( setRetry((n) => n + 1)} /> ) : !data ? ( ) : ( <>
{( [ ["total", "合计"], ["delivered", "交车"], ["returned", "还车"], ["replaced", "替换交车"], ] as const ).map(([key, title]) => ( {title} {key === "total" ? ( data.totals[key] ) : ( )} ))}
({ key, label: ["交车", "还车", "替换交车"][i], action: (row: FlowDailyPoint) => setSelection({ date: row.date, type: key }), }), ), ]} /> )}
!v && setSelection(null)}> 交还车明细 {details.length} 车次 ·{" "} {selection && ("date" in selection ? selection.date : `${selection.start} — ${selection.end}`)}
); }