Files
zhixing-system/zhixing-web/src/features/selection/components/signal-detail-panel.tsx
T

156 lines
5.1 KiB
TypeScript
Raw Normal View History

import { lazy, Suspense } from "react"
import { Database, Search } from "lucide-react"
import { useSelectionChart } from "../api/selection.query"
import { Badge } from "@/shared/ui/badge"
import { Card } from "@/shared/ui/card"
import type { SelectionStockResult } from "../api/selection.types"
import { PatternScoreDetails } from "./pattern-score"
import { categoryToneClass, getCategoryLabel } from "./selection-presentation"
import { SignalDetails } from "./signal-details"
import { PatternCaseImage } from "./pattern-case-image"
const SelectionChart = lazy(() =>
import("./selection-chart").then((module) => ({
default: module.SelectionChart,
})),
)
interface SignalDetailPanelProps {
stock: SelectionStockResult | undefined
}
export function SignalDetailPanel({ stock }: SignalDetailPanelProps) {
const chartQuery = useSelectionChart(stock?.ts_code, stock?.target_trade_date)
if (!stock) {
return (
<Card
aria-label="当前股票详情"
2026-08-10 16:58:16 +08:00
className="hidden min-h-0 overflow-auto rounded-md border-border/80 bg-card p-3 md:flex md:flex-col md:justify-center"
>
<div className="space-y-2 text-center">
<Search
className="mx-auto size-5 text-muted-foreground"
aria-hidden="true"
/>
<p className="font-medium">暂无选中股票</p>
<p className="text-xs leading-5 text-muted-foreground">
从左侧列表选择一只股票查看详情。
</p>
</div>
</Card>
)
}
return (
<aside
aria-label="当前股票详情"
2026-08-10 16:58:16 +08:00
className="hidden min-h-0 overflow-auto rounded-md border border-border/80 bg-card p-3 md:block"
>
<p className="text-[10px] font-bold tracking-[0.08em] text-muted-foreground uppercase">
Selected stock
</p>
<div className="mt-2 flex items-start justify-between gap-3">
<div className="min-w-0">
<h2 className="truncate text-xl font-semibold tracking-tight">
{stock.name}
</h2>
<p className="font-mono text-xs text-muted-foreground">
{stock.ts_code}
</p>
</div>
<Database
className="mt-1 size-4 shrink-0 text-muted-foreground"
aria-hidden="true"
/>
</div>
<p className="mt-4 text-3xl font-semibold tabular-nums tracking-tight">
¥ {stock.close.toFixed(2)}
</p>
<div className="mt-3 flex flex-wrap gap-1">
{stock.signals.map((signal) => (
<Badge
key={signal.category}
className={categoryToneClass(signal.category)}
variant="outline"
>
{getCategoryLabel(signal.category)}
</Badge>
))}
</div>
<dl className="mt-4 divide-y divide-border/60 border-y border-border/60 text-xs">
<div className="flex items-center justify-between gap-3 py-2.5">
<dt className="text-muted-foreground">目标交易日</dt>
<dd className="font-medium tabular-nums">
{stock.target_trade_date}
</dd>
</div>
<div className="flex items-center justify-between gap-3 py-2.5">
<dt className="text-muted-foreground">策略</dt>
<dd className="font-medium">知行 B1</dd>
</div>
</dl>
<section className="mt-4" aria-labelledby="pattern-score-details">
<h3
className="mb-2 text-xs font-medium text-muted-foreground"
id="pattern-score-details"
>
图形相似度评分
</h3>
<PatternScoreDetails score={stock.score} />
</section>
<section className="mt-4" aria-labelledby="technical-chart">
<h3
className="mb-2 text-xs font-medium text-muted-foreground"
id="technical-chart"
>
技术图表
</h3>
{chartQuery.isPending ? (
<p className="text-xs text-muted-foreground">正在加载行情图表…</p>
) : chartQuery.isError ? (
<p className="text-xs text-destructive">行情图表暂时不可用。</p>
) : chartQuery.data?.points.length ? (
<Suspense
fallback={
<p className="text-xs text-muted-foreground">正在加载图表组件…</p>
}
>
<SelectionChart chart={chartQuery.data} />
</Suspense>
) : (
<p className="text-xs text-muted-foreground">暂无可用行情数据。</p>
)}
</section>
<PatternCaseImage score={stock.score} />
<section className="mt-4" aria-labelledby="signal-detail-metrics">
<h3
className="mb-2 text-xs font-medium text-muted-foreground"
id="signal-detail-metrics"
>
关键指标
</h3>
<div className="space-y-4">
{stock.signals.map((signal) => (
<section key={signal.category} className="space-y-2">
<h4 className="text-xs font-medium text-foreground">
{getCategoryLabel(signal.category)}
</h4>
<SignalDetails details={signal.details} />
</section>
))}
</div>
</section>
</aside>
)
}