diff --git a/src/lib/admin-prd.ts b/src/lib/admin-prd.ts index dd9b236..f46cb12 100644 --- a/src/lib/admin-prd.ts +++ b/src/lib/admin-prd.ts @@ -47,11 +47,8 @@ export const PRD_RISK_VIEW = "prd.risk.view" as const; export const PRD_RISK_MANAGE = "prd.risk.manage" as const; export const PRD_ODDS_VIEW = "prd.odds.view" as const; -/** 钱包补单/冲正(冲正 + 手工处理) */ -export const PRD_WALLET_WRITE_ANY = [ - PRD_WALLET_ADJUST_MANAGE, - PRD_WALLET_RECONCILE_MANAGE, -] as const; +/** 钱包补单/冲正(冲正、补入账、手工结案等会影响资金状态的动作) */ +export const PRD_WALLET_WRITE_ANY = [PRD_WALLET_ADJUST_MANAGE] as const; /** 玩家列表页(与侧栏 requiredAny 一致) */ export const PRD_PLAYERS_ACCESS_ANY = [ diff --git a/src/modules/dashboard/agent-dashboard-console.tsx b/src/modules/dashboard/agent-dashboard-console.tsx index 08124a2..d52a696 100644 --- a/src/modules/dashboard/agent-dashboard-console.tsx +++ b/src/modules/dashboard/agent-dashboard-console.tsx @@ -18,6 +18,7 @@ import { import { getAdminDashboard } from "@/api/admin-dashboard"; import { useAsyncEffect } from "@/hooks/use-async-effect"; +import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; import { useTranslationRef } from "@/hooks/use-translation-ref"; import { useCachedPlayTypeOptions } from "@/hooks/use-cached-play-type-options"; import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog"; @@ -50,6 +51,7 @@ import { LotteryApiBizError } from "@/types/api/errors"; export function AgentDashboardConsole(): ReactElement { const { t, i18n } = useTranslation(["dashboard", "common", "agents"]); const tRef = useTranslationRef(["dashboard", "common"]); + const formatDt = useAdminDateTimeFormatter(); const profile = useAdminProfile(); const agent = profile?.agent ?? null; const permissions = useMemo(() => profile?.permissions ?? [], [profile?.permissions]); @@ -252,7 +254,7 @@ export function AgentDashboardConsole(): ReactElement { {t("agent.settlementCycle", { cycle: overview.settlement_cycle })} {overview.latest_bet_at - ? t("agent.latestBetAt", { time: new Date(overview.latest_bet_at).toLocaleString() }) + ? t("agent.latestBetAt", { time: formatDt(overview.latest_bet_at) }) : t("agent.noBetToday")} diff --git a/src/modules/dashboard/dashboard-current-draw-card.tsx b/src/modules/dashboard/dashboard-current-draw-card.tsx index ed0a8fe..9852bb9 100644 --- a/src/modules/dashboard/dashboard-current-draw-card.tsx +++ b/src/modules/dashboard/dashboard-current-draw-card.tsx @@ -9,7 +9,9 @@ import { buttonVariants } from "@/components/ui/button"; import { AdminNoResourceState } from "@/components/admin/admin-no-resource-state"; import { Card, CardContent } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; -import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; +import { formatAdminInstantInTimeZone } from "@/lib/admin-datetime"; +import { getAdminRequestLocale } from "@/lib/admin-locale"; +import { LOTTERY_SCHEDULE_TIMEZONE } from "@/lib/lottery-schedule-timezone"; import { cn } from "@/lib/utils"; import type { DrawCurrentSnapshot } from "@/types/api/public-draw"; @@ -62,7 +64,11 @@ export function DashboardCurrentDrawCard({ loading = false, }: DashboardCurrentDrawCardProps): ReactElement { const { t } = useTranslation(["dashboard", "draws"]); - const formatDt = useAdminDateTimeFormatter(); + const formatDt = (iso: string | null | undefined): string => + formatAdminInstantInTimeZone(iso, { + locale: getAdminRequestLocale(), + timeZone: LOTTERY_SCHEDULE_TIMEZONE, + }); if (loading) { return ( diff --git a/src/modules/draws/draw-create-dialog.tsx b/src/modules/draws/draw-create-dialog.tsx index a18ce85..370877b 100644 --- a/src/modules/draws/draw-create-dialog.tsx +++ b/src/modules/draws/draw-create-dialog.tsx @@ -84,7 +84,7 @@ export function DrawCreateDialog({ {t("createDraw.title")} - {t("createDraw.description", { tz: "Local" })} + {t("createDraw.description", { tz: scheduleTimezone ?? LOTTERY_SCHEDULE_TIMEZONE })} diff --git a/src/modules/draws/draw-detail-console.tsx b/src/modules/draws/draw-detail-console.tsx index b334c80..0986d5c 100644 --- a/src/modules/draws/draw-detail-console.tsx +++ b/src/modules/draws/draw-detail-console.tsx @@ -20,7 +20,9 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { AdminNoResourceState } from "@/components/admin/admin-no-resource-state"; import { AdminLoadingState } from "@/components/admin/admin-loading-state"; -import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; +import { formatAdminInstantInTimeZone } from "@/lib/admin-datetime"; +import { getAdminRequestLocale } from "@/lib/admin-locale"; +import { LOTTERY_SCHEDULE_TIMEZONE } from "@/lib/lottery-schedule-timezone"; import { useConfirmAction } from "@/hooks/use-confirm-action"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance"; @@ -46,7 +48,14 @@ type ScheduleStep = { }; function ScheduleTimeline({ steps }: { steps: ScheduleStep[] }) { - const formatDt = useAdminDateTimeFormatter(); + const formatDt = useCallback( + (iso: string | null | undefined) => + formatAdminInstantInTimeZone(iso, { + locale: getAdminRequestLocale(), + timeZone: LOTTERY_SCHEDULE_TIMEZONE, + }), + [], + ); return (
    @@ -112,7 +121,7 @@ export function DrawDetailConsole({ drawId }: { drawId: string }) { } finally { setLoading(false); } - }, [idNum]); + }, [idNum, tRef]); async function runAction(name: string, action: () => Promise): Promise { if (!Number.isFinite(idNum)) return; diff --git a/src/modules/draws/draw-edit-dialog.tsx b/src/modules/draws/draw-edit-dialog.tsx index 02b7f1f..b690908 100644 --- a/src/modules/draws/draw-edit-dialog.tsx +++ b/src/modules/draws/draw-edit-dialog.tsx @@ -17,8 +17,9 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { formatAdminInstant } from "@/lib/admin-datetime"; +import { formatAdminInstantInTimeZone } from "@/lib/admin-datetime"; import { getAdminRequestLocale } from "@/lib/admin-locale"; +import { LOTTERY_SCHEDULE_TIMEZONE } from "@/lib/lottery-schedule-timezone"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminDrawListItem } from "@/types/api/admin-draws"; @@ -30,9 +31,10 @@ type DrawEditDialogProps = { onSaved: () => void | Promise; }; -function isoToScheduleValue(iso: string | null): string { - return formatAdminInstant(iso, { +function isoToScheduleValue(iso: string | null, timeZone: string): string { + return formatAdminInstantInTimeZone(iso, { locale: getAdminRequestLocale(), + timeZone, }); } @@ -55,12 +57,13 @@ export function DrawEditDialog({ if (!open || draw == null) { return; } - setDrawTime(isoToScheduleValue(draw.draw_time)); - setCloseTime(isoToScheduleValue(draw.close_time)); - setStartTime(isoToScheduleValue(draw.start_time)); + const tz = scheduleTimezone ?? LOTTERY_SCHEDULE_TIMEZONE; + setDrawTime(isoToScheduleValue(draw.draw_time, tz)); + setCloseTime(isoToScheduleValue(draw.close_time, tz)); + setStartTime(isoToScheduleValue(draw.start_time, tz)); setDrawNo(draw.draw_no); }); - }, [open, draw]); + }, [open, draw, scheduleTimezone]); async function submit(): Promise { if (draw == null) { @@ -95,7 +98,7 @@ export function DrawEditDialog({ {t("editDraw.title")} {t("editDraw.description", { - tz: "Local", + tz: scheduleTimezone ?? LOTTERY_SCHEDULE_TIMEZONE, drawNo: draw?.draw_no ?? "", })} diff --git a/src/modules/draws/draws-index-console.tsx b/src/modules/draws/draws-index-console.tsx index cd5fda9..82818b1 100644 --- a/src/modules/draws/draws-index-console.tsx +++ b/src/modules/draws/draws-index-console.tsx @@ -14,10 +14,11 @@ import { postAdminCancelDraw, postAdminGenerateDrawPlan, } from "@/api/admin-draws"; -import { formatAdminInstant } from "@/lib/admin-datetime"; +import { formatAdminInstantInTimeZone } from "@/lib/admin-datetime"; import { getAdminRequestLocale } from "@/lib/admin-locale"; +import { LOTTERY_SCHEDULE_TIMEZONE } from "@/lib/lottery-schedule-timezone"; import { AdminTableLoadingRow } from "@/components/admin/admin-loading-state"; -import { AdminNoResourceState, AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state"; +import { AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state"; import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu"; import { Button } from "@/components/ui/button"; import { AdminTableExportButton } from "@/components/admin/admin-table-export-button"; @@ -52,7 +53,6 @@ import { import { formatAdminMinorUnits } from "@/lib/money"; import { useConfirmAction } from "@/hooks/use-confirm-action"; import { useExportLabels } from "@/hooks/use-export-labels"; -import { adminHasAnyPermission } from "@/lib/admin-permissions"; import { cn } from "@/lib/utils"; import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; @@ -60,7 +60,6 @@ import type { AdminDrawListData, AdminDrawListItem } from "@/types/api/admin-dra import { drawStatusLabel } from "./draw-display"; import { canManageDrawResults, canViewDrawFinance } from "@/lib/draw-access"; -import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd"; import { DrawStatusBadge } from "./draw-status-badge"; /** 下拉「不限」;请求时不传 status */ @@ -102,8 +101,9 @@ export function DrawsIndexConsole() { const [data, setData] = useState(null); const formatDt = useCallback( (iso: string | null | undefined) => - formatAdminInstant(iso, { + formatAdminInstantInTimeZone(iso, { locale: getAdminRequestLocale(), + timeZone: LOTTERY_SCHEDULE_TIMEZONE, }), [], ); @@ -156,7 +156,7 @@ export function DrawsIndexConsole() { } finally { setLoading(false); } - }, [page, perPage, appliedDrawNo, appliedStatus, appliedAgentNodeId]); + }, [page, perPage, appliedDrawNo, appliedStatus, appliedAgentNodeId, tRef]); async function generatePlan(): Promise { setGenerating(true); @@ -559,6 +559,7 @@ export function DrawsIndexConsole() { ) : null} @@ -571,6 +572,7 @@ export function DrawsIndexConsole() { } }} draw={editDraw} + scheduleTimezone={LOTTERY_SCHEDULE_TIMEZONE} onSaved={load} /> ) : null} diff --git a/src/modules/settlement/settlement-adjustments-table.tsx b/src/modules/settlement/settlement-adjustments-table.tsx index c8a3478..f781c47 100644 --- a/src/modules/settlement/settlement-adjustments-table.tsx +++ b/src/modules/settlement/settlement-adjustments-table.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import type { SettlementAdjustmentRow } from "@/api/admin-agent-settlement"; import { AdminNoResourceState } from "@/components/admin/admin-no-resource-state"; import { AdminLoadingState } from "@/components/admin/admin-loading-state"; +import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; import { formatSettlementPeriodSpan } from "@/lib/agent-settlement-period-range"; import { formatDashboardMoneyMinor } from "@/modules/dashboard/use-dashboard-analytics"; import { @@ -30,6 +31,7 @@ export function SettlementAdjustmentsTable({ onOpenBill, }: SettlementAdjustmentsTableProps): React.ReactElement { const { t } = useTranslation(["settlementCenter", "common"]); + const formatTs = useAdminDateTimeFormatter(); if (loading) { return ; @@ -71,7 +73,7 @@ export function SettlementAdjustmentsTable({ {formatDashboardMoneyMinor(row.amount, currencyCode)} {row.reason ?? "—"} - {row.created_at ?? "—"} + {formatTs(row.created_at)} {row.original_bill_id != null ? (