Files
zhixing-system/zhixing-web/src/features/selection/components/execution-status-drawer.tsx
T

126 lines
4.2 KiB
TypeScript

import { XIcon } from "lucide-react"
import type { RefObject } from "react"
import { Badge } from "@/shared/ui/badge"
import { Button } from "@/shared/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/shared/ui/dialog"
import type { SelectionResults } from "../api/selection.types"
import { IncompleteEvaluationTable } from "./incomplete-evaluation-table"
import { getSelectionRunStatusPresentation } from "./selection-presentation"
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">
<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>
<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>
)
}
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>
)
}