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,116 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { getAdminDraw } from "@/api/admin-draws";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
import { LotteryApiBizError } from "@/types/api/errors";
import type { AdminDrawShowData } from "@/types/api/admin-draws";
import { DrawStatusBadge } from "./draw-status-badge";
function Field({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="grid gap-1 sm:grid-cols-[10rem_1fr] sm:items-start">
<Label className="text-muted-foreground">{label}</Label>
<div className="text-sm">{children}</div>
</div>
);
}
export function DrawDetailConsole({ drawId }: { drawId: string }) {
const idNum = Number(drawId);
const formatDt = useAdminDateTimeFormatter();
const [data, setData] = useState<AdminDrawShowData | 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 getAdminDraw(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]);
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 (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
<code className="rounded bg-muted px-1 py-0.5 text-xs">status</code>{" "}
tick DB
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap items-center gap-2">
<span className="font-mono text-base font-semibold">{data.draw_no}</span>
<DrawStatusBadge status={data.status} label={`DB · ${data.status}`} />
<DrawStatusBadge
status={data.hall_preview_status}
label={`大厅预览 · ${data.hall_preview_status}`}
/>
</div>
<Separator />
<div className="grid gap-4">
<Field label="业务日">{data.business_date}</Field>
<Field label="流水序号">{data.sequence_no}</Field>
<Field label="开始时间">{formatDt(data.start_time)}</Field>
<Field label="封盘时间">{formatDt(data.close_time)}</Field>
<Field label="计划开奖">{formatDt(data.draw_time)}</Field>
<Field label="冷静期结束">{formatDt(data.cooling_end_time)}</Field>
<Field label="结果来源">{data.result_source ?? "—"}</Field>
<Field label="当前结果版本">{data.current_result_version}</Field>
<Field label="结算版本">{data.settle_version}</Field>
<Field label="是否重开">{data.is_reopened ? "是" : "否"}</Field>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
<CardDescription> / </CardDescription>
</CardHeader>
<CardContent className="flex flex-wrap gap-6 text-sm">
<span>{data.result_batch_counts.total}</span>
<span className="text-amber-600 dark:text-amber-400">
{data.result_batch_counts.pending_review}
</span>
<span className="text-emerald-600 dark:text-emerald-400">
{data.result_batch_counts.published}
</span>
</CardContent>
</Card>
</div>
);
}