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

@@ -10,6 +10,7 @@ import { useMemberStore } from '@/stores/useMemberStore';
import { BugRow } from './BugRow';
import { BugDetailDrawer } from './BugDetailDrawer';
import { Pagination, usePagination } from '@/components/Pagination';
import { FilterSelect } from '@/components/FilterSelect';
import { SearchInput, matchTitleOrNo } from '@/components/SearchInput';
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
import { BUG_STATUS_LABEL, BUG_SEVERITY_LABEL, aggregateBugActualHours, bugIntervals } from '@/lib/bug';
@@ -118,19 +119,27 @@ export function BugTab({ versionId, requirementIds }: Props) {
<div className="flex items-center gap-2 px-1 flex-wrap">
<Filter className="h-3.5 w-3.5 text-[var(--ink-muted)]" />
<SearchInput value={keyword} onChange={(v) => { setKeyword(v); setPage(1); }} placeholder="搜索标题/编号" />
<select value={filterAssignee} onChange={(e) => { setFilterAssignee(e.target.value); setPage(1); }} className="h-7 rounded-md border border-[var(--line)] bg-[var(--bg-card)] px-2 text-[11px] text-[var(--ink-soft)] focus:border-[var(--accent)] focus:outline-none">
<option value=""></option>
{currentMember && <option value="__me">Bug</option>}
{assignees.filter((a) => a !== currentUserName).map((a) => <option key={a} value={a}>{a}</option>)}
</select>
<select value={filterStatus} onChange={(e) => { setFilterStatus(e.target.value); setPage(1); }} className="h-7 rounded-md border border-[var(--line)] bg-[var(--bg-card)] px-2 text-[11px] text-[var(--ink-soft)] focus:border-[var(--accent)] focus:outline-none">
<option value=""></option>
{(Object.entries(BUG_STATUS_LABEL) as [BugStatus, string][]).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
</select>
<select value={filterSeverity} onChange={(e) => { setFilterSeverity(e.target.value); setPage(1); }} className="h-7 rounded-md border border-[var(--line)] bg-[var(--bg-card)] px-2 text-[11px] text-[var(--ink-soft)] focus:border-[var(--accent)] focus:outline-none">
<option value=""></option>
{(Object.entries(BUG_SEVERITY_LABEL) as [BugSeverity, string][]).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
</select>
<FilterSelect
value={filterAssignee || 'all'}
onChange={(value) => { setFilterAssignee(value === 'all' ? '' : value); setPage(1); }}
options={[
...(currentMember ? [{ value: '__me', label: '我的Bug' }] : []),
...assignees.filter((assignee) => assignee !== currentUserName).map((assignee) => ({ value: assignee, label: assignee })),
]}
allLabel="全部修复人"
/>
<FilterSelect
value={filterStatus || 'all'}
onChange={(value) => { setFilterStatus(value === 'all' ? '' : value); setPage(1); }}
options={(Object.entries(BUG_STATUS_LABEL) as [BugStatus, string][]).map(([value, label]) => ({ value, label }))}
allLabel="全部状态"
/>
<FilterSelect
value={filterSeverity || 'all'}
onChange={(value) => { setFilterSeverity(value === 'all' ? '' : value); setPage(1); }}
options={(Object.entries(BUG_SEVERITY_LABEL) as [BugSeverity, string][]).map(([value, label]) => ({ value, label }))}
allLabel="全部严重程度"
/>
{hasFilter && <button onClick={() => { setFilterAssignee(''); setFilterStatus(''); setFilterSeverity(''); setKeyword(''); setPage(1); }} className="text-[11px] text-[var(--accent)] hover:underline"></button>}
<span className="ml-auto text-[11px] text-[var(--ink-muted)]">{filteredBugs.length} </span>
</div>