feat: 添加日期处理库和日历选择器,更新管理员抽奖模块

This commit is contained in:
2026-05-09 17:40:35 +08:00
parent f19cdb48ad
commit ac3f28459b
28 changed files with 2186 additions and 117 deletions

View File

@@ -0,0 +1,112 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { getAdminDrawResultBatches } from "@/api/admin-draws";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, 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 { AdminDrawBatchesData } from "@/types/api/admin-draws";
import { DrawStatusBadge } from "./draw-status-badge";
export function DrawReviewConsole({ drawId }: { drawId: string }) {
const idNum = Number(drawId);
const [data, setData] = useState<AdminDrawBatchesData | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const load = useCallback(async () => {
if (!Number.isFinite(idNum)) {
setError("无效的期号 ID");
setLoading(false);
return;
}
setLoading(true);
setError(null);
try {
setData(await getAdminDrawResultBatches(idNum));
} catch (e) {
setData(null);
setError(e instanceof LotteryApiBizError ? e.message : "加载失败");
} finally {
setLoading(false);
}
}, [idNum]);
useEffect(() => {
const timer = window.setTimeout(() => {
void load();
}, 0);
return () => window.clearTimeout(timer);
}, [load]);
const pending = useMemo(() => data?.batches.filter((b) => b.status === "pending_review") ?? [], [
data,
]);
if (loading && !data) {
return <p className="text-sm text-muted-foreground"></p>;
}
if (error || !data) {
return <p className="text-sm text-destructive">{error ?? "无数据"}</p>;
}
return (
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
RNG 23
DB <DrawStatusBadge status={data.draw_status} />
</CardDescription>
</CardHeader>
<CardContent>
{pending.length === 0 ? (
<p className="text-sm text-muted-foreground py-6 text-center">
pending_review
</p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead> ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{pending.map((b) => (
<TableRow key={b.id}>
<TableCell className="font-mono text-xs">{b.id}</TableCell>
<TableCell>v{b.result_version}</TableCell>
<TableCell>{b.items.length}</TableCell>
<TableCell className="text-right">
<Link
href={`/admin/draws/${drawId}/review/${b.id}`}
className={cn(buttonVariants({ size: "sm" }))}
>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
);
}