"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Eye } from "lucide-react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { getSettlementAdjustments, getSettlementPayments, type SettlementAdjustmentRow, type SettlementPaymentRow, } from "@/api/admin-agent-settlement"; import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer"; import { AdminTableLoadingRow } from "@/components/admin/admin-loading-state"; import { AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state"; import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; import { AdminTableMoney, adminMoneyCellClassName } from "@/components/admin/admin-table-money"; import { formatDashboardMoneyMinor } from "@/modules/dashboard/use-dashboard-analytics"; import { LotteryApiBizError } from "@/types/api/errors"; import { settlementAdjustmentTypeLabel } from "@/modules/settlement/settlement-status-label"; import { cn } from "@/lib/utils"; const OPERATION_TYPES = ["all", "payment", "adjustment", "reversal", "bad_debt"] as const; type OperationTypeFilter = (typeof OPERATION_TYPES)[number]; type OperationFilters = { billId: string; keyword: string; operationType: OperationTypeFilter; }; type SettlementOperationRow = { key: string; kind: Exclude; recordId: number; billId: number; amount: number; summary: string; detail: string | null; sortAt: string; }; function defaultFilters(): OperationFilters { return { billId: "", keyword: "", operationType: "all", }; } function paymentRow(row: SettlementPaymentRow): SettlementOperationRow { const payer = row.payer_type === "platform" ? "platform" : `${row.payer_type}#${row.payer_id}`; const payee = row.payee_type === "platform" ? "platform" : `${row.payee_type}#${row.payee_id}`; return { key: `payment:${row.id}`, kind: "payment", recordId: row.id, billId: row.settlement_bill_id, amount: row.amount, summary: row.method?.trim() || "—", detail: `${payer} → ${payee}${row.proof ? ` · ${row.proof}` : ""}${row.remark ? ` · ${row.remark}` : ""}`, sortAt: row.confirmed_at ?? row.created_at ?? "", }; } function adjustmentRow(row: SettlementAdjustmentRow): SettlementOperationRow { const kind = row.adjustment_type === "bad_debt" ? "bad_debt" : row.adjustment_type === "reversal" ? "reversal" : "adjustment"; return { key: `adjustment:${row.id}`, kind, recordId: row.id, billId: row.original_bill_id ?? 0, amount: row.amount, summary: row.reason?.trim() || "—", detail: null, sortAt: row.created_at ?? "", }; } function matchesFilters(row: SettlementOperationRow, filters: OperationFilters): boolean { if (filters.operationType !== "all" && row.kind !== filters.operationType) { return false; } const billId = Number(filters.billId.trim()); if (filters.billId.trim() !== "" && (!Number.isInteger(billId) || billId <= 0 || row.billId !== billId)) { return false; } const keyword = filters.keyword.trim().toLowerCase(); if (keyword === "") { return true; } const haystack = [ String(row.billId), String(row.recordId), row.summary, row.detail ?? "", row.kind, ] .join(" ") .toLowerCase(); return haystack.includes(keyword); } type SettlementOperationsPanelProps = { adminSiteId: number; settlementPeriodId: number; currencyCode: string; refreshKey?: number; onOpenBill: (billId: number) => void; }; export function SettlementOperationsPanel({ adminSiteId, settlementPeriodId, currencyCode, refreshKey = 0, onOpenBill, }: SettlementOperationsPanelProps): React.ReactElement { const { t } = useTranslation(["settlementCenter", "agents", "common"]); const formatTs = useAdminDateTimeFormatter(); const initialFilters = useMemo(() => defaultFilters(), []); const [draft, setDraft] = useState(initialFilters); const [applied, setApplied] = useState(initialFilters); const [allRows, setAllRows] = useState([]); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(20); const load = useCallback(async () => { setLoading(true); try { const [paymentData, adjustmentData] = await Promise.all([ getSettlementPayments({ admin_site_id: adminSiteId, settlement_period_id: settlementPeriodId, }), getSettlementAdjustments({ admin_site_id: adminSiteId, settlement_period_id: settlementPeriodId, }), ]); const merged = [ ...(paymentData.items ?? []).map(paymentRow), ...(adjustmentData.items ?? []).map(adjustmentRow), ].sort((a, b) => b.sortAt.localeCompare(a.sortAt) || b.key.localeCompare(a.key)); setAllRows(merged); } catch (err: unknown) { setAllRows([]); toast.error( err instanceof LotteryApiBizError ? err.message : t("settlementCenter:operations.loadFailed", { defaultValue: "收付与调账记录加载失败" }), ); } finally { setLoading(false); } }, [adminSiteId, settlementPeriodId, t]); useEffect(() => { void load(); }, [load, refreshKey]); useEffect(() => { setDraft(initialFilters); setApplied(initialFilters); setPage(1); }, [initialFilters, settlementPeriodId]); const filteredRows = useMemo( () => allRows.filter((row) => matchesFilters(row, applied)), [allRows, applied], ); const total = filteredRows.length; const lastPage = Math.max(1, Math.ceil(total / perPage)); const pageRows = filteredRows.slice((page - 1) * perPage, page * perPage); const operationTypeLabel = (value: OperationTypeFilter): string => { if (value === "all") { return t("operations.filterAllTypes", { defaultValue: "全部类型" }); } if (value === "payment") { return t("operations.typePayment", { defaultValue: "登记收付" }); } return settlementAdjustmentTypeLabel(value, t); }; const kindBadgeClass = (kind: SettlementOperationRow["kind"]): string => { if (kind === "payment") { return "border-emerald-200/60 bg-emerald-50 text-emerald-700 dark:border-emerald-800/60 dark:bg-emerald-950/30 dark:text-emerald-400"; } if (kind === "bad_debt") { return "border-rose-200/60 bg-rose-50 text-rose-700 dark:border-rose-800/60 dark:bg-rose-950/30 dark:text-rose-400"; } if (kind === "reversal") { return "border-amber-200/60 bg-amber-50 text-amber-700 dark:border-amber-800/60 dark:bg-amber-950/30 dark:text-amber-400"; } return "border-blue-200/60 bg-blue-50 text-blue-700 dark:border-blue-800/60 dark:bg-blue-950/30 dark:text-blue-400"; }; const runSearch = (): void => { setPage(1); setApplied({ ...draft }); }; const resetFilters = (): void => { setDraft(initialFilters); setApplied(initialFilters); setPage(1); }; const colSpan = 7; return (
setDraft((d) => ({ ...d, billId: e.target.value }))} onKeyDown={(e) => { if (e.key === "Enter") { runSearch(); } }} className="bg-background/50 transition-colors focus:bg-background" />
setDraft((d) => ({ ...d, keyword: e.target.value }))} onKeyDown={(e) => { if (e.key === "Enter") { runSearch(); } }} className="bg-background/50 transition-colors focus:bg-background" />
{t("columns.time", { defaultValue: "时间" })} {t("operations.operationType", { defaultValue: "操作类型" })} {t("columns.billId", { defaultValue: "账单 ID" })} {t("columns.amount", { defaultValue: "金额" })} {t("columns.summary", { defaultValue: "摘要" })} {t("columns.detail", { defaultValue: "说明" })} {t("common:table.actions", { defaultValue: "操作" })} {loading ? : null} {!loading && pageRows.length === 0 ? ( ) : null} {!loading ? pageRows.map((row) => ( {formatTs(row.sortAt)} {operationTypeLabel(row.kind)} {row.billId > 0 ? `#${row.billId}` : "—"} {formatDashboardMoneyMinor(row.amount, currencyCode)} {row.summary} {row.detail ?? "—"} e.stopPropagation()} > {row.billId > 0 ? ( onOpenBill(row.billId), }, ]} /> ) : ( "—" )} )) : null}
{!loading && total > 0 ? ( { setPerPage(value); setPage(1); }} /> ) : null}
); }