将 AdminRiskPage 改名为 AdminRiskIndexPage,并接入 RiskIndexConsole 组件。

This commit is contained in:
2026-05-11 11:52:53 +08:00
parent 78045de9a3
commit 0103d25426
16 changed files with 1033 additions and 15 deletions

View File

@@ -0,0 +1,192 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { getAdminRiskPoolLockLogs } from "@/api/admin-risk";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
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 {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
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 { AdminRiskLockLogListData, AdminRiskLockLogRow } from "@/types/api/admin-risk";
const ACTION_ALL = "__all__";
export function RiskLockLogsConsole({ drawId }: { drawId: number }) {
const formatDt = useAdminDateTimeFormatter();
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(25);
const [data, setData] = useState<AdminRiskLockLogListData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [draftNumber, setDraftNumber] = useState("");
const [appliedNumber, setAppliedNumber] = useState("");
const [draftAction, setDraftAction] = useState<string>(ACTION_ALL);
const [appliedAction, setAppliedAction] = useState<string>(ACTION_ALL);
const load = useCallback(async () => {
setLoading(true);
setError(null);
try {
const d = await getAdminRiskPoolLockLogs(drawId, {
page,
per_page: perPage,
normalized_number: appliedNumber.trim() === "" ? undefined : appliedNumber.trim(),
action_type:
appliedAction === ACTION_ALL
? undefined
: (appliedAction as "lock" | "release"),
});
setData(d);
} catch (e) {
const msg =
e instanceof LotteryApiBizError ? e.message : "加载占用流水失败";
setError(msg);
setData(null);
} finally {
setLoading(false);
}
}, [drawId, page, perPage, appliedAction, appliedNumber]);
useEffect(() => {
queueMicrotask(() => {
void load();
});
}, [load]);
return (
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
/ `risk_pool_lock_logs`
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid max-w-full gap-3 sm:grid-cols-[minmax(0,8rem)_minmax(0,10rem)_auto] sm:items-end">
<div className="space-y-1.5">
<Label htmlFor="risk-log-number">4 </Label>
<Input
id="risk-log-number"
inputMode="numeric"
maxLength={4}
value={draftNumber}
onChange={(e) => setDraftNumber(e.target.value.replace(/\D/g, "").slice(0, 4))}
placeholder="可选"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="risk-log-action"></Label>
<Select
modal={false}
value={draftAction}
onValueChange={(v) => {
if (v) setDraftAction(v);
}}
>
<SelectTrigger id="risk-log-action" size="sm" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={ACTION_ALL}></SelectItem>
<SelectItem value="lock"> lock</SelectItem>
<SelectItem value="release"> release</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex gap-2 sm:justify-end">
<Button
type="button"
size="sm"
onClick={() => {
setAppliedNumber(draftNumber);
setAppliedAction(draftAction);
setPage(1);
}}
>
</Button>
</div>
</div>
{error ? <p className="text-sm text-destructive">{error}</p> : null}
{loading && !data ? (
<p className="text-sm text-muted-foreground"></p>
) : (
<>
<div className="overflow-x-auto rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(data?.items ?? []).map((row: AdminRiskLockLogRow) => (
<TableRow key={row.id}>
<TableCell className="whitespace-nowrap text-xs text-muted-foreground">
{row.created_at ? formatDt(row.created_at) : "—"}
</TableCell>
<TableCell className="font-mono text-sm">{row.normalized_number}</TableCell>
<TableCell className="text-sm">{row.action_type}</TableCell>
<TableCell className="text-right text-sm tabular-nums">
{formatAdminMinorUnits(row.amount)}
</TableCell>
<TableCell className="text-xs text-muted-foreground">
{row.source_reason ?? "—"}
</TableCell>
<TableCell className="font-mono text-xs">{row.ticket_no ?? "—"}</TableCell>
<TableCell className="text-xs">{row.play_code ?? "—"}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{data ? (
<AdminListPaginationFooter
selectId={`risk-logs-${drawId}`}
total={data.meta.total}
page={data.meta.current_page}
lastPage={data.meta.last_page}
perPage={data.meta.per_page}
loading={loading}
onPerPageChange={(n) => {
setPerPage(n);
setPage(1);
}}
onPageChange={setPage}
/>
) : null}
</>
)}
</CardContent>
</Card>
);
}