From 68e374465029f3dba3ad530169f6f4e62bb4c62b Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 25 May 2026 05:19:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=A9=E6=B0=94=E5=90=88=E7=BA=A6=E5=B8=82?= =?UTF-8?q?=E5=9C=BA=E6=94=B9=E4=B8=BA=E6=8C=89=E5=9F=8E=E5=B8=82=E5=88=86?= =?UTF-8?q?=E7=BB=84=E6=8A=98=E5=8F=A0=EF=BC=8C=E5=B1=95=E5=BC=80=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=89=80=E6=9C=89=E6=B8=A9=E5=BA=A6=E6=A1=A3=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/ScanTerminalDashboard.tsx | 146 +++++++++++++++++- 1 file changed, 144 insertions(+), 2 deletions(-) diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index 1a801118..221a648a 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -4,7 +4,9 @@ import clsx from "clsx"; import Link from "next/link"; import { Activity, + ChevronDown, ChevronLeft, + ChevronRight, GraduationCap, LineChart, Menu, @@ -12,7 +14,7 @@ import { Table2, UserRound, } from "lucide-react"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { Fragment, useEffect, useMemo, useRef, useState } from "react"; import type { ProAccessState, ScanOpportunityRow } from "@/lib/dashboard-types"; import { getInitialLocaleFromNavigator } from "@/lib/i18n"; import { isBrowserLocalFullAccess } from "@/lib/local-dev-access"; @@ -267,7 +269,7 @@ function KoyfinMarketPanel({ }) { return ( - void; + rows: ScanOpportunityRow[]; + selectedId?: string | null; +}) { + const [expandedCities, setExpandedCities] = useState>(new Set()); + + const cityGroups = useMemo(() => { + const map = new Map(); + rows.forEach((row) => { + const key = String(row.city || "").toLowerCase(); + if (!map.has(key)) map.set(key, []); + map.get(key)!.push(row); + }); + return Array.from(map.entries()) + .map(([city, cityRows]) => { + const best = cityRows.reduce((a, b) => + Math.abs(Number(b.edge_percent || 0)) > Math.abs(Number(a.edge_percent || 0)) ? b : a + ); + const buckets = [...cityRows].sort((a, b) => + Number(b.edge_percent || 0) - Number(a.edge_percent || 0) + ); + return { city, displayName: rowName(best), best, buckets }; + }) + .sort((a, b) => + Math.abs(Number(b.best.edge_percent || 0)) - Math.abs(Number(a.best.edge_percent || 0)) + ); + }, [rows]); + + const toggle = (city: string) => { + setExpandedCities((prev) => { + const next = new Set(prev); + if (next.has(city)) next.delete(city); + else next.add(city); + return next; + }); + }; + + const labelExpand = isEn ? "Expand" : "展开"; + const labelBuckets = isEn ? "buckets" : "档位"; + + return ( +
+ + + + + + + + + + + + + + {cityGroups.map(({ city, displayName, best, buckets }) => { + const isExpanded = expandedCities.has(city); + const hasMultiple = buckets.length > 1; + return ( + + onSelect(best)} + > + + + + + + + + + {isExpanded && buckets.map((row) => ( + onSelect(row)} + > + + + + + + + + ))} + + ); + })} + +
+ + + {isEn ? "City / Best Signal" : "城市 / 最佳信号"} + {isEn ? "Edge" : "优势"}LiveDEB{isEn ? "Mkt" : "市场"}{isEn ? "Liq" : "流动性"}
+ + +
{displayName}
+
+ {best.target_label || ""} + {hasMultiple && !isExpanded ? ` +${buckets.length - 1}` : ""} +
+
+ {pct(best.edge_percent)} + {temp(best.current_temp || best.current_max_so_far, best.temp_symbol)}{temp(best.deb_prediction, best.temp_symbol)}{pct(best.market_probability ?? best.market_event_probability)}{money(best.book_liquidity ?? best.market_liquidity ?? best.volume)}
+ + {row.target_label || "--"} + + {pct(row.edge_percent)} + {temp(row.current_temp || row.current_max_so_far, row.temp_symbol)}{temp(row.deb_prediction, row.temp_symbol)}{pct(row.market_probability ?? row.market_event_probability)}{money(row.book_liquidity ?? row.market_liquidity ?? row.volume)}
+
+ ); +} + function PolyWeatherTerminal({ generatedText, isEn,