移除未使用的 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 detailContentStyles from "./DetailPanelContent.module.css";
|
||||||
import detailSectionsStyles from "./DetailPanelSections.module.css";
|
import detailSectionsStyles from "./DetailPanelSections.module.css";
|
||||||
import futureForecastModalStyles from "./FutureForecastModal.module.css";
|
import futureForecastModalStyles from "./FutureForecastModal.module.css";
|
||||||
import historyModalStyles from "./HistoryModal.module.css";
|
|
||||||
import modalChromeStyles from "./ModalChrome.module.css";
|
import modalChromeStyles from "./ModalChrome.module.css";
|
||||||
import scanTerminalStyles from "./ScanTerminal.module.css";
|
import scanTerminalStyles from "./ScanTerminal.module.css";
|
||||||
import scanTerminalBoardStyles from "./ScanTerminalBoard.module.css";
|
import scanTerminalBoardStyles from "./ScanTerminalBoard.module.css";
|
||||||
@@ -48,6 +47,5 @@ export const scanRootClass = clsx(
|
|||||||
detailSectionsStyles.root,
|
detailSectionsStyles.root,
|
||||||
modalChromeStyles.root,
|
modalChromeStyles.root,
|
||||||
futureForecastModalStyles.root,
|
futureForecastModalStyles.root,
|
||||||
historyModalStyles.root,
|
|
||||||
monitorPanelStyles.root,
|
monitorPanelStyles.root,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user