import { useEffect, useMemo, useState } from "react" import { useNavigate, useSearch } from "@tanstack/react-router" import { Input } from "@/shared/ui/input" import { Pagination } from "@/shared/ui/pagination" import type { SelectionResults, SelectionSignal } from "../api/selection.types" import { SignalDetailPanel } from "./signal-detail-panel" import { SignalRecordList } from "./signal-record-list" import { getSignalKey, matchesCategory, signalCategoryOptions, type SignalCategoryFilter, } from "./selection-presentation" import { SignalTable } from "./signal-table" const PAGE_SIZE_OPTIONS = [5, 10, 20] as const interface SelectionResultsWorkbenchProps { result: SelectionResults } export function SelectionResultsWorkbench({ result, }: SelectionResultsWorkbenchProps) { const search = useSearch({ from: "/_workspace/selection" }) const navigate = useNavigate({ from: "/selection" }) const [query, setQuery] = useState("") const [category, setCategory] = useState("all") const [selectedKey, setSelectedKey] = useState(null) const [expandedKeys, setExpandedKeys] = useState>( new Set(), ) const filteredSignals = useMemo(() => { const normalized = query.trim().toLowerCase() return result.signals.filter((signal) => { const matchesQuery = normalized.length === 0 || signal.name.toLowerCase().includes(normalized) || signal.ts_code.toLowerCase().includes(normalized) return matchesQuery && matchesCategory(signal, category) }) }, [category, query, result.signals]) const page = search.page const pageSize = search.pageSize const pageCount = Math.max(1, Math.ceil(filteredSignals.length / pageSize)) const currentPage = Math.min(page, pageCount) const currentSignals = filteredSignals.slice( (currentPage - 1) * pageSize, currentPage * pageSize, ) const selectedSignal = currentSignals.find((signal) => getSignalKey(signal) === selectedKey) ?? currentSignals[0] useEffect(() => { if (page > pageCount) { void navigate({ search: (previous) => ({ ...previous, page: pageCount }), }) } }, [navigate, page, pageCount]) function updateSearch(next: { page?: number; pageSize?: number }) { void navigate({ search: (previous) => ({ ...previous, ...next }) }) } function handleQueryChange(value: string) { setQuery(value) updateSearch({ page: 1 }) } function handleCategoryChange(value: SignalCategoryFilter) { setCategory(value) updateSearch({ page: 1 }) } function handleToggleExpanded(signal: SelectionSignal) { const key = getSignalKey(signal) setExpandedKeys((previous) => { const next = new Set(previous) if (next.has(key)) next.delete(key) else next.add(key) return next }) } return (
handleQueryChange(event.target.value)} placeholder="搜索股票名称或代码" type="search" value={query} /> 筛选结果 {filteredSignals.length} 条
setSelectedKey(getSignalKey(signal))} selectedKey={selectedSignal ? getSignalKey(selectedSignal) : null} signals={currentSignals} /> setSelectedKey(getSignalKey(signal))} onToggleExpanded={handleToggleExpanded} signals={currentSignals} />
updateSearch({ page: nextPage })} onPageSizeChange={(nextPageSize) => updateSearch({ page: 1, pageSize: nextPageSize }) } page={currentPage} pageSize={pageSize} pageSizeOptions={PAGE_SIZE_OPTIONS} total={filteredSignals.length} />
) }