From 6222f6057370c38aa811b53781c8d4c73c77215a Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 30 May 2026 22:37:08 +0800 Subject: [PATCH] Improve terminal chart loading states --- .../scan-terminal/TemperatureChartCanvas.tsx | 53 ++++++++++++++++++- .../__tests__/temperatureStatsLabels.test.ts | 5 ++ .../__tests__/terminalGridPolicy.test.ts | 36 ++++++++++++- .../dashboard/scan-terminal/utils.ts | 2 + 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/frontend/components/dashboard/scan-terminal/TemperatureChartCanvas.tsx b/frontend/components/dashboard/scan-terminal/TemperatureChartCanvas.tsx index 574238f2..41b9c1a8 100644 --- a/frontend/components/dashboard/scan-terminal/TemperatureChartCanvas.tsx +++ b/frontend/components/dashboard/scan-terminal/TemperatureChartCanvas.tsx @@ -64,10 +64,37 @@ function shouldKeepTemperatureChartLoading({ zoomedData: Array>; }) { if (!row?.city) return false; - if (isHourlyLoading) return true; + if (!isHourlyLoading) return false; return !hasDrawableTemperatureChartContent({ activeSeries, probabilityOverlay, zoomedData }); } +function TemperatureChartSkeleton({ compact }: { compact: boolean }) { + const horizontalLines = compact ? 5 : 7; + const verticalLines = compact ? 5 : 8; + + return ( +
+
+ {Array.from({ length: horizontalLines }).map((_, index) => ( + + ))} + {Array.from({ length: verticalLines }).map((_, index) => ( + + ))} +
+
+
+ ); +} + function TemperatureChartCanvasComponent({ isEn, compact, @@ -173,6 +200,11 @@ function TemperatureChartCanvasComponent({ hasRunwayData && individualRunwaySeriesCount > 1 && collapsedRunwaySeries.length < chartSeries.length; + const hasDrawableChartContent = hasDrawableTemperatureChartContent({ + activeSeries, + probabilityOverlay, + zoomedData, + }); const shouldShowChartLoading = shouldKeepTemperatureChartLoading({ row, isHourlyLoading, @@ -180,6 +212,9 @@ function TemperatureChartCanvasComponent({ probabilityOverlay, zoomedData, }); + const shouldRenderChart = canRenderChart && hasDrawableChartContent; + const shouldShowEmptyState = Boolean(row?.city) && !isHourlyLoading && !hasDrawableChartContent; + const shouldShowBackgroundRefresh = isHourlyLoading && hasDrawableChartContent; return (
@@ -242,7 +277,8 @@ function TemperatureChartCanvasComponent({ )}
- {canRenderChart && !shouldShowChartLoading && ( + {!shouldRenderChart && } + {shouldRenderChart && ( )} + {shouldShowEmptyState && ( +
+
+ {isEn ? "No drawable chart data yet" : "暂无可绘制图表数据"} +
+
+ )}
+ {shouldShowBackgroundRefresh && ( +
+ + {isEn ? "Updating" : "更新中"} +
+ )} {shouldShowChartLoading && (
diff --git a/frontend/components/dashboard/scan-terminal/__tests__/temperatureStatsLabels.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/temperatureStatsLabels.test.ts index 8e4653d5..d86a8893 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/temperatureStatsLabels.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/temperatureStatsLabels.test.ts @@ -1,4 +1,5 @@ import { __buildTemperatureStatsLabelsForTest } from "@/components/dashboard/scan-terminal/TemperatureStatsBars"; +import { temp } from "@/components/dashboard/scan-terminal/utils"; function assert(condition: unknown, message: string) { if (!condition) throw new Error(message); @@ -46,4 +47,8 @@ export function runTests() { assert(zh.primary === "天文台实测 (10分钟)", "Chinese primary label should remain unchanged"); assert(zh.compactSecondary === "当日最高", "Chinese Shenzhen compact secondary label should remain 当日最高"); + + assert(temp(null, "°C") === "--", "empty temperature values should not render as 0.0°C while city detail is loading"); + assert(temp(undefined, "°C") === "--", "undefined temperature values should not render as 0.0°C while city detail is loading"); + assert(temp("", "°C") === "--", "blank temperature values should not render as 0.0°C while city detail is loading"); } diff --git a/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts index e88d4e9c..781d39c2 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/terminalGridPolicy.test.ts @@ -121,6 +121,19 @@ export function runTests() { ); assert( __shouldKeepTemperatureChartLoadingForTest({ + row: { city: "Moscow" } as any, + isHourlyLoading: true, + activeSeries: [], + probabilityOverlay: null, + zoomedData: [ + { label: "00:00", ts: 1 }, + { label: "05:00", ts: 2 }, + ], + }), + "temperature chart should show the loading skeleton while the first detail fetch is in flight and no drawable data exists", + ); + assert( + !__shouldKeepTemperatureChartLoadingForTest({ row: { city: "Moscow" } as any, isHourlyLoading: false, activeSeries: [], @@ -130,7 +143,7 @@ export function runTests() { { label: "05:00", ts: 2 }, ], }), - "temperature chart must keep loading instead of rendering an empty axis grid when no drawable series is available", + "temperature chart must stop showing an indefinite loading overlay after the detail fetch finishes without drawable data", ); assert( !__shouldKeepTemperatureChartLoadingForTest({ @@ -153,4 +166,25 @@ export function runTests() { }), "temperature chart should render once a visible series has drawable values", ); + assert( + !__shouldKeepTemperatureChartLoadingForTest({ + row: { city: "Moscow" } as any, + isHourlyLoading: true, + activeSeries: [ + { + key: "current", + label: "Current reference", + source: "Live", + color: "#009688", + values: [13, 13], + }, + ] as any, + probabilityOverlay: null, + zoomedData: [ + { label: "00:00", ts: 1, current: 13 }, + { label: "05:00", ts: 2, current: 13 }, + ], + }), + "temperature chart must render seeded or cached data immediately while full detail continues loading in the background", + ); } diff --git a/frontend/components/dashboard/scan-terminal/utils.ts b/frontend/components/dashboard/scan-terminal/utils.ts index 9d949f4d..249758cf 100644 --- a/frontend/components/dashboard/scan-terminal/utils.ts +++ b/frontend/components/dashboard/scan-terminal/utils.ts @@ -17,6 +17,8 @@ export function money(value: unknown) { } export function temp(value: unknown, unit?: string | null) { + if (value == null) return "--"; + if (typeof value === "string" && value.trim() === "") return "--"; const n = Number(value); if (!Number.isFinite(n)) return "--"; return `${n.toFixed(1)}${unit || "°"}`;