"use client"; import React from "react"; import { Star } from "lucide-react"; import { useI18n } from "@/hooks/useI18n"; import type { DistributionPreviewPoint, ScanOpportunityRow, } from "@/lib/dashboard-types"; import { getLocalizedCityName } from "@/lib/dashboard-home-copy"; import { formatTemperatureValue, normalizeTemperatureLabel, normalizeTemperatureSymbol, } from "@/lib/dashboard-utils"; type PhaseMeta = { label: string; tone: "green" | "amber" | "blue" | "red"; }; function formatPercent(value?: number | null, signed = false) { if (value == null || Number.isNaN(Number(value))) return "--"; const numeric = Number(value); return `${signed && numeric >= 0 ? "+" : ""}${numeric.toFixed(1)}%`; } function formatVolume(value?: number | null) { const numeric = Number(value || 0); if (!Number.isFinite(numeric) || numeric <= 0) return "--"; if (numeric >= 1_000_000) return `$${(numeric / 1_000_000).toFixed(1)}M`; if (numeric >= 1_000) return `$${(numeric / 1_000).toFixed(0)}K`; return `$${numeric.toFixed(0)}`; } function formatWindowMinutes(value: number | null | undefined, locale: string) { if (value == null || !Number.isFinite(Number(value))) return "--"; const minutes = Math.max(0, Math.round(Number(value))); const hours = Math.floor(minutes / 60); const remains = minutes % 60; if (locale === "en-US") { if (hours <= 0) return `${remains}m left`; return `${hours}h ${remains}m left`; } if (hours <= 0) return `剩余 ${remains} 分钟`; return `剩余 ${hours}h ${remains}m`; } function formatAction( row: ScanOpportunityRow, locale: string, tempSymbol?: string | null, ) { const formattedTarget = normalizeTemperatureLabel(row.target_label, tempSymbol); if (row.action) { return row.target_label ? row.action.replace(String(row.target_label), formattedTarget || String(row.target_label)) : row.action; } if (row.side === "yes") { return `${locale === "en-US" ? "Buy Yes" : "买入 Yes"} ${formattedTarget || ""}`.trim(); } if (row.side === "no") { return `${locale === "en-US" ? "Buy No" : "买入 No"} ${formattedTarget || ""}`.trim(); } return "--"; } export function getWindowPhaseMeta( row: Pick, locale: string, ): PhaseMeta { const mode = String(row.window_phase || "").toLowerCase(); if (mode === "active_peak") { return { label: locale === "en-US" ? "Peak Window" : "峰值窗口", tone: "red", }; } if (mode === "setup_today") { return { label: locale === "en-US" ? "Touch Play" : "触达博弈", tone: "red", }; } if (mode === "early_today") { return { label: locale === "en-US" ? "Early Today" : "日内早段", tone: "blue", }; } if (mode === "tomorrow" || mode === "week_ahead") { return { label: locale === "en-US" ? "Early" : "早期机会", tone: "blue", }; } if (mode === "post_peak") { return { label: locale === "en-US" ? "Post Peak" : "峰后确认", tone: "amber", }; } if (row.trend_alignment) { return { label: locale === "en-US" ? "Trend" : "趋势确认", tone: "amber", }; } return { label: locale === "en-US" ? "Tradable" : "可交易", tone: "green", }; } function scoreTone(score?: number | null) { const numeric = Number(score || 0); if (numeric >= 85) return "green"; if (numeric >= 70) return "yellow"; return "gray"; } function ProbabilityPreview({ row, locale, }: { row: ScanOpportunityRow; locale: string; }) { const preview = Array.isArray(row.distribution_preview) ? row.distribution_preview.filter( (item): item is DistributionPreviewPoint => Boolean(item && (item.label || item.value != null)), ) : []; const tempSymbol = normalizeTemperatureSymbol(row.target_unit || row.temp_symbol); if (!preview.length) { const targetBase = row.target_value ?? row.target_threshold ?? row.target_lower ?? row.target_upper ?? null; const targetLabel = targetBase != null ? formatTemperatureValue(Number(targetBase), tempSymbol) : normalizeTemperatureLabel(row.target_label, tempSymbol) || "--"; preview.push({ label: targetLabel, model_probability: row.model_event_probability, market_probability: row.market_event_probability, highlighted: true, }); } return (
{preview.slice(0, 6).map((item) => (
{normalizeTemperatureLabel(item.label, tempSymbol) || item.label || "--"} {locale === "en-US" ? "Model" : "模型"}
{formatPercent( item.model_probability != null ? item.model_probability * 100 : null, )}

{locale === "en-US" ? "Market" : "市场"}
{formatPercent( item.market_probability != null ? item.market_probability * 100 : null, )}
))}
); } function ScoreRing({ score }: { score?: number | null }) { const displayScore = Math.max(0, Math.min(100, Number(score || 0))); return (
{displayScore.toFixed(0)}
); } export function OpportunityTable({ rows, selectedRowId, onSelectRow, }: { rows: ScanOpportunityRow[]; selectedRowId?: string | null; onSelectRow?: (row: ScanOpportunityRow) => void; }) { const { locale } = useI18n(); const isEn = locale === "en-US"; if (!rows.length) { return (
{isEn ? "No main signal right now" : "当前无主信号"}
{isEn ? "No row passed the price, spread, liquidity, and edge thresholds." : "当前没有机会同时满足价格、点差、流动性和 edge 过滤。"}
); } return (
{isEn ? "City / Market" : "城市 / 市场"} {isEn ? "Local Time / Phase" : "当前时间 / 阶段"} {isEn ? "EMOS vs Market" : "EMOS 分布 vs 市场分布"} {isEn ? "Best Opportunity" : "最佳机会"} {isEn ? "Edge" : "边际优势"} {isEn ? "Score" : "综合得分"}
{rows.map((row, index) => { const phaseMeta = getWindowPhaseMeta(row, locale); const tempSymbol = normalizeTemperatureSymbol(row.target_unit || row.temp_symbol); const localizedCityName = getLocalizedCityName( row.city, row.city_display_name || row.display_name || row.city, locale, ); const selected = selectedRowId === row.id; const scoreClass = scoreTone(row.final_score); return ( ); })}
); }