import type { AcquisitionSlot, CandidateEvaluation } from "@umt/contracts"; import { Link2 } from "lucide-react"; import type { ReactNode } from "react"; import { formatDateTime } from "~/lib/utils.ts"; import { CANDIDATE_DECISION, SLOT_STATUS, slotReasonText } from "./labels.ts"; import { Chip, ChipList, Tone } from "./parts.tsx"; import { summarizeCompletion, summarizeHard, summarizeSoft, summarizeUpgrade } from "./slot-rules.tsx"; /** 一个 Slot 的卡片(卡内子块,不叠玻璃):状态、规则摘要、当前资产、候选拒绝理由 */ export function SlotCard({ slot, currentAsset, unitLabel, candidates = [], actions, }: { slot: AcquisitionSlot; /** 当前资产的一行说明(交付特征) */ currentAsset?: ReactNode; /** 绑定到某个内容单元时的说明 */ unitLabel?: string; candidates?: CandidateEvaluation[]; actions?: ReactNode; }) { const st = SLOT_STATUS[slot.status]; const hard = summarizeHard(slot.hardConditions, slot.fallback); const soft = summarizeSoft(slot.softPreferences); const upgrade = summarizeUpgrade(slot.upgradeThreshold); // 服务端按 (Slot, 来源资源) 只保留最新一次评估;这里再按资源去重兜底(列表已按评估时间新→旧) const latest = candidates.filter( (c, i) => candidates.findIndex((x) => x.sourceReleaseId === c.sourceReleaseId) === i, ); return (

{slot.name}

{unitLabel ? {unitLabel} : null} {slot.requireDistinctAsset ? ( 需独立文件{slot.distinctGroup ? ` · ${slot.distinctGroup}` : ""} ) : null} r{slot.revision} {actions}
{slot.statusReason ? (

{slotReasonText(slot.statusReason)}

) : null}
硬条件
软偏好
升级
完成条件
{summarizeCompletion(slot.completion)}
当前文件
{currentAsset ?? "无"}
{latest.length ? (

最近评估的资源和理由(每个资源只列最近一次)

) : null}

最近评估 {formatDateTime(slot.lastEvaluatedAt)} {slot.nextEvaluateAt ? ` · 下次 ${formatDateTime(slot.nextEvaluateAt)}` : ""}

); }