"use client"; import { Check, Eye, HandCoins, X } from "lucide-react"; import { useCallback, useState } from "react"; import { useExportLabels } from "@/hooks/use-export-labels"; import { useTranslation } from "react-i18next"; import { useAsyncEffect } from "@/hooks/use-async-effect"; import { useTranslationRef } from "@/hooks/use-translation-ref"; import { toast } from "sonner"; import { getAdminSettlementBatches, postAdminApproveSettlementBatch, postAdminPayoutSettlementBatch, postAdminRejectSettlementBatch, } from "@/api/admin-settlement"; import { AdminAgentFilter } from "@/components/admin/admin-agent-filter"; import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer"; import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu"; import { AdminStatusBadge } from "@/components/admin/admin-status-badge"; import { AdminTableExportButton } from "@/components/admin/admin-table-export-button"; import { ModuleScaffold } from "@/components/admin/module-scaffold"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; 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 { Textarea } from "@/components/ui/textarea"; import { AdminLoadingState, AdminLoadingInline, AdminTableLoadingRow } from "@/components/admin/admin-loading-state"; import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog"; import { adminHasAnyPermission } from "@/lib/admin-permissions"; import { formatAdminMinorUnits } from "@/lib/money"; import { cn } from "@/lib/utils"; import { PRD_PAYOUT_MANAGE, PRD_PAYOUT_REVIEW } from "@/lib/admin-prd"; import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminSettlementBatchListData, AdminSettlementBatchRow } from "@/types/api/admin-settlement"; const STATUS_ALL = "__all__"; const STATUS_OPTIONS: { value: string; label: string }[] = [ { value: STATUS_ALL, label: "statusOptions.all" }, { value: "running", label: "statusOptions.running" }, { value: "pending_review", label: "statusOptions.pending_review" }, { value: "approved", label: "statusOptions.approved" }, { value: "rejected", label: "statusOptions.rejected" }, { value: "paid", label: "statusOptions.paid" }, { value: "completed", label: "statusOptions.completed" }, { value: "failed", label: "statusOptions.failed" }, ]; type SettlementAction = "approve" | "reject" | "payout"; type PendingAction = { row: AdminSettlementBatchRow; action: SettlementAction; }; function settlementStatusText(value: string, t: (key: string) => string): string { const option = STATUS_OPTIONS.find((item) => item.value === value); return option ? t(option.label) : value; } function settlementReviewStatusText(value: string | null, t: (key: string) => string): string { if (!value) return "—"; const key = `reviewStatusOptions.${value}`; const translated = t(key); return translated === key ? value : translated; } export function SettlementBatchesConsole() { const { t } = useTranslation(["settlement", "common"]); const tRef = useTranslationRef(["settlement", "common"]); const exportLabels = useExportLabels("settlementBatches"); const profile = useAdminProfile(); useAdminCurrencyCatalog(); const canReviewSettlement = adminHasAnyPermission(profile?.permissions, [PRD_PAYOUT_REVIEW]); const canManagePayout = adminHasAnyPermission(profile?.permissions, [PRD_PAYOUT_MANAGE]); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [draftDrawNo, setDraftDrawNo] = useState(""); const [appliedDrawNo, setAppliedDrawNo] = useState(""); const [draftStatus, setDraftStatus] = useState(STATUS_ALL); const [appliedStatus, setAppliedStatus] = useState(STATUS_ALL); const [agentNodeId, setAgentNodeId] = useState(undefined); const [appliedAgentNodeId, setAppliedAgentNodeId] = useState(undefined); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(10); const [actingId, setActingId] = useState(null); const [pendingAction, setPendingAction] = useState(null); const [reviewRemark, setReviewRemark] = useState(""); const load = useCallback(async () => { setLoading(true); setError(null); try { const d = await getAdminSettlementBatches({ page, per_page: perPage, draw_no: appliedDrawNo.trim() || undefined, status: appliedStatus === STATUS_ALL || appliedStatus.trim() === "" ? undefined : appliedStatus.trim(), agent_node_id: appliedAgentNodeId, }); setData(d); } catch (e) { setError(e instanceof LotteryApiBizError ? e.message : tRef.current("loadFailed")); setData(null); } finally { setLoading(false); } }, [page, perPage, appliedDrawNo, appliedStatus, appliedAgentNodeId]); useAsyncEffect(() => { void load(); }, [page, perPage, appliedDrawNo, appliedStatus, appliedAgentNodeId]); const applyFilters = () => { setAppliedDrawNo(draftDrawNo); setAppliedStatus(draftStatus); setAppliedAgentNodeId(agentNodeId); setPage(1); }; async function runBatchAction(batchId: number, label: string, action: () => Promise): Promise { setActingId(batchId); try { await action(); toast.success(t("actionSuccess", { name: label })); setPendingAction(null); setReviewRemark(""); await load(); } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : t("actionFailed", { name: label })); } finally { setActingId(null); } } const actionLabel = (action: SettlementAction): string => { if (action === "approve") { return t("approve"); } if (action === "reject") { return t("reject"); } return t("runPayout"); }; const confirmPendingAction = (): void => { if (!pendingAction) { return; } const { row, action } = pendingAction; const remark = reviewRemark.trim() || undefined; if (action === "approve") { void runBatchAction(row.id, actionLabel(action), () => postAdminApproveSettlementBatch(row.id, remark)); return; } if (action === "reject") { void runBatchAction(row.id, actionLabel(action), () => postAdminRejectSettlementBatch(row.id, remark)); return; } void runBatchAction(row.id, actionLabel(action), () => postAdminPayoutSettlementBatch(row.id)); }; const openActionDialog = (row: AdminSettlementBatchRow, action: SettlementAction): void => { setReviewRemark(""); setPendingAction({ row, action }); }; return (
{t("batchList")}
setDraftDrawNo(e.target.value)} placeholder={t("placeholderDrawNo")} className="w-full font-mono sm:w-[18rem] xl:w-[20rem]" />
{error ?

{error}

: null}
{t("table.id", { ns: "common" })} {t("drawNo")} {t("totalBet")} {t("actualDeduct")} {t("payoutTotal")} {t("platformProfit")} {t("reviewStatus")} {t("status")} {loading && !data ? : null} {(data?.items ?? []).map((row: AdminSettlementBatchRow) => ( {row.id} {row.draw_no ?? "—"} {formatAdminMinorUnits(row.total_bet_amount, row.currency_code ?? "NPR")} {formatAdminMinorUnits(row.total_actual_deduct, row.currency_code ?? "NPR")} {formatAdminMinorUnits(row.total_payout_amount, row.currency_code ?? "NPR")} {formatAdminMinorUnits(row.platform_profit, row.currency_code ?? "NPR")} {row.review_status ? ( {settlementReviewStatusText(row.review_status, t)} ) : ( )} {settlementStatusText(row.status, t)} openActionDialog(row, "approve"), }, { key: "reject", label: t("reject"), icon: X, hidden: !canReviewSettlement, disabled: actingId !== null || row.status !== "pending_review", onClick: () => openActionDialog(row, "reject"), }, { key: "payout", label: t("payout"), icon: HandCoins, hidden: !canManagePayout, disabled: actingId !== null || row.status !== "approved" || row.review_status !== "approved", onClick: () => openActionDialog(row, "payout"), }, ]} /> ))}
{data ? ( { setPerPage(n); setPage(1); }} onPageChange={setPage} /> ) : null}
{ if (!open && actingId === null) { setPendingAction(null); setReviewRemark(""); } }} > {pendingAction ? ( <> {t("confirmAction", { name: actionLabel(pendingAction.action) })} {pendingAction.action === "payout" ? t("confirmPayoutDesc") : t("confirmActionDesc", { drawNo: pendingAction.row.draw_no ?? "—" })}

{t("confirmAmountLine", { actual: formatAdminMinorUnits( pendingAction.row.total_actual_deduct, pendingAction.row.currency_code ?? "NPR", ), payout: formatAdminMinorUnits( pendingAction.row.total_payout_amount, pendingAction.row.currency_code ?? "NPR", ), profit: formatAdminMinorUnits( pendingAction.row.platform_profit, pendingAction.row.currency_code ?? "NPR", ), })}

{pendingAction.action !== "payout" ? (