R31-1 R31-4: 收口详情页工作台化与 E2E 契约

这个提交包含在:
2026-03-24 08:58:36 +08:00
父节点 994d98e74c
当前提交 279be83931
共修改 17 个文件,包含 906 行新增和 455 行删除
+17 -10
查看文件
@@ -15,6 +15,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ".
import { toast } from "sonner";
import { isApiError } from "../api/types";
import { mapApiErrorMessage } from "../api/errorMessages";
import { TEST_IDS } from "../lib/testIds";
function errorMessage(error: unknown, fallback: string) {
if (isApiError(error)) {
@@ -135,7 +136,13 @@ export function WeeklyReportsPage() {
<div className="text-sm font-medium text-zinc-900">按周归档</div>
<div className="text-sm text-zinc-500">默认按周一归档;提交后会同步更新 Portal 与项目摘要。</div>
</div>
<input className="input-std w-[220px]" type="date" value={weekStart} onChange={(event) => setWeekStart(event.target.value)} />
<input
aria-label="周报归档周"
className="input-std w-[220px]"
type="date"
value={weekStart}
onChange={(event) => setWeekStart(event.target.value)}
/>
</FilterBar>
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
@@ -151,16 +158,16 @@ export function WeeklyReportsPage() {
{reportQuery.isError ? <div className="text-sm text-red-600">{errorMessage(reportQuery.error, "加载周报失败")}</div> : null}
<div className="grid gap-4">
<div className="grid gap-2">
<label className="text-sm text-zinc-500">本周完成</label>
<Textarea value={draft.highlightsText} onChange={(event) => setDraft((prev) => ({ ...prev, highlightsText: event.target.value }))} />
<label className="text-sm text-zinc-500" htmlFor="weekly-report-highlights">本周完成</label>
<Textarea id="weekly-report-highlights" value={draft.highlightsText} onChange={(event) => setDraft((prev) => ({ ...prev, highlightsText: event.target.value }))} />
</div>
<div className="grid gap-2">
<label className="text-sm text-zinc-500">当前阻塞</label>
<Textarea value={draft.blockersText} onChange={(event) => setDraft((prev) => ({ ...prev, blockersText: event.target.value }))} />
<label className="text-sm text-zinc-500" htmlFor="weekly-report-blockers">当前阻塞</label>
<Textarea id="weekly-report-blockers" value={draft.blockersText} onChange={(event) => setDraft((prev) => ({ ...prev, blockersText: event.target.value }))} />
</div>
<div className="grid gap-2">
<label className="text-sm text-zinc-500">下周计划</label>
<Textarea value={draft.nextWeekText} onChange={(event) => setDraft((prev) => ({ ...prev, nextWeekText: event.target.value }))} />
<label className="text-sm text-zinc-500" htmlFor="weekly-report-next-week">下周计划</label>
<Textarea id="weekly-report-next-week" value={draft.nextWeekText} onChange={(event) => setDraft((prev) => ({ ...prev, nextWeekText: event.target.value }))} />
</div>
<label className="flex items-center gap-2 text-sm text-zinc-700">
<input type="checkbox" checked={draft.needsHelp} onChange={(event) => setDraft((prev) => ({ ...prev, needsHelp: event.target.checked }))} />
@@ -193,9 +200,9 @@ export function WeeklyReportsPage() {
</div>
))}
<div className="flex flex-wrap justify-end gap-2">
<Button type="button" variant="secondary" onClick={() => createTodoMutation.mutate()} disabled={createTodoMutation.isPending}>转成待办</Button>
<Button type="button" variant="secondary" onClick={() => saveMutation.mutate()} disabled={saveMutation.isPending}>保存草稿</Button>
<Button type="button" onClick={() => submitMutation.mutate()} disabled={submitMutation.isPending}>提交周报</Button>
<Button data-testid={TEST_IDS.weeklyReport.createTodo} type="button" variant="secondary" onClick={() => createTodoMutation.mutate()} disabled={createTodoMutation.isPending}>转成待办</Button>
<Button data-testid={TEST_IDS.weeklyReport.save} type="button" variant="secondary" onClick={() => saveMutation.mutate()} disabled={saveMutation.isPending}>保存草稿</Button>
<Button data-testid={TEST_IDS.weeklyReport.submit} type="button" onClick={() => submitMutation.mutate()} disabled={submitMutation.isPending}>提交周报</Button>
</div>
</div>
</SectionPanel>