"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { RefreshCw } from "lucide-react"; import { useCityDetails, useDashboardActions } from "@/hooks/useDashboardStore"; import { useI18n } from "@/hooks/useI18n"; import type { CityDetail } from "@/lib/dashboard-types"; const CHINA_RUNWAY_CITIES = [ { key: "beijing", zh: "北京", en: "Beijing", icao: "ZBAA" }, { key: "shanghai", zh: "上海", en: "Shanghai", icao: "ZSPD" }, { key: "guangzhou", zh: "广州", en: "Guangzhou", icao: "ZGGG" }, { key: "shenzhen", zh: "深圳", en: "Shenzhen", icao: "ZGSZ" }, { key: "chengdu", zh: "成都", en: "Chengdu", icao: "ZUUU" }, { key: "chongqing", zh: "重庆", en: "Chongqing", icao: "ZUCK" }, { key: "wuhan", zh: "武汉", en: "Wuhan", icao: "ZHHH" }, ] as const; function formatTemp(value: number | null | undefined, symbol = "°C") { return value == null || !Number.isFinite(Number(value)) ? "-" : `${Number(value).toFixed(1)}${symbol}`; } function getRunwayRows(detail?: CityDetail | null) { return detail?.amos?.runway_obs?.point_temperatures ?? []; } function sourceIsAmsc(detail?: CityDetail | null) { return detail?.amos?.source === "amsc_awos"; } function RunwayCityCard({ detail, isEn, label, }: { detail?: CityDetail | null; isEn: boolean; label: (typeof CHINA_RUNWAY_CITIES)[number]; }) { const rows = getRunwayRows(detail); const tempSymbol = detail?.temp_symbol || "°C"; const range = detail?.amos?.runway_temp_range; const obsLocal = detail?.amos?.observation_time_local || detail?.amos?.observation_time; const hasAmsc = sourceIsAmsc(detail) && rows.length > 0; return (
{isEn ? label.en : label.zh} / {label.icao} {obsLocal || "--"}
AMSC AWOS {range ? `${range[0].toFixed(1)}–${range[1].toFixed(1)}${tempSymbol}` : "--"}
{isEn ? "Runway-point air temperature" : "跑道观测点气温"} {isEn ? "not pavement temp" : "非道面温度"}
{hasAmsc ? ( <>
{isEn ? "Runway" : "跑道"} TDZ / MID / END
{rows.map((row) => (
{row.runway || "--"} {formatTemp(row.tdz_temp, tempSymbol)} /{" "} {formatTemp(row.mid_temp, tempSymbol)} /{" "} {formatTemp(row.end_temp, tempSymbol)}
))} {detail?.amos?.raw_metar ? (
METAR {detail.amos.raw_metar.replace(/^METAR\s+/i, "")}
) : null} ) : (
{isEn ? "No AMSC runway observation loaded yet." : "暂无 AMSC 跑道观测。"}
)}
); } export function RunwayObservationsPanel() { const { locale } = useI18n(); const isEn = locale === "en-US"; const { cityDetailsByName } = useCityDetails(); const { ensureCityDetail } = useDashboardActions(); const [refreshing, setRefreshing] = useState(false); const loadAll = useCallback( async (force: boolean) => { setRefreshing(true); try { await Promise.allSettled( CHINA_RUNWAY_CITIES.map((city) => ensureCityDetail(city.key, force, "panel"), ), ); } finally { setRefreshing(false); } }, [ensureCityDetail], ); useEffect(() => { void loadAll(false); }, [loadAll]); const cards = useMemo( () => CHINA_RUNWAY_CITIES.map((city) => ({ ...city, detail: cityDetailsByName[city.key], })), [cityDetailsByName], ); return (
{isEn ? "Runway Observations" : "跑道观测"}
{cards.map((city) => ( ))}
); }