移除未使用的 HistoryModal 历史对账组件
该组件未被任何地方引用,属于死代码。同时移除关联 CSS Module 及 scan-root-styles.ts 中的 barrel 注册。
This commit is contained in:
@@ -1,530 +0,0 @@
|
||||
/* History modal styles moved out of Dashboard.module.css. */
|
||||
|
||||
.root :global(.history-btn) {
|
||||
background: rgba(34, 211, 238, 0.1);
|
||||
color: var(--accent-cyan);
|
||||
border: 1px solid rgba(34, 211, 238, 0.3);
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.root :global(.history-btn:hover) {
|
||||
background: rgba(34, 211, 238, 0.2);
|
||||
border-color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.root :global(.history-btn:disabled) {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.root :global(.modal-content.history-modal) {
|
||||
width: min(96vw, 1180px);
|
||||
max-width: 1180px;
|
||||
max-height: calc(100vh - 32px);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .modal-body) {
|
||||
padding: 24px 28px 28px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-stats) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .h-stat-card) {
|
||||
min-width: 0;
|
||||
padding: 16px 18px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.035) 0%,
|
||||
rgba(255, 255, 255, 0.02) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .h-stat-card .label) {
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .h-stat-card .val) {
|
||||
font-size: 32px;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-chart-wrapper) {
|
||||
height: 420px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
padding: 12px 14px 8px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-chart-wrapper canvas) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-chart-loading) {
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0.025),
|
||||
rgba(96, 165, 250, 0.08),
|
||||
rgba(255, 255, 255, 0.025)
|
||||
);
|
||||
background-size: 220% 100%;
|
||||
animation: history-chart-loading 1.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes history-chart-loading {
|
||||
0% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-modal-loading) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-loading) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
width: min(100%, 420px);
|
||||
padding: 24px 26px;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(96, 165, 250, 0.16);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(10, 18, 36, 0.72) 0%,
|
||||
rgba(8, 14, 30, 0.5) 100%
|
||||
);
|
||||
box-shadow:
|
||||
0 18px 48px rgba(0, 0, 0, 0.28),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-scan) {
|
||||
position: relative;
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(14, 165, 233, 0.08) 0%,
|
||||
rgba(14, 165, 233, 0.02) 62%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-ring) {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(56, 189, 248, 0.16);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-ring-1) {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-ring-2) {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-sweep) {
|
||||
position: absolute;
|
||||
inset: 5px;
|
||||
border-radius: 50%;
|
||||
background: conic-gradient(
|
||||
from 120deg,
|
||||
rgba(34, 211, 238, 0) 0deg,
|
||||
rgba(34, 211, 238, 0.04) 220deg,
|
||||
rgba(56, 189, 248, 0.78) 300deg,
|
||||
rgba(253, 224, 71, 0.32) 332deg,
|
||||
rgba(34, 211, 238, 0) 360deg
|
||||
);
|
||||
animation: history-fetch-spin 2.2s linear infinite;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-core) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(103, 232, 249, 0.95);
|
||||
box-shadow: 0 0 18px rgba(34, 211, 238, 0.42);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-bars) {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: 7px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-bar) {
|
||||
width: 8px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(250, 204, 21, 0.18) 0%,
|
||||
rgba(251, 146, 60, 0.9) 100%
|
||||
);
|
||||
animation: history-fetch-pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-bar-1) {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-bar-2) {
|
||||
height: 18px;
|
||||
animation-delay: 0.18s;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-bar-3) {
|
||||
height: 14px;
|
||||
animation-delay: 0.34s;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-bar-4) {
|
||||
height: 20px;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-lines) {
|
||||
position: relative;
|
||||
width: 180px;
|
||||
height: 22px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-line) {
|
||||
position: absolute;
|
||||
left: -26%;
|
||||
height: 1px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(34, 211, 238, 0) 0%,
|
||||
rgba(125, 211, 252, 0.72) 48%,
|
||||
rgba(34, 211, 238, 0) 100%
|
||||
);
|
||||
animation: history-fetch-flow 2.4s linear infinite;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-line-1) {
|
||||
top: 4px;
|
||||
width: 122px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-line-2) {
|
||||
top: 11px;
|
||||
width: 168px;
|
||||
animation-delay: -0.8s;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-line-3) {
|
||||
top: 18px;
|
||||
width: 138px;
|
||||
animation-delay: -1.4s;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-copy) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-copy strong) {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(103, 232, 249, 0.94);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-copy span) {
|
||||
max-width: 300px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: rgba(203, 213, 225, 0.82);
|
||||
}
|
||||
|
||||
@keyframes history-fetch-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes history-fetch-pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scaleY(0.86);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scaleY(1.08);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes history-fetch-flow {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(150%);
|
||||
}
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference) {
|
||||
margin-top: 18px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||
border-radius: 16px;
|
||||
background: rgba(15, 23, 42, 0.42);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference) {
|
||||
margin-top: 18px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgba(34, 211, 238, 0.18);
|
||||
border-radius: 16px;
|
||||
background: rgba(8, 47, 73, 0.2);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-title) {
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-scroll),
|
||||
.root :global(.history-modal .history-peak-reference-scroll) {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
padding-right: 6px;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-scroll) {
|
||||
scrollbar-color: rgba(34, 211, 238, 0.5) rgba(15, 23, 42, 0.32);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-scroll) {
|
||||
scrollbar-color: rgba(96, 165, 250, 0.5) rgba(15, 23, 42, 0.32);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-scroll::-webkit-scrollbar) {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-scroll::-webkit-scrollbar-track) {
|
||||
background: rgba(15, 23, 42, 0.32);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-scroll::-webkit-scrollbar-thumb) {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(56, 189, 248, 0.72) 0%,
|
||||
rgba(96, 165, 250, 0.52) 100%
|
||||
);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-row),
|
||||
.root :global(.history-modal .history-model-reference-row) {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(72px, 88px) 1fr;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-date) {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-body) {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-summary) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 14px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-summary strong) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-models) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-model) {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.12);
|
||||
border-radius: 8px;
|
||||
background: rgba(15, 23, 42, 0.38);
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-name) {
|
||||
overflow: hidden;
|
||||
color: var(--accent-cyan);
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-error) {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-meta) {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.root :global(.history-modal .modal-body) {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-stats) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-chart-wrapper) {
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-modal-loading) {
|
||||
min-height: 340px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-loading) {
|
||||
width: min(100%, 360px);
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-scroll),
|
||||
.root :global(.history-modal .history-model-reference-scroll) {
|
||||
max-height: 320px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-models) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.root :global(.modal-content.history-modal) {
|
||||
width: min(100vw - 12px, 100%);
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-stats) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .h-stat-card .val) {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-chart-wrapper) {
|
||||
height: 300px;
|
||||
padding: 8px 10px 6px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-modal-loading) {
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-loading) {
|
||||
width: 100%;
|
||||
padding: 18px 16px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-lines) {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-fetch-scan) {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference),
|
||||
.root :global(.history-modal .history-model-reference) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-scroll),
|
||||
.root :global(.history-modal .history-model-reference-scroll) {
|
||||
max-height: 260px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-peak-reference-row),
|
||||
.root :global(.history-modal .history-model-reference-row) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.root :global(.history-modal .history-model-reference-models) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,424 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
useDashboardHistory,
|
||||
useDashboardSelection,
|
||||
useHistoryData,
|
||||
useProAccess,
|
||||
} from "@/hooks/useDashboardStore";
|
||||
import { useI18n } from "@/hooks/useI18n";
|
||||
import { ProFeaturePaywall } from "@/components/dashboard/ProFeaturePaywall";
|
||||
import { getHistorySummary } from "@/lib/dashboard-utils";
|
||||
|
||||
const HistoryChart = dynamic(
|
||||
() =>
|
||||
import("@/components/dashboard/HistoryChart").then(
|
||||
(module) => module.HistoryChart,
|
||||
),
|
||||
{
|
||||
loading: () => <div className="history-chart-wrapper history-chart-loading" />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
|
||||
export function HistoryModal() {
|
||||
const history = useDashboardHistory();
|
||||
const selection = useDashboardSelection();
|
||||
const { proAccess } = useProAccess();
|
||||
const { t, locale } = useI18n();
|
||||
const { data, error, isLoading, isOpen, isRecordsLoading, meta } = useHistoryData();
|
||||
const selectedDetail = selection.selectedDetail;
|
||||
const selectedCity = selection.selectedCity;
|
||||
const isPro = proAccess.subscriptionActive;
|
||||
const isProLoading = proAccess.loading;
|
||||
const isNoaaSettlement =
|
||||
selectedDetail?.current?.settlement_source === "noaa" ||
|
||||
selectedDetail?.current?.settlement_source_label === "NOAA";
|
||||
const noaaStationCode = String(
|
||||
selectedDetail?.current?.station_code ||
|
||||
selectedDetail?.risk?.icao ||
|
||||
"NOAA",
|
||||
)
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
const noaaStationName =
|
||||
String(selectedDetail?.current?.station_name || "").trim() ||
|
||||
String(selectedDetail?.risk?.airport || "").trim() ||
|
||||
noaaStationCode;
|
||||
const summary = useMemo(
|
||||
() => getHistorySummary(data, selectedDetail?.local_date),
|
||||
[data, 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],
|
||||
);
|
||||
const modelReferenceRows = useMemo(
|
||||
() =>
|
||||
summary.recentData
|
||||
.filter(
|
||||
(row) =>
|
||||
row.actual != null &&
|
||||
row.model_reference?.available &&
|
||||
(row.model_reference.models?.length || 0) > 0,
|
||||
)
|
||||
.slice(-5)
|
||||
.reverse(),
|
||||
[summary.recentData],
|
||||
);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal-overlay"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="history-modal-title"
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
history.closeHistory();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isProLoading ? (
|
||||
<div
|
||||
className="modal-content"
|
||||
style={{ padding: "40px", textAlign: "center" }}
|
||||
>
|
||||
<div style={{ color: "var(--text-muted)" }}>
|
||||
{t("dashboard.loading")}
|
||||
</div>
|
||||
</div>
|
||||
) : !isPro ? (
|
||||
<>
|
||||
<div className="modal-content" style={{ padding: "24px 28px 0", textAlign: "center" }}>
|
||||
<h3 style={{ fontSize: 18, fontWeight: 800, color: "var(--color-text-primary)", margin: "0 0 8px" }}>
|
||||
{t("history.previewTitle")}
|
||||
</h3>
|
||||
<p style={{ fontSize: 13, color: "var(--color-text-secondary)", lineHeight: 1.6, margin: 0 }}>
|
||||
{t("history.previewDesc")}
|
||||
</p>
|
||||
</div>
|
||||
<ProFeaturePaywall feature="history" onClose={history.closeHistory} />
|
||||
</>
|
||||
) : (
|
||||
<div className="modal-content history-modal">
|
||||
<div className="modal-header">
|
||||
<div className="modal-title-stack">
|
||||
<div className="modal-overline">
|
||||
<span>{locale === "en-US" ? "Audit workspace" : "对账工作台"}</span>
|
||||
<span className="modal-overline-sep">•</span>
|
||||
<span>{selectedCity?.toUpperCase() || ""}</span>
|
||||
</div>
|
||||
<h2 id="history-modal-title">
|
||||
{t("history.title", {
|
||||
city: selectedCity?.toUpperCase() || "",
|
||||
})}
|
||||
</h2>
|
||||
<div className="modal-subtitle">
|
||||
{locale === "en-US"
|
||||
? "Observed highs, DEB path, and historical baseline consistency."
|
||||
: "查看实测最高温、DEB 路径与历史基线是否一致。"}
|
||||
</div>
|
||||
{meta?.mode === "preview" ? (
|
||||
<div className="modal-header-meta">
|
||||
<span className="modal-meta-pill">
|
||||
{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"
|
||||
: "完整记录已到齐"}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="modal-close"
|
||||
aria-label={t("history.closeAria")}
|
||||
onClick={history.closeHistory}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{isNoaaSettlement && (
|
||||
<div className="modal-callout modal-callout-info">
|
||||
{t("lang") === "en-US"
|
||||
? `${selectedDetail?.display_name || 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.`
|
||||
: `${selectedDetail?.display_name || selectedCity || "该城市"}历史对账已按 NOAA ${noaaStationCode}(${noaaStationName})结算口径对齐:采用该日最终完成质控后的最高整度摄氏值。`}
|
||||
</div>
|
||||
)}
|
||||
{isLoading ? (
|
||||
<div className="history-modal-loading">
|
||||
<div className="history-fetch-loading">
|
||||
<div className="history-fetch-scan" aria-hidden="true">
|
||||
<span className="history-fetch-ring history-fetch-ring-1" />
|
||||
<span className="history-fetch-ring history-fetch-ring-2" />
|
||||
<span className="history-fetch-sweep" />
|
||||
<span className="history-fetch-core" />
|
||||
</div>
|
||||
<div className="history-fetch-bars" aria-hidden="true">
|
||||
<span className="history-fetch-bar history-fetch-bar-1" />
|
||||
<span className="history-fetch-bar history-fetch-bar-2" />
|
||||
<span className="history-fetch-bar history-fetch-bar-3" />
|
||||
<span className="history-fetch-bar history-fetch-bar-4" />
|
||||
</div>
|
||||
<div className="history-fetch-lines" aria-hidden="true">
|
||||
<span className="history-fetch-line history-fetch-line-1" />
|
||||
<span className="history-fetch-line history-fetch-line-2" />
|
||||
<span className="history-fetch-line history-fetch-line-3" />
|
||||
</div>
|
||||
<div className="history-fetch-copy">
|
||||
<strong>
|
||||
{locale === "en-US"
|
||||
? "Scanning archived settlement history"
|
||||
: "正在扫描历史结算档案"}
|
||||
</strong>
|
||||
<span>
|
||||
{locale === "en-US"
|
||||
? "Reconciling settled highs, DEB traces, and baseline forecasts..."
|
||||
: "正在对齐实测高温、DEB 轨迹与基线预报..."}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="modal-section-heading">
|
||||
<div className="modal-section-kicker">
|
||||
{locale === "en-US" ? "Performance snapshot" : "表现快照"}
|
||||
</div>
|
||||
<h3>
|
||||
{locale === "en-US"
|
||||
? "Recent settlement performance"
|
||||
: "近期结算表现"}
|
||||
</h3>
|
||||
<div className="modal-section-note">
|
||||
{locale === "en-US"
|
||||
? "Hit rate, MAE, and baseline comparison across recent settled days."
|
||||
: "查看近期已结算样本里的命中率、误差与基线对照。"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="history-stats">
|
||||
{error ? (
|
||||
<span style={{ color: "var(--accent-red)" }}>
|
||||
{t("history.error")}
|
||||
</span>
|
||||
) : !summary.recentData.length ? (
|
||||
<span style={{ color: "var(--text-muted)" }}>
|
||||
{t("history.empty")}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<div className="h-stat-card">
|
||||
<span className="label">{t("history.debHitRate")}</span>
|
||||
<span className="val">
|
||||
{summary.hitRate != null ? `${summary.hitRate}%` : "--"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-stat-card">
|
||||
<span className="label">{t("history.debMae")}</span>
|
||||
<span className="val">
|
||||
{summary.debMae != null ? `${summary.debMae}°` : "--"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-stat-card">
|
||||
<span className="label">{t("history.bestModelMae")}</span>
|
||||
<span className="val">
|
||||
{summary.bestModelMae != null
|
||||
? `${summary.bestModelMae}°${
|
||||
summary.bestModelName
|
||||
? ` (${summary.bestModelName})`
|
||||
: ""
|
||||
}`
|
||||
: "--"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-stat-card">
|
||||
<span className="label">{t("history.debVsBest")}</span>
|
||||
<span className="val">
|
||||
{summary.debWinRateVsBest != null
|
||||
? `${summary.debWinRateVsBest}% (${summary.debWinDaysVsBest}/${summary.debVsBestComparableDays})`
|
||||
: "--"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-stat-card">
|
||||
<span className="label">{t("history.sample")}</span>
|
||||
<span className="val">
|
||||
{t("history.sampleDays", { count: summary.settledCount })}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!error && <HistoryChart />}
|
||||
{!error && modelReferenceRows.length > 0 && (
|
||||
<div className="history-model-reference">
|
||||
<div className="modal-section-heading">
|
||||
<div className="modal-section-kicker">
|
||||
{locale === "en-US" ? "Reference layer" : "参考层"}
|
||||
</div>
|
||||
<div className="history-peak-reference-title">
|
||||
{locale === "en-US"
|
||||
? "Model Reference at Cutoff"
|
||||
: "当时模型参考"}
|
||||
</div>
|
||||
<div className="modal-section-note">
|
||||
{locale === "en-US"
|
||||
? "These are archived model snapshots used for audit context. Settlement truth still comes only from the finalized observation source."
|
||||
: "这里展示当时归档的模型快照,仅用于解释判断背景;结算真值仍只来自最终实况来源。"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="history-model-reference-scroll">
|
||||
{modelReferenceRows.map((row) => {
|
||||
const models = (row.model_reference?.models || []).slice(0, 6);
|
||||
return (
|
||||
<div key={row.date} className="history-model-reference-row">
|
||||
<div className="history-peak-reference-date">
|
||||
{row.date}
|
||||
</div>
|
||||
<div className="history-model-reference-body">
|
||||
<div className="history-model-reference-summary">
|
||||
<span>
|
||||
{locale === "en-US" ? "Actual" : "最终实测"}{" "}
|
||||
<strong>
|
||||
{row.actual}
|
||||
{selectedDetail?.temp_symbol || "°C"}
|
||||
</strong>
|
||||
</span>
|
||||
<span>
|
||||
DEB{" "}
|
||||
<strong>
|
||||
{row.model_reference?.deb?.value ?? row.deb ?? "--"}
|
||||
{selectedDetail?.temp_symbol || "°C"}
|
||||
</strong>
|
||||
{row.model_reference?.deb?.error != null
|
||||
? ` / ${locale === "en-US" ? "err" : "误差"} ${row.model_reference.deb.error}${selectedDetail?.temp_symbol || "°C"}`
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
<div className="history-model-reference-models">
|
||||
{models.map((model) => (
|
||||
<div
|
||||
key={`${row.date}-${model.model}`}
|
||||
className="history-model-reference-model"
|
||||
>
|
||||
<span className="history-model-name">
|
||||
{model.model}
|
||||
</span>
|
||||
<span>
|
||||
{model.value}
|
||||
{selectedDetail?.temp_symbol || "°C"}
|
||||
</span>
|
||||
<span className="history-model-error">
|
||||
{model.error != null
|
||||
? `${locale === "en-US" ? "err" : "误差"} ${model.error}${selectedDetail?.temp_symbol || "°C"}`
|
||||
: locale === "en-US"
|
||||
? "pending"
|
||||
: "待结算"}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!error && settledPeakRows.length > 0 && (
|
||||
<div className="history-peak-reference">
|
||||
<div className="modal-section-heading">
|
||||
<div className="modal-section-kicker">
|
||||
{locale === "en-US" ? "Reference table" : "参考表"}
|
||||
</div>
|
||||
<div className="history-peak-reference-title">
|
||||
{locale === "en-US"
|
||||
? "Peak-12h DEB Reference (Approx.)"
|
||||
: "峰值前 12 小时 DEB 参考(近似)"}
|
||||
</div>
|
||||
<div className="modal-section-note">
|
||||
{locale === "en-US"
|
||||
? "Use peak-minus-12h DEB as a fast sanity check for settled highs."
|
||||
: "用峰值前 12 小时的 DEB 作为历史结算高温的快速校验。"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="history-peak-reference-scroll">
|
||||
{settledPeakRows.map((row) => (
|
||||
<div key={row.date} className="history-peak-reference-row">
|
||||
<div className="history-peak-reference-date">
|
||||
{row.date}
|
||||
</div>
|
||||
<div className="history-peak-reference-meta">
|
||||
<div>
|
||||
{locale === "en-US" ? "Peak ref" : "峰值参考"}:{" "}
|
||||
<span style={{ color: "var(--text-primary)" }}>
|
||||
{row.actual}
|
||||
{selectedDetail?.temp_symbol || "°C"} @{" "}
|
||||
{row.actual_peak_time}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{locale === "en-US" ? "DEB@-12h" : "峰值前12小时 DEB"}:{" "}
|
||||
<span style={{ color: "var(--text-primary)" }}>
|
||||
{row.deb_at_peak_minus_12h}
|
||||
{selectedDetail?.temp_symbol || "°C"} @{" "}
|
||||
{row.deb_at_peak_minus_12h_time}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{locale === "en-US" ? "Actual" : "最终实测"}:{" "}
|
||||
<span style={{ color: "var(--text-primary)" }}>
|
||||
{row.actual}
|
||||
{selectedDetail?.temp_symbol || "°C"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{locale === "en-US" ? "Error" : "误差"}:{" "}
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
(row.deb_at_peak_minus_12h_error ?? 0) > 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}${selectedDetail?.temp_symbol || "°C"}`
|
||||
: "--"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import detailChromeStyles from "./DetailPanelChrome.module.css";
|
||||
import detailContentStyles from "./DetailPanelContent.module.css";
|
||||
import detailSectionsStyles from "./DetailPanelSections.module.css";
|
||||
import futureForecastModalStyles from "./FutureForecastModal.module.css";
|
||||
import historyModalStyles from "./HistoryModal.module.css";
|
||||
import modalChromeStyles from "./ModalChrome.module.css";
|
||||
import scanTerminalStyles from "./ScanTerminal.module.css";
|
||||
import scanTerminalBoardStyles from "./ScanTerminalBoard.module.css";
|
||||
@@ -48,6 +47,5 @@ export const scanRootClass = clsx(
|
||||
detailSectionsStyles.root,
|
||||
modalChromeStyles.root,
|
||||
futureForecastModalStyles.root,
|
||||
historyModalStyles.root,
|
||||
monitorPanelStyles.root,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user