Enhance ledger operations and referral lifecycle
Add traceable ledger filtering and permanent referral codes so operators can investigate credit activity without weakening immutable accounting guarantees.
This commit is contained in:
@@ -5,8 +5,14 @@ import {
|
||||
type LegacyColumnDef,
|
||||
useLegacyTable,
|
||||
} from "@tanstack/react-table/legacy";
|
||||
import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-react";
|
||||
import { type ReactNode } from "react";
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
ArrowUpDown,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
import { Fragment, type ReactNode, useState } from "react";
|
||||
import type { SortOrder } from "../api/types";
|
||||
import { cn } from "../lib/utils";
|
||||
import { EmptyState } from "./primitives";
|
||||
@@ -23,6 +29,9 @@ export function DataTable<T extends RowData>({
|
||||
sort,
|
||||
sortableColumns,
|
||||
onSortChange,
|
||||
renderExpandedRow,
|
||||
getRowId,
|
||||
expandLabel = "详情",
|
||||
}: {
|
||||
data: T[];
|
||||
columns: DataColumn<T>[];
|
||||
@@ -33,7 +42,11 @@ export function DataTable<T extends RowData>({
|
||||
sort?: { key: string; order: SortOrder };
|
||||
sortableColumns?: Record<string, string>;
|
||||
onSortChange?: (key: string, order: SortOrder) => void;
|
||||
renderExpandedRow?: (row: T) => ReactNode;
|
||||
getRowId?: (row: T) => string;
|
||||
expandLabel?: string;
|
||||
}) {
|
||||
const [expandedRows, setExpandedRows] = useState<Set<string>>(() => new Set());
|
||||
const table = useLegacyTable({
|
||||
data,
|
||||
columns,
|
||||
@@ -94,25 +107,72 @@ export function DataTable<T extends RowData>({
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
{renderExpandedRow ? (
|
||||
<th
|
||||
className="sticky top-0 z-[1] border-b border-border bg-surface/95 px-5 py-3.5 text-left text-[11px] font-bold uppercase tracking-[0.08em] text-muted backdrop-blur"
|
||||
scope="col"
|
||||
>
|
||||
{expandLabel}
|
||||
</th>
|
||||
) : null}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
className="group transition-colors hover:bg-surface-muted/70"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className="border-b border-border/70 px-5 py-4 align-middle text-foreground last:text-right group-last:border-b-0"
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
{table.getRowModel().rows.map((row) => {
|
||||
const expansionKey = getRowId?.(row.original) ?? row.id;
|
||||
const expanded = expandedRows.has(expansionKey);
|
||||
const panelId = `expanded-row-${expansionKey.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
||||
return (
|
||||
<Fragment key={row.id}>
|
||||
<tr className="group transition-colors hover:bg-surface-muted/70">
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className="border-b border-border/70 px-5 py-4 align-middle text-foreground last:text-right group-last:border-b-0"
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
{renderExpandedRow ? (
|
||||
<td className="border-b border-border/70 px-5 py-4 text-right align-middle">
|
||||
<button
|
||||
className="inline-flex min-h-8 items-center gap-1.5 rounded-lg px-2 text-xs font-semibold text-primary transition hover:bg-primary-soft focus-visible:ring-4 focus-visible:ring-primary/15"
|
||||
type="button"
|
||||
aria-expanded={expanded}
|
||||
aria-controls={panelId}
|
||||
onClick={() =>
|
||||
setExpandedRows((current) => {
|
||||
const next = new Set(current);
|
||||
if (next.has(expansionKey)) next.delete(expansionKey);
|
||||
else next.add(expansionKey);
|
||||
return next;
|
||||
})
|
||||
}
|
||||
>
|
||||
{expanded ? (
|
||||
<ChevronDown className="size-3.5" aria-hidden />
|
||||
) : (
|
||||
<ChevronRight className="size-3.5" aria-hidden />
|
||||
)}
|
||||
{expanded ? "收起" : "展开"}
|
||||
</button>
|
||||
</td>
|
||||
) : null}
|
||||
</tr>
|
||||
{renderExpandedRow && expanded ? (
|
||||
<tr id={panelId}>
|
||||
<td
|
||||
className="border-b border-border bg-surface-muted/45 px-5 py-4"
|
||||
colSpan={row.getVisibleCells().length + 1}
|
||||
>
|
||||
{renderExpandedRow(row.original)}
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,12 @@ export function TableToolbar({
|
||||
children,
|
||||
active,
|
||||
onClear,
|
||||
clearLabel = "清除筛选",
|
||||
}: {
|
||||
children: ReactNode;
|
||||
active: boolean;
|
||||
onClear: () => void;
|
||||
clearLabel?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
@@ -24,7 +26,7 @@ export function TableToolbar({
|
||||
disabled={!active}
|
||||
onClick={onClear}
|
||||
>
|
||||
清除筛选
|
||||
{clearLabel}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user