diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx
index 19f36035..4af43d8b 100644
--- a/frontend/components/dashboard/ScanTerminalDashboard.tsx
+++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx
@@ -4,10 +4,7 @@ import clsx from "clsx";
import Link from "next/link";
import {
Activity,
- BarChart3,
- Bell,
ChevronLeft,
- Gauge,
GraduationCap,
LineChart,
Menu,
@@ -47,6 +44,7 @@ import { Panel } from "@/components/dashboard/scan-terminal/Panel";
import { GroupedMarketTable } from "@/components/dashboard/scan-terminal/GroupedMarketTable";
import { TrainingDashboard } from "@/components/dashboard/scan-terminal/TrainingDashboard";
import { LiveTemperatureThresholdChart } from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart";
+import { MarketOverviewView } from "@/components/dashboard/scan-terminal/MarketOverviewView";
import { rowName, pct, money, temp, edgeClass } from "@/components/dashboard/scan-terminal/utils";
function createEmptyAccess(loading = true): ProAccessState {
@@ -168,13 +166,6 @@ function tablePrice(row: ScanOpportunityRow) {
return formatPrice(row.midpoint, row.ask, row.bid);
}
-function ticker(row: ScanOpportunityRow) {
- return String(row.airport || row.market_key || row.city || "--")
- .replace(/[^A-Za-z0-9]/g, "")
- .slice(0, 6)
- .toUpperCase();
-}
-
function KoyfinRowsTable({
compact = false,
isEn,
@@ -198,11 +189,6 @@ function KoyfinRowsTable({
{isEn ? "Weather Contract" : "天气合约"}
|
- {!compact && (
-
- {isEn ? "Ticker" : "代码"}
- |
- )}
{isEn ? "Price" : "价格"}
|
@@ -236,11 +222,6 @@ function KoyfinRowsTable({
{row.target_label || row.market_question || row.airport || "--"}
- {!compact && (
-
- {ticker(row)}
- |
- )}
{tablePrice(row)}
|
@@ -353,9 +334,6 @@ function PolyWeatherTerminal({
const NAV_ITEMS = [
{ key: "contracts", Icon: Table2, labelEn: "Contracts", labelZh: "天气合约" },
{ key: "signals", Icon: LineChart, labelEn: "Signals", labelZh: "交易信号" },
- { key: "analytics", Icon: BarChart3, labelEn: "Analytics", labelZh: "分析图表" },
- { key: "watchlist", Icon: Gauge, labelEn: "Watchlist", labelZh: "自选监控" },
- { key: "alerts", Icon: Bell, labelEn: "Alerts", labelZh: "实时预警" },
{ key: "markets", Icon: Activity, labelEn: "Markets", labelZh: "市场概览" },
{ key: "training", Icon: GraduationCap, labelEn: "Training", labelZh: "训练数据" },
];
@@ -564,214 +542,135 @@ function PolyWeatherTerminal({
- {/* Region tabs */}
-
- {TRADING_REGIONS.map((r) => ({
- key: r.key,
- labelEn: r.labelEn.toUpperCase(),
- labelZh: r.labelZh,
- })).map((tab) => (
-
- ))}
-
- {/* Mobile layout */}
-
-
+ ) : activeNavKey === "markets" ? (
+
{
+ const regionKey = String(row.trading_region || "").toLowerCase();
+ if (regionKey) setSelectedRegionKey(regionKey);
+ setSelectedRow(row);
+ setActiveNavKey("contracts");
+ }}
/>
-
- {mobileActiveGroup?.rows.map((row) => (
-
+ {/* Region tabs */}
+
+ {TRADING_REGIONS.map((r) => ({
+ key: r.key,
+ labelEn: r.labelEn.toUpperCase(),
+ labelZh: r.labelZh,
+ })).map((tab) => (
+
+ ))}
+
+ {/* Mobile layout */}
+
+
- ))}
-
- {/* Mobile Selected Row Detail */}
- {selectedRow && (
-
-
{rowName(selectedRow)}
-
- {[
- ["Obs", temp(selectedRow.current_temp, selectedRow.temp_symbol)],
- ["High", temp(selectedRow.current_max_so_far, selectedRow.temp_symbol)],
- ["DEB", temp(selectedRow.deb_prediction, selectedRow.temp_symbol)],
- ["Gap", temp(selectedRow.signed_gap ?? selectedRow.gap_to_target, selectedRow.temp_symbol)],
- ["Edge", pct(selectedRow.edge_percent)],
- ["Market", formatPrice(selectedRow.midpoint, selectedRow.ask, selectedRow.bid)],
- ].map(([label, value]) => (
-
+
+ {mobileActiveGroup?.rows.map((row) => (
+
))}
+ {/* Mobile Selected Row Detail */}
+ {selectedRow && (
+
+
{rowName(selectedRow)}
+
+ {[
+ ["Obs", temp(selectedRow.current_temp, selectedRow.temp_symbol)],
+ ["High", temp(selectedRow.current_max_so_far, selectedRow.temp_symbol)],
+ ["DEB", temp(selectedRow.deb_prediction, selectedRow.temp_symbol)],
+ ["Gap", temp(selectedRow.signed_gap ?? selectedRow.gap_to_target, selectedRow.temp_symbol)],
+ ["Edge", pct(selectedRow.edge_percent)],
+ ["Market", formatPrice(selectedRow.midpoint, selectedRow.ask, selectedRow.bid)],
+ ].map(([label, value]) => (
+
+ ))}
+
+
+ )}
- )}
-
- {/* Desktop layout */}
-
-
-
-
+ {/* Desktop layout */}
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+ >
+ )}
);
}
-function RegionalWhaleWatch({
- isEn,
- rows,
-}: {
- isEn: boolean;
- rows: ScanOpportunityRow[];
-}) {
- type HolderInfo = { city: string; holders: Array<{ proxyWallet?: string; amount?: number; outcomeIndex?: number; pseudonym?: string; name?: string }> | null; loading: boolean };
- const [holderMap, setHolderMap] = useState>({});
-
- const regions = ["americas", "europe", "asia_pacific", "middle_east_africa"];
- const regionLabels: Record = {
- americas: isEn ? "Americas" : "美洲",
- europe: isEn ? "Europe" : "欧洲",
- asia_pacific: isEn ? "Asia-Pacific" : "亚太",
- middle_east_africa: isEn ? "ME & Africa" : "中东非洲",
- };
- const topByRegion = useMemo(() => {
- return regions.map((region) => {
- const regionRows = rows
- .filter((row) => row.trading_region === region)
- .sort((a, b) => (Number(b.volume || b.book_liquidity || 0)) - (Number(a.volume || a.book_liquidity || 0)))
- .slice(0, 2);
- return { region, label: regionLabels[region] || region, rows: regionRows };
- }).filter((r) => r.rows.length > 0);
- }, [rows, isEn]);
-
- useEffect(() => {
- topByRegion.forEach(({ rows: regionRows }) => {
- regionRows.forEach((row) => {
- const city = String(row.city || "").toLowerCase();
- if (!city || holderMap[city]?.loading || holderMap[city]?.holders) return;
- setHolderMap((prev) => ({ ...prev, [city]: { city, holders: null, loading: true } }));
- fetch(`/api/city/${encodeURIComponent(city)}/holders?limit=6`, { cache: "no-store", headers: { Accept: "application/json" } })
- .then(async (res) => {
- const json = await res.json() as { holders?: Array<{ proxyWallet?: string; amount?: number; outcomeIndex?: number; pseudonym?: string; name?: string }>; available?: boolean };
- setHolderMap((prev) => ({ ...prev, [city]: { city, holders: json.holders || [], loading: false } }));
- })
- .catch(() => setHolderMap((prev) => ({ ...prev, [city]: { city, holders: null, loading: false } })));
- });
- });
- }, [topByRegion]);
-
- if (!topByRegion.length) return null;
-
- return (
-
-
- {topByRegion.map(({ region, label, rows: regionRows }) => (
-
-
{label}
- {regionRows.map((row) => {
- const city = String(row.city || "").toLowerCase();
- const info = holderMap[city];
- const vol = Number(row.volume || row.book_liquidity || 0);
- return (
-
-
-
- {row.city_display_name || row.city}
-
-
- {row.target_label || "--"}
-
-
- {vol >= 1000 ? `$${(vol / 1000).toFixed(1)}K` : `$${Math.round(vol)}`}
-
-
- {info?.loading ? (
-
{isEn ? "Loading holders..." : "加载持仓..."}
- ) : info?.holders?.length ? (
- info.holders.slice(0, 3).map((h, i) => (
-
-
- {h.name || h.pseudonym || (h.proxyWallet ? `${String(h.proxyWallet).slice(0, 6)}...${String(h.proxyWallet).slice(-4)}` : "--")}
-
-
- {h.outcomeIndex === 0 ? "YES" : h.outcomeIndex === 1 ? "NO" : ""}{" "}
- {h.amount != null ? `${Number(h.amount).toFixed(0)}` : ""}
-
-
- ))
- ) : info ? (
-
{isEn ? "No holder data" : "无持仓数据"}
- ) : null}
-
- );
- })}
-
- ))}
-
-
- );
-}
-
function ScanTerminalScreen() {
const [proAccess, setProAccess] = useState(() =>
createEmptyAccess(true),