"use client"; import type { ChartConfiguration } from "chart.js"; import { useMemo } from "react"; import { useChart } from "@/hooks/useChart"; import { useDashboardStore, useHistoryData } from "@/hooks/useDashboardStore"; import { useI18n } from "@/hooks/useI18n"; import { ProFeaturePaywall } from "@/components/dashboard/ProFeaturePaywall"; import { getHistorySummary } from "@/lib/dashboard-utils"; function HistoryChart() { const store = useDashboardStore(); const { locale } = useI18n(); const { data } = useHistoryData(); const isNoaaSettlement = store.selectedDetail?.current?.settlement_source === "noaa" || store.selectedDetail?.current?.settlement_source_label === "NOAA"; const noaaStationCode = String( store.selectedDetail?.current?.station_code || store.selectedDetail?.risk?.icao || "NOAA", ) .trim() .toUpperCase(); const summary = useMemo( () => getHistorySummary(data, store.selectedDetail?.local_date), [data, store.selectedDetail?.local_date], ); const hasMgm = store.selectedCity === "ankara" && summary.mgmSeriesComplete && summary.mgms.some((value) => value != null); const hasBestBaseline = Boolean(summary.bestModelName) && summary.bestModelName !== "MGM" && summary.bestModelSeries.some((value) => value != null); const canvasRef = useChart(() => { const datasets: NonNullable< ChartConfiguration<"line">["data"] >["datasets"] = [ { backgroundColor: "rgba(248, 113, 113, 0.1)", borderColor: "#f87171", borderWidth: 2, data: summary.actuals, label: isNoaaSettlement ? locale === "en-US" ? `NOAA Settled High (${noaaStationCode})` : `NOAA 结算最高温 (${noaaStationCode})` : locale === "en-US" ? "Observed High" : "实测最高温", pointBackgroundColor: "#f87171", pointBorderColor: "#fff", pointHoverRadius: 7, pointRadius: 5, tension: 0.2, }, { backgroundColor: "transparent", borderColor: "#34d399", borderDash: [5, 4], borderWidth: 2, data: summary.debs, label: locale === "en-US" ? "DEB Fusion" : "DEB 融合", pointHoverRadius: 6, pointRadius: 4, tension: 0.2, }, ]; if (hasMgm) { datasets.push({ backgroundColor: "transparent", borderColor: "#fb923c", borderWidth: 2, data: summary.mgms, label: locale === "en-US" ? "MGM Official Forecast" : "MGM 官方预报", pointHoverRadius: 6, pointRadius: 4, tension: 0.2, }); } if (hasBestBaseline) { datasets.push({ backgroundColor: "transparent", borderColor: "#60a5fa", borderDash: [4, 3], borderWidth: 2, data: summary.bestModelSeries, label: locale === "en-US" ? `Best Baseline (${summary.bestModelName})` : `最佳单模型 (${summary.bestModelName})`, pointHoverRadius: 6, pointRadius: 4, tension: 0.2, }); } return { data: { datasets, labels: summary.dates, }, options: { interaction: { intersect: false, mode: "index" }, maintainAspectRatio: false, plugins: { legend: { labels: { boxHeight: 12, boxWidth: 34, color: "#94a3b8", font: { family: "Inter", size: 14 }, padding: 18, }, }, tooltip: { backgroundColor: "rgba(15, 23, 42, 0.9)", borderColor: "rgba(255, 255, 255, 0.1)", borderWidth: 1, bodyFont: { family: "Inter", size: 13 }, titleFont: { family: "Inter", size: 13, weight: 600 }, callbacks: { label: (ctx) => `${ctx.dataset.label}: ${ctx.parsed.y?.toFixed(1)}°`, }, }, }, responsive: true, scales: { x: { grid: { color: "rgba(255,255,255,0.04)" }, ticks: { color: "#64748b", font: { family: "Inter", size: 12 }, padding: 8, }, }, y: { grid: { color: "rgba(255,255,255,0.04)" }, ticks: { color: "#64748b", font: { family: "Inter", size: 12 }, padding: 8, }, }, }, }, type: "line", } satisfies ChartConfiguration<"line">; }, [hasBestBaseline, hasMgm, isNoaaSettlement, noaaStationCode, summary, locale]); if (!summary.recentData.length) return null; return (
); } export function HistoryModal() { const store = useDashboardStore(); const { t, locale } = useI18n(); const { data, error, isLoading, isOpen, isRecordsLoading, meta } = useHistoryData(); const isPro = store.proAccess.subscriptionActive; const isProLoading = store.proAccess.loading; const isNoaaSettlement = store.selectedDetail?.current?.settlement_source === "noaa" || store.selectedDetail?.current?.settlement_source_label === "NOAA"; const noaaStationCode = String( store.selectedDetail?.current?.station_code || store.selectedDetail?.risk?.icao || "NOAA", ) .trim() .toUpperCase(); const noaaStationName = String(store.selectedDetail?.current?.station_name || "").trim() || String(store.selectedDetail?.risk?.airport || "").trim() || noaaStationCode; const summary = useMemo( () => getHistorySummary(data, store.selectedDetail?.local_date), [data, store.selectedDetail?.local_date], ); const settledPeakRows = useMemo( () => summary.recentData .filter( (row) => row.actual != null && row.actual_peak_time && row.deb_at_peak_minus_12h != null, ) .reverse(), [summary.recentData], ); if (!isOpen) return null; return (
{ if (event.target === event.currentTarget) { store.closeHistory(); } }} > {isProLoading ? (
{t("dashboard.loading")}
) : !isPro ? ( ) : (
{locale === "en-US" ? "Audit workspace" : "对账工作台"} {store.selectedCity?.toUpperCase() || ""}

{t("history.title", { city: store.selectedCity?.toUpperCase() || "", })}

{locale === "en-US" ? "Observed highs, DEB path, and historical baseline consistency." : "查看实测最高温、DEB 路径与历史基线是否一致。"}
{meta?.mode === "preview" ? (
{isRecordsLoading ? locale === "en-US" ? "Full records syncing" : "完整记录补齐中" : meta.hasMore ? locale === "en-US" ? `Preview ${meta.previewCount}/${meta.fullCount}` : `预览 ${meta.previewCount}/${meta.fullCount}` : locale === "en-US" ? "Full set loaded" : "完整记录已到齐"}
) : null}
{isNoaaSettlement && (
{t("lang") === "en-US" ? `${store.selectedDetail?.display_name || store.selectedCity || "This city"} historical actuals are aligned to NOAA ${noaaStationCode} (${noaaStationName}) settlement rules: use the highest rounded whole-degree Celsius reading after the date is finalized.` : `${store.selectedDetail?.display_name || store.selectedCity || "该城市"}历史对账已按 NOAA ${noaaStationCode}(${noaaStationName})结算口径对齐:采用该日最终完成质控后的最高整度摄氏值。`}
)} {isLoading ? (
{locale === "en-US" ? "Scanning archived settlement history" : "正在扫描历史结算档案"} {locale === "en-US" ? "Reconciling settled highs, DEB traces, and baseline forecasts..." : "正在对齐实测高温、DEB 轨迹与基线预报..."}
) : ( <>
{locale === "en-US" ? "Performance snapshot" : "表现快照"}

{locale === "en-US" ? "Recent settlement performance" : "近期结算表现"}

{locale === "en-US" ? "Hit rate, MAE, and baseline comparison across recent settled days." : "查看近期已结算样本里的命中率、误差与基线对照。"}
{error ? ( {t("history.error")} ) : !summary.recentData.length ? ( {t("history.empty")} ) : ( <>
{t("history.debHitRate")} {summary.hitRate != null ? `${summary.hitRate}%` : "--"}
{t("history.debMae")} {summary.debMae != null ? `${summary.debMae}°` : "--"}
{t("history.bestModelMae")} {summary.bestModelMae != null ? `${summary.bestModelMae}°${ summary.bestModelName ? ` (${summary.bestModelName})` : "" }` : "--"}
{t("history.debVsBest")} {summary.debWinRateVsBest != null ? `${summary.debWinRateVsBest}% (${summary.debWinDaysVsBest}/${summary.debVsBestComparableDays})` : "--"}
{t("history.sample")} {t("history.sampleDays", { count: summary.settledCount })}
)}
{!error && } {!error && settledPeakRows.length > 0 && (
{locale === "en-US" ? "Reference table" : "参考表"}
{locale === "en-US" ? "Peak-12h DEB Reference (Approx.)" : "峰值前 12 小时 DEB 参考(近似)"}
{locale === "en-US" ? "Use peak-minus-12h DEB as a fast sanity check for settled highs." : "用峰值前 12 小时的 DEB 作为历史结算高温的快速校验。"}
{settledPeakRows.map((row) => (
{row.date}
{locale === "en-US" ? "Peak ref" : "峰值参考"}:{" "} {row.actual} {store.selectedDetail?.temp_symbol || "°C"} @{" "} {row.actual_peak_time}
{locale === "en-US" ? "DEB@-12h" : "峰值前12小时 DEB"}:{" "} {row.deb_at_peak_minus_12h} {store.selectedDetail?.temp_symbol || "°C"} @{" "} {row.deb_at_peak_minus_12h_time}
{locale === "en-US" ? "Actual" : "最终实测"}:{" "} {row.actual} {store.selectedDetail?.temp_symbol || "°C"}
{locale === "en-US" ? "Error" : "误差"}:{" "} 0 ? "#f59e0b" : "#34d399", }} > {row.deb_at_peak_minus_12h_error != null ? `${row.deb_at_peak_minus_12h_error > 0 ? "+" : ""}${row.deb_at_peak_minus_12h_error}${store.selectedDetail?.temp_symbol || "°C"}` : "--"}
))}
)} )}
)}
); }