清理时区逻辑:TRADING_REGIONS→REGIONS,移除 detectLocalRegion/TZ fallback,用 CITY_REGION 硬编码替代

This commit is contained in:
2569718930@qq.com
2026-05-25 20:23:44 +08:00
parent 798e2d6cc2
commit 2cd43b607d
3 changed files with 32 additions and 65 deletions
@@ -27,9 +27,9 @@ import {
getGapColor, getGapColor,
getSignalLabel, getSignalLabel,
getSignalState, getSignalState,
resolveTradingRegionKey, getCityRegion,
TRADING_REGIONS, REGIONS,
detectLocalRegion, getDefaultRegion,
} from "@/components/dashboard/scan-terminal/continent-grouping"; } from "@/components/dashboard/scan-terminal/continent-grouping";
import { MobileCityCard } from "@/components/dashboard/scan-terminal/MobileCityCard"; import { MobileCityCard } from "@/components/dashboard/scan-terminal/MobileCityCard";
import { MobileRegionTabs } from "@/components/dashboard/scan-terminal/MobileRegionTabs"; import { MobileRegionTabs } from "@/components/dashboard/scan-terminal/MobileRegionTabs";
@@ -290,7 +290,7 @@ function PolyWeatherTerminal({
const filteredRegionRows = useMemo(() => { const filteredRegionRows = useMemo(() => {
return rows.filter( return rows.filter(
(row) => resolveTradingRegionKey(row) === selectedRegionKey, (row) => getCityRegion(row) === selectedRegionKey,
); );
}, [rows, selectedRegionKey]); }, [rows, selectedRegionKey]);
@@ -504,7 +504,7 @@ function PolyWeatherTerminal({
<> <>
{/* Region tabs */} {/* Region tabs */}
<div className="flex shrink-0 items-center gap-1 overflow-x-auto rounded-[4px] border border-[#cfd6df] bg-white p-1 mb-2 scrollbar-none"> <div className="flex shrink-0 items-center gap-1 overflow-x-auto rounded-[4px] border border-[#cfd6df] bg-white p-1 mb-2 scrollbar-none">
{TRADING_REGIONS.filter((r) => visibleRegions.has(r.key)).map((r) => ({ {REGIONS.filter((r) => visibleRegions.has(r.key)).map((r) => ({
key: r.key, key: r.key,
labelEn: r.labelEn.toUpperCase(), labelEn: r.labelEn.toUpperCase(),
labelZh: r.labelZh, labelZh: r.labelZh,
@@ -540,7 +540,7 @@ function PolyWeatherTerminal({
id="region-selector-popover" id="region-selector-popover"
className="hidden absolute right-0 top-full mt-1 z-50 bg-white border border-slate-200 rounded-lg shadow-lg p-2 min-w-[180px]" className="hidden absolute right-0 top-full mt-1 z-50 bg-white border border-slate-200 rounded-lg shadow-lg p-2 min-w-[180px]"
> >
{TRADING_REGIONS.map((r) => { {REGIONS.map((r) => {
const checked = visibleRegions.has(r.key); const checked = visibleRegions.has(r.key);
return ( return (
<label <label
@@ -649,7 +649,7 @@ function ScanTerminalScreen() {
const stored = localStorage.getItem("polyweather_visible_regions"); const stored = localStorage.getItem("polyweather_visible_regions");
if (stored) return new Set(JSON.parse(stored)); if (stored) return new Set(JSON.parse(stored));
} catch {} } catch {}
return new Set(TRADING_REGIONS.map((r) => r.key)); return new Set(REGIONS.map((r) => r.key));
}); });
const toggleRegion = useCallback((key: string) => { const toggleRegion = useCallback((key: string) => {
setVisibleRegions((prev) => { setVisibleRegions((prev) => {
@@ -734,7 +734,7 @@ function ScanTerminalScreen() {
}, []); }, []);
useEffect(() => { useEffect(() => {
setSelectedRegionKey(detectLocalRegion()); setSelectedRegionKey(getDefaultRegion());
setLocalTimezoneOffsetSeconds(-new Date().getTimezoneOffset() * 60); setLocalTimezoneOffsetSeconds(-new Date().getTimezoneOffset() * 60);
}, []); }, []);
@@ -13,7 +13,14 @@ import {
XAxis, XAxis,
YAxis, YAxis,
} from "recharts"; } from "recharts";
import type { AmosData, AirportCurrentConditions, CityDetail, ScanOpportunityRow } from "@/lib/dashboard-types"; import type {
AmosData,
AirportCurrentConditions,
CityDetail,
ScanOpportunityRow,
ForecastDay,
DailyModelForecast,
} from "@/lib/dashboard-types";
import { buildDebBaselinePath } from "@/lib/temperature-chart-paths"; import { buildDebBaselinePath } from "@/lib/temperature-chart-paths";
import { Panel } from "@/components/dashboard/scan-terminal/Panel"; import { Panel } from "@/components/dashboard/scan-terminal/Panel";
import { rowName, temp } from "@/components/dashboard/scan-terminal/utils"; import { rowName, temp } from "@/components/dashboard/scan-terminal/utils";
@@ -1,6 +1,6 @@
import type { ScanOpportunityRow } from "@/lib/dashboard-types"; import type { ScanOpportunityRow } from "@/lib/dashboard-types";
export const TRADING_REGIONS = [ export const REGIONS = [
{ key: "east_asia", labelEn: "East Asia", labelZh: "东亚", sort: 1 }, { key: "east_asia", labelEn: "East Asia", labelZh: "东亚", sort: 1 },
{ key: "southeast_asia", labelEn: "Southeast Asia", labelZh: "东南亚", sort: 2 }, { key: "southeast_asia", labelEn: "Southeast Asia", labelZh: "东南亚", sort: 2 },
{ key: "central_asia", labelEn: "Central / South Asia", labelZh: "中亚 / 南亚", sort: 3 }, { key: "central_asia", labelEn: "Central / South Asia", labelZh: "中亚 / 南亚", sort: 3 },
@@ -10,23 +10,11 @@ export const TRADING_REGIONS = [
{ key: "north_america", labelEn: "North America", labelZh: "北美", sort: 7 }, { key: "north_america", labelEn: "North America", labelZh: "北美", sort: 7 },
] as const; ] as const;
export type TradingRegionKey = (typeof TRADING_REGIONS)[number]["key"]; export type RegionKey = (typeof REGIONS)[number]["key"];
/** Map browser timezone offset (hours) to the closest trading region. */ const REGION_KEYS = new Set<string>(REGIONS.map((r) => r.key));
export function detectLocalRegion(): TradingRegionKey {
const offset = -new Date().getTimezoneOffset() / 60; // UTC offset in hours
if (offset >= 8) return "east_asia";
if (offset >= 7) return "southeast_asia";
if (offset >= 4.5) return "central_asia";
if (offset >= 2) return "west_asia";
if (offset >= -2) return "europe_africa";
if (offset >= -4) return "south_america";
return "north_america";
}
const TRADING_REGION_KEYS = new Set<string>(TRADING_REGIONS.map((region) => region.key)); const CITY_REGION: Record<string, RegionKey> = {
const CITY_REGION_FALLBACK: Record<string, TradingRegionKey> = {
beijing: "east_asia", beijing: "east_asia",
busan: "east_asia", busan: "east_asia",
chengdu: "east_asia", chengdu: "east_asia",
@@ -79,51 +67,23 @@ const CITY_REGION_FALLBACK: Record<string, TradingRegionKey> = {
wellington: "east_asia", wellington: "east_asia",
}; };
function normalizeRegionValue(value?: string | null) { function normalizeCity(value?: string | null) {
return String(value || "") return String(value || "").trim().toLowerCase()
.trim() .replace(/[_-]+/g, " ").replace(/\s+/g, " ");
.toLowerCase()
.replace(/[-\s]+/g, "_");
} }
function normalizeCityValue(value?: string | null) { export function getCityRegion(row: ScanOpportunityRow): RegionKey | null {
return String(value || "") const key = normalizeCity(row.city || row.city_display_name || row.display_name);
.trim() return CITY_REGION[key] || null;
.toLowerCase()
.replace(/[_-]+/g, " ")
.replace(/\s+/g, " ");
} }
function finiteNumber(value: unknown) { /** Default region on first load — East Asia. */
const numeric = Number(value); export function getDefaultRegion(): RegionKey {
return Number.isFinite(numeric) ? numeric : null; return "east_asia";
}
export function resolveTradingRegionKey(row: ScanOpportunityRow): TradingRegionKey | null {
const direct = normalizeRegionValue(row.trading_region);
if (TRADING_REGION_KEYS.has(direct)) return direct as TradingRegionKey;
const cityKey = normalizeCityValue(row.city || row.city_display_name || row.display_name);
const cityRegion = CITY_REGION_FALLBACK[cityKey];
if (cityRegion) return cityRegion;
const offset = finiteNumber(row.tz_offset_seconds);
if (offset !== null) {
const hours = offset / 3600;
if (hours >= 8) return "east_asia";
if (hours >= 7) return "southeast_asia";
if (hours >= 4.5) return "central_asia";
if (hours >= 2) return "west_asia";
if (hours >= -2) return "europe_africa";
if (hours >= -4) return "south_america";
return "north_america";
}
return null;
} }
export interface ContinentGroup { export interface ContinentGroup {
key: TradingRegionKey | "active_signals"; key: RegionKey | "active_signals";
labelEn: string; labelEn: string;
labelZh: string; labelZh: string;
sort: number; sort: number;
@@ -203,7 +163,7 @@ export function buildContinentGroups(rows: ScanOpportunityRow[], isEn: boolean):
const regionMap = new Map<string, ScanOpportunityRow[]>(); const regionMap = new Map<string, ScanOpportunityRow[]>();
for (const row of rows) { for (const row of rows) {
const region = resolveTradingRegionKey(row) || "unknown"; const region = getCityRegion(row) || "unknown";
if (!regionMap.has(region)) regionMap.set(region, []); if (!regionMap.has(region)) regionMap.set(region, []);
regionMap.get(region)!.push(row); regionMap.get(region)!.push(row);
} }
@@ -229,7 +189,7 @@ export function buildContinentGroups(rows: ScanOpportunityRow[], isEn: boolean):
}); });
} }
for (const region of TRADING_REGIONS) { for (const region of REGIONS) {
const regionRows = regionMap.get(region.key) || []; const regionRows = regionMap.get(region.key) || [];
if (regionRows.length === 0) continue; if (regionRows.length === 0) continue;