fix: enhance loading state handling in NetworkChart component

This commit is contained in:
hamster1963
2026-06-01 11:19:41 +08:00
parent fcaff0bae0
commit 57aaba3918
2 changed files with 57 additions and 6 deletions
+53 -4
View File
@@ -1,6 +1,6 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { m } from "framer-motion";
import * as React from "react";
import { useCallback, useMemo } from "react";
@@ -41,7 +41,6 @@ import {
} from "@/lib/nezha-api";
import { cn, formatTime } from "@/lib/utils";
import type { NezhaMonitor, ServerMonitorChart } from "@/types/nezha-api";
import NetworkChartLoading from "./NetworkChartLoading";
import { Label } from "./ui/label";
import { Switch } from "./ui/switch";
@@ -51,6 +50,8 @@ interface ResultItem {
[key: string]: number;
}
const MIN_PERIOD_LOADING_MS = 500;
/**
* Helper method to calculate packet loss from delay data
*/
@@ -148,10 +149,11 @@ export function NetworkChart({
}
}, [isLogin, period]);
const { data: monitorData } = useQuery({
const { data: monitorData, isPlaceholderData } = useQuery({
queryKey: ["monitor", server_id, period],
queryFn: () => fetchMonitor(server_id, period),
enabled: show,
placeholderData: keepPreviousData,
refetchOnMount: true,
refetchOnWindowFocus: true,
refetchInterval: 10000,
@@ -215,6 +217,7 @@ export function NetworkChart({
chartData={transformedData}
serverName={monitorData.data[0].server_name}
formattedData={formattedData}
isPeriodLoading={isPlaceholderData}
period={period}
onPeriodChange={setPeriod}
isLogin={isLogin}
@@ -228,6 +231,7 @@ export const NetworkChartClient = React.memo(function NetworkChart({
chartData,
serverName,
formattedData,
isPeriodLoading,
period,
onPeriodChange,
isLogin,
@@ -237,11 +241,14 @@ export const NetworkChartClient = React.memo(function NetworkChart({
chartData: ServerMonitorChart;
serverName: string;
formattedData: ResultItem[];
isPeriodLoading: boolean;
period: MonitorPeriod;
onPeriodChange: (period: MonitorPeriod) => void;
isLogin: boolean;
}) {
const { t } = useTranslation();
const [showPeriodLoading, setShowPeriodLoading] = React.useState(false);
const loadingStartedAtRef = React.useRef<number | null>(null);
const TIME_RANGE_OPTIONS: { value: MonitorPeriod; label: string }[] = [
{ value: "1d", label: t("monitor.period1d") },
@@ -249,6 +256,36 @@ export const NetworkChartClient = React.memo(function NetworkChart({
{ value: "30d", label: t("monitor.period30d") },
];
React.useEffect(() => {
let timeoutId: number | undefined;
if (isPeriodLoading) {
loadingStartedAtRef.current = Date.now();
setShowPeriodLoading(true);
return;
}
const loadingStartedAt = loadingStartedAtRef.current;
if (loadingStartedAt === null) {
setShowPeriodLoading(false);
return;
}
const elapsed = Date.now() - loadingStartedAt;
const remaining = Math.max(0, MIN_PERIOD_LOADING_MS - elapsed);
timeoutId = window.setTimeout(() => {
setShowPeriodLoading(false);
loadingStartedAtRef.current = null;
}, remaining);
return () => {
if (timeoutId !== undefined) {
window.clearTimeout(timeoutId);
}
};
}, [isPeriodLoading]);
const customBackgroundImage =
(window.CustomBackgroundImage as string) !== ""
? window.CustomBackgroundImage
@@ -631,7 +668,10 @@ export const NetworkChartClient = React.memo(function NetworkChart({
)}
<ChartContainer
config={chartConfig}
className="aspect-auto h-[250px] w-full"
className={cn(
"aspect-auto h-62.5 w-full transition-opacity",
showPeriodLoading && "opacity-60",
)}
>
<ComposedChart
accessibilityLayer
@@ -746,6 +786,15 @@ export const NetworkChartClient = React.memo(function NetworkChart({
{chartElements}
</ComposedChart>
</ChartContainer>
{showPeriodLoading && (
<div className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-md backdrop-blur-[1px]">
<div className="flex size-9 items-center justify-center">
<div className="absolute inset-0 flex items-center justify-center">
<div className="size-4 rounded-full border-2 border-muted-foreground/20 border-t-muted-foreground/70 animate-spin" />
</div>
</div>
</div>
)}
</div>
</CardContent>
</Card>
+3 -1
View File
@@ -40,11 +40,13 @@ const RESPONSIVE_CHART_TYPES = new Set<React.ElementType>([
function shouldUseResponsiveContainer(children: React.ReactNode) {
const elements = React.Children.toArray(children);
return elements.length > 0 &&
return (
elements.length > 0 &&
elements.every(
(child) =>
React.isValidElement(child) &&
RESPONSIVE_CHART_TYPES.has(child.type as React.ElementType),
)
);
}