feat(dashboard, risk): add SWR query hook and lazy load charts
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -23,10 +24,6 @@ import { getAdminRequestLocale } from "@/lib/admin-locale";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DashboardKpiCard } from "@/modules/dashboard/dashboard-visuals";
|
||||
import { DASHBOARD_CHART_COLORS } from "@/modules/dashboard/dashboard-chart-config";
|
||||
import {
|
||||
DailyTrendChart,
|
||||
PlayBreakdownChart,
|
||||
} from "@/modules/dashboard/dashboard-trend-charts";
|
||||
import {
|
||||
DASHBOARD_ANALYTICS_PERIODS,
|
||||
DASHBOARD_RANKING_METRICS,
|
||||
@@ -34,6 +31,16 @@ import {
|
||||
type DashboardAnalyticsState,
|
||||
} from "@/modules/dashboard/use-dashboard-analytics";
|
||||
|
||||
// recharts 图表组件懒加载
|
||||
const DailyTrendChart = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-trend-charts").then((m) => ({ default: m.DailyTrendChart })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const PlayBreakdownChart = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-trend-charts").then((m) => ({ default: m.PlayBreakdownChart })),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
function computeDeltaPercent(series: number[]): string | null {
|
||||
if (series.length < 2) {
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useMemo, useState, type ReactElement, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -35,16 +36,6 @@ import { AdminNoResourceState, AdminTableNoResourceRow } from "@/components/admi
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
AbnormalTransferPanelFooter,
|
||||
CapUsageBar,
|
||||
FinanceStructureChart,
|
||||
HotUsageBars,
|
||||
ResultBatchQueueSummary,
|
||||
PlatformLifetimePayoutSnapshot,
|
||||
DashboardPanelCard,
|
||||
SettlementStatusChart,
|
||||
} from "@/modules/dashboard/dashboard-visuals";
|
||||
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
|
||||
import { adminWeekdayKeyForDate, formatAdminCalendarToday } from "@/lib/admin-datetime";
|
||||
import { normalizeAdminLanguage } from "@/i18n";
|
||||
@@ -66,6 +57,40 @@ 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 DashboardPanelCard = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.DashboardPanelCard })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const AbnormalTransferPanelFooter = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.AbnormalTransferPanelFooter })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const CapUsageBar = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.CapUsageBar })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const FinanceStructureChart = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.FinanceStructureChart })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const HotUsageBars = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.HotUsageBars })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const ResultBatchQueueSummary = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.ResultBatchQueueSummary })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const PlatformLifetimePayoutSnapshot = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.PlatformLifetimePayoutSnapshot })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const SettlementStatusChart = dynamic(
|
||||
() => import("@/modules/dashboard/dashboard-visuals").then((m) => ({ default: m.SettlementStatusChart })),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
type HotPlayTab = "4D" | "3D" | "2D" | "special";
|
||||
|
||||
function formatMoneyMinor(minor: number, currencyCode: string | null): string {
|
||||
|
||||
@@ -1,42 +1,28 @@
|
||||
"use client";
|
||||
import { AdminLoadingState, AdminLoadingInline, AdminTableLoadingRow } from "@/components/admin/admin-loading-state";
|
||||
import { AdminLoadingInline } from "@/components/admin/admin-loading-state";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAsyncEffect } from "@/hooks/use-async-effect";
|
||||
import { useTranslationRef } from "@/hooks/use-translation-ref";
|
||||
import { useApiQuery } from "@/hooks/use-api-query";
|
||||
|
||||
import { getAdminDraw } from "@/api/admin-draws";
|
||||
import { drawStatusLabel, hallPreviewDiffersFromDbStatus } from "@/modules/draws/draw-display";
|
||||
import { DrawStatusBadge } from "@/modules/draws/draw-status-badge";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { AdminDrawShowData } from "@/types/api/admin-draws";
|
||||
|
||||
export function RiskDrawHeader({ drawId }: { drawId: number }) {
|
||||
const { t } = useTranslation(["risk", "draws"]);
|
||||
const tRef = useTranslationRef(["risk", "draws"]);
|
||||
const [draw, setDraw] = useState<AdminDrawShowData | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setError(null);
|
||||
try {
|
||||
const d = await getAdminDraw(drawId);
|
||||
setDraw(d);
|
||||
} catch (e) {
|
||||
const msg =
|
||||
e instanceof LotteryApiBizError ? e.message : tRef.current("drawInfoLoadFailed");
|
||||
setError(msg);
|
||||
setDraw(null);
|
||||
}
|
||||
}, [drawId]);
|
||||
|
||||
useAsyncEffect(() => {
|
||||
void load();
|
||||
}, [drawId]);
|
||||
const { data: draw, error } = useApiQuery(
|
||||
["admin/draws", drawId],
|
||||
() => getAdminDraw(drawId),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <p className="text-sm text-destructive">{error}</p>;
|
||||
const msg =
|
||||
error instanceof LotteryApiBizError ? error.message : tRef.current("drawInfoLoadFailed");
|
||||
return <p className="text-sm text-destructive">{msg}</p>;
|
||||
}
|
||||
|
||||
if (!draw) {
|
||||
|
||||
Reference in New Issue
Block a user