"use client"; import Link from "next/link"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { getAdminRiskPools, postAdminRiskPoolManualClose, postAdminRiskPoolRecover, } from "@/api/admin-risk"; import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer"; import { AdminTableExportButton } from "@/components/admin/admin-table-export-button"; import { Button } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 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 { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog"; import { formatAdminMinorUnits } from "@/lib/money"; import { cn } from "@/lib/utils"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminRiskPoolListData, AdminRiskPoolRow } from "@/types/api/admin-risk"; const SORT_OPTIONS: { value: "usage_desc" | "locked_desc" | "remaining_asc" | "number_asc"; label: string }[] = [ { value: "usage_desc", label: "sortUsageDesc" }, { value: "locked_desc", label: "sortLockedDesc" }, { value: "remaining_asc", label: "sortRemainingAsc" }, { value: "number_asc", label: "sortNumberAsc" }, ]; function riskSortLabel( value: "usage_desc" | "locked_desc" | "remaining_asc" | "number_asc", t: (key: string) => string, ): string { const option = SORT_OPTIONS.find((item) => item.value === value); return option ? t(option.label) : value; } type RiskFilter = "all" | "sold_out" | "high_risk"; type RiskPoolsConsoleProps = { drawId: number; title: string; soldOutOnly: boolean; defaultSort: "usage_desc" | "locked_desc" | "remaining_asc" | "number_asc"; allowSortChange?: boolean; }; export function RiskPoolsConsole({ drawId, title, soldOutOnly, defaultSort, allowSortChange = false, }: RiskPoolsConsoleProps) { const { t } = useTranslation(["risk", "common"]); useAdminCurrencyCatalog(); const [sort, setSort] = useState(defaultSort); const [filter, setFilter] = useState(soldOutOnly ? "sold_out" : "all"); const [number, setNumber] = useState(""); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(10); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [actingNumber, setActingNumber] = useState(null); const [error, setError] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); try { const d = await getAdminRiskPools(drawId, { page, per_page: perPage, sold_out_only: filter === "sold_out", high_risk_only: filter === "high_risk", normalized_number: number.trim(), sort, }); setData(d); } catch (e) { const msg = e instanceof LotteryApiBizError ? e.message : t("loadPoolsFailed"); setError(msg); setData(null); } finally { setLoading(false); } }, [drawId, filter, number, page, perPage, sort, t]); useEffect(() => { queueMicrotask(() => { void load(); }); }, [load]); const handleManualStatus = useCallback( async (row: AdminRiskPoolRow) => { setActingNumber(row.normalized_number); try { const updated = row.is_sold_out ? await postAdminRiskPoolRecover(drawId, row.normalized_number) : await postAdminRiskPoolManualClose(drawId, row.normalized_number); setData((current) => { if (!current) return current; return { ...current, items: current.items.map((item) => item.normalized_number === updated.normalized_number ? updated : item, ), }; }); toast.success(row.is_sold_out ? t("recoverSuccess") : t("manualCloseSuccess")); } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : t("actionFailed")); } finally { setActingNumber(null); } }, [drawId, t], ); return ( {title}
{ setNumber(event.target.value.replace(/\D/g, "").slice(0, 4)); setPage(1); }} />
{[ ["all", t("filterAll")], ["sold_out", t("filterSoldOut")], ["high_risk", t("filterHighRisk")], ].map(([value, label]) => ( ))}
{allowSortChange ? (
) : null}
{error ?

{error}

: null} {loading && !data ? (

{t("states.loading", { ns: "common" })}

) : ( <>
{t("searchNumber")} {t("capAmount")} {t("lockedAmount")} {t("remainingAmount")} {t("usageRatio")} {t("poolStatus")} {t("actions")} {(data?.items ?? []).map((row: AdminRiskPoolRow) => { const highRisk = (row.usage_ratio ?? 0) >= 0.8; const acting = actingNumber === row.normalized_number; const currencyCode = data?.currency_code ?? "NPR"; return ( {row.normalized_number} {formatAdminMinorUnits(row.total_cap_amount, currencyCode)} {formatAdminMinorUnits(row.locked_amount, currencyCode)} {formatAdminMinorUnits(row.remaining_amount, currencyCode)} {row.usage_ratio != null ? `${(row.usage_ratio * 100).toFixed(2)}%` : "—"} {row.is_sold_out ? t("soldOut") : highRisk ? t("warning") : t("normal")}
{t("view")}
); })}
{data ? ( { setPerPage(n); setPage(1); }} onPageChange={setPage} /> ) : null} )}
); }