import { useEffect, useMemo, useState } from "react"; import { ArrowUpDown, ChevronLeft, ChevronRight, Download, FileClock, RotateCcw } from "lucide-react"; import { useNavigate, useSearchParams } from "react-router"; import { toast } from "sonner"; import { Button } from "~/components/ui/button"; import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; import { FilterSelect, MultiSelectFilter, OperationsPageHeader } from "./OperationsUi"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table"; import { exportRowsToExcel } from "~/lib/exportExcel"; import { type MonitoringReport, type ReportStatus } from "./reportData"; import { useReportStore } from "./reportStore"; type Filters = { bank: string[]; name: string[]; version: string[]; modelId: string[]; month: string[]; type: string[]; status: string[]; }; type SortKey = "bank" | "modelId" | "month" | "generatedAt"; function emptyFilters(): Filters { return { bank: [], name: [], version: [], modelId: [], month: [], type: [], status: [] }; } function unique(values: string[]): string[] { return [...new Set(values)].sort((left, right) => right.localeCompare(left, "zh-CN")); } function parseValues(params: URLSearchParams, key: string): string[] { return [...new Set(params.getAll(key).flatMap((value) => value.split(",")).filter(Boolean))]; } function statusClass(status: ReportStatus): string { if (status === "已发送业务团队") return "bg-success-soft text-success-strong"; if (status === "编辑中") return "bg-brand-soft text-primary"; return "bg-warning-soft text-warning"; } function exportReports(rows: MonitoringReport[]) { const header = ["银行", "模型名称", "版本", "模型ID", "月份", "报告类型", "生成时间", "输出日期", "状态"]; return exportRowsToExcel({ fileName: "模型历史报告汇总", sheetName: "历史报告汇总", headers: header, rows: rows.map((row) => [row.bank, row.modelName, row.version, row.modelId, row.monitorMonth, row.type, row.generatedAt, row.outputDate, row.status]), }); } function matches(values: string[], value: string): boolean { return !values.length || values.includes(value); } function SortableHead({ label, sortKey, currentKey, onSort }: { label: string; sortKey: SortKey; currentKey: SortKey; onSort: (key: SortKey) => void }) { return onSort(sortKey)}>{label}; } export default function ReportSummaryPage() { const navigate = useNavigate(); const reports = useReportStore((state) => state.reports); const [searchParams, setSearchParams] = useSearchParams(); const [filters, setFilters] = useState(() => ({ bank: parseValues(searchParams, "bank"), name: parseValues(searchParams, "name"), version: parseValues(searchParams, "version"), modelId: parseValues(searchParams, "modelId"), month: parseValues(searchParams, "month"), type: parseValues(searchParams, "type"), status: parseValues(searchParams, "status"), })); const [sort, setSort] = useState<{ key: SortKey; direction: "asc" | "desc" }>({ key: "month", direction: "desc" }); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); useEffect(() => { const params = new URLSearchParams(); (Object.keys(filters) as Array).forEach((key) => filters[key].forEach((value) => params.append(key, value))); setSearchParams(params, { replace: true }); }, [filters, setSearchParams]); const filteredRows = useMemo(() => reports.filter((report) => ( matches(filters.bank, report.bank) && matches(filters.name, report.modelName) && matches(filters.version, report.version) && matches(filters.modelId, report.modelId) && matches(filters.month, report.monitorMonth) && matches(filters.type, report.type) && matches(filters.status, report.status) )), [filters, reports]); const rows = useMemo(() => [...filteredRows].sort((left, right) => { const pairs: Record = { bank: [left.bank, right.bank], modelId: [left.modelId, right.modelId], month: [left.monitorMonth, right.monitorMonth], generatedAt: [left.generatedAt, right.generatedAt], }; const [a, b] = pairs[sort.key]; const result = a.localeCompare(b, "zh-CN"); return sort.direction === "asc" ? result : -result; }), [filteredRows, sort]); useEffect(() => setPage(1), [filters, pageSize, sort]); const pageCount = Math.max(1, Math.ceil(rows.length / pageSize)); const safePage = Math.min(page, pageCount); const pageRows = rows.slice((safePage - 1) * pageSize, safePage * pageSize); const update = (key: K, value: string[]) => setFilters((current) => ({ ...current, [key]: value })); const changeSort = (key: SortKey) => setSort((current) => current.key === key ? { key, direction: current.direction === "asc" ? "desc" : "asc" } : { key, direction: "asc" }); return ( void exportReports(rows)}>导出 Excel} /> 全部报告共 {rows.length} 份 · 当前第 {safePage} / {pageCount} 页 setFilters(emptyFilters())}>重置筛选 void exportReports(rows)}>导出 Excel item.bank))} onChange={(value) => update("bank", value)} /> item.modelName))} onChange={(value) => update("name", value)} /> item.version))} onChange={(value) => update("version", value)} /> item.modelId))} onChange={(value) => update("modelId", value)} /> item.monitorMonth))} onChange={(value) => update("month", value)} /> update("type", value)} /> update("status", value)} /> 模型名称版本报告类型输出日期状态操作 {pageRows.length ? pageRows.map((report) => {report.bank}{report.modelName}{report.version}{report.modelId}{report.monitorMonth}{report.type}{report.generatedAt}{report.outputDate}{report.status}{report.unreadDays >= 5 && report.status !== "已发送业务团队" && 超 {report.unreadDays} 日}{report.synced && 已同步} navigate(`/operations/reports?report=${report.reportId}`)}>打开 toast.info("PDF 排版与下载将在报告服务接口接入后启用")}>导出 PDF) : 当前筛选条件下没有报告} 每页 setPageSize(Number(value))} />条 setPage((value) => Math.max(1, value - 1))}>{safePage} / {pageCount}= pageCount} onClick={() => setPage((value) => Math.min(pageCount, value + 1))}> ); }