Vitron-Front/app/routes/admin.ledger.transactions.tsx
Arda Samadi cedeef4746 feat(admin): ledger UI — overview/reconciliation, payouts, transactions, per-seller (slice 2a)
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>
2026-07-25 12:28:11 +03:30

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>
);
}