import { Clipboard, Filter, UserRound } from "lucide-react"; import { type ReactNode, useMemo } from "react"; import { toast } from "sonner"; import type { LedgerEntry, LedgerQuery, SortOrder } from "../../api/types"; import { DataTable, type DataColumn } from "../../components/data-table"; import { Badge, Button } from "../../components/primitives"; import { formatDateTime, formatNumber, formatSignedCredits } from "../../lib/format"; import { ledgerActionLabel, ledgerActionTone, ledgerEntryTypeLabel, ledgerUsageClass, ledgerUsageLabel, } from "./ledger-format"; export function LedgerTable({ entries, caption, emptyTitle, showUser = false, sort, order, onSortChange, onReferenceFilter, footer, }: { entries: LedgerEntry[]; caption: string; emptyTitle: string; showUser?: boolean; sort: NonNullable; order: SortOrder; onSortChange: (sort: NonNullable, order: SortOrder) => void; onReferenceFilter: (referenceId: string) => void; footer?: ReactNode; }) { const columns = useMemo[]>(() => { const result: DataColumn[] = [ { accessorKey: "createdAt", header: "时间", cell: ({ getValue }) => ( {formatDateTime(String(getValue()))} ), }, ]; if (showUser) { result.push({ accessorKey: "userId", header: "用户", cell: ({ getValue }) => { const userId = String(getValue()); return ( {userId} ); }, }); } result.push( { accessorKey: "type", header: "账务动作", cell: ({ getValue }) => ( {ledgerActionLabel(String(getValue()))} ), }, { accessorKey: "entryType", header: "业务来源", cell: ({ getValue }) => ( {ledgerEntryTypeLabel(String(getValue() ?? ""))} ), }, { accessorKey: "usageType", header: "消费能力", cell: ({ getValue }) => { const usageType = getValue() ? String(getValue()) : undefined; return usageType ? ( {ledgerUsageLabel(usageType)} ) : ( ); }, }, { accessorKey: "amount", header: "变动", cell: ({ getValue }) => { const value = Number(getValue()); return ( = 0 ? "text-success" : "text-danger"}`} > {formatSignedCredits(value)} ); }, }, { accessorKey: "balanceAfter", header: "结余", cell: ({ getValue }) => ( {formatNumber(Number(getValue()))} ), }, ); return result; }, [showUser]); return ( onSortChange(nextSort as NonNullable, nextOrder) } renderExpandedRow={(entry) => ( )} getRowId={(entry) => entry.entryId} footer={footer} /> ); } function LedgerDetails({ entry, onReferenceFilter, }: { entry: LedgerEntry; onReferenceFilter: (referenceId: string) => void; }) { const details = entry.details; return (
{entry.referenceId ? (
) : ( "—" )}
{entry.reasonCode || "—"} {details?.kind === "manualGrant" ? ( <> {details.reason || "—"} {details.operatorName || "—"} ) : null} {details?.kind === "storeKit" ? ( <> {details.productId || "—"} {details.environment || "—"} {formatDateTime(details.purchasedAt)} ) : null} {details?.kind === "referral" ? ( <> {referralRoleLabel(details.role)} ) : null} {details?.kind === "usage" ? ( ) : null}
); } function DetailItem({ label, children }: { label: string; children: ReactNode }) { return (
{label}
{children}
); } function Identifier({ value }: { value?: string }) { if (!value) return ; return ( {value} ); } async function copyIdentifier(value: string) { try { await navigator.clipboard.writeText(value); toast.success("ID 已复制"); } catch { toast.error("无法复制,请手动选择 ID"); } } function referralRoleLabel(role?: string): string { if (!role) return "—"; const labels: Record = { inviter: "邀请人", invitee: "受邀人", INVITER: "邀请人", INVITEE: "受邀人", }; return labels[role] ?? role; }