diff --git a/frontend/components/dashboard/Dashboard.module.css b/frontend/components/dashboard/Dashboard.module.css index e3558c5f..757c3ef8 100644 --- a/frontend/components/dashboard/Dashboard.module.css +++ b/frontend/components/dashboard/Dashboard.module.css @@ -774,8 +774,39 @@ .root :global(.panel-body) { overflow-y: auto; flex: 1; + position: relative; padding: 0 20px 24px; } + +.root :global(.panel-sync-blocker) { + position: sticky; + top: 0; + z-index: 6; + display: flex; + align-items: center; + gap: 10px; + margin: 0 0 12px; + padding: 12px 14px; + border: 1px solid rgba(34, 211, 238, 0.22); + border-radius: 8px; + background: rgba(8, 16, 30, 0.96); + color: var(--text-primary); + box-shadow: 0 14px 34px rgba(2, 6, 23, 0.38); + backdrop-filter: blur(18px); + font-size: 12px; + font-weight: 650; + line-height: 1.45; +} + +.root :global(.panel-content-stale) { + opacity: 0.28; + pointer-events: none; + user-select: none; +} + +.root :global(.panel-content-stale canvas) { + visibility: hidden; +} .root :global(.panel-body::-webkit-scrollbar) { width: 4px; } diff --git a/frontend/components/dashboard/DetailPanel.tsx b/frontend/components/dashboard/DetailPanel.tsx index 8a7acd93..25a2269a 100644 --- a/frontend/components/dashboard/DetailPanel.tsx +++ b/frontend/components/dashboard/DetailPanel.tsx @@ -196,6 +196,7 @@ export function DetailPanel() { (detail.forecast?.daily?.length ?? 0) <= 1), ); const isPanelSyncing = store.loadingState.cityDetail; + const isShowingCachedDetailDuringSync = Boolean(detail && isPanelSyncing); const blurActiveElement = () => { if (typeof document === "undefined") return; @@ -384,7 +385,18 @@ export function DetailPanel() { -
+
+ {isShowingCachedDetailDuringSync ? ( +
+
+ ) : null} +
{!hasBasicPanelContent ? (
@@ -479,6 +491,7 @@ export function DetailPanel() { {heavyContentReady ? : null} )} +
); diff --git a/frontend/hooks/useDashboardStore.tsx b/frontend/hooks/useDashboardStore.tsx index e3ca2b07..706e8878 100644 --- a/frontend/hooks/useDashboardStore.tsx +++ b/frontend/hooks/useDashboardStore.tsx @@ -788,11 +788,7 @@ export function DashboardStoreProvider({ return; } - const cachedDetail = cityDetailsByName[cityName]; - const needsDetailRefresh = hasSparseDetailCoverage( - cachedDetail, - cachedDetail?.local_date, - ); + const needsDetailRefresh = true; setLoadingState((current) => ({ ...current, cityDetail: true })); const detailPromise = ensureCityDetail(cityName, needsDetailRefresh, "panel"); void Promise.allSettled([summaryPromise, detailPromise]) diff --git a/frontend/hooks/useLeafletMap.ts b/frontend/hooks/useLeafletMap.ts index add1cd15..e8d98e72 100644 --- a/frontend/hooks/useLeafletMap.ts +++ b/frontend/hooks/useLeafletMap.ts @@ -593,6 +593,13 @@ export function useLeafletMap({ } async function maybeAutoShowNearbyStations() { + if (selectedCity && isLoadingDetail) { + autoNearbyCityRef.current = selectedCity; + clearNearbyRefreshTimer(); + layer.clearLayers(); + return; + } + if (handlingAutoNearbyRef.current) { return; } @@ -700,7 +707,7 @@ export function useLeafletMap({ map.off("zoomend", syncVisibility); map.off("moveend", maybeAutoShowNearbyStations); }; - }, [cityDetailsByName, selectedCity, selectedDetail, suspendMotion]); + }, [cityDetailsByName, selectedCity, selectedDetail, suspendMotion, isLoadingDetail]); // Centralized City Selection Zoom Effect // Higher level than selection: we only flyTo once the data is loaded (selectedDetail)