"use client"; import { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import { useAsyncEffect } from "@/hooks/use-async-effect"; import { useTranslationRef } from "@/hooks/use-translation-ref"; import { getAdminJackpotPoolAdjustments, getAdminJackpotPools, postAdminJackpotManualBurst, postAdminJackpotPoolAdjustment, putAdminJackpotPool, } from "@/api/admin-jackpot"; import { useConfirmAction } from "@/hooks/use-confirm-action"; import { adminHasAnyPermission } from "@/lib/admin-permissions"; import { formatAdminMinorDecimal, parseAdminMajorToMinor } from "@/lib/money"; import { PRD_JACKPOT_MANAGE, PRD_JACKPOT_MANUAL_BURST } from "@/lib/admin-prd"; import { ModuleScaffold } from "@/components/admin/module-scaffold"; import { AdminNoResourceState, AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state"; import { Button } 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 { Switch } from "@/components/ui/switch"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { toast } from "sonner"; import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminJackpotPoolAdjustmentRow, AdminJackpotPoolRow } from "@/types/api/admin-jackpot"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { AdminLoadingState, AdminLoadingInline, AdminTableLoadingRow } from "@/components/admin/admin-loading-state"; import { formatRatioAsPercent, percentUiToRatio, ratioToPercentUi, } from "@/lib/admin-rate-percent"; type Draft = { contribution_rate: string; trigger_threshold: string; payout_rate: string; force_trigger_draw_gap: string; min_bet_amount: string; combo_trigger_play_codes: string; status: string; manual_burst_draw_id: string; }; type AdjustmentDraft = { direction: "increase" | "decrease"; amount: string; reason: string; }; function toDraft(p: AdminJackpotPoolRow): Draft { return { contribution_rate: ratioToPercentUi(p.contribution_rate), trigger_threshold: formatAdminMinorDecimal(p.trigger_threshold, p.currency_code), payout_rate: ratioToPercentUi(p.payout_rate), force_trigger_draw_gap: String(p.force_trigger_draw_gap), min_bet_amount: formatAdminMinorDecimal(p.min_bet_amount, p.currency_code), combo_trigger_play_codes: p.combo_trigger_play_codes.join(","), status: String(p.status), manual_burst_draw_id: "", }; } type JackpotPoolsConsoleProps = { /** 嵌入运营配置单页时去掉外层脚手架与重复标题 */ embedded?: boolean; }; export function JackpotPoolsConsole({ embedded = false }: JackpotPoolsConsoleProps) { const { t } = useTranslation(["jackpot", "common"]); const tRef = useTranslationRef(["jackpot", "common"]); const profile = useAdminProfile(); const canManageJackpot = adminHasAnyPermission(profile?.permissions, [PRD_JACKPOT_MANAGE]); const canManualBurst = adminHasAnyPermission(profile?.permissions, [PRD_JACKPOT_MANUAL_BURST]); const { request: requestConfirm, ConfirmDialog: ConfirmActionDialog } = useConfirmAction(); const [items, setItems] = useState([]); const [drafts, setDrafts] = useState>({}); const [loading, setLoading] = useState(true); const [savingId, setSavingId] = useState(null); const [burstingId, setBurstingId] = useState(null); const [confirmBurstPoolId, setConfirmBurstPoolId] = useState(null); const [adjustmentDrafts, setAdjustmentDrafts] = useState>({}); const [adjustmentRows, setAdjustmentRows] = useState>({}); const [adjustingId, setAdjustingId] = useState(null); const load = useCallback(async () => { setLoading(true); try { const res = await getAdminJackpotPools(); setItems(res.items); const d: Record = {}; const adjDrafts: Record = {}; const adjRows: Record = {}; for (const p of res.items) { d[p.id] = toDraft(p); adjDrafts[p.id] = { direction: "increase", amount: "", reason: "" }; try { const ledger = await getAdminJackpotPoolAdjustments(p.id, { per_page: 5 }); adjRows[p.id] = ledger.items; } catch { adjRows[p.id] = []; } } setDrafts(d); setAdjustmentDrafts(adjDrafts); setAdjustmentRows(adjRows); } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : tRef.current("loadFailed")); } finally { setLoading(false); } }, []); useAsyncEffect(() => { void load(); }, []); const updateDraft = (id: number, patch: Partial) => { setDrafts((prev) => ({ ...prev, [id]: { ...prev[id], ...patch }, })); }; const updateAdjustmentDraft = (id: number, patch: Partial) => { setAdjustmentDrafts((prev) => ({ ...prev, [id]: { ...(prev[id] ?? { direction: "increase", amount: "", reason: "" }), ...patch }, })); }; const save = async (p: AdminJackpotPoolRow) => { const d = drafts[p.id]; if (!d) return; setSavingId(p.id); try { await putAdminJackpotPool(p.id, { contribution_rate: percentUiToRatio(d.contribution_rate), trigger_threshold: parseAdminMajorToMinor(d.trigger_threshold, p.currency_code) ?? 0, payout_rate: percentUiToRatio(d.payout_rate), force_trigger_draw_gap: Number.parseInt(d.force_trigger_draw_gap, 10), min_bet_amount: parseAdminMajorToMinor(d.min_bet_amount, p.currency_code) ?? 0, combo_trigger_play_codes: d.combo_trigger_play_codes .split(",") .map((v) => v.trim().toLowerCase()) .filter(Boolean), status: Number.parseInt(d.status, 10), }); toast.success(t("saveSuccess")); await load(); } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : t("saveFailed")); } finally { setSavingId(null); } }; const submitAdjustment = async (p: AdminJackpotPoolRow) => { const adj = adjustmentDrafts[p.id]; if (!adj) return; const minor = parseAdminMajorToMinor(adj.amount, p.currency_code); if (minor === null || minor <= 0) { toast.error(t("adjustmentAmountInvalid")); return; } const trimmedReason = adj.reason.trim(); if (trimmedReason.length < 3) { toast.error(t("adjustmentReasonRequired")); return; } const amountDelta = adj.direction === "decrease" ? -minor : minor; setAdjustingId(p.id); try { const res = await postAdminJackpotPoolAdjustment(p.id, { amount_delta: amountDelta, reason: trimmedReason, }); toast.success(t("adjustmentSuccess")); setItems((prev) => prev.map((row) => row.id === p.id ? { ...row, current_amount: res.pool.current_amount } : row, ), ); updateAdjustmentDraft(p.id, { amount: "", reason: "" }); const ledger = await getAdminJackpotPoolAdjustments(p.id, { per_page: 5 }); setAdjustmentRows((prev) => ({ ...prev, [p.id]: ledger.items })); } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : t("adjustmentFailed")); } finally { setAdjustingId(null); } }; const manualBurst = async (p: AdminJackpotPoolRow) => { const d = drafts[p.id]; if (!d) return; const drawRef = d.manual_burst_draw_id.trim(); if (drawRef.length === 0) { toast.error(t("invalidDrawId")); return; } setBurstingId(p.id); try { const res = await postAdminJackpotManualBurst(p.id, { draw_id: drawRef }); toast.success( `${t("manualBurstSuccess")} · ${res.draw_no} · ${res.winner_count} ${t("winnerCount")}`, ); await load(); } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : t("manualBurstFailed")); } finally { setBurstingId(null); setConfirmBurstPoolId(null); } }; const poolList = (
{loading ? : null} {!loading && items.length === 0 ? ( ) : null} {items.map((p) => { const d = drafts[p.id] ?? toDraft(p); const adj = adjustmentDrafts[p.id] ?? { direction: "increase", amount: "", reason: "" }; const ledger = adjustmentRows[p.id] ?? []; const currentAmount = formatAdminMinorDecimal(p.current_amount, p.currency_code); const triggerThreshold = formatAdminMinorDecimal(p.trigger_threshold, p.currency_code); const minBetAmount = formatAdminMinorDecimal(p.min_bet_amount, p.currency_code); const statusOn = d.status === "1"; return (

{p.currency_code}

{t("configTitle")}

{t("displayBalance", { amount: currentAmount })}

{t("currentAmount")}

{currentAmount}

{t("status")}

{statusOn ? t("enabled") : t("disabled")}

{t("payoutRate")}

{formatRatioAsPercent(percentUiToRatio(d.payout_rate))}

{t("forceTriggerGap")}

{d.force_trigger_draw_gap}

{canManageJackpot ? (

{t("balanceAdjustmentTitle")}

{t("balanceAdjustmentHint")}

updateAdjustmentDraft(p.id, { amount: e.target.value })} />