删除 _unused_CityGroupedTable(137行) + KoyfinRowsTable 改为外部导入
ScanTerminalDashboard 1060→841 行。
This commit is contained in:
@@ -47,6 +47,7 @@ import { Panel } from "@/components/dashboard/scan-terminal/Panel";
|
||||
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 { KoyfinRowsTable } from "@/components/dashboard/scan-terminal/KoyfinRowsTable";
|
||||
import { rowName, pct, money, temp, edgeClass } from "@/components/dashboard/scan-terminal/utils";
|
||||
|
||||
function createEmptyAccess(loading = true): ProAccessState {
|
||||
@@ -168,89 +169,6 @@ function tablePrice(row: ScanOpportunityRow) {
|
||||
return formatPrice(row.midpoint, row.ask, row.bid);
|
||||
}
|
||||
|
||||
function KoyfinRowsTable({
|
||||
compact = false,
|
||||
isEn,
|
||||
onSelect,
|
||||
rows,
|
||||
selectedId,
|
||||
}: {
|
||||
compact?: boolean;
|
||||
isEn: boolean;
|
||||
onSelect: (row: ScanOpportunityRow) => void;
|
||||
rows: ScanOpportunityRow[];
|
||||
selectedId?: string | null;
|
||||
}) {
|
||||
return (
|
||||
<table className="w-full border-collapse text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-200 bg-[#f3f5f7] text-[11px] uppercase tracking-wide text-slate-500">
|
||||
<th className="w-5 px-2 py-1 text-left font-black">
|
||||
<span className="block h-3 w-3 rounded-[2px] border border-slate-300 bg-white" />
|
||||
</th>
|
||||
<th className="px-1.5 py-1 text-left font-black">
|
||||
{isEn ? "City" : "城市"}
|
||||
</th>
|
||||
<th className="px-1.5 py-1 text-right font-black">
|
||||
{isEn ? "Price" : "价格"}
|
||||
</th>
|
||||
<th className="px-1.5 py-1 text-right font-black">
|
||||
{isEn ? "Chg" : "变化"}
|
||||
</th>
|
||||
<th className="px-2 py-1 text-right font-black">%</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row) => {
|
||||
const edge = Number(row.edge_percent ?? row.signed_gap ?? row.gap ?? 0);
|
||||
const positive = edge >= 0;
|
||||
return (
|
||||
<tr
|
||||
key={row.id}
|
||||
onClick={() => onSelect(row)}
|
||||
className={clsx(
|
||||
"cursor-pointer border-b border-slate-100 hover:bg-blue-50/70",
|
||||
selectedId === row.id && "bg-blue-50",
|
||||
)}
|
||||
>
|
||||
<td className="px-2 py-1">
|
||||
<span className="block h-3 w-3 rounded-[2px] border border-slate-300 bg-white" />
|
||||
</td>
|
||||
<td className="px-1.5 py-1">
|
||||
<div className="truncate font-bold text-slate-800">
|
||||
{rowName(row)}
|
||||
</div>
|
||||
<div className="truncate text-[10px] font-medium text-slate-400">
|
||||
{row.target_label || row.market_question || row.airport || "--"}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-1.5 py-1 text-right font-mono font-bold text-slate-800">
|
||||
{tablePrice(row)}
|
||||
</td>
|
||||
<td
|
||||
className={clsx(
|
||||
"px-1.5 py-1 text-right font-mono font-bold",
|
||||
positive ? "text-emerald-700" : "text-red-600",
|
||||
)}
|
||||
>
|
||||
{Number.isFinite(edge) ? `${positive ? "+" : ""}${edge.toFixed(1)}` : "--"}
|
||||
</td>
|
||||
<td
|
||||
className={clsx(
|
||||
"px-2 py-1 text-right font-mono font-bold",
|
||||
positive ? "text-emerald-700" : "text-red-600",
|
||||
)}
|
||||
>
|
||||
{pct(row.market_probability ?? row.market_event_probability ?? row.model_probability)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
function CityRegionList({
|
||||
isEn,
|
||||
rows,
|
||||
@@ -347,143 +265,6 @@ function CityContractDetail({
|
||||
);
|
||||
}
|
||||
|
||||
function _unused_CityGroupedTable({
|
||||
compact,
|
||||
isEn,
|
||||
onSelect,
|
||||
rows,
|
||||
selectedId,
|
||||
}: {
|
||||
compact?: boolean;
|
||||
isEn: boolean;
|
||||
onSelect: (row: ScanOpportunityRow) => void;
|
||||
rows: ScanOpportunityRow[];
|
||||
selectedId?: string | null;
|
||||
}) {
|
||||
const [expandedCities, setExpandedCities] = useState<Set<string>>(new Set());
|
||||
|
||||
const cityGroups = useMemo(() => {
|
||||
const map = new Map<string, ScanOpportunityRow[]>();
|
||||
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 (
|
||||
<div className="overflow-auto h-full">
|
||||
<table className="w-full min-w-[780px] border-collapse text-[11px]">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-200 bg-[#f3f5f7] text-[9px] uppercase tracking-wide text-slate-500">
|
||||
<th className="w-6 px-2 py-1.5 text-left font-black" />
|
||||
<th className="px-1.5 py-1.5 text-left font-black">
|
||||
{isEn ? "City / Best Signal" : "城市 / 最佳信号"}
|
||||
</th>
|
||||
<th className="px-1.5 py-1.5 text-right font-black">{isEn ? "Edge" : "优势"}</th>
|
||||
<th className="px-1.5 py-1.5 text-right font-black">{isEn ? "Model" : "模型"}</th>
|
||||
<th className="px-1.5 py-1.5 text-right font-black">Live</th>
|
||||
<th className="px-1.5 py-1.5 text-right font-black">DEB</th>
|
||||
<th className="px-1.5 py-1.5 text-right font-black">{isEn ? "Mkt" : "盘口"}</th>
|
||||
<th className="px-1.5 py-1.5 text-right font-black">{isEn ? "Spr/Liq" : "价差/流动性"}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{cityGroups.map(({ city, displayName, best, buckets }) => {
|
||||
const isExpanded = expandedCities.has(city);
|
||||
const hasMultiple = buckets.length > 1;
|
||||
return (
|
||||
<Fragment key={city}>
|
||||
<tr
|
||||
className={clsx(
|
||||
"cursor-pointer border-b border-slate-100 hover:bg-slate-50/80",
|
||||
selectedId === best.id && "bg-blue-50/50"
|
||||
)}
|
||||
onClick={() => { onSelect(best); if (hasMultiple) toggle(city); }}
|
||||
>
|
||||
<td className="px-2 py-1.5">
|
||||
<button
|
||||
type="button"
|
||||
className="grid h-4 w-4 place-items-center text-slate-400 hover:text-slate-700"
|
||||
onClick={(e) => { e.stopPropagation(); toggle(city); }}
|
||||
title={`${labelExpand} ${hasMultiple ? `${buckets.length} ${labelBuckets}` : ""}`}
|
||||
>
|
||||
{hasMultiple ? (
|
||||
isExpanded ? <ChevronDown size={11} /> : <ChevronRight size={11} />
|
||||
) : (
|
||||
<span className="text-[8px] text-slate-300">—</span>
|
||||
)}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-1.5 py-1.5">
|
||||
<div className="font-bold text-slate-800">{displayName}</div>
|
||||
<div className="text-[9px] text-slate-400">
|
||||
{best.target_label || ""}
|
||||
{hasMultiple && !isExpanded ? ` +${buckets.length - 1}` : ""}
|
||||
</div>
|
||||
</td>
|
||||
<td className={clsx("px-1.5 py-1.5 text-right font-mono font-bold", edgeClass(best.edge_percent))}>
|
||||
{pct(best.edge_percent)}
|
||||
</td>
|
||||
<td className="px-1.5 py-1.5 text-right font-mono text-blue-700">{pct(best.model_probability ?? best.model_event_probability)}</td>
|
||||
<td className="px-1.5 py-1.5 text-right font-mono">{temp(best.current_temp || best.current_max_so_far, best.temp_symbol)}</td>
|
||||
<td className="px-1.5 py-1.5 text-right font-mono">{temp(best.deb_prediction, best.temp_symbol)}</td>
|
||||
<td className="px-1.5 py-1.5 text-right font-mono">{formatPrice(best.midpoint, best.ask, best.bid)}</td>
|
||||
<td className="px-1.5 py-1.5 text-right font-mono">{formatSpreadLiquidity(best.spread, best.book_liquidity ?? best.market_liquidity)}</td>
|
||||
</tr>
|
||||
{isExpanded && buckets.map((row) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
className="border-b border-slate-50 bg-slate-50/30 text-slate-500"
|
||||
>
|
||||
<td className="px-2 py-1 pl-2" />
|
||||
<td className="px-1.5 py-1 pl-3">
|
||||
<span className="text-[10px]">{row.target_label || "--"}</span>
|
||||
</td>
|
||||
<td className={clsx("px-1.5 py-1 text-right font-mono text-[10px] font-bold", edgeClass(row.edge_percent))}>
|
||||
{pct(row.edge_percent)}
|
||||
</td>
|
||||
<td className="px-1.5 py-1 text-right font-mono text-[10px] text-blue-700">{pct(row.model_probability ?? row.model_event_probability)}</td>
|
||||
<td className="px-1.5 py-1 text-right font-mono text-[10px]">{temp(row.current_temp || row.current_max_so_far, row.temp_symbol)}</td>
|
||||
<td className="px-1.5 py-1 text-right font-mono text-[10px]">{temp(row.deb_prediction, row.temp_symbol)}</td>
|
||||
<td className="px-1.5 py-1 text-right font-mono text-[10px]">{formatPrice(row.midpoint, row.ask, row.bid)}</td>
|
||||
<td className="px-1.5 py-1 text-right font-mono text-[10px]">{formatSpreadLiquidity(row.spread, row.book_liquidity ?? row.market_liquidity)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PolyWeatherTerminal({
|
||||
generatedText,
|
||||
isEn,
|
||||
|
||||
@@ -236,6 +236,11 @@ def _build_quick_row(
|
||||
distribution = probs.get("distribution") or []
|
||||
local_date = str(data.get("local_date") or "")
|
||||
local_time = str(data.get("local_time") or "")
|
||||
city_meta = CITIES.get(city) or {}
|
||||
tz_offset = data.get("utc_offset_seconds")
|
||||
if tz_offset is None:
|
||||
tz_offset = _safe_int(city_meta.get("tz"), 0)
|
||||
market_region = _market_region_from_tz_offset(tz_offset)
|
||||
|
||||
id_parts = [city, local_date or "today"]
|
||||
if data.get("temp_symbol") == "°F":
|
||||
@@ -249,7 +254,7 @@ def _build_quick_row(
|
||||
"airport": str(risk.get("airport") or ""),
|
||||
"local_date": local_date,
|
||||
"local_time": local_time,
|
||||
"tz_offset_seconds": data.get("utc_offset_seconds"),
|
||||
"tz_offset_seconds": tz_offset,
|
||||
"temp_symbol": data.get("temp_symbol"),
|
||||
"risk_level": risk.get("level"),
|
||||
"current_temp": curr.get("temp"),
|
||||
@@ -260,8 +265,10 @@ def _build_quick_row(
|
||||
if v is not None
|
||||
},
|
||||
"distribution_preview": distribution[:6] if distribution else [],
|
||||
"trading_region": data.get("trading_region"),
|
||||
"trading_region_sort": data.get("trading_region_sort"),
|
||||
"trading_region": market_region["key"],
|
||||
"trading_region_label": market_region["label_en"],
|
||||
"trading_region_label_zh": market_region["label_zh"],
|
||||
"trading_region_sort": market_region.get("sort_order", 0),
|
||||
"active": True,
|
||||
"closed": False,
|
||||
"tradable": False,
|
||||
|
||||
Reference in New Issue
Block a user