Files
lotteryAdmin/src/modules/draws/draw-finance-console.tsx

186 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { getAdminDrawFinanceSummary } from "@/api/admin-draws";
import { postAdminRunDrawSettlement } from "@/api/admin-settlement";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";
import { LotteryApiBizError } from "@/types/api/errors";
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
import { toast } from "sonner";
export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactElement {
const idNum = Number(drawId);
const [data, setData] = useState<AdminDrawFinanceSummaryData | null>(null);
const [err, setErr] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [settling, setSettling] = useState(false);
const load = useCallback(async () => {
if (!Number.isFinite(idNum) || idNum < 1) {
setErr("无效的期号 ID");
setLoading(false);
return;
}
setLoading(true);
setErr(null);
try {
setData(await getAdminDrawFinanceSummary(idNum));
} catch (e) {
setErr(e instanceof LotteryApiBizError ? e.message : "加载失败");
setData(null);
} finally {
setLoading(false);
}
}, [idNum]);
async function runSettlement(): Promise<void> {
if (!Number.isFinite(idNum) || idNum < 1) return;
setSettling(true);
try {
const res = await postAdminRunDrawSettlement(idNum);
toast.success(res.ran ? "已触发结算" : "当前状态不可结算或已处理");
await load();
} catch (e) {
toast.error(e instanceof LotteryApiBizError ? e.message : "触发结算失败");
} finally {
setSettling(false);
}
}
useEffect(() => {
queueMicrotask(() => {
void load();
});
}, [load]);
if (loading && !data) {
return <p className="text-muted-foreground text-sm"></p>;
}
if (err || !data) {
return <p className="text-destructive text-sm">{err ?? "无数据"}</p>;
}
return (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent className="grid gap-3 text-sm sm:grid-cols-2 lg:grid-cols-3">
<div>
<span className="text-muted-foreground"></span>
<p className="font-mono font-semibold">{data.draw_no}</p>
</div>
<div>
<span className="text-muted-foreground"></span>
<p>{data.draw_status}</p>
</div>
<div>
<span className="text-muted-foreground"> / </span>
<p className="tabular-nums">
{data.order_count} / {data.ticket_item_count}
</p>
</div>
<div>
<span className="text-muted-foreground"></span>
<p className="tabular-nums font-medium">{data.total_bet_minor}</p>
</div>
<div>
<span className="text-muted-foreground"></span>
<p className="tabular-nums font-medium">{data.total_payout_minor}</p>
</div>
<div>
<span className="text-muted-foreground"></span>
<p
className={cn(
"tabular-nums font-semibold",
data.approx_house_gross_minor >= 0 ? "text-emerald-600" : "text-destructive",
)}
>
{data.approx_house_gross_minor}
</p>
</div>
</CardContent>
</Card>
<div className="flex flex-wrap gap-2">
<Button type="button" variant="secondary" size="sm" onClick={() => void load()}>
</Button>
<Button type="button" size="sm" disabled={settling} onClick={() => void runSettlement()}>
{settling ? "处理中…" : "触发结算"}
</Button>
<Link
href="/admin/settlement-batches"
className={cn(buttonVariants({ variant: "outline", size: "sm" }))}
>
</Link>
</div>
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent>
{data.settlement_batches.length === 0 ? (
<p className="text-muted-foreground text-sm"></p>
) : (
<div className="overflow-x-auto rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-20">ID</TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right">Jackpot</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.settlement_batches.map((b) => (
<TableRow key={b.id}>
<TableCell className="font-mono text-xs">{b.id}</TableCell>
<TableCell className="text-xs">{b.status}</TableCell>
<TableCell className="text-right tabular-nums text-xs">
{b.total_ticket_count}
</TableCell>
<TableCell className="text-right tabular-nums text-xs">
{b.total_win_count}
</TableCell>
<TableCell className="text-right tabular-nums text-xs">
{b.total_payout_amount}
</TableCell>
<TableCell className="text-right tabular-nums text-xs">
{b.total_jackpot_payout_amount}
</TableCell>
<TableCell className="font-mono text-[11px] text-muted-foreground">
{b.finished_at ?? "—"}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</CardContent>
</Card>
</div>
);
}