import { useEffect, useState } from 'react'; import { ChevronLeft, ChevronRight, ReceiptText, Route, Search } from 'lucide-react'; import Blur from '../../components/Blur'; import { ErrorState, LoadingState } from '../../components/ui/surface'; import { fetchEtcBills, fetchEtcRecords } from './api'; import type { EtcBillResponse, EtcTollRecordResponse } from './types'; import type { EtcDetailView, EtcDrillContext } from './etc-drill-context'; import { DateRangeInputs } from './AnalysisDateFilters'; function fmtMoney(value: number): string { return `¥${value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } function costTypeLabel(value: number): string { if (value === 1) return '客户承担'; if (value === 2) return '我方承担'; return '承担方待确认'; } function paymentLabel(receivable: number, paid: number): string { if (receivable > 0 && paid >= receivable) return '已收'; if (paid > 0) return '部分收款'; return '未收'; } export default function ETCDetails({ context, onContextChange, }: { context: EtcDrillContext; onContextChange: (context: EtcDrillContext, mode: 'push' | 'replace') => void; }) { const { view, startDate, endDate, search, page } = context; const [draftSearch, setDraftSearch] = useState(search); const [records, setRecords] = useState(null); const [bills, setBills] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => setDraftSearch(search), [search]); useEffect(() => { let cancelled = false; setLoading(true); setError(null); if (view === 'records') setRecords(null); else setBills(null); const query = { page, limit: 20, startDate: startDate || undefined, endDate: endDate || undefined, search: search || undefined, }; const request = view === 'records' ? fetchEtcRecords(query) : fetchEtcBills(query); request .then(result => { if (cancelled) return; if (page > result.totalPages) { onContextChange({ ...context, page: result.totalPages }, 'replace'); return; } if (view === 'records') setRecords(result as EtcTollRecordResponse); else setBills(result as EtcBillResponse); }) .catch(loadError => { if (!cancelled) setError(loadError instanceof Error ? loadError.message : String(loadError)); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [context, endDate, onContextChange, page, search, startDate, view]); const activeData = view === 'records' ? records : bills; const totalPages = activeData?.totalPages || 1; const applySearch = () => { onContextChange({ ...context, search: draftSearch.trim(), page: 1 }, 'replace'); }; const changeView = (next: EtcDetailView) => { onContextChange( { ...context, view: next, page: 1 }, next === view ? 'replace' : 'push', ); }; const updateDate = (field: 'start' | 'end', value: string) => { let nextStart = field === 'start' ? value : startDate; let nextEnd = field === 'end' ? value : endDate; if (nextStart && nextEnd && nextStart > nextEnd) [nextStart, nextEnd] = [nextEnd, nextStart]; onContextChange({ ...context, startDate: nextStart, endDate: nextEnd, page: 1 }, 'replace'); }; return (

ETC 明细下钻

{view === 'records' ? '通行流水与费用承担' : '客户账期、应收与收款'}

{activeData ? (
{activeData.total} 条结果 {view === 'records' && records ? ( 通行费 {fmtMoney(records.summary.tollAmount)} · 服务费 {fmtMoney(records.summary.serviceFee)} · 合计 {fmtMoney(records.summary.totalAmount)} ) : view === 'bills' && bills ? ( 应收 {fmtMoney(bills.summary.receivableAmount)} · 已收 {fmtMoney(bills.summary.paidAmount)} ) : null}
) : null} {error ? ( ) : loading && !activeData ? ( ) : view === 'records' && records ? ( records.items.length === 0 ? (
暂无匹配的通行记录
首次同步后将按通行时间倒序展示
) : ( <>
{records.items.map(item => ( ))}
通行时间车牌 / 客户入口 → 出口承担费用
{item.transTime}
{item.plate}
{item.customerName || '未关联客户'}
{item.entryStation || '-'} → {item.exitStation || '-'} {costTypeLabel(item.costType)}
{fmtMoney(item.totalAmount)}
通行 {fmtMoney(item.tollAmount)} · 服务 {fmtMoney(item.serviceFee)}
{records.items.map(item => (
{item.plate}
{item.transTime}
{fmtMoney(item.totalAmount)}
{item.entryStation || '-'} → {item.exitStation || '-'}
{item.customerName || '未关联客户'}{costTypeLabel(item.costType)}
))}
) ) : view === 'bills' && bills ? ( bills.items.length === 0 ? (
暂无匹配的结算账单
生成账单后将按账期结束日倒序展示
) : ( <>
{bills.items.map(item => ( ))}
账单 / 客户账期通行收款状态应收 / 已收
{item.billCode}
{item.customerName || '未指定客户'}
{item.periodStart} → {item.periodEnd} {item.tollCount} 次 · {fmtMoney(item.tollAmount)} {paymentLabel(item.receivableAmount, item.paidAmount)}
{fmtMoney(item.receivableAmount)}
已收 {fmtMoney(item.paidAmount)}
{bills.items.map(item => (
{item.billCode}
{item.customerName || '未指定客户'}
{fmtMoney(item.receivableAmount)}
已收 {fmtMoney(item.paidAmount)}
{item.periodStart} → {item.periodEnd}
{item.tollCount} 次通行{paymentLabel(item.receivableAmount, item.paidAmount)}
))}
) ) : null} {activeData ? ( ) : null}
); }