feat: 更新管理员导航,添加结算和奖池模块,并优化活动匹配逻辑

This commit is contained in:
2026-05-11 15:35:41 +08:00
parent 0103d25426
commit f083b28fc6
19 changed files with 1226 additions and 5 deletions

View File

@@ -0,0 +1,236 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { getAdminJackpotContributions, getAdminJackpotPayoutLogs } from "@/api/admin-jackpot";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { ModuleScaffold } from "@/components/admin/module-scaffold";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
import { formatAdminMinorUnits } from "@/lib/money";
import { LotteryApiBizError } from "@/types/api/errors";
import type {
AdminJackpotContributionsData,
AdminJackpotPayoutLogsData,
} from "@/types/api/admin-jackpot";
export function JackpotRecordsConsole() {
const formatDt = useAdminDateTimeFormatter();
const [drawNo, setDrawNo] = useState("");
const [appliedDrawNo, setAppliedDrawNo] = useState("");
const [payouts, setPayouts] = useState<AdminJackpotPayoutLogsData | null>(null);
const [pPage, setPPage] = useState(1);
const [pPer, setPPer] = useState(15);
const [contribs, setContribs] = useState<AdminJackpotContributionsData | null>(null);
const [cPage, setCPage] = useState(1);
const [cPer, setCPer] = useState(15);
const [loadingP, setLoadingP] = useState(true);
const [loadingC, setLoadingC] = useState(true);
const [err, setErr] = useState<string | null>(null);
const loadPayouts = useCallback(async () => {
setLoadingP(true);
try {
const d = await getAdminJackpotPayoutLogs({
page: pPage,
per_page: pPer,
draw_no: appliedDrawNo.trim() || undefined,
});
setPayouts(d);
} catch (e) {
setErr(e instanceof LotteryApiBizError ? e.message : "派彩记录加载失败");
} finally {
setLoadingP(false);
}
}, [pPage, pPer, appliedDrawNo]);
const loadContribs = useCallback(async () => {
setLoadingC(true);
try {
const d = await getAdminJackpotContributions({
page: cPage,
per_page: cPer,
draw_no: appliedDrawNo.trim() || undefined,
});
setContribs(d);
} catch (e) {
setErr(e instanceof LotteryApiBizError ? e.message : "蓄水记录加载失败");
} finally {
setLoadingC(false);
}
}, [cPage, cPer, appliedDrawNo]);
useEffect(() => {
queueMicrotask(() => {
void loadPayouts();
});
}, [loadPayouts]);
useEffect(() => {
queueMicrotask(() => {
void loadContribs();
});
}, [loadContribs]);
const applyDraw = () => {
setAppliedDrawNo(drawNo);
setPPage(1);
setCPage(1);
};
return (
<ModuleScaffold>
<Card className="mb-6">
<CardHeader className="pb-3">
<CardTitle className="text-base"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-3 sm:flex-row sm:items-end">
<div className="flex max-w-xs flex-1 flex-col gap-1.5">
<Label htmlFor="jk-draw"></Label>
<Input
id="jk-draw"
className="font-mono"
value={drawNo}
onChange={(e) => setDrawNo(e.target.value)}
placeholder="可选"
/>
</div>
<Button type="button" onClick={applyDraw}>
</Button>
</CardContent>
</Card>
{err ? <p className="text-destructive mb-4 text-sm">{err}</p> : null}
<Card className="mb-8">
<CardHeader>
<CardTitle className="text-base">Jackpot </CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{loadingP && !payouts ? (
<p className="text-muted-foreground text-sm"></p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(payouts?.items ?? []).map((r) => (
<TableRow key={r.id}>
<TableCell className="font-mono text-xs">{r.id}</TableCell>
<TableCell className="font-mono text-xs">{r.draw_no ?? "—"}</TableCell>
<TableCell className="text-xs">{r.trigger_type}</TableCell>
<TableCell className="text-right font-mono text-xs tabular-nums">
{formatAdminMinorUnits(r.total_payout_amount, r.currency_code ?? "NPR")}
</TableCell>
<TableCell className="text-right tabular-nums">{r.winner_count}</TableCell>
<TableCell className="text-xs text-muted-foreground whitespace-nowrap">
{formatDt(r.created_at)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
{payouts ? (
<AdminListPaginationFooter
selectId="jk-payout-per"
total={payouts.meta.total}
page={payouts.meta.current_page}
lastPage={payouts.meta.last_page}
perPage={payouts.meta.per_page}
loading={loadingP}
onPerPageChange={(n) => {
setPPer(n);
setPPage(1);
}}
onPageChange={setPPage}
/>
) : null}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Jackpot </CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{loadingC && !contribs ? (
<p className="text-muted-foreground text-sm"></p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(contribs?.items ?? []).map((r) => (
<TableRow key={r.id}>
<TableCell className="font-mono text-xs">{r.id}</TableCell>
<TableCell className="font-mono text-xs">{r.draw_no ?? "—"}</TableCell>
<TableCell className="font-mono text-xs">{r.ticket_no ?? "—"}</TableCell>
<TableCell className="max-w-[10rem] truncate text-xs">
{r.player_username ?? "—"}
</TableCell>
<TableCell className="text-right font-mono text-xs tabular-nums">
{formatAdminMinorUnits(r.contribution_amount, r.currency_code ?? "NPR")}
</TableCell>
<TableCell className="text-xs text-muted-foreground whitespace-nowrap">
{formatDt(r.created_at)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
{contribs ? (
<AdminListPaginationFooter
selectId="jk-contrib-per"
total={contribs.meta.total}
page={contribs.meta.current_page}
lastPage={contribs.meta.last_page}
perPage={contribs.meta.per_page}
loading={loadingC}
onPerPageChange={(n) => {
setCPer(n);
setCPage(1);
}}
onPageChange={setCPage}
/>
) : null}
</CardContent>
</Card>
</ModuleScaffold>
);
}