Files
writer-work-flow/apps/web/components/review/AcceptPanel.tsx
Yaojia Wang 6e5d0e88f6 feat(ux): R3 验收前清单 — AcceptPanel「本次将更新」预览
新 lib/review/accept-preview.ts (buildAcceptPreview):据现有 client state 列验收前预期项——晋升版次/提炼摘要/写回裁决/伏笔到期扫描/伏笔建议待登记。仅列后端 accept 事务确实做的事(核对 projects.py accept 端点),不声称改人物 latest_state,带「以验收回执为准」免责。
AcceptPanel 加 foreshadowCount prop + 未阻断时渲染清单;ReviewReport 传 foreshadow.length。
TDD: 4 测。前端门禁绿: lint/tsc/vitest 205/build。
2026-06-20 16:00:51 +02:00

109 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import type { AcceptResponse } from "@/lib/api/types";
import { buildAcceptPreview } from "@/lib/review/accept-preview";
interface AcceptPanelProps {
conflictCount: number;
unresolvedCount: number;
// R3当前伏笔建议数验收前清单提醒只读不自动落库
foreshadowCount: number;
accepting: boolean;
result: AcceptResponse | null;
onAccept: () => void;
}
// 验收 gateUX §9 / §8.4):未决禁验收(置灰 + 文案),验收后呈现「本次将更新」清单。
export function AcceptPanel({
conflictCount,
unresolvedCount,
foreshadowCount,
accepting,
result,
onAccept,
}: AcceptPanelProps) {
const blocked = unresolvedCount > 0;
const preview = buildAcceptPreview({
conflictCount,
foreshadowSuggestions: foreshadowCount,
});
if (result) {
return (
<div className="rounded border border-pass/50 bg-panel p-4">
<h3 className="mb-2 text-sm font-semibold text-pass"></h3>
<ul className="space-y-1 text-xs text-ink-soft">
<li>
<span className="font-mono text-ink">v{result.accepted_version}</span>
</li>
<li>
{result.digest_added ? "已新增一行" : "未变更"}
</li>
<li>
<span className="font-mono text-ink">
{result.decisions_recorded}
</span>{" "}
</li>
{result.review_id ? (
<li className="truncate">
<span className="font-mono">{result.review_id}</span>
</li>
) : null}
</ul>
</div>
);
}
return (
<div className="rounded border border-line bg-panel p-4">
{blocked ? (
<p className="mb-2 text-xs text-conflict" role="status">
{unresolvedCount}
</p>
) : (
<>
<p className="mb-2 text-xs text-ink-soft">
{conflictCount === 0
? "无冲突,可直接验收。"
: "全部冲突已裁决,可验收本章。"}
</p>
{/* R3验收前「本次将更新」清单预期落库口径以验收回执为准。 */}
<div className="mb-3 rounded border border-line/70 bg-bg/50 p-3">
<h4 className="mb-1.5 text-xs font-semibold text-ink">
</h4>
<ul className="space-y-1.5">
{preview.map((item) => (
<li key={item.label} className="flex gap-2 text-xs text-ink-soft">
<span className="text-cinnabar" aria-hidden="true">
</span>
<span>
<span className="text-ink">{item.label}</span>
<span className="text-ink-soft">{item.detail}</span>
</span>
</li>
))}
</ul>
<p className="mt-2 text-[11px] text-ink-soft/70">
</p>
</div>
</>
)}
<button
type="button"
onClick={onAccept}
disabled={blocked || accepting}
aria-disabled={blocked || accepting}
className="w-full rounded bg-cinnabar px-4 py-2 text-sm text-panel hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40"
>
{accepting ? "验收中…" : "全部处理完 → 验收本章"}
</button>
</div>
);
}