Files
PolyWeather/frontend/components/dashboard/scan-terminal/CityCardHeader.tsx
T
2569718930@qq.com b26ec05b81 AMOS 跑道温度范围:城市决策卡头部展示"跑道实况 14.6~15.2℃"
后端:
- amos_station_sources.py 新增 runway_temp_range (跑道温度 min,max)
- 取所有有效跑道温度的最小值和最大值

前端:
- CityCardHeader 新增 observedLabel prop(标签自定义)
- 有 AMOS 数据时显示"跑道实况"替代"当前温度"
- 温度展示格式:14.6~15.2℃(跑道温度范围)
- 无 AMOS 时回退原有 METAR 温度显示
2026-05-10 20:19:48 +08:00

133 lines
3.8 KiB
TypeScript

"use client";
import { ChevronDown, RefreshCw, X } from "lucide-react";
import type { MouseEvent } from "react";
import {
CityStatusTags,
type CityStatusTag,
type StatusTone,
} from "@/components/dashboard/scan-terminal/CityStatusTags";
import {
DataFreshnessBar,
type DataFreshnessRow,
} from "@/components/dashboard/scan-terminal/DataFreshnessBar";
export function CityCardHeader({
aiStatusLabel,
aiStatusTone,
collapseId,
collapsed,
currentTempText,
observedLabel,
dataFreshnessRows,
debText,
detailLocalTime,
displayName,
expectedHighText,
freshnessSeparator,
isEn,
isRefreshing,
modelRange,
onRefresh,
onRemove,
onToggleCollapsed,
peakWindow,
removing,
rowLocalTime,
statusTags,
}: {
aiStatusLabel: string;
aiStatusTone: StatusTone;
collapseId: string;
collapsed: boolean;
currentTempText: string;
observedLabel?: string;
dataFreshnessRows: DataFreshnessRow[];
debText: string;
detailLocalTime?: string | null;
displayName: string;
expectedHighText: string;
freshnessSeparator: string;
isEn: boolean;
isRefreshing: boolean;
modelRange: string;
onRefresh: (event: MouseEvent<HTMLButtonElement>) => void;
onRemove: (event: MouseEvent<HTMLButtonElement>) => void;
onToggleCollapsed: () => void;
peakWindow: string;
removing?: boolean;
rowLocalTime?: string | null;
statusTags: CityStatusTag[];
}) {
return (
<header className="scan-ai-city-hero">
<div className="scan-ai-city-hero-left">
<span className="scan-ai-city-kicker">
{isEn ? "Deep analysis" : "城市深度分析"}
</span>
<h3>{displayName}</h3>
<CityStatusTags tags={statusTags} />
<DataFreshnessBar
aiStatusLabel={aiStatusLabel}
aiStatusTone={aiStatusTone}
freshnessSeparator={freshnessSeparator}
isEn={isEn}
rows={dataFreshnessRows}
/>
</div>
<div className="scan-ai-city-hero-side">
<div className="scan-ai-city-metrics">
<span>
<small>{observedLabel || (isEn ? "Observed" : "当前温度")}</small>
<b>{currentTempText}</b>
</span>
<span className="primary">
<small>{isEn ? "Expected high" : "预计最高温"}</small>
<b>{expectedHighText}</b>
</span>
<span>
<small>{isEn ? "Peak" : "峰值时间"}</small>
<b>{peakWindow}</b>
</span>
</div>
<div className="scan-ai-city-actions">
<button
type="button"
className="scan-ai-city-icon-button"
onClick={onRefresh}
aria-label={isEn ? `Refresh ${displayName} analysis` : `刷新 ${displayName} 深度分析`}
title={
isEn
? "Refresh city data, chart and AI analysis"
: "刷新城市数据、温度走势图和 AI 分析"
}
disabled={isRefreshing}
>
<RefreshCw size={15} className={isRefreshing ? "spin" : undefined} />
</button>
<button
type="button"
className="scan-ai-city-icon-button danger"
onClick={onRemove}
aria-label={isEn ? `Remove ${displayName}` : `移除 ${displayName}`}
title={isEn ? "Remove city" : "移除城市"}
disabled={removing}
>
<X size={15} />
</button>
<button
type="button"
className="scan-ai-city-collapse"
onClick={onToggleCollapsed}
aria-expanded={!collapsed}
aria-controls={collapseId}
>
<ChevronDown size={15} />
{collapsed ? (isEn ? "Expand" : "展开") : (isEn ? "Collapse" : "收起")}
</button>
</div>
</div>
</header>
);
}