diff --git a/frontend/components/dashboard/FutureForecastModal.tsx b/frontend/components/dashboard/FutureForecastModal.tsx index cbdee5ea..a062bd47 100644 --- a/frontend/components/dashboard/FutureForecastModal.tsx +++ b/frontend/components/dashboard/FutureForecastModal.tsx @@ -711,15 +711,18 @@ export function FutureForecastModal() { const dateStr = store.futureModalDate; const isPro = store.proAccess.subscriptionActive; const isProLoading = store.proAccess.loading; + const hasModalContext = Boolean(detail && dateStr); const [showDeferredTodaySections, setShowDeferredTodaySections] = useState(false); const [freshMarketScan, setFreshMarketScan] = useState( null, ); - if (!detail || !dateStr) return null; - useEffect(() => { + if (!hasModalContext) { + setShowDeferredTodaySections(false); + return; + } setShowDeferredTodaySections(false); if (typeof window === "undefined") { setShowDeferredTodaySections(true); @@ -750,23 +753,23 @@ export function FutureForecastModal() { clearTimeout(timeoutId); } }; - }, [dateStr, detail]); + }, [dateStr, detail, hasModalContext]); const isToday = store.forecastModalMode === "today" || - (store.forecastModalMode == null && dateStr === detail.local_date); - const detailDepth = detail.detail_depth || "full"; + (store.forecastModalMode == null && dateStr === detail?.local_date); + const detailDepth = detail?.detail_depth || "full"; const isFullDetailReady = detailDepth === "full"; const isStructureSyncing = store.loadingState.futureDeep || !isFullDetailReady; const isAnyLayerSyncing = isStructureSyncing; const isTodayBlockingRefresh = isToday && isStructureSyncing; - const activeMarketScan = freshMarketScan || detail.market_scan || null; + const activeMarketScan = freshMarketScan || detail?.market_scan || null; useEffect(() => { setFreshMarketScan(null); - if (!isToday || !isFullDetailReady || !isPro) return; - const cityName = String(detail.name || detail.display_name || "").trim(); + if (!hasModalContext || !isToday || !isFullDetailReady || !isPro) return; + const cityName = String(detail?.name || detail?.display_name || "").trim(); if (!cityName || !dateStr) return; let cancelled = false; @@ -777,7 +780,7 @@ export function FutureForecastModal() { .getCityMarketScan(cityName, { force: false, lite: false, - marketSlug: detail.market_scan?.primary_market?.slug || null, + marketSlug: detail?.market_scan?.primary_market?.slug || null, targetDate: dateStr, }) .then((payload) => { @@ -810,16 +813,19 @@ export function FutureForecastModal() { }; }, [ dateStr, - detail.display_name, - detail.local_date, - detail.market_scan?.primary_market?.slug, - detail.name, - detail.updated_at, + detail?.display_name, + detail?.local_date, + detail?.market_scan?.primary_market?.slug, + detail?.name, + detail?.updated_at, + hasModalContext, isFullDetailReady, isPro, isToday, ]); + if (!detail || !dateStr) return null; + const view = getFutureModalView(detail, dateStr, locale); const scorePosition = `${50 + view.front.score / 2}%`; const barStyle = { diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index aa88db56..bf40382e 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -34,6 +34,7 @@ import { import { I18nProvider, useI18n } from "@/hooks/useI18n"; import { dashboardClient } from "@/lib/dashboard-client"; import type { + CityDetail, DistributionPreviewPoint, MarketScan, PrimarySignal, @@ -238,6 +239,122 @@ function buildComparisonBuckets( ]; } +function MapCityDetailPanel({ detail }: { detail: CityDetail | null }) { + const { locale } = useI18n(); + const isEn = locale === "en-US"; + + if (!detail) { + return ( + + ); + } + + const cityName = getLocalizedCityName( + detail.name, + detail.display_name || detail.name, + locale, + ); + const tempSymbol = detail.temp_symbol || "°C"; + const currentTemp = detail.current?.temp; + const dayHigh = + detail.current?.max_so_far ?? + detail.airport_current?.max_so_far ?? + detail.airport_primary?.max_so_far ?? + null; + const airportLabel = getLocalizedAirportName( + detail.name, + detail.risk?.airport || detail.risk?.icao || "", + locale, + ); + const peakLabel = + Number.isFinite(Number(detail.peak?.first_h)) && Number.isFinite(Number(detail.peak?.last_h)) + ? `${String(Number(detail.peak?.first_h)).padStart(2, "0")}:00-${String( + Number(detail.peak?.last_h) + 1, + ).padStart(2, "0")}:00` + : "--"; + + return ( + + ); +} + function DetailPanel({ row, marketScan, @@ -788,6 +905,8 @@ function ScanTerminalScreen() { }, [selectedRow, detailByRowId]); const selectedDetail = selectedRow ? detailByRowId[selectedRow.id] : null; + const mapFocusedDetail = store.selectedDetail; + const mapFocusedCity = store.selectedCity; const resolvedView: ContentView = activeSection === "markets" ? "map" @@ -982,11 +1101,15 @@ function ScanTerminalScreen() { - + {resolvedView === "map" && mapFocusedCity ? ( + + ) : ( + + )} );