Fix runway chart collapse and ops cleanup
This commit is contained in:
@@ -15,7 +15,11 @@ import {
|
||||
} from "recharts";
|
||||
import type { ScanOpportunityRow } from "@/lib/dashboard-types";
|
||||
import { TemperatureTooltipContent } from "@/components/dashboard/scan-terminal/TemperatureTooltipContent";
|
||||
import type { EvidenceSeries, ProbabilityOverlay } from "@/components/dashboard/scan-terminal/temperature-chart-logic";
|
||||
import {
|
||||
getTemperatureSeriesForRunwayDetailsMode,
|
||||
type EvidenceSeries,
|
||||
type ProbabilityOverlay,
|
||||
} from "@/components/dashboard/scan-terminal/temperature-chart-logic";
|
||||
|
||||
type CityThreshold = {
|
||||
threshold: number;
|
||||
@@ -120,23 +124,25 @@ export function TemperatureChartCanvas({
|
||||
const individualRunwaySeriesCount = chartSeries.filter(
|
||||
(series) => series.key.startsWith("runway_") && series.key !== "runway_max",
|
||||
).length;
|
||||
const collapsedRunwaySeries = getTemperatureSeriesForRunwayDetailsMode(
|
||||
row?.city || "",
|
||||
chartSeries,
|
||||
false,
|
||||
);
|
||||
const canToggleRunwayDetails =
|
||||
hasRunwayData &&
|
||||
individualRunwaySeriesCount > 1 &&
|
||||
chartSeries.some((series) => series.key === "runway_max");
|
||||
collapsedRunwaySeries.length < chartSeries.length;
|
||||
|
||||
return (
|
||||
<div className={clsx("relative flex flex-1 flex-col p-2", compact ? "min-h-[120px]" : "min-h-[240px]")}>
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-1.5 px-3 py-1.5 text-[11px] border-b border-[#e2e8f0] bg-white">
|
||||
{chartSeries.length > 1 &&
|
||||
chartSeries
|
||||
.filter((s) => {
|
||||
const isIndividualRunway = s.key.startsWith("runway_") && s.key !== "runway_max";
|
||||
if (showRunwayDetails) {
|
||||
return s.key !== "runway_max";
|
||||
}
|
||||
return !isIndividualRunway;
|
||||
})
|
||||
getTemperatureSeriesForRunwayDetailsMode(
|
||||
row?.city || "",
|
||||
chartSeries,
|
||||
showRunwayDetails,
|
||||
)
|
||||
.map((s) => (
|
||||
<button
|
||||
key={s.key}
|
||||
|
||||
+58
@@ -850,6 +850,64 @@ export function runTests() {
|
||||
"runway header should prefer runway-history current temp even when the latest detail payload lacks runway_obs snapshot",
|
||||
);
|
||||
|
||||
const wuhanRunwayChart = __buildTemperatureChartDataForTest(
|
||||
{
|
||||
city: "wuhan",
|
||||
local_date: "2026-05-27",
|
||||
local_time: "13:54",
|
||||
tz_offset_seconds: 8 * 60 * 60,
|
||||
temp_symbol: "°C",
|
||||
} as any,
|
||||
{
|
||||
localTime: "13:54",
|
||||
times: ["00:00", "12:00", "18:00", "23:00"],
|
||||
temps: [22.0, 30.0, 29.0, 25.0],
|
||||
runwayPlateHistory: {
|
||||
"04/22": [
|
||||
{ time: "13:52", temp: 24.0 },
|
||||
{ time: "13:54", temp: 24.2 },
|
||||
],
|
||||
"05L/23R": [
|
||||
{ time: "13:52", temp: 25.0 },
|
||||
{ time: "13:54", temp: 25.5 },
|
||||
],
|
||||
},
|
||||
runwayBandHistory: [
|
||||
{ time: "2026-05-27T13:52:00+08:00", low_temp: 24.0, high_temp: 25.0, avg_temp: 24.5 },
|
||||
{ time: "2026-05-27T13:54:00+08:00", low_temp: 24.2, high_temp: 25.5, avg_temp: 24.9 },
|
||||
],
|
||||
} as any,
|
||||
"1D",
|
||||
);
|
||||
const wuhanCollapsedRunwaySeries = __getActiveTemperatureSeriesForTest(
|
||||
"wuhan",
|
||||
wuhanRunwayChart.series,
|
||||
{},
|
||||
false,
|
||||
);
|
||||
assert(
|
||||
wuhanCollapsedRunwaySeries.some((item: any) => item.key === runwayKey("04/22")),
|
||||
"collapsed runway view should keep the settlement runway series",
|
||||
);
|
||||
assert(
|
||||
!wuhanCollapsedRunwaySeries.some((item: any) => item.key === runwayKey("05L/23R")),
|
||||
"collapsed runway view should hide auxiliary runway detail series",
|
||||
);
|
||||
assert(
|
||||
!wuhanCollapsedRunwaySeries.some((item: any) => item.key === "runway_max"),
|
||||
"collapsed runway view should not replace the settlement runway with runway max",
|
||||
);
|
||||
const wuhanCollapsedWithSettlementHidden = __getActiveTemperatureSeriesForTest(
|
||||
"wuhan",
|
||||
wuhanRunwayChart.series,
|
||||
{ [runwayKey("04/22")]: false },
|
||||
false,
|
||||
);
|
||||
assert(
|
||||
!wuhanCollapsedWithSettlementHidden.some((item: any) => item.key === "runway_max"),
|
||||
"hiding the settlement runway should not reveal runway max in collapsed runway view",
|
||||
);
|
||||
|
||||
const newYorkMetrics = __getObservationDisplayMetricsForTest(
|
||||
{
|
||||
city: "new york",
|
||||
|
||||
@@ -115,21 +115,44 @@ function getVisibleTemperatureSeries(
|
||||
});
|
||||
}
|
||||
|
||||
function getActiveTemperatureSeries(
|
||||
function isIndividualRunwaySeriesKey(seriesKey: string) {
|
||||
return seriesKey.startsWith("runway_") && seriesKey !== "runway_max";
|
||||
}
|
||||
|
||||
function isSettlementRunwaySeriesKey(city: string, seriesKey: string) {
|
||||
if (!isIndividualRunwaySeriesKey(seriesKey)) return false;
|
||||
const cityKey = normalizeCityKey(city);
|
||||
const settlementPairs = SETTLEMENT_RUNWAY_PAIRS[cityKey] || [];
|
||||
if (!settlementPairs.length) return false;
|
||||
const normalized = seriesKey
|
||||
.replace(/^runway_/, "")
|
||||
.split("_")
|
||||
.map(normalizeRunwayLabel)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.join("/");
|
||||
return settlementPairs.some((pair) => pairKey(pair) === normalized);
|
||||
}
|
||||
|
||||
function getTemperatureSeriesForRunwayDetailsMode(
|
||||
city: string,
|
||||
chartSeries: EvidenceSeries[],
|
||||
userToggledKeys: Record<string, boolean>,
|
||||
series: EvidenceSeries[],
|
||||
showRunwayDetails: boolean,
|
||||
) {
|
||||
const rawVisible = getVisibleTemperatureSeries(city, chartSeries, userToggledKeys);
|
||||
const hasRunwayMax = rawVisible.some((item) => item.key === "runway_max");
|
||||
const hasRunwayMax = series.some((item) => item.key === "runway_max");
|
||||
const hasSettlementRunway = series.some((item) =>
|
||||
isSettlementRunwaySeriesKey(city, item.key),
|
||||
);
|
||||
|
||||
return rawVisible.filter((item) => {
|
||||
const isIndividualRunway =
|
||||
item.key.startsWith("runway_") && item.key !== "runway_max";
|
||||
return series.filter((item) => {
|
||||
const isIndividualRunway = isIndividualRunwaySeriesKey(item.key);
|
||||
if (showRunwayDetails) {
|
||||
return item.key !== "runway_max";
|
||||
}
|
||||
if (hasSettlementRunway) {
|
||||
if (item.key === "runway_max") return false;
|
||||
return !isIndividualRunway || isSettlementRunwaySeriesKey(city, item.key);
|
||||
}
|
||||
if (!hasRunwayMax) {
|
||||
return true;
|
||||
}
|
||||
@@ -137,6 +160,20 @@ function getActiveTemperatureSeries(
|
||||
});
|
||||
}
|
||||
|
||||
function getActiveTemperatureSeries(
|
||||
city: string,
|
||||
chartSeries: EvidenceSeries[],
|
||||
userToggledKeys: Record<string, boolean>,
|
||||
showRunwayDetails: boolean,
|
||||
) {
|
||||
const modeSeries = getTemperatureSeriesForRunwayDetailsMode(
|
||||
city,
|
||||
chartSeries,
|
||||
showRunwayDetails,
|
||||
);
|
||||
return getVisibleTemperatureSeries(city, modeSeries, userToggledKeys);
|
||||
}
|
||||
|
||||
function buildRunwayPlates(
|
||||
amos: AmosData | null | undefined,
|
||||
row: ScanOpportunityRow | null,
|
||||
@@ -2156,6 +2193,7 @@ export {
|
||||
buildRunwayPlates,
|
||||
fetchHourlyForecastForCity,
|
||||
getActiveTemperatureSeries,
|
||||
getTemperatureSeriesForRunwayDetailsMode,
|
||||
getLiveObservationLabels,
|
||||
getObservationDisplayMetrics,
|
||||
getVisibleTemperatureSeries,
|
||||
|
||||
Reference in New Issue
Block a user