修复 region 过滤透传和 PolyWeatherTerminal selectedCity 类型错误
This commit is contained in:
@@ -252,35 +252,103 @@ function KoyfinRowsTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function KoyfinMarketPanel({
|
function CityRegionList({
|
||||||
compact,
|
|
||||||
isEn,
|
isEn,
|
||||||
onSelect,
|
|
||||||
rows,
|
rows,
|
||||||
selectedId,
|
selectedCity,
|
||||||
title,
|
onSelectCity,
|
||||||
}: {
|
}: {
|
||||||
compact?: boolean;
|
|
||||||
isEn: boolean;
|
isEn: boolean;
|
||||||
onSelect: (row: ScanOpportunityRow) => void;
|
|
||||||
rows: ScanOpportunityRow[];
|
rows: ScanOpportunityRow[];
|
||||||
selectedId?: string | null;
|
selectedCity: string | null;
|
||||||
title: string;
|
onSelectCity: (city: string) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const cities = useMemo(() => {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const result: { city: string; name: string; contractCount: number; bestEdge: number | null; signal: string }[] = [];
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const key = String(row.city || "").toLowerCase();
|
||||||
|
if (seen.has(key)) return;
|
||||||
|
seen.add(key);
|
||||||
|
const cityRows = rows.filter((r) => String(r.city || "").toLowerCase() === key);
|
||||||
|
const bestEdge = cityRows.reduce((best, r) => {
|
||||||
|
const e = r.edge_percent ?? 0;
|
||||||
|
return Math.abs(e) > Math.abs(best) ? e : best;
|
||||||
|
}, 0);
|
||||||
|
result.push({
|
||||||
|
city: key,
|
||||||
|
name: rowName(row),
|
||||||
|
contractCount: cityRows.length,
|
||||||
|
bestEdge,
|
||||||
|
signal: cityRows[0]?.signal_status || "",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, [rows]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel title={title}>
|
<Panel title={isEn ? "Cities" : "城市"} className="shrink-0">
|
||||||
<CityGroupedTable
|
<div className="divide-y divide-slate-100 overflow-auto max-h-[320px]">
|
||||||
compact={compact}
|
{cities.map(({ city, name, contractCount, bestEdge }) => (
|
||||||
isEn={isEn}
|
<button
|
||||||
onSelect={onSelect}
|
key={city}
|
||||||
rows={rows}
|
type="button"
|
||||||
selectedId={selectedId}
|
onClick={() => onSelectCity(city)}
|
||||||
/>
|
className={clsx(
|
||||||
|
"flex w-full items-center justify-between px-3 py-2 text-left hover:bg-blue-50/70 transition-colors",
|
||||||
|
selectedCity === city && "bg-blue-50",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="text-[12px] font-bold text-slate-800 truncate">{name}</div>
|
||||||
|
<div className="text-[11px] text-slate-400">{contractCount} {isEn ? "contracts" : "个合约"}</div>
|
||||||
|
</div>
|
||||||
|
<span className={clsx("shrink-0 font-mono text-[12px] font-black", edgeClass(bestEdge))}>
|
||||||
|
{pct(bestEdge)}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CityGroupedTable({
|
function CityContractDetail({
|
||||||
|
isEn,
|
||||||
|
rows,
|
||||||
|
selectedCity,
|
||||||
|
selectedId,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
isEn: boolean;
|
||||||
|
rows: ScanOpportunityRow[];
|
||||||
|
selectedCity: string | null;
|
||||||
|
selectedId?: string | null;
|
||||||
|
onSelect: (row: ScanOpportunityRow) => void;
|
||||||
|
}) {
|
||||||
|
const cityRows = useMemo(
|
||||||
|
() => (selectedCity ? rows.filter((r) => String(r.city || "").toLowerCase() === selectedCity) : []),
|
||||||
|
[rows, selectedCity],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel title={isEn ? "Contracts" : "合约"} className="flex-1 min-h-0">
|
||||||
|
{!selectedCity ? (
|
||||||
|
<div className="flex items-center justify-center p-6 text-[12px] text-slate-400">
|
||||||
|
{isEn ? "Select a city above" : "在上方选择城市"}
|
||||||
|
</div>
|
||||||
|
) : !cityRows.length ? (
|
||||||
|
<div className="flex items-center justify-center p-6 text-[12px] text-slate-400">
|
||||||
|
{isEn ? "No contracts for this city" : "该城市暂无合约"}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<KoyfinRowsTable compact isEn={isEn} onSelect={onSelect} rows={cityRows} selectedId={selectedId} />
|
||||||
|
)}
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _unused_CityGroupedTable({
|
||||||
compact,
|
compact,
|
||||||
isEn,
|
isEn,
|
||||||
onSelect,
|
onSelect,
|
||||||
@@ -445,6 +513,8 @@ function PolyWeatherTerminal({
|
|||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
setSearchQuery: (val: string) => void;
|
setSearchQuery: (val: string) => void;
|
||||||
searchInputRef: React.RefObject<HTMLInputElement | null>;
|
searchInputRef: React.RefObject<HTMLInputElement | null>;
|
||||||
|
selectedCity: string | null;
|
||||||
|
setSelectedCity: (city: string | null) => void;
|
||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
@@ -766,12 +836,18 @@ function PolyWeatherTerminal({
|
|||||||
{/* Desktop layout */}
|
{/* Desktop layout */}
|
||||||
<div className="hidden h-full min-h-0 lg:grid lg:grid-cols-[0.96fr_1.72fr] gap-2">
|
<div className="hidden h-full min-h-0 lg:grid lg:grid-cols-[0.96fr_1.72fr] gap-2">
|
||||||
<div className="flex min-h-0 flex-col gap-2">
|
<div className="flex min-h-0 flex-col gap-2">
|
||||||
<KoyfinMarketPanel
|
<CityRegionList
|
||||||
isEn={isEn}
|
isEn={isEn}
|
||||||
onSelect={setSelectedRow}
|
|
||||||
rows={filteredRegionRows}
|
rows={filteredRegionRows}
|
||||||
|
selectedCity={selectedCity}
|
||||||
|
onSelectCity={setSelectedCity}
|
||||||
|
/>
|
||||||
|
<CityContractDetail
|
||||||
|
isEn={isEn}
|
||||||
|
rows={filteredRegionRows}
|
||||||
|
selectedCity={selectedCity}
|
||||||
selectedId={selectedRow?.id}
|
selectedId={selectedRow?.id}
|
||||||
title={isEn ? "Weather Contract Markets" : "天气合约市场"}
|
onSelect={setSelectedRow}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -909,6 +985,7 @@ function ScanTerminalScreen() {
|
|||||||
}, [rows, searchQuery]);
|
}, [rows, searchQuery]);
|
||||||
|
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const [selectedCity, setSelectedCity] = useState<string | null>(null);
|
||||||
const selectedRow = useMemo(
|
const selectedRow = useMemo(
|
||||||
() => filteredRows.find((row) => row.id === selectedId) || filteredRows[0] || null,
|
() => filteredRows.find((row) => row.id === selectedId) || filteredRows[0] || null,
|
||||||
[filteredRows, selectedId],
|
[filteredRows, selectedId],
|
||||||
@@ -951,6 +1028,8 @@ function ScanTerminalScreen() {
|
|||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
setSearchQuery={setSearchQuery}
|
setSearchQuery={setSearchQuery}
|
||||||
searchInputRef={searchInputRef}
|
searchInputRef={searchInputRef}
|
||||||
|
selectedCity={selectedCity}
|
||||||
|
setSelectedCity={setSelectedCity}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user