feat(版本): 记录计划需求进展活动

关键改动:

- 记录产品/UI 计划需求覆盖进展,并写入工作活动与实体日志

- 日报和小宝预警补充需求进展活动证据

- 调整版本列表与需求覆盖面板展示,并补充测试

Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
Script Generator
2026-06-30 15:35:36 +08:00
parent f82ec1ea6f
commit 04520bd8c5
13 changed files with 173 additions and 82 deletions

View File

@@ -7,6 +7,7 @@ import type { Requirement } from '@/lib/requirement';
import type { RequirementCoverageStatus, VersionPlan, VersionPlanLog, VersionPlanLogView } from '@/lib/version-plan';
import {
canSaveRequirementCoverageDraft,
canOpenRequirementCoverageRecord,
getRequirementCoverage,
getRequirementCoverageStatus,
getRequirementCoverageSummary,
@@ -139,6 +140,7 @@ export function PlanRequirementCoveragePanel({ plan, requirements, canEdit, curr
const status = getRequirementCoverageStatus(plan, req.id);
const coverage = getRequirementCoverage(plan, req.id);
const isEditing = editingRequirementId === req.id;
const canOpenRecord = canOpenRequirementCoverageRecord(status, canEdit);
return (
<div key={req.id} className="rounded-md px-2 py-1.5 hover:bg-[var(--bg-card)]">
<div className="flex min-w-0 items-start gap-2">
@@ -158,14 +160,16 @@ export function PlanRequirementCoveragePanel({ plan, requirements, canEdit, curr
</div>
)}
</div>
{canEdit && (
<button
type="button"
onClick={() => isEditing ? closeEditor() : openEditor(req)}
className="shrink-0 rounded-md border border-[var(--line)] px-2 py-1 text-[11px] font-medium text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)]"
>
{isEditing ? '收起' : '记录'}
</button>
{canEdit && (canOpenRecord || isEditing) && (
<div className="flex shrink-0 items-center gap-1.5">
<button
type="button"
onClick={() => isEditing ? closeEditor() : openEditor(req)}
className="rounded-md border border-[var(--line)] px-2 py-1 text-[11px] font-medium text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)]"
>
{isEditing ? '收起' : '记录'}
</button>
</div>
)}
</div>
{isEditing && (

View File

@@ -63,17 +63,28 @@ export function DailyReportPanel({ report }: Props) {
<span className="text-[10px] text-[var(--ink-muted)]">{items.length} </span>
</div>
<div className="space-y-2">
{items.map((item) => (
<div key={item.id} className="rounded-lg border border-[var(--line)] bg-[var(--bg)] p-3">
<div className="flex items-start justify-between gap-2">
<p className="min-w-0 flex-1 break-words text-[12px] font-medium leading-5 text-[var(--ink)]">{item.summary}</p>
<span className="shrink-0 text-[10px] text-[var(--ink-muted)]">{formatDateTimeShort(item.occurredAt)}</span>
{items.map((item) => {
const completedContent = typeof item.metadata?.completedContent === 'string' ? item.metadata.completedContent : '';
const remainingContent = typeof item.metadata?.remainingContent === 'string' ? item.metadata.remainingContent : '';
return (
<div key={item.id} className="rounded-lg border border-[var(--line)] bg-[var(--bg)] p-3">
<div className="flex items-start justify-between gap-2">
<p className="min-w-0 flex-1 break-words text-[12px] font-medium leading-5 text-[var(--ink)]">{item.summary}</p>
<span className="shrink-0 text-[10px] text-[var(--ink-muted)]">{formatDateTimeShort(item.occurredAt)}</span>
</div>
{(completedContent || remainingContent) && (
<div className="mt-2 space-y-1 border-l-2 border-amber-200 pl-2 text-[11px] leading-5 text-[var(--ink-soft)]">
{completedContent && <div className="break-words">{completedContent}</div>}
{remainingContent && <div className="break-words text-amber-700">{remainingContent}</div>}
</div>
)}
{item.context && (
<p className="mt-1 truncate text-[10px] text-[var(--ink-muted)]" title={item.context}>{item.context}</p>
)}
</div>
{item.context && (
<p className="mt-1 truncate text-[10px] text-[var(--ink-muted)]" title={item.context}>{item.context}</p>
)}
</div>
))}
);
})}
</div>
</section>
);