101 lines
2.9 KiB
TypeScript
101 lines
2.9 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import clsx from "clsx";
|
|||
|
|
import type { Locale } from "@/lib/i18n";
|
|||
|
|
|
|||
|
|
type FutureForecastModalHeaderProps = {
|
|||
|
|
cityDisplayName: string;
|
|||
|
|
dateStr: string;
|
|||
|
|
isAnyLayerSyncing: boolean;
|
|||
|
|
isPro: boolean;
|
|||
|
|
isProLoading: boolean;
|
|||
|
|
isToday: boolean;
|
|||
|
|
locale: Locale;
|
|||
|
|
onClose: () => void;
|
|||
|
|
onRefresh: () => void;
|
|||
|
|
t: (key: string, params?: Record<string, string | number>) => string;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export function FutureForecastModalHeader({
|
|||
|
|
cityDisplayName,
|
|||
|
|
dateStr,
|
|||
|
|
isAnyLayerSyncing,
|
|||
|
|
isPro,
|
|||
|
|
isProLoading,
|
|||
|
|
isToday,
|
|||
|
|
locale,
|
|||
|
|
onClose,
|
|||
|
|
onRefresh,
|
|||
|
|
t,
|
|||
|
|
}: FutureForecastModalHeaderProps) {
|
|||
|
|
const cityLabel = cityDisplayName.toUpperCase();
|
|||
|
|
return (
|
|||
|
|
<div className="modal-header">
|
|||
|
|
<div className="modal-title-stack">
|
|||
|
|
<div className="modal-overline">
|
|||
|
|
<span>{locale === "en-US" ? "Analysis workspace" : "分析工作台"}</span>
|
|||
|
|
<span className="modal-overline-sep">•</span>
|
|||
|
|
<span>{cityLabel}</span>
|
|||
|
|
</div>
|
|||
|
|
<h2 id="future-modal-title" className="future-modal-title-with-actions">
|
|||
|
|
<span>
|
|||
|
|
{isToday
|
|||
|
|
? t("future.todayTitle", {
|
|||
|
|
city: cityLabel,
|
|||
|
|
})
|
|||
|
|
: t("future.dateTitle", {
|
|||
|
|
city: cityLabel,
|
|||
|
|
date: dateStr,
|
|||
|
|
})}
|
|||
|
|
</span>
|
|||
|
|
<button
|
|||
|
|
className={clsx("future-refresh-btn", isAnyLayerSyncing && "spinning")}
|
|||
|
|
disabled={!isPro || isProLoading}
|
|||
|
|
onClick={onRefresh}
|
|||
|
|
title={
|
|||
|
|
!isPro
|
|||
|
|
? locale === "en-US"
|
|||
|
|
? "Pro subscription required"
|
|||
|
|
: "需要 Pro 订阅"
|
|||
|
|
: locale === "en-US"
|
|||
|
|
? "Refresh Data"
|
|||
|
|
: "刷新数据"
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
<svg
|
|||
|
|
width="14"
|
|||
|
|
height="14"
|
|||
|
|
viewBox="0 0 24 24"
|
|||
|
|
fill="none"
|
|||
|
|
stroke="currentColor"
|
|||
|
|
strokeWidth="2.5"
|
|||
|
|
strokeLinecap="round"
|
|||
|
|
strokeLinejoin="round"
|
|||
|
|
>
|
|||
|
|
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
|||
|
|
<path d="M3 3v5h5" />
|
|||
|
|
</svg>
|
|||
|
|
</button>
|
|||
|
|
</h2>
|
|||
|
|
<div className="modal-subtitle">
|
|||
|
|
{isToday
|
|||
|
|
? locale === "en-US"
|
|||
|
|
? "Base signal first, then probability and model layers."
|
|||
|
|
: "先看基础信号,再看概率层和模型层。"
|
|||
|
|
: locale === "en-US"
|
|||
|
|
? "Forward date view with phased model and structure sync."
|
|||
|
|
: "未来日期视图,模型层与结构层分阶段补齐。"}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
className="modal-close"
|
|||
|
|
aria-label={isToday ? t("future.closeTodayAria") : t("future.closeDateAria")}
|
|||
|
|
onClick={onClose}
|
|||
|
|
>
|
|||
|
|
×
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|