refactor: 更新风险监控页面标题为国际化支持,优化风险池控制台的标题处理
This commit is contained in:
@@ -25,6 +25,7 @@ import { useAdminProfile } from "@/stores/admin-session";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { drawResultSourceLabel, drawStatusLabel } from "./draw-display";
|
||||
import { DrawStatusBadge } from "./draw-status-badge";
|
||||
import {
|
||||
PRD_DRAW_REOPEN_MANAGE,
|
||||
@@ -33,12 +34,6 @@ import {
|
||||
PRD_PAYOUT_REVIEW,
|
||||
} from "./draw-prd";
|
||||
|
||||
function drawStatusText(status: string, t: (key: string) => string): string {
|
||||
const key = `statusOptions.${status}`;
|
||||
const translated = t(key);
|
||||
return translated === key ? status : translated;
|
||||
}
|
||||
|
||||
function Field({ label, children }: { label: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="grid gap-1 sm:grid-cols-[10rem_1fr] sm:items-start">
|
||||
@@ -123,13 +118,13 @@ export function DrawDetailConsole({ drawId }: { drawId: string }) {
|
||||
<div className="flex flex-col items-end gap-1 text-right">
|
||||
<DrawStatusBadge
|
||||
status={data.status}
|
||||
label={drawStatusText(data.status, t)}
|
||||
label={drawStatusLabel(data.status, t)}
|
||||
/>
|
||||
<p className="flex flex-wrap items-center justify-end gap-2 text-sm text-muted-foreground">
|
||||
<span>{t("hallPreviewStatus", { status: "" }).replace(/\{\{status\}\}/, "").replace(/\s+$/, "")}</span>
|
||||
<span>{t("hallPreviewStatusLabel")}</span>
|
||||
<DrawStatusBadge
|
||||
status={data.hall_preview_status}
|
||||
label={drawStatusText(data.hall_preview_status, t)}
|
||||
label={drawStatusLabel(data.hall_preview_status, t)}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
@@ -147,7 +142,7 @@ export function DrawDetailConsole({ drawId }: { drawId: string }) {
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<Field label={t("resultSource")}>{data.result_source ?? "—"}</Field>
|
||||
<Field label={t("resultSource")}>{drawResultSourceLabel(data.result_source, t)}</Field>
|
||||
<Field label={t("currentResultVersion")}>{data.current_result_version}</Field>
|
||||
<Field label={t("settleVersion")}>{data.settle_version}</Field>
|
||||
<Field label={t("isReopened")}>{data.is_reopened ? t("yes") : t("no")}</Field>
|
||||
|
||||
62
src/modules/draws/draw-display.ts
Normal file
62
src/modules/draws/draw-display.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
type DrawTranslate = (
|
||||
key: string,
|
||||
options?: { ns?: string; index?: number },
|
||||
) => string;
|
||||
|
||||
/** 期号状态文案(draws.statusOptions) */
|
||||
export function drawStatusLabel(status: string, t: DrawTranslate): string {
|
||||
const key = `statusOptions.${status}`;
|
||||
const translated = t(key, { ns: "draws" });
|
||||
return translated === key ? status : translated;
|
||||
}
|
||||
|
||||
/** 开奖结果来源(draws.resultSourceOptions) */
|
||||
export function drawResultSourceLabel(
|
||||
source: string | null | undefined,
|
||||
t: DrawTranslate,
|
||||
): string {
|
||||
if (source == null || source === "") {
|
||||
return "—";
|
||||
}
|
||||
const key = `resultSourceOptions.${source}`;
|
||||
const translated = t(key, { ns: "draws" });
|
||||
return translated === key ? source : translated;
|
||||
}
|
||||
|
||||
/** 开奖结果批次状态(draws.batchStatusOptions) */
|
||||
export function drawBatchStatusLabel(status: string, t: DrawTranslate): string {
|
||||
const key = `batchStatusOptions.${status}`;
|
||||
const translated = t(key, { ns: "draws" });
|
||||
return translated === key ? status : translated;
|
||||
}
|
||||
|
||||
/** 结算批次状态(settlement.statusOptions) */
|
||||
export function settlementBatchStatusLabel(status: string, t: DrawTranslate): string {
|
||||
const key = `statusOptions.${status}`;
|
||||
const translated = t(key, { ns: "settlement" });
|
||||
return translated === key ? status : translated;
|
||||
}
|
||||
|
||||
/** 奖项类型 + 序号 → 展示名(draws.resultSlots) */
|
||||
export function drawPrizeTypeLabel(
|
||||
prizeType: string,
|
||||
prizeIndex: number,
|
||||
t: DrawTranslate,
|
||||
): string {
|
||||
if (prizeType === "first") {
|
||||
return t("resultSlots.first", { ns: "draws" });
|
||||
}
|
||||
if (prizeType === "second") {
|
||||
return t("resultSlots.second", { ns: "draws" });
|
||||
}
|
||||
if (prizeType === "third") {
|
||||
return t("resultSlots.third", { ns: "draws" });
|
||||
}
|
||||
if (prizeType === "starter") {
|
||||
return t("resultSlots.starter", { ns: "draws", index: prizeIndex + 1 });
|
||||
}
|
||||
if (prizeType === "consolation") {
|
||||
return t("resultSlots.consolation", { ns: "draws", index: prizeIndex + 1 });
|
||||
}
|
||||
return prizeType;
|
||||
}
|
||||
@@ -26,18 +26,16 @@ import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
|
||||
import { useExportLabels } from "@/hooks/use-export-labels";
|
||||
import { formatAdminMinorUnits } from "@/lib/money";
|
||||
|
||||
import { drawStatusLabel, settlementBatchStatusLabel } from "./draw-display";
|
||||
import { PRD_PAYOUT_MANAGE, PRD_PAYOUT_REVIEW } from "./draw-prd";
|
||||
|
||||
function drawStatusText(status: string, t: (key: string) => string): string {
|
||||
const key = `statusOptions.${status}`;
|
||||
const translated = t(key);
|
||||
return translated === key ? status : translated;
|
||||
}
|
||||
|
||||
export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactElement {
|
||||
const { t } = useTranslation(["draws", "common"]);
|
||||
const { t } = useTranslation(["draws", "settlement", "common"]);
|
||||
useAdminCurrencyCatalog();
|
||||
const idNum = Number(drawId);
|
||||
const profile = useAdminProfile();
|
||||
const canRunSettlement = adminHasAnyPermission(profile?.permissions, [
|
||||
@@ -96,6 +94,9 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
||||
return <p className="text-destructive text-sm">{err ?? t("states.noData", { ns: "common" })}</p>;
|
||||
}
|
||||
|
||||
const currencyCode = data.currency_code ?? "NPR";
|
||||
const formatMoney = (minor: number) => formatAdminMinorUnits(minor, currencyCode);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
@@ -110,7 +111,7 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t("status")}</span>
|
||||
<p className="mt-1">
|
||||
<DrawStatusBadge status={data.draw_status} label={drawStatusText(data.draw_status, t)} />
|
||||
<DrawStatusBadge status={data.draw_status} label={drawStatusLabel(data.draw_status, t)} />
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -121,11 +122,11 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t("actualBet")}</span>
|
||||
<p className="tabular-nums font-medium">{data.total_bet_minor}</p>
|
||||
<p className="tabular-nums font-medium">{formatMoney(data.total_bet_minor)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t("currentPayout")}</span>
|
||||
<p className="tabular-nums font-medium">{data.total_payout_minor}</p>
|
||||
<p className="tabular-nums font-medium">{formatMoney(data.total_payout_minor)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t("grossProfit")}</span>
|
||||
@@ -135,7 +136,7 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
||||
data.approx_house_gross_minor >= 0 ? "text-emerald-600" : "text-destructive",
|
||||
)}
|
||||
>
|
||||
{data.approx_house_gross_minor}
|
||||
{formatMoney(data.approx_house_gross_minor)}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -185,7 +186,7 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
||||
<TableHead className="text-right">{t("ticketCount")}</TableHead>
|
||||
<TableHead className="text-right">{t("winCount")}</TableHead>
|
||||
<TableHead className="text-right">{t("payoutTotal")}</TableHead>
|
||||
<TableHead className="text-right">{t("jackpot")}</TableHead>
|
||||
<TableHead className="text-right">{t("jackpotPayout")}</TableHead>
|
||||
<TableHead>{t("finishedAt")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -194,7 +195,9 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
||||
<TableRow key={b.id}>
|
||||
<TableCell className="font-mono text-xs">{b.id}</TableCell>
|
||||
<TableCell>
|
||||
<AdminStatusBadge status={b.status}>{drawStatusText(b.status, t)}</AdminStatusBadge>
|
||||
<AdminStatusBadge status={b.status}>
|
||||
{settlementBatchStatusLabel(b.status, t)}
|
||||
</AdminStatusBadge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right tabular-nums text-xs">
|
||||
{b.total_ticket_count}
|
||||
@@ -203,10 +206,10 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
||||
{b.total_win_count}
|
||||
</TableCell>
|
||||
<TableCell className="text-right tabular-nums text-xs">
|
||||
{b.total_payout_amount}
|
||||
{formatMoney(b.total_payout_amount)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right tabular-nums text-xs">
|
||||
{b.total_jackpot_payout_amount}
|
||||
{formatMoney(b.total_jackpot_payout_amount)}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-[11px] text-muted-foreground">
|
||||
{b.finished_at ?? "—"}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useAdminProfile } from "@/stores/admin-session";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { AdminDrawBatchRow, AdminDrawBatchesData } from "@/types/api/admin-draws";
|
||||
|
||||
import { drawBatchStatusLabel, drawPrizeTypeLabel, drawStatusLabel } from "./draw-display";
|
||||
import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd";
|
||||
|
||||
export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchId: string }) {
|
||||
@@ -73,7 +74,12 @@ export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchI
|
||||
setPublishing(true);
|
||||
try {
|
||||
const res = await postAdminPublishResultBatch(idNum, batchNum);
|
||||
toast.success(t("publishSuccess", { drawNo: res.draw_no, status: res.status }));
|
||||
toast.success(
|
||||
t("publishSuccess", {
|
||||
drawNo: res.draw_no,
|
||||
status: drawStatusLabel(res.status, t),
|
||||
}),
|
||||
);
|
||||
await load();
|
||||
} catch (e) {
|
||||
const msg = e instanceof LotteryApiBizError ? e.message : t("publishFailed");
|
||||
@@ -125,7 +131,9 @@ export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchI
|
||||
{!canPublish && canManageDraw ? (
|
||||
<Alert>
|
||||
<AlertTitle>{t("cannotPublish")}</AlertTitle>
|
||||
<AlertDescription>{t("cannotPublishDesc", { status: batch.status })}</AlertDescription>
|
||||
<AlertDescription>
|
||||
{t("cannotPublishDesc", { status: drawBatchStatusLabel(batch.status, t) })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
{canPublish ? (
|
||||
@@ -147,7 +155,9 @@ export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchI
|
||||
<TableBody>
|
||||
{batch.items.map((it) => (
|
||||
<TableRow key={`${it.prize_type}-${it.prize_index}`}>
|
||||
<TableCell className="text-xs">{it.prize_type}</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{drawPrizeTypeLabel(it.prize_type, it.prize_index, t)}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs">{it.prize_index}</TableCell>
|
||||
<TableCell className="font-mono text-sm font-semibold">{it.number_4d}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useAdminProfile } from "@/stores/admin-session";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { AdminDrawBatchRow, AdminDrawBatchesData } from "@/types/api/admin-draws";
|
||||
|
||||
import { drawPrizeTypeLabel } from "./draw-display";
|
||||
import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd";
|
||||
import { DrawStatusBadge } from "./draw-status-badge";
|
||||
|
||||
@@ -129,7 +130,9 @@ function BatchTable({ batch }: { batch: AdminDrawBatchRow }) {
|
||||
<TableBody>
|
||||
{batch.items.map((it) => (
|
||||
<TableRow key={`${it.prize_type}-${it.prize_index}`}>
|
||||
<TableCell className="text-xs">{it.prize_type}</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{drawPrizeTypeLabel(it.prize_type, it.prize_index, t)}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs">{it.prize_index}</TableCell>
|
||||
<TableCell className="font-mono text-sm font-semibold">{it.number_4d}</TableCell>
|
||||
<TableCell className="hidden font-mono text-xs sm:table-cell">
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useAdminProfile } from "@/stores/admin-session";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { AdminDrawBatchesData } from "@/types/api/admin-draws";
|
||||
|
||||
import { drawStatusLabel } from "./draw-display";
|
||||
import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd";
|
||||
import { DrawStatusBadge } from "./draw-status-badge";
|
||||
|
||||
@@ -129,13 +130,12 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">{t("manualResultEntry")}</CardTitle>
|
||||
<p className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
{t("currentStatusAndDraft", {
|
||||
status: data.draw_status,
|
||||
}).split(data.draw_status)[0]}
|
||||
<DrawStatusBadge status={data.draw_status} />
|
||||
{t("currentStatusAndDraft", {
|
||||
status: data.draw_status,
|
||||
}).split(data.draw_status)[1] ?? ""}
|
||||
<span>{t("currentStatusLabel")}</span>
|
||||
<DrawStatusBadge
|
||||
status={data.draw_status}
|
||||
label={drawStatusLabel(data.draw_status, t)}
|
||||
/>
|
||||
<span>· {t("currentStatusDraftHint")}</span>
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
|
||||
@@ -27,7 +27,9 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
|
||||
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
|
||||
import { formatAdminMinorUnits } from "@/lib/money";
|
||||
import { useExportLabels } from "@/hooks/use-export-labels";
|
||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -35,6 +37,7 @@ import { useAdminProfile } from "@/stores/admin-session";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { AdminDrawListData, AdminDrawListItem } from "@/types/api/admin-draws";
|
||||
|
||||
import { drawStatusLabel } from "./draw-display";
|
||||
import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd";
|
||||
import { DrawStatusBadge } from "./draw-status-badge";
|
||||
|
||||
@@ -67,7 +70,9 @@ function drawAdminStatusSelectLabel(raw: unknown, t: (key: string) => string): s
|
||||
export function DrawsIndexConsole() {
|
||||
const { t } = useTranslation(["draws", "common"]);
|
||||
const exportLabels = useExportLabels("drawsList");
|
||||
useAdminCurrencyCatalog();
|
||||
const formatDt = useAdminDateTimeFormatter();
|
||||
const defaultCurrency = "NPR";
|
||||
const profile = useAdminProfile();
|
||||
const canManageDraw = adminHasAnyPermission(profile?.permissions, [PRD_DRAW_RESULT_MANAGE]);
|
||||
const [data, setData] = useState<AdminDrawListData | null>(null);
|
||||
@@ -271,22 +276,28 @@ export function DrawsIndexConsole() {
|
||||
<TableCell>
|
||||
<DrawStatusBadge
|
||||
status={row.status}
|
||||
label={drawAdminStatusSelectLabel(row.status, t)}
|
||||
label={drawStatusLabel(row.status, t)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-xs tabular-nums">
|
||||
{row.total_bet_minor ?? "—"}
|
||||
<TableCell className="text-right text-xs tabular-nums">
|
||||
{row.total_bet_minor != null
|
||||
? formatAdminMinorUnits(row.total_bet_minor, defaultCurrency)
|
||||
: "—"}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-xs tabular-nums">
|
||||
{row.total_payout_minor ?? "—"}
|
||||
<TableCell className="text-right text-xs tabular-nums">
|
||||
{row.total_payout_minor != null
|
||||
? formatAdminMinorUnits(row.total_payout_minor, defaultCurrency)
|
||||
: "—"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={cn(
|
||||
"text-right font-mono text-xs tabular-nums",
|
||||
"text-right text-xs tabular-nums",
|
||||
(row.profit_loss_minor ?? 0) < 0 ? "text-destructive" : "text-emerald-600",
|
||||
)}
|
||||
>
|
||||
{row.profit_loss_minor ?? "—"}
|
||||
{row.profit_loss_minor != null
|
||||
? formatAdminMinorUnits(row.profit_loss_minor, defaultCurrency)
|
||||
: "—"}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Link
|
||||
|
||||
Reference in New Issue
Block a user