feat(工作台): 细化计划进展工时证据

关键改动:

- 支持需求覆盖和调研方向开始工作记录

- 日报按计划记录开始时间和进度下次开始时间计算证据

- 更新版本编辑校验、项目展示和 workflow 说明

Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
Script Generator
2026-07-01 11:02:38 +08:00
parent e5ce2d221e
commit 8947aeaac7
18 changed files with 1125 additions and 94 deletions

View File

@@ -47,6 +47,13 @@ function defaultPlanEndLocal(): string {
return isoToLocal(d.toISOString());
}
function defaultProgressNextStartLocal(): string {
const d = new Date();
d.setDate(d.getDate() + 1);
d.setHours(9, 30, 0, 0);
return isoToLocal(d.toISOString());
}
export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel, readOnly = false }: Props) {
const { tasks, changeStatus, setBlocked, deleteTask, updateTask } = useDevTaskStore();
const addProgressNote = useWorkActivityStore((s) => s.addProgressNote);
@@ -65,6 +72,7 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel,
const [progressBlocker, setProgressBlocker] = useState('');
const [progressHelperId, setProgressHelperId] = useState('');
const [progressDelayRisk, setProgressDelayRisk] = useState('');
const [progressNextStartLocal, setProgressNextStartLocal] = useState(() => defaultProgressNextStartLocal());
const task = tasks.find((t) => t.id === taskId);
if (!task) return null;
@@ -92,6 +100,10 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel,
const planEndISO = localToISO(planEndLocal);
const planStartBeforeEnd = Boolean(planStartISO && planEndISO && planStartISO < planEndISO);
const planEstimateHours = planStartBeforeEnd ? calcWorkHours(planStartISO, planEndISO) : 0;
const progressNextStartISO = localToISO(progressNextStartLocal);
const canRecordProgress = Boolean(
(progressNote.trim() || progressBlocker.trim() || progressDelayRisk.trim()) && progressNextStartISO,
);
const openPlanInput = () => {
if (readOnly) return;
@@ -160,6 +172,10 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel,
const blocker = progressBlocker.trim();
const delayRisk = progressDelayRisk.trim();
if (!note && !blocker && !delayRisk) return;
if (!progressNextStartISO) {
alert('请填写下次开始时间');
return;
}
addProgressNote({
actorId: task.assigneeId,
@@ -167,6 +183,7 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel,
sourceId: task.id,
title: task.title,
note: note || '今日进展已更新',
nextStartAt: progressNextStartISO,
blocker: blocker || undefined,
helperId: progressHelperId || undefined,
delayRisk: delayRisk || undefined,
@@ -175,6 +192,7 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel,
setProgressBlocker('');
setProgressHelperId('');
setProgressDelayRisk('');
setProgressNextStartLocal(defaultProgressNextStartLocal());
};
return (
@@ -381,11 +399,25 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel,
placeholder="延期风险"
className="h-8 w-full rounded-lg border border-[var(--line)] bg-[var(--bg)] px-2 text-[12px] focus:border-orange-400 focus:outline-none"
/>
<div className="flex justify-end">
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-end gap-2">
<div>
<label className="mb-1 flex items-center gap-1 text-[11px] text-[var(--ink-muted)]">
<span className="text-red-500">*</span>
</label>
<WorkDateTimePicker
value={progressNextStartLocal}
onChange={setProgressNextStartLocal}
placeholder="选择下次开始时间"
defaultHour={9}
popoverAlign="right"
className="bg-[var(--bg)]"
/>
</div>
<button
onClick={handleProgressNote}
disabled={!progressNote.trim() && !progressBlocker.trim() && !progressDelayRisk.trim()}
className="h-8 px-3 rounded-lg text-[12px] font-medium bg-[var(--accent)] text-white hover:bg-[var(--accent-hover)] disabled:opacity-50"
disabled={!canRecordProgress}
className="h-9 px-3 rounded-lg text-[12px] font-medium bg-[var(--accent)] text-white hover:bg-[var(--accent-hover)] disabled:opacity-50"
>
</button>