feat(版本): 完善计划覆盖与工作台待办

关键改动:

- 增加计划日志汇总、需求覆盖草稿校验与对应测试

- 抽取工作台工作项 Hook,补充待办计数能力

- 优化版本详情中计划、任务、测试用例和 Bug 的筛选与展示

Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
Script Generator
2026-06-30 13:43:18 +08:00
parent a6a4d7d3d9
commit 52a88626d4
23 changed files with 1082 additions and 281 deletions

View File

@@ -2,6 +2,7 @@
import { useState } from 'react';
import { Plus, X, Search } from 'lucide-react';
import { FilterSelect } from '@/components/FilterSelect';
import type { Requirement, ChangeReason } from '@/lib/requirement';
import type { DevTask } from '@/lib/dev-task';
import { REQ_STATUS_LABEL, REQ_STATUS_COLOR, CHANGE_REASON_LABEL } from '@/lib/requirement';
@@ -161,18 +162,23 @@ function ChangeRequirementModal({ versionMembers, currentUserName, onClose, onSu
<div className="space-y-3">
<div>
<label className="text-[12px] font-medium text-[var(--ink-soft)] mb-1 block"> <span className="text-red-500">*</span></label>
<select value={changeBy} onChange={(e) => setChangeBy(e.target.value)} className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none">
{versionMembers.map((m) => <option key={`${m.role}-${m.name}`} value={m.name}>{m.name}</option>)}
</select>
<FilterSelect
value={changeBy || 'all'}
onChange={(value) => setChangeBy(value === 'all' ? '' : value)}
options={versionMembers.map((member) => ({ value: member.name, label: member.name }))}
allLabel="选择变更人员"
className="w-full"
/>
</div>
<div>
<label className="text-[12px] font-medium text-[var(--ink-soft)] mb-1 block"> <span className="text-red-500">*</span></label>
<select value={changeReason} onChange={(e) => setChangeReason(e.target.value as ChangeReason)} className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none">
<option value=""></option>
{Object.entries(CHANGE_REASON_LABEL).map(([key, label]) => (
<option key={key} value={key}>{label}</option>
))}
</select>
<FilterSelect
value={changeReason || 'all'}
onChange={(value) => setChangeReason(value === 'all' ? '' : value as ChangeReason)}
options={Object.entries(CHANGE_REASON_LABEL).map(([key, label]) => ({ value: key, label }))}
allLabel="请选择变更原因"
className="w-full"
/>
</div>
<div>
<label className="text-[12px] font-medium text-[var(--ink-soft)] mb-1 block"> <span className="text-red-500">*</span></label>