fix(dev-task): 体验优化 — 新建弹窗默认值+详情卡片分区+筛选分页+胶囊联动

- 新建弹窗:负责人默认当前用户,提示版本截止日期,超期需填写原因
- 详情抽屉:重写为卡片分区布局,展示关联需求,状态流转按钮醒目化
- 详情实时更新:改为从 store 实时读取而非 prop 快照
- 列表增加筛选:负责人/状态/阻塞/任务类型 + 分页(每页20条)
- 工时展示:列表行显示 实际Xh / 预计Yh 格式
- 概览胶囊联动:dev 阶段进度由 DevTask 完成度驱动
- 概览统计卡片:替换 mock 为真实 DevTask 数据(待开发/开发中/阻塞中)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-11 18:51:33 +08:00
parent a560634091
commit d619846b93
6 changed files with 274 additions and 97 deletions

View File

@@ -1,7 +1,7 @@
'use client';
import { useState, useMemo } from 'react';
import { X } from 'lucide-react';
import { X, AlertTriangle } from 'lucide-react';
import { useDevTaskStore } from '@/stores/useDevTaskStore';
import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore';
import { useRequirementStore } from '@/stores/useRequirementStore';
@@ -12,11 +12,12 @@ import type { Priority } from '@/lib/derive';
interface Props {
versionId: string;
requirementIds: string[];
versionDeadline?: string;
onClose: () => void;
onCreated?: () => void;
}
export function DevTaskCreateModal({ versionId, requirementIds, onClose, onCreated }: Props) {
export function DevTaskCreateModal({ versionId, requirementIds, versionDeadline, onClose, onCreated }: Props) {
const { createTask, tasks } = useDevTaskStore();
const { categories } = useTaskCategoryStore();
const { requirements } = useRequirementStore();
@@ -35,16 +36,18 @@ export function DevTaskCreateModal({ versionId, requirementIds, onClose, onCreat
const [title, setTitle] = useState('');
const [requirementId, setRequirementId] = useState(versionReqs[0]?.id || '');
const [categoryId, setCategoryId] = useState(categories[0]?.id || '');
const [assigneeId, setAssigneeId] = useState('');
const [assigneeId, setAssigneeId] = useState(user?.name || '');
const [priority, setPriority] = useState<Priority>('P2');
const [estimateHours, setEstimateHours] = useState<number>(8);
const [dueDate, setDueDate] = useState('');
const [predecessorIds, setPredecessorIds] = useState<string[]>([]);
const [description, setDescription] = useState('');
const [overdueReason, setOverdueReason] = useState('');
const selectedReq = versionReqs.find((r) => r.id === requirementId);
const effectivePriority = selectedReq?.priority || priority;
const canSubmit = title.trim() && requirementId && categoryId && assigneeId && estimateHours > 0;
const isOverdue = !!(dueDate && versionDeadline && dueDate > versionDeadline);
const canSubmit = title.trim() && requirementId && categoryId && assigneeId && estimateHours > 0 && (!isOverdue || overdueReason.trim());
const handleSubmit = () => {
if (!canSubmit) return;
@@ -65,6 +68,7 @@ export function DevTaskCreateModal({ versionId, requirementIds, onClose, onCreat
blockedById: undefined,
predecessorIds: predecessorIds.length > 0 ? predecessorIds : undefined,
riskLevel: undefined,
overdueReason: isOverdue ? overdueReason.trim() : undefined,
createdBy: user?.name || '系统',
});
onCreated?.();
@@ -118,8 +122,20 @@ export function DevTaskCreateModal({ versionId, requirementIds, onClose, onCreat
<div>
<label className="block text-[12px] text-[var(--ink-soft)] mb-1"></label>
<input type="date" value={dueDate} onChange={(e) => setDueDate(e.target.value)} className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none" />
{versionDeadline && (
<p className="text-[10px] text-[var(--ink-muted)] mt-0.5">{versionDeadline}</p>
)}
</div>
</div>
{isOverdue && (
<div className="rounded-lg border border-orange-200 bg-orange-50 p-3 space-y-2">
<div className="flex items-center gap-1.5 text-[11px] text-orange-700">
<AlertTriangle className="h-3.5 w-3.5" />
{versionDeadline}
</div>
<input value={overdueReason} onChange={(e) => setOverdueReason(e.target.value)} placeholder="超期原因(必填)" className="h-8 w-full rounded-md border border-orange-200 bg-white px-2 text-[12px] focus:border-orange-400 focus:outline-none" />
</div>
)}
{versionTasks.length > 0 && (
<div>
<label className="block text-[12px] text-[var(--ink-soft)] mb-1"></label>