清理时区逻辑:TRADING_REGIONS→REGIONS,移除 detectLocalRegion/TZ fallback,用 CITY_REGION 硬编码替代
This commit is contained in:
@@ -27,9 +27,9 @@ import {
|
||||
getGapColor,
|
||||
getSignalLabel,
|
||||
getSignalState,
|
||||
resolveTradingRegionKey,
|
||||
TRADING_REGIONS,
|
||||
detectLocalRegion,
|
||||
getCityRegion,
|
||||
REGIONS,
|
||||
getDefaultRegion,
|
||||
} from "@/components/dashboard/scan-terminal/continent-grouping";
|
||||
import { MobileCityCard } from "@/components/dashboard/scan-terminal/MobileCityCard";
|
||||
import { MobileRegionTabs } from "@/components/dashboard/scan-terminal/MobileRegionTabs";
|
||||
@@ -290,7 +290,7 @@ function PolyWeatherTerminal({
|
||||
|
||||
const filteredRegionRows = useMemo(() => {
|
||||
return rows.filter(
|
||||
(row) => resolveTradingRegionKey(row) === selectedRegionKey,
|
||||
(row) => getCityRegion(row) === selectedRegionKey,
|
||||
);
|
||||
}, [rows, selectedRegionKey]);
|
||||
|
||||
@@ -504,7 +504,7 @@ function PolyWeatherTerminal({
|
||||
<>
|
||||
{/* 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">
|
||||
{TRADING_REGIONS.filter((r) => visibleRegions.has(r.key)).map((r) => ({
|
||||
{REGIONS.filter((r) => visibleRegions.has(r.key)).map((r) => ({
|
||||
key: r.key,
|
||||
labelEn: r.labelEn.toUpperCase(),
|
||||
labelZh: r.labelZh,
|
||||
@@ -540,7 +540,7 @@ function PolyWeatherTerminal({
|
||||
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]"
|
||||
>
|
||||
{TRADING_REGIONS.map((r) => {
|
||||
{REGIONS.map((r) => {
|
||||
const checked = visibleRegions.has(r.key);
|
||||
return (
|
||||
<label
|
||||
@@ -649,7 +649,7 @@ function ScanTerminalScreen() {
|
||||
const stored = localStorage.getItem("polyweather_visible_regions");
|
||||
if (stored) return new Set(JSON.parse(stored));
|
||||
} catch {}
|
||||
return new Set(TRADING_REGIONS.map((r) => r.key));
|
||||
return new Set(REGIONS.map((r) => r.key));
|
||||
});
|
||||
const toggleRegion = useCallback((key: string) => {
|
||||
setVisibleRegions((prev) => {
|
||||
@@ -734,7 +734,7 @@ function ScanTerminalScreen() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedRegionKey(detectLocalRegion());
|
||||
setSelectedRegionKey(getDefaultRegion());
|
||||
setLocalTimezoneOffsetSeconds(-new Date().getTimezoneOffset() * 60);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -13,7 +13,14 @@ import {
|
||||
XAxis,
|
||||
YAxis,
|
||||
} 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 { Panel } from "@/components/dashboard/scan-terminal/Panel";
|
||||
import { rowName, temp } from "@/components/dashboard/scan-terminal/utils";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ScanOpportunityRow } from "@/lib/dashboard-types";
|
||||
|
||||
export const TRADING_REGIONS = [
|
||||
export const REGIONS = [
|
||||
{ key: "east_asia", labelEn: "East Asia", labelZh: "东亚", sort: 1 },
|
||||
{ key: "southeast_asia", labelEn: "Southeast Asia", labelZh: "东南亚", sort: 2 },
|
||||
{ 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 },
|
||||
] 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. */
|
||||
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 REGION_KEYS = new Set<string>(REGIONS.map((r) => r.key));
|
||||
|
||||
const TRADING_REGION_KEYS = new Set<string>(TRADING_REGIONS.map((region) => region.key));
|
||||
|
||||
const CITY_REGION_FALLBACK: Record<string, TradingRegionKey> = {
|
||||
const CITY_REGION: Record<string, RegionKey> = {
|
||||
beijing: "east_asia",
|
||||
busan: "east_asia",
|
||||
chengdu: "east_asia",
|
||||
@@ -79,51 +67,23 @@ const CITY_REGION_FALLBACK: Record<string, TradingRegionKey> = {
|
||||
wellington: "east_asia",
|
||||
};
|
||||
|
||||
function normalizeRegionValue(value?: string | null) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[-\s]+/g, "_");
|
||||
function normalizeCity(value?: string | null) {
|
||||
return String(value || "").trim().toLowerCase()
|
||||
.replace(/[_-]+/g, " ").replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
function normalizeCityValue(value?: string | null) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[_-]+/g, " ")
|
||||
.replace(/\s+/g, " ");
|
||||
export function getCityRegion(row: ScanOpportunityRow): RegionKey | null {
|
||||
const key = normalizeCity(row.city || row.city_display_name || row.display_name);
|
||||
return CITY_REGION[key] || null;
|
||||
}
|
||||
|
||||
function finiteNumber(value: unknown) {
|
||||
const numeric = Number(value);
|
||||
return Number.isFinite(numeric) ? numeric : null;
|
||||
}
|
||||
|
||||
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;
|
||||
/** Default region on first load — East Asia. */
|
||||
export function getDefaultRegion(): RegionKey {
|
||||
return "east_asia";
|
||||
}
|
||||
|
||||
export interface ContinentGroup {
|
||||
key: TradingRegionKey | "active_signals";
|
||||
key: RegionKey | "active_signals";
|
||||
labelEn: string;
|
||||
labelZh: string;
|
||||
sort: number;
|
||||
@@ -203,7 +163,7 @@ export function buildContinentGroups(rows: ScanOpportunityRow[], isEn: boolean):
|
||||
const regionMap = new Map<string, ScanOpportunityRow[]>();
|
||||
|
||||
for (const row of rows) {
|
||||
const region = resolveTradingRegionKey(row) || "unknown";
|
||||
const region = getCityRegion(row) || "unknown";
|
||||
if (!regionMap.has(region)) regionMap.set(region, []);
|
||||
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) || [];
|
||||
if (regionRows.length === 0) continue;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user