feat(dashboard, i18n): enhance agent dashboard and localization support
Updated the agent dashboard to include new metrics for today's bets and payouts, improving visibility for users. Enhanced localization files with additional hints and labels for better user experience across English, Nepali, and Chinese. Introduced new functions for formatting business dates and improved the handling of analytics permissions in the dashboard components.
This commit is contained in:
@@ -4,7 +4,7 @@ import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useMemo, useState, type ReactElement } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AlertTriangle, ClipboardList, RefreshCw, Shield, Wallet } from "lucide-react";
|
||||
import { AlertTriangle, BarChart3, ClipboardList, RefreshCw, Shield, TrendingUp, Wallet } from "lucide-react";
|
||||
|
||||
import { getAdminDashboardByScope } from "@/api/admin-dashboard";
|
||||
import { useAdminPlayTypeCatalog } from "@/hooks/use-admin-play-type-catalog";
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
} from "@/modules/dashboard/dashboard-analytics-panel";
|
||||
import { DashboardCurrentDrawCard } from "@/modules/dashboard/dashboard-current-draw-card";
|
||||
import { useDashboardAnalytics } from "@/modules/dashboard/use-dashboard-analytics";
|
||||
import {
|
||||
formatDashboardMoneyMinor,
|
||||
} from "@/modules/dashboard/use-dashboard-analytics";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { AdminNoResourceState, AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
@@ -39,12 +42,18 @@ import type {
|
||||
AdminDashboardLifetimeFinance,
|
||||
AdminDashboardPlatformRisk,
|
||||
AdminDashboardResultBatchQueue,
|
||||
AdminDashboardTodayFinance,
|
||||
AdminDashboardWarning,
|
||||
} from "@/types/api/admin-dashboard";
|
||||
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
|
||||
import type { AdminRiskPoolRow } from "@/types/api/admin-risk";
|
||||
import type { DrawCurrentSnapshot } from "@/types/api/public-draw";
|
||||
|
||||
// recharts 图表组件懒加载,避免 ~200KB 进入主 bundle
|
||||
const DashboardKpiCard = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.DashboardKpiCard })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const DashboardPanelCard = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.DashboardPanelCard })),
|
||||
{ ssr: false },
|
||||
@@ -172,6 +181,8 @@ export function DashboardConsole(): ReactElement {
|
||||
const [lifetimeFinance, setLifetimeFinance] = useState<AdminDashboardLifetimeFinance | null>(
|
||||
null,
|
||||
);
|
||||
const [todayFinance, setTodayFinance] = useState<AdminDashboardTodayFinance | null>(null);
|
||||
const [apiWarnings, setApiWarnings] = useState<AdminDashboardWarning[]>([]);
|
||||
const [platformRisk, setPlatformRisk] = useState<AdminDashboardPlatformRisk | null>(null);
|
||||
const [riskLocked, setRiskLocked] = useState(0);
|
||||
const [riskCap, setRiskCap] = useState(0);
|
||||
@@ -193,6 +204,8 @@ export function DashboardConsole(): ReactElement {
|
||||
setDrawPanel(null);
|
||||
setResultBatchQueue(null);
|
||||
setLifetimeFinance(null);
|
||||
setTodayFinance(null);
|
||||
setApiWarnings([]);
|
||||
setPlatformRisk(null);
|
||||
setDrawId(null);
|
||||
setRiskLocked(0);
|
||||
@@ -214,6 +227,8 @@ export function DashboardConsole(): ReactElement {
|
||||
}
|
||||
setResultBatchQueue(d.result_batch_queue);
|
||||
setLifetimeFinance(d.lifetime_finance);
|
||||
setTodayFinance(d.today_finance);
|
||||
setApiWarnings(d.warnings ?? []);
|
||||
setPlatformRisk(d.platform_risk);
|
||||
if (d.draw != null) {
|
||||
setDrawPanel(d.draw);
|
||||
@@ -239,7 +254,10 @@ export function DashboardConsole(): ReactElement {
|
||||
}, []);
|
||||
|
||||
const currency =
|
||||
lifetimeFinance?.currency_code ?? finance?.currency_code ?? null;
|
||||
todayFinance?.currency_code
|
||||
?? lifetimeFinance?.currency_code
|
||||
?? finance?.currency_code
|
||||
?? null;
|
||||
const canFinance = capabilities?.draw_finance_risk ?? false;
|
||||
const platformLocked = coerceAdminMinor(platformRisk?.locked_amount);
|
||||
const platformCap = coerceAdminMinor(platformRisk?.cap_amount);
|
||||
@@ -296,6 +314,13 @@ export function DashboardConsole(): ReactElement {
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{!loading && apiWarnings.length > 0 ? (
|
||||
<Alert className="border-amber-200 bg-amber-50 dark:border-amber-900/60 dark:bg-amber-950/30">
|
||||
<AlertTitle>{t("notice")}</AlertTitle>
|
||||
<AlertDescription>{apiWarnings.map((w) => w.message).join(" ")}</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<section className="flex min-w-0 flex-col gap-4">
|
||||
<DashboardCurrentDrawCard
|
||||
key={`${hall?.draw_no ?? "empty"}:${hall?.seconds_to_close ?? 0}:${loading ? "loading" : "ready"}`}
|
||||
@@ -303,6 +328,42 @@ export function DashboardConsole(): ReactElement {
|
||||
drawId={drawId}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
{canFinance && !loading && todayFinance ? (
|
||||
<div className="grid min-w-0 grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<DashboardKpiCard
|
||||
label={t("todayBetTotal")}
|
||||
value={formatDashboardMoneyMinor(todayFinance.total_bet_minor, currency)}
|
||||
icon={<TrendingUp className="size-4" />}
|
||||
hint={t("todayBusinessDateHint", { date: todayFinance.business_date })}
|
||||
/>
|
||||
<DashboardKpiCard
|
||||
label={t("todayProfit")}
|
||||
signedAmountMinor={todayFinance.approx_house_gross_minor}
|
||||
currencyCode={currency}
|
||||
icon={<BarChart3 className="size-4" />}
|
||||
hint={t("todayPayoutHint", {
|
||||
amount: formatDashboardMoneyMinor(todayFinance.total_payout_minor, currency),
|
||||
})}
|
||||
/>
|
||||
<DashboardKpiCard
|
||||
label={t("lifetimeProfit")}
|
||||
signedAmountMinor={lifetimeFinance?.approx_house_gross_minor}
|
||||
currencyCode={currency}
|
||||
icon={<Wallet className="size-4" />}
|
||||
hint={
|
||||
lifetimeFinance
|
||||
? t("lifetimeActivityHint", {
|
||||
draws: lifetimeFinance.draw_count,
|
||||
days: lifetimeFinance.business_day_count,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
value={lifetimeFinance ? undefined : "—"}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid min-w-0 grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<DashboardPanelCard
|
||||
href={pendingReviewHref(drawId, resultBatchQueue)}
|
||||
@@ -337,7 +398,7 @@ export function DashboardConsole(): ReactElement {
|
||||
>
|
||||
<AbnormalTransferPanelFooter
|
||||
total={abnormalTransferTotal}
|
||||
walletPermission={capabilities?.wallet_transfer_view ?? true}
|
||||
walletPermission={capabilities?.wallet_transfer_view ?? false}
|
||||
/>
|
||||
</DashboardPanelCard>
|
||||
|
||||
@@ -345,16 +406,6 @@ export function DashboardConsole(): ReactElement {
|
||||
href="/admin/risk"
|
||||
title={t("riskCapUsage")}
|
||||
value={`${platformUsagePct.toFixed(1)}%`}
|
||||
subtitle={
|
||||
platformCap > 0
|
||||
? t("platformLockedAndCap", {
|
||||
locked: formatMoneyMinor(platformLocked, currency),
|
||||
cap: formatMoneyMinor(platformCap, currency),
|
||||
})
|
||||
: t("platformCapNotConfigured", {
|
||||
locked: formatMoneyMinor(platformLocked, currency),
|
||||
})
|
||||
}
|
||||
actionLabel={t("occupancyDetails")}
|
||||
icon={<Shield className="size-5" aria-hidden />}
|
||||
accent={
|
||||
@@ -380,7 +431,7 @@ export function DashboardConsole(): ReactElement {
|
||||
|
||||
<DashboardPanelCard
|
||||
href="/admin/reports"
|
||||
title={t("payoutComposition")}
|
||||
title={t("lifetimePayout")}
|
||||
value={
|
||||
lifetimeFinance
|
||||
? formatMoneyMinor(lifetimeFinance.total_payout_minor, currency)
|
||||
|
||||
Reference in New Issue
Block a user