From 6df18ec3f00473c46c38d6bc6da691ee707c23a6 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 25 May 2026 05:02:52 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=A4=A9=E6=B0=94=E5=90=88?= =?UTF-8?q?=E7=BA=A6=E5=B8=82=E5=9C=BA=E7=9A=84=20Ticker/=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=88=97=E5=8F=8A=20ticker=20=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/ScanTerminalDashboard.tsx | 329 ++++++------------ 1 file changed, 114 insertions(+), 215 deletions(-) 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]) => ( -
-
{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]) => ( +
+
{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),