"use client"; import { RefreshCw } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { getAdminWalletTransactions } from "@/api/admin-wallet"; import { AdminAgentIdentityCells, AdminAgentIdentityHeads } from "@/components/admin/admin-agent-columns"; import { AdminDateRangeField } from "@/components/admin/admin-date-range-field"; 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 { AdminPlayerIdentityCells, AdminPlayerIdentityHeads } from "@/components/admin/admin-player-identity-columns"; import { AdminStatusBadge } from "@/components/admin/admin-status-badge"; import { AdminTableExportButton } from "@/components/admin/admin-table-export-button"; import { AdminTableMoney, adminMoneyCellClassName } from "@/components/admin/admin-table-money"; import { PlayerLedgerSourceBadge } from "@/components/admin/player-funding-badges"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; 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 { useAsyncEffect } from "@/hooks/use-async-effect"; import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; import { useExportLabels } from "@/hooks/use-export-labels"; import { useTranslationRef } from "@/hooks/use-translation-ref"; import { formatAdminMinorUnits } from "@/lib/money"; import { cn } from "@/lib/utils"; import { WalletCellMonoId } from "@/modules/wallet/wallet-cell-mono-id"; import { WALLET_FILTER_ALL, WALLET_TXN_BIZ_OPTIONS, WALLET_TXN_STATUS_OPTIONS, emptyTxnFilters, txnFiltersFromSearchParams, walletAdminSelectDisplayedLabel, type TxnFilters, } from "@/modules/wallet/wallet-filter-utils"; import { walletStatusLabel, walletTxnBizTypeLabel } from "@/modules/wallet/wallet-labels"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminWalletTxnListData } from "@/types/api/admin-wallet"; function parsePlayerId(raw: string): number | undefined { if (raw.trim() === "") { return undefined; } const id = Number(raw); return !Number.isNaN(id) && id > 0 ? id : undefined; } export function WalletTxnsPanel(): React.ReactElement { const { t } = useTranslation(["wallet", "common"]); const { t: tSettlement } = useTranslation("settlementCenter"); const tRef = useTranslationRef(["wallet", "common"]); const exportLabels = useExportLabels("walletTransactions"); const formatTs = useAdminDateTimeFormatter(); const searchParams = useSearchParams(); const urlFilterKey = searchParams.toString(); const router = useRouter(); const pathname = usePathname(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [err, setErr] = useState(null); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(10); const [draft, setDraft] = useState(() => txnFiltersFromSearchParams(searchParams)); const [applied, setApplied] = useState(() => txnFiltersFromSearchParams(searchParams)); useEffect(() => { const next = txnFiltersFromSearchParams(searchParams); setDraft(next); setApplied(next); setPage(1); }, [urlFilterKey, searchParams]); const load = useCallback(async () => { setLoading(true); setErr(null); try { const d = await getAdminWalletTransactions({ page, per_page: perPage, abnormal: applied.abnormalOnly || undefined, player_id: parsePlayerId(applied.playerId), player_account: applied.playerAccount.trim() || undefined, txn_no: applied.txnNo.trim() || undefined, external_ref_no: applied.externalRefNo.trim() || undefined, created_from: applied.createdFrom.trim() || undefined, created_to: applied.createdTo.trim() || undefined, biz_type: applied.bizType.trim() || undefined, status: applied.abnormalOnly ? undefined : applied.statusCsv.trim() || undefined, }); setData(d); } catch (e) { setErr(e instanceof LotteryApiBizError ? e.message : tRef.current("loadFailed")); setData(null); } finally { setLoading(false); } }, [page, perPage, applied, tRef]); useAsyncEffect(() => { void load(); }, [load]); const runSearch = () => { const next = draft.abnormalOnly ? { ...draft, statusCsv: "" } : { ...draft }; setDraft(next); setApplied(next); setPage(1); }; const resetFilters = () => { setDraft(emptyTxnFilters); setApplied(emptyTxnFilters); setPage(1); router.replace(pathname); }; const showLedgerColumn = data?.items.some((row) => row.ledger_source === "credit_ledger") ?? false; const txnTableColSpan = showLedgerColumn ? 13 : 12; const hasDeepLink = applied.txnNo !== "" || applied.playerId !== "" || applied.externalRefNo !== ""; return (

{t("walletTransactions")}

setDraft((d) => ({ ...d, txnNo: e.target.value }))} />
setDraft((d) => ({ ...d, externalRefNo: e.target.value }))} />
setDraft((d) => ({ ...d, playerAccount: e.target.value }))} />
setDraft((d) => ({ ...d, playerId: e.target.value }))} />
setDraft((d) => ({ ...d, createdFrom: r.from, createdTo: r.to })) } />
{applied.abnormalOnly ? (

{t("txnAbnormalFilterActive")}

) : null} {hasDeepLink ? (

{t("txnDeepLinkActive")}

) : null} {err ?

{err}

: null} {(loading && !data) || data ? ( <>
{t("txnNo")} {t("externalRefNo")} {showLedgerColumn ? ( {t("ledgerChannel")} ) : null} {t("type")} {t("amount")} {t("direction")} {t("status")} {t("requestTime")} {t("finishedTime")} {loading && !data ? ( ) : !data || data.items.length === 0 ? ( ) : ( data.items.map((row) => ( {showLedgerColumn ? ( ) : null} {walletTxnBizTypeLabel(row.biz_type, row.ledger_source, t, tSettlement)} {row.amount_formatted ?? formatAdminMinorUnits(row.amount)} {row.direction === 1 ? t("in") : t("out")} {walletStatusLabel(row.status, t)} {formatTs(row.created_at)} {formatTs(row.updated_at)} )) )}
{data ? ( { setPerPage(next); setPage(1); }} onPageChange={setPage} /> ) : null} ) : null}
); }