拆分 FutureForecastModalContent:提取 TodayLayout 组件
- 新建 FutureForecastTodayLayout:封装今日视图双栏布局(左侧卡片+右侧图表) - FutureForecastModalContent 从 1098 行降至 999 行 - 纯 JSX 提取,不修改任何业务逻辑、状态或数据流 Tested: npx tsc --noEmit ✓
This commit is contained in:
@@ -21,25 +21,15 @@ import type {
|
|||||||
ProAccessState,
|
ProAccessState,
|
||||||
} from "@/lib/dashboard-types";
|
} from "@/lib/dashboard-types";
|
||||||
import { FutureForecastForwardView } from "./FutureForecastForwardView";
|
import { FutureForecastForwardView } from "./FutureForecastForwardView";
|
||||||
import {
|
|
||||||
FutureModelForecastPanel,
|
|
||||||
FutureProbabilityPanel,
|
|
||||||
FutureTemperaturePathChart,
|
|
||||||
} from "./FutureForecastModalPanels";
|
|
||||||
import { FutureForecastTodayDecisionBrief } from "./FutureForecastTodayDecisionBrief";
|
import { FutureForecastTodayDecisionBrief } from "./FutureForecastTodayDecisionBrief";
|
||||||
import { FutureForecastTodayEvidenceGrid } from "./FutureForecastTodayEvidenceGrid";
|
import { FutureForecastTodayLayout } from "./FutureForecastTodayLayout";
|
||||||
import { FutureForecastModalHeader } from "./FutureForecastModalHeader";
|
import { FutureForecastModalHeader } from "./FutureForecastModalHeader";
|
||||||
import {
|
import {
|
||||||
FutureRefreshLock,
|
FutureRefreshLock,
|
||||||
FutureSyncStatusStrip,
|
FutureSyncStatusStrip,
|
||||||
type FutureSyncStatusItem,
|
type FutureSyncStatusItem,
|
||||||
} from "./FutureForecastModalStatus";
|
} from "./FutureForecastModalStatus";
|
||||||
import {
|
import { type FuturePaceSignalItem } from "./FutureForecastTodayCards";
|
||||||
FutureAnchorStatusCard,
|
|
||||||
FuturePaceCard,
|
|
||||||
FuturePaceLoadingCard,
|
|
||||||
type FuturePaceSignalItem,
|
|
||||||
} from "./FutureForecastTodayCards";
|
|
||||||
import {
|
import {
|
||||||
TODAY_MARKET_SCAN_AUTO_REFRESH_MS,
|
TODAY_MARKET_SCAN_AUTO_REFRESH_MS,
|
||||||
clamp,
|
clamp,
|
||||||
@@ -966,121 +956,32 @@ export function FutureForecastModalContent({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isToday ? (
|
{isToday ? (
|
||||||
<div className="future-v2-layout">
|
<FutureForecastTodayLayout
|
||||||
<aside className="future-v2-left">
|
locale={locale}
|
||||||
<FutureAnchorStatusCard
|
isToday={isToday}
|
||||||
locale={locale}
|
dateStr={dateStr}
|
||||||
currentTempText={currentTempText}
|
detail={detail}
|
||||||
weatherSummary={weatherSummary}
|
currentTempText={currentTempText}
|
||||||
obsTime={detail.current?.obs_time}
|
weatherSummary={weatherSummary}
|
||||||
daylightProgress={daylightProgress}
|
daylightProgress={daylightProgress}
|
||||||
sunrise={detail.forecast?.sunrise}
|
topObservedTemp={topObservedTemp}
|
||||||
sunset={detail.forecast?.sunset}
|
gapToBaseBucket={gapToBaseBucket}
|
||||||
topObservedTemp={topObservedTemp}
|
pathStatus={pathStatus}
|
||||||
tempSymbol={detail.temp_symbol}
|
showDeferredTodaySections={showDeferredTodaySections}
|
||||||
gapToBaseBucket={gapToBaseBucket}
|
paceView={paceView}
|
||||||
pathStatus={pathStatus}
|
paceSignalItems={paceSignalItems}
|
||||||
/>
|
baseCaseBucket={baseCaseBucket}
|
||||||
|
upsideBucket={intradayMeteorology.upside_bucket}
|
||||||
{showDeferredTodaySections && paceView ? (
|
nextObservationTime={nextObservationTime}
|
||||||
<FuturePaceCard
|
airportMetarAnchor={airportMetarAnchor}
|
||||||
locale={locale}
|
confirmationRules={confirmationRules}
|
||||||
paceView={paceView}
|
invalidationRules={invalidationRules}
|
||||||
tempSymbol={detail.temp_symbol}
|
meteorologySignals={meteorologySignals}
|
||||||
signalItems={paceSignalItems}
|
modelSummary={modelSummary}
|
||||||
/>
|
probabilityTitle={probabilityTitle}
|
||||||
) : isToday ? (
|
probabilitySummary={probabilitySummary}
|
||||||
<FuturePaceLoadingCard locale={locale} />
|
activeMarketScan={activeMarketScan}
|
||||||
) : null}
|
/>
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main className="future-v2-right">
|
|
||||||
<section className="future-modal-section future-v2-main-chart">
|
|
||||||
<div className="modal-section-heading">
|
|
||||||
<div className="modal-section-kicker">
|
|
||||||
{locale === "en-US" ? "Primary view" : "主视图"}
|
|
||||||
</div>
|
|
||||||
<h3>
|
|
||||||
{locale === "en-US"
|
|
||||||
? "Today's temperature path (anchor obs + models)"
|
|
||||||
: "今日气温路径(锚点观测 + 模型)"}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<FutureTemperaturePathChart dateStr={dateStr} forceToday={isToday} />
|
|
||||||
<div className="future-v2-chart-thresholds">
|
|
||||||
<span>
|
|
||||||
{locale === "en-US" ? "Base" : "基准"} ·{" "}
|
|
||||||
{baseCaseBucket || "--"}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{locale === "en-US" ? "Upside" : "上修"} ·{" "}
|
|
||||||
{intradayMeteorology.upside_bucket || "--"}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{locale === "en-US" ? "Invalidates at" : "失效观察"} ·{" "}
|
|
||||||
{nextObservationTime}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<FutureForecastTodayEvidenceGrid
|
|
||||||
airportMetarAnchor={airportMetarAnchor}
|
|
||||||
confirmationRules={confirmationRules}
|
|
||||||
invalidationRules={invalidationRules}
|
|
||||||
locale={locale}
|
|
||||||
meteorologySignals={meteorologySignals}
|
|
||||||
modelSummary={modelSummary}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="future-modal-grid">
|
|
||||||
<section className="future-modal-section">
|
|
||||||
<div className="modal-section-heading">
|
|
||||||
<div className="modal-section-kicker">
|
|
||||||
{locale === "en-US" ? "Probability read" : "概率判断"}
|
|
||||||
</div>
|
|
||||||
<h3>{probabilityTitle}</h3>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="future-text-block"
|
|
||||||
style={{ marginBottom: "12px" }}
|
|
||||||
>
|
|
||||||
{probabilitySummary}
|
|
||||||
</div>
|
|
||||||
<div style={{ position: "relative", minHeight: "120px" }}>
|
|
||||||
<FutureProbabilityPanel
|
|
||||||
detail={detail}
|
|
||||||
targetDate={dateStr}
|
|
||||||
marketScan={activeMarketScan}
|
|
||||||
hideTitle
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="future-modal-section">
|
|
||||||
<div className="modal-section-heading">
|
|
||||||
<div className="modal-section-kicker">
|
|
||||||
{locale === "en-US" ? "Model layer" : "模型层"}
|
|
||||||
</div>
|
|
||||||
<h3>
|
|
||||||
{locale === "en-US"
|
|
||||||
? "Model Range & Spread"
|
|
||||||
: "模型区间与分歧"}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="future-text-block"
|
|
||||||
style={{ marginBottom: "12px" }}
|
|
||||||
>
|
|
||||||
{modelSummary}
|
|
||||||
</div>
|
|
||||||
<FutureModelForecastPanel
|
|
||||||
detail={detail}
|
|
||||||
targetDate={dateStr}
|
|
||||||
hideTitle
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<FutureForecastForwardView
|
<FutureForecastForwardView
|
||||||
dateStr={dateStr}
|
dateStr={dateStr}
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { MarketScan, CityDetail } from "@/lib/dashboard-types";
|
||||||
|
import type { FuturePaceSignalItem } from "./FutureForecastTodayCards";
|
||||||
|
import { FutureForecastTodayEvidenceGrid } from "./FutureForecastTodayEvidenceGrid";
|
||||||
|
import {
|
||||||
|
FutureModelForecastPanel,
|
||||||
|
FutureProbabilityPanel,
|
||||||
|
FutureTemperaturePathChart,
|
||||||
|
} from "./FutureForecastModalPanels";
|
||||||
|
import {
|
||||||
|
FutureAnchorStatusCard,
|
||||||
|
FuturePaceCard,
|
||||||
|
FuturePaceLoadingCard,
|
||||||
|
} from "./FutureForecastTodayCards";
|
||||||
|
|
||||||
|
type Locale = string;
|
||||||
|
|
||||||
|
interface WeatherSummaryView {
|
||||||
|
weatherIcon: string;
|
||||||
|
weatherText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DaylightProgressView {
|
||||||
|
phase: string;
|
||||||
|
percent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type PaceView = any;
|
||||||
|
|
||||||
|
export interface FutureForecastTodayLayoutProps {
|
||||||
|
locale: Locale;
|
||||||
|
isToday: boolean;
|
||||||
|
dateStr: string;
|
||||||
|
detail: CityDetail;
|
||||||
|
currentTempText: string;
|
||||||
|
weatherSummary: WeatherSummaryView;
|
||||||
|
daylightProgress: DaylightProgressView | null;
|
||||||
|
topObservedTemp: number | string | null | undefined;
|
||||||
|
gapToBaseBucket: number | null;
|
||||||
|
pathStatus: string;
|
||||||
|
showDeferredTodaySections: boolean;
|
||||||
|
paceView: PaceView | null;
|
||||||
|
paceSignalItems: FuturePaceSignalItem[];
|
||||||
|
baseCaseBucket: string | undefined;
|
||||||
|
upsideBucket: string | null | undefined;
|
||||||
|
nextObservationTime: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
airportMetarAnchor: any;
|
||||||
|
confirmationRules: string[];
|
||||||
|
invalidationRules: string[];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
meteorologySignals: any[];
|
||||||
|
modelSummary: string;
|
||||||
|
probabilityTitle: string;
|
||||||
|
probabilitySummary: string;
|
||||||
|
activeMarketScan: MarketScan | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FutureForecastTodayLayout(props: FutureForecastTodayLayoutProps) {
|
||||||
|
const {
|
||||||
|
locale,
|
||||||
|
isToday,
|
||||||
|
dateStr,
|
||||||
|
detail,
|
||||||
|
currentTempText,
|
||||||
|
weatherSummary,
|
||||||
|
daylightProgress,
|
||||||
|
topObservedTemp,
|
||||||
|
gapToBaseBucket,
|
||||||
|
pathStatus,
|
||||||
|
showDeferredTodaySections,
|
||||||
|
paceView,
|
||||||
|
paceSignalItems,
|
||||||
|
baseCaseBucket,
|
||||||
|
upsideBucket,
|
||||||
|
nextObservationTime,
|
||||||
|
airportMetarAnchor,
|
||||||
|
confirmationRules,
|
||||||
|
invalidationRules,
|
||||||
|
meteorologySignals,
|
||||||
|
modelSummary,
|
||||||
|
probabilityTitle,
|
||||||
|
probabilitySummary,
|
||||||
|
activeMarketScan,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="future-v2-layout">
|
||||||
|
<aside className="future-v2-left">
|
||||||
|
<FutureAnchorStatusCard
|
||||||
|
locale={locale}
|
||||||
|
currentTempText={currentTempText}
|
||||||
|
weatherSummary={weatherSummary}
|
||||||
|
obsTime={detail.current?.obs_time}
|
||||||
|
daylightProgress={daylightProgress}
|
||||||
|
sunrise={detail.forecast?.sunrise}
|
||||||
|
sunset={detail.forecast?.sunset}
|
||||||
|
topObservedTemp={topObservedTemp}
|
||||||
|
tempSymbol={detail.temp_symbol}
|
||||||
|
gapToBaseBucket={gapToBaseBucket}
|
||||||
|
pathStatus={pathStatus}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showDeferredTodaySections && paceView ? (
|
||||||
|
<FuturePaceCard
|
||||||
|
locale={locale}
|
||||||
|
paceView={paceView}
|
||||||
|
tempSymbol={detail.temp_symbol}
|
||||||
|
signalItems={paceSignalItems}
|
||||||
|
/>
|
||||||
|
) : isToday ? (
|
||||||
|
<FuturePaceLoadingCard locale={locale} />
|
||||||
|
) : null}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main className="future-v2-right">
|
||||||
|
<section className="future-modal-section future-v2-main-chart">
|
||||||
|
<div className="modal-section-heading">
|
||||||
|
<div className="modal-section-kicker">
|
||||||
|
{locale === "en-US" ? "Primary view" : "主视图"}
|
||||||
|
</div>
|
||||||
|
<h3>
|
||||||
|
{locale === "en-US"
|
||||||
|
? "Today's temperature path (anchor obs + models)"
|
||||||
|
: "今日气温路径(锚点观测 + 模型)"}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<FutureTemperaturePathChart dateStr={dateStr} forceToday={isToday} />
|
||||||
|
<div className="future-v2-chart-thresholds">
|
||||||
|
<span>
|
||||||
|
{locale === "en-US" ? "Base" : "基准"} ·{" "}
|
||||||
|
{baseCaseBucket || "--"}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{locale === "en-US" ? "Upside" : "上修"} ·{" "}
|
||||||
|
{upsideBucket || "--"}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{locale === "en-US" ? "Invalidates at" : "失效观察"} ·{" "}
|
||||||
|
{nextObservationTime}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<FutureForecastTodayEvidenceGrid
|
||||||
|
airportMetarAnchor={airportMetarAnchor}
|
||||||
|
confirmationRules={confirmationRules}
|
||||||
|
invalidationRules={invalidationRules}
|
||||||
|
locale={locale}
|
||||||
|
meteorologySignals={meteorologySignals}
|
||||||
|
modelSummary={modelSummary}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="future-modal-grid">
|
||||||
|
<section className="future-modal-section">
|
||||||
|
<div className="modal-section-heading">
|
||||||
|
<div className="modal-section-kicker">
|
||||||
|
{locale === "en-US" ? "Probability read" : "概率判断"}
|
||||||
|
</div>
|
||||||
|
<h3>{probabilityTitle}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="future-text-block" style={{ marginBottom: "12px" }}>
|
||||||
|
{probabilitySummary}
|
||||||
|
</div>
|
||||||
|
<div style={{ position: "relative", minHeight: "120px" }}>
|
||||||
|
<FutureProbabilityPanel
|
||||||
|
detail={detail}
|
||||||
|
targetDate={dateStr}
|
||||||
|
marketScan={activeMarketScan}
|
||||||
|
hideTitle
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className="future-modal-section">
|
||||||
|
<div className="modal-section-heading">
|
||||||
|
<div className="modal-section-kicker">
|
||||||
|
{locale === "en-US" ? "Model layer" : "模型层"}
|
||||||
|
</div>
|
||||||
|
<h3>
|
||||||
|
{locale === "en-US"
|
||||||
|
? "Model Range & Spread"
|
||||||
|
: "模型区间与分歧"}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="future-text-block" style={{ marginBottom: "12px" }}>
|
||||||
|
{modelSummary}
|
||||||
|
</div>
|
||||||
|
<FutureModelForecastPanel detail={detail} targetDate={dateStr} hideTitle />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user