Ledger section with sub-tabs: overview (money held/owed, payouts by status, and a reconciliation warning card), payouts + transactions lists, per-seller balances list, and a per-seller detail showing recorded vs derived balance + discrepancy, bank accounts and payout history. Enabled the Ledger nav item. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
2.7 KiB
TypeScript
103 lines
2.7 KiB
TypeScript
import { useState } from "react";
|
|
import {
|
|
useLedgerTransactions,
|
|
type LedgerTransactionRow,
|
|
} from "~/requestHandler/use-admin-hooks";
|
|
import {
|
|
AdminTable,
|
|
AdminPagination,
|
|
AdminToolbar,
|
|
AdminSearch,
|
|
AdminSelect,
|
|
StatusBadge,
|
|
faToman,
|
|
faDate,
|
|
useDebouncedValue,
|
|
type Column,
|
|
} from "~/components/admin/DataTable";
|
|
|
|
const PAGE_SIZE = 25;
|
|
const TYPE: Record<string, { label: string; tone: "green" | "blue" }> = {
|
|
deposit: { label: "واریز", tone: "green" },
|
|
purchase: { label: "خرید", tone: "blue" },
|
|
};
|
|
|
|
export default function LedgerTransactions() {
|
|
const [page, setPage] = useState(1);
|
|
const [search, setSearch] = useState("");
|
|
const [type, setType] = useState("");
|
|
const debouncedSearch = useDebouncedValue(search);
|
|
|
|
const { data, isLoading } = useLedgerTransactions({
|
|
page,
|
|
page_size: PAGE_SIZE,
|
|
search: debouncedSearch,
|
|
transaction_type: type,
|
|
});
|
|
|
|
const reset = (fn: () => void) => {
|
|
fn();
|
|
setPage(1);
|
|
};
|
|
|
|
const columns: Column<LedgerTransactionRow>[] = [
|
|
{
|
|
header: "نوع",
|
|
render: (t) => {
|
|
const ty = TYPE[t.transactionType] || { label: t.transactionType, tone: "blue" as const };
|
|
return <StatusBadge label={ty.label} tone={ty.tone} />;
|
|
},
|
|
},
|
|
{
|
|
header: "مبلغ",
|
|
render: (t) => <span className="tabular-nums whitespace-nowrap">{faToman(t.amount)}</span>,
|
|
},
|
|
{
|
|
header: "کاربر",
|
|
render: (t) => (
|
|
<span className="text-GRAY text-[11px] tabular-nums" dir="ltr">
|
|
{t.userPhone || "—"}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
header: "سفارش",
|
|
render: (t) => (
|
|
<span className="text-GRAY tabular-nums">{t.order ? t.order.slice(0, 8) : "—"}</span>
|
|
),
|
|
},
|
|
{
|
|
header: "تاریخ",
|
|
render: (t) => <span className="text-GRAY whitespace-nowrap">{faDate(t.createdAt)}</span>,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<AdminToolbar>
|
|
<AdminSearch
|
|
value={search}
|
|
onChange={(v) => reset(() => setSearch(v))}
|
|
placeholder="جستجوی تلفن کاربر…"
|
|
/>
|
|
<AdminSelect
|
|
value={type}
|
|
onChange={(v) => reset(() => setType(v))}
|
|
options={[
|
|
{ value: "", label: "همه انواع" },
|
|
{ value: "deposit", label: "واریز" },
|
|
{ value: "purchase", label: "خرید" },
|
|
]}
|
|
/>
|
|
</AdminToolbar>
|
|
<AdminTable
|
|
columns={columns}
|
|
rows={data?.results || []}
|
|
isLoading={isLoading}
|
|
getRowKey={(t) => t.id}
|
|
/>
|
|
<AdminPagination page={page} pageSize={PAGE_SIZE} count={data?.count || 0} onPageChange={setPage} />
|
|
</div>
|
|
);
|
|
}
|