Files
lotteryFront/src/features/hall/hall-draw-panel.tsx

167 lines
5.8 KiB
TypeScript

"use client";
import { Hourglass, Landmark, TimerReset } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { drawStatusHud, isHallSealedCountdownUi } from "@/features/draw/draw-status-meta";
import type { HallDrawLiveSnapshot } from "@/features/hall/use-hall-draw-live";
import { formatSecondsClock } from "@/lib/format-gmt";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { cn } from "@/lib/utils";
import type { DrawCurrentPayload } from "@/types/api/draw-current";
function CurrentTime({ payload }: { payload: DrawCurrentPayload }) {
const { t } = useTranslation("player");
const source = payload.close_time ?? payload.draw_time ?? payload.start_time;
const formatted = source ? formatLotteryInstant(source) : null;
if (!formatted) {
return (
<>
<span className="text-lg font-black tabular-nums text-[#0b3f96]">--:--:--</span>
<span className="mt-1 text-[11px] text-slate-500">{t("draw.currentTime")}</span>
</>
);
}
const parts = formatted.split(" ");
const date = parts.slice(0, -1).join(" ");
const time = parts.at(-1);
return (
<>
<span className="text-lg font-black tabular-nums text-[#0b3f96]">{time}</span>
<span className="mt-1 text-[11px] text-slate-500">{date}</span>
</>
);
}
function CloseTime({
hud,
payload,
}: {
hud: ReturnType<typeof drawStatusHud>;
payload: DrawCurrentPayload;
}) {
const { t } = useTranslation("player");
const sealedCountdown = isHallSealedCountdownUi(payload.status);
let seconds = 0;
let label = t("draw.closesIn");
if (hud.countdownKind === "close") {
seconds = Math.max(0, payload.seconds_to_close);
} else if (hud.countdownKind === "draw") {
seconds = Math.max(0, payload.seconds_to_draw);
label = sealedCountdown ? t("draw.drawsIn") : t("draw.closesIn");
} else if (hud.countdownKind === "cooldown") {
seconds = Math.max(0, payload.seconds_remaining_in_cooldown ?? 0);
label = t("draw.coolDown");
}
return (
<>
<span className="text-lg font-black tabular-nums text-[#ff143d]">
{formatSecondsClock(seconds)}
</span>
<span className="mt-1 text-[11px] text-slate-500">{label}</span>
</>
);
}
export function HallDrawPanel({ drawLive }: { drawLive: HallDrawLiveSnapshot }) {
const { raw, display, error, reload } = drawLive;
const { t } = useTranslation("player");
if (error) {
return (
<section className="mb-4 rounded-xl border border-red-200 bg-red-50 px-3 py-3 text-sm text-red-700">
<p>{t(error, { defaultValue: error })}</p>
<Button
type="button"
variant="outline"
size="sm"
className="mt-2 border-red-200 bg-white text-red-700"
onClick={() => void reload()}
>
{t("actions.retry")}
</Button>
</section>
);
}
if (raw === undefined || display === undefined) {
return (
<section className="mb-4 rounded-xl border border-[#e3ebf6] bg-white p-3 shadow-sm">
<div className="grid grid-cols-3 gap-2">
<Skeleton className="h-14 rounded-lg" />
<Skeleton className="h-14 rounded-lg" />
<Skeleton className="h-14 rounded-lg" />
</div>
</section>
);
}
if (raw === null || display === null) {
return (
<section className="mb-4 rounded-xl border border-[#e3ebf6] bg-white px-3 py-4 text-center text-sm text-slate-500 shadow-sm">
{t("draw.noIssue")}
</section>
);
}
const hud = drawStatusHud(display.status);
const sealedUi = isHallSealedCountdownUi(display.status);
return (
<section
className={cn(
"mb-4 overflow-hidden rounded-xl border border-[#e1e9f5] bg-white shadow-[0_6px_20px_rgba(15,23,42,0.06)]",
sealedUi && "border-red-200 bg-red-50/30",
)}
aria-label={t("draw.currentIssue")}
>
<div className="grid grid-cols-[1fr_1.05fr_1fr] divide-x divide-[#e7edf6]">
<div className="flex min-w-0 flex-col items-center justify-center px-2 py-3 text-center">
<div className="min-w-0 max-w-full overflow-x-auto">
<p className="text-[11px] font-semibold text-slate-500">{t("draw.issueNo")}</p>
<p className="whitespace-nowrap text-xs font-black tabular-nums text-[#ff143d] sm:text-sm">
{display.draw_no}
</p>
</div>
</div>
<div className="flex min-w-0 flex-col items-center justify-center px-2 py-3 text-center">
<CurrentTime payload={display} />
</div>
<div className="relative flex min-w-0 flex-col items-center justify-center px-2 py-3 text-center">
<CloseTime hud={hud} payload={display} />
<Hourglass
className={cn(
"absolute right-2 top-1/2 size-5 -translate-y-1/2",
sealedUi ? "text-[#ff143d]" : "text-red-300",
)}
aria-hidden
/>
</div>
</div>
{sealedUi ? (
<div className="flex items-center gap-2 border-t border-red-100 bg-red-50 px-3 py-2 text-xs font-medium text-red-600">
<TimerReset className="size-4" aria-hidden />
{t("draw.sealedNotice")}
</div>
) : (
<div className="flex items-center justify-between border-t border-[#eef3f9] bg-[#fbfdff] px-3 py-1.5 text-[11px] text-slate-500">
<span className="inline-flex items-center gap-1.5">
<span className={cn("size-2 rounded-full", hud.dotClass)} />
{t(hud.labelKey, { defaultValue: hud.labelKey })}
</span>
<span className="inline-flex items-center gap-1">
<Landmark className="size-3.5" aria-hidden />
{t("draw.hall")}
</span>
</div>
)}
</section>
);
}