import { useEffect, useMemo, useRef, useState } from 'react'; import { Check, Eye, Search, X } from 'lucide-react'; import { adminApi, type ReportImportReviewBatch, type ReportImportReviewItem } from '@/api/adminApi'; import { Button, DateRangeInput, Input, Modal, Pagination, Select, Table, Tag, Textarea, type DateRangeValue, type TableColumn, } from '@/components/ui'; import { formatDateTime } from '@/utils/dateTime'; const statusLabels: Record = { pending_review: '待审核', partially_reviewed: '部分已审核', partially_approved: '部分通过', approved: '已通过', rejected: '已驳回', failed: '导入失败', }; const itemStatusLabels: Record = { pending_review: '待审核', approved: '已通过', rejected: '已驳回', invalid: '数据异常', }; function itemName(item: ReportImportReviewItem) { if (item.reportType === 'signature') return String(item.payload.name ?? '-'); return String(item.payload.url ?? '-'); } function tone(status: string): 'neutral' | 'info' | 'success' | 'warning' | 'danger' { if (status === 'approved') return 'success'; if (['rejected', 'failed', 'invalid'].includes(status)) return 'danger'; if (['partially_reviewed', 'partially_approved'].includes(status)) return 'warning'; return status === 'pending_review' ? 'info' : 'neutral'; } export function ReportImportAuditPanel({ reportType, initialBatchId, }: { reportType: 'signature' | 'drainage'; initialBatchId?: string; }) { const initialBatchHandled = useRef(false); const [data, setData] = useState<{ items: ReportImportReviewBatch[]; total: number; page: number; pageSize: number }>( { items: [], total: 0, page: 1, pageSize: 20 }, ); const [page, setPage] = useState(1); const pageSize = 20; const [keyword, setKeyword] = useState(''); const [status, setStatus] = useState('all'); const [submittedDateRange, setSubmittedDateRange] = useState({}); const [detail, setDetail] = useState(); const [selected, setSelected] = useState>(new Set()); const [rejectOpen, setRejectOpen] = useState(false); const [reason, setReason] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); function load(targetPage = page) { adminApi .listReportImportReviewBatches({ reportType, status: status === 'all' ? undefined : status, keyword: keyword.trim() || undefined, startAt: submittedDateRange.start, endAt: submittedDateRange.end, page: targetPage, pageSize, }) .then((result) => { setData(result); setError(''); if (!initialBatchHandled.current && initialBatchId) { setDetail(result.items.find((item) => item.id === initialBatchId)); initialBatchHandled.current = true; } else if (detail) setDetail(result.items.find((item) => item.id === detail.id)); }) .catch((failure: Error) => setError(failure.message || '导入审核批次加载失败')); } useEffect(() => { load(page); }, [page, pageSize, reportType, status, submittedDateRange.end, submittedDateRange.start]); const pendingItems = detail?.items.filter((item) => item.status === 'pending_review') ?? []; const allPendingSelected = pendingItems.length > 0 && pendingItems.every((item) => selected.has(item.id)); async function review(decision: 'approve' | 'reject') { if (!detail) return; const itemIds = selected.size ? [...selected] : pendingItems.map((item) => item.id); if (!itemIds.length) return; setBusy(true); setError(''); try { await adminApi.reviewReportImportItems(detail.id, { decision, itemIds, reason: reason.trim() || undefined }); window.dispatchEvent(new Event('cmpp-audit-count-refresh')); setSelected(new Set()); setReason(''); setRejectOpen(false); const refreshed = await adminApi.listReportImportReviewBatches({ reportType, status: status === 'all' ? undefined : status, keyword: keyword.trim() || undefined, startAt: submittedDateRange.start, endAt: submittedDateRange.end, page, pageSize, }); setData(refreshed); setDetail(refreshed.items.find((item) => item.id === detail.id)); } catch (failure) { setError(failure instanceof Error ? failure.message : '导入批次审核失败'); } finally { setBusy(false); } } const columns = useMemo>>( () => [ { key: 'file', title: '导入批次/文件', render: (record) => (
{record.id}
{record.fileName}
), }, { key: 'tenant', title: '企业/应用', render: (record) => (
{record.tenant?.name ?? record.tenantId}
{record.application?.name ?? '未指定应用'}
), }, { key: 'summary', title: '明细统计', render: (record) => { const approved = record.items.filter((item) => item.status === 'approved').length; const pending = record.items.filter((item) => item.status === 'pending_review').length; const abnormal = record.items.filter((item) => item.status === 'invalid').length; return `共${record.items.length} · 待审${pending} · 通过${approved} · 异常${abnormal}`; }, }, { key: 'status', title: '状态', render: (record) => {statusLabels[record.status] ?? record.status}, }, { key: 'createdAt', title: '导入时间', render: (record) => formatDateTime(record.createdAt) }, { key: 'actions', title: '操作', align: 'right', render: (record) => ( ), }, ], [], ); const itemColumns: Array> = [ { key: 'select', title: '', width: '48px', render: (item) => ( setSelected((current) => { const next = new Set(current); if (next.has(item.id)) next.delete(item.id); else next.add(item.id); return next; }) } type="checkbox" /> ), }, { key: 'row', title: '文件行', width: '80px', render: (item) => `第${item.rowNumber}行` }, { key: 'name', title: reportType === 'signature' ? '签名' : '引流url或号码', render: (item) => {itemName(item)}, }, { key: 'operation', title: '变更类型', width: '100px', render: (item) => (item.operation === 'create' ? '新增' : item.operation === 'update' ? '修改' : '无效'), }, { key: 'status', title: '状态', width: '110px', render: (item) => {itemStatusLabels[item.status] ?? item.status}, }, { key: 'reason', title: '说明', render: (item) => item.errorMessage || item.reviewReason || '-' }, ]; return (
{error ?

{error}

: null}
setKeyword(event.target.value)} placeholder="搜索导入批次或文件" value={keyword} /> setSelected(allPendingSelected ? new Set() : new Set(pendingItems.map((item) => item.id))) } type="checkbox" /> 选择全部待审核项 ) : null} {rejectOpen ? ( } onClose={() => setRejectOpen(false)} open title="驳回导入资料" >