2026-08-10 13:42:53 +08:00
|
|
|
import { XIcon } from "lucide-react"
|
|
|
|
|
import type { RefObject } from "react"
|
|
|
|
|
|
2026-08-10 14:50:56 +08:00
|
|
|
import { Badge } from "@/shared/ui/badge"
|
|
|
|
|
import { Button } from "@/shared/ui/button"
|
2026-08-10 13:42:53 +08:00
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogClose,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogFooter,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
} from "@/shared/ui/dialog"
|
2026-08-10 14:50:56 +08:00
|
|
|
|
|
|
|
|
import type { SelectionResults } from "../api/selection.types"
|
|
|
|
|
import { IncompleteEvaluationTable } from "./incomplete-evaluation-table"
|
|
|
|
|
import { getSelectionRunStatusPresentation } from "./selection-presentation"
|
2026-08-10 13:42:53 +08:00
|
|
|
|
|
|
|
|
interface ExecutionStatusDrawerProps {
|
|
|
|
|
finalFocus: RefObject<HTMLElement | null>
|
|
|
|
|
onOpenChange: (open: boolean) => void
|
|
|
|
|
open: boolean
|
|
|
|
|
result: SelectionResults
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ExecutionStatusDrawer({
|
|
|
|
|
finalFocus,
|
|
|
|
|
onOpenChange,
|
|
|
|
|
open,
|
|
|
|
|
result,
|
|
|
|
|
}: ExecutionStatusDrawerProps) {
|
|
|
|
|
const statusPresentation = getSelectionRunStatusPresentation(result.status)
|
|
|
|
|
const statusLabel = statusPresentation?.label ?? result.status
|
|
|
|
|
const tradeDate = result.target_trade_date ?? "未指定"
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
<DialogContent
|
|
|
|
|
aria-label="选股执行状态"
|
|
|
|
|
className="top-0 right-0 left-auto flex h-[100svh] min-h-[100svh] w-[min(600px,100vw)] max-w-none translate-x-full translate-y-0 flex-col grid-cols-1 gap-0 rounded-l-xl rounded-r-none border-y-0 border-r-0 p-4 data-open:translate-x-0 data-closed:translate-x-full sm:max-w-none"
|
|
|
|
|
finalFocus={finalFocus}
|
|
|
|
|
id="selection-execution-status-drawer"
|
|
|
|
|
showCloseButton={false}
|
|
|
|
|
>
|
|
|
|
|
<DialogHeader className="shrink-0 gap-1 pr-10">
|
|
|
|
|
<DialogTitle className="text-lg">知行 B1 执行状态</DialogTitle>
|
|
|
|
|
<DialogDescription>
|
|
|
|
|
目标交易日:{tradeDate};当前状态:{statusLabel};未完成评估:
|
|
|
|
|
{result.failures.length} 条。
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
<div className="min-h-0 flex-1 overflow-y-auto py-4">
|
2026-08-10 14:50:56 +08:00
|
|
|
<section
|
|
|
|
|
aria-label="执行摘要"
|
|
|
|
|
className="mb-4 rounded-lg border border-border/70 bg-muted/20 p-3"
|
|
|
|
|
>
|
|
|
|
|
<h2 className="sr-only">执行摘要</h2>
|
|
|
|
|
<dl className="grid grid-cols-2 gap-x-4 gap-y-3 sm:grid-cols-3">
|
|
|
|
|
<SummaryMetric label="目标股票数" value={result.target_count} />
|
|
|
|
|
<SummaryMetric label="实际参与数" value={result.eligible_count} />
|
|
|
|
|
<SummaryMetric
|
|
|
|
|
label="命中股票数"
|
|
|
|
|
value={result.selected_stock_count}
|
|
|
|
|
/>
|
|
|
|
|
<SummaryMetric label="信号条数" value={result.signal_count} />
|
|
|
|
|
<SummaryMetric
|
|
|
|
|
label="数据覆盖率"
|
|
|
|
|
value={`${(result.coverage * 100).toFixed(2)}%`}
|
|
|
|
|
/>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<dt className="text-[11px] text-muted-foreground">执行状态</dt>
|
|
|
|
|
<dd>
|
|
|
|
|
{statusPresentation ? (
|
|
|
|
|
<Badge variant={statusPresentation.variant}>
|
|
|
|
|
{statusLabel}
|
|
|
|
|
</Badge>
|
|
|
|
|
) : (
|
|
|
|
|
<span className="text-sm font-semibold">{statusLabel}</span>
|
|
|
|
|
)}
|
|
|
|
|
</dd>
|
|
|
|
|
</div>
|
|
|
|
|
</dl>
|
|
|
|
|
</section>
|
2026-08-10 13:42:53 +08:00
|
|
|
<IncompleteEvaluationTable failures={result.failures} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<DialogFooter className="shrink-0 -mx-4 -mb-4 rounded-none px-4 py-3">
|
|
|
|
|
<DialogClose render={<Button variant="outline" />}>关闭</DialogClose>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
|
|
|
|
|
<DialogClose
|
|
|
|
|
render={
|
|
|
|
|
<Button
|
|
|
|
|
aria-label="关闭执行状态"
|
|
|
|
|
className="absolute top-3 right-3 size-11 md:size-8"
|
|
|
|
|
size="icon"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<XIcon aria-hidden="true" />
|
|
|
|
|
</DialogClose>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-08-10 14:50:56 +08:00
|
|
|
|
|
|
|
|
function SummaryMetric({
|
|
|
|
|
label,
|
|
|
|
|
value,
|
|
|
|
|
}: {
|
|
|
|
|
label: string
|
|
|
|
|
value: number | string
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<dt className="text-[11px] text-muted-foreground">{label}</dt>
|
|
|
|
|
<dd className="text-base font-semibold tracking-tight tabular-nums">
|
|
|
|
|
{value}
|
|
|
|
|
</dd>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|