'use client'; import { useState, useMemo, useEffect } from 'react'; import { X, AlertTriangle } from 'lucide-react'; import { useDevTaskStore } from '@/stores/useDevTaskStore'; import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useMemberStore } from '@/stores/useMemberStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { FilterSelect } from '@/components/FilterSelect'; import { WorkDateTimePicker } from '@/components/WorkDateTimePicker'; import { calcWorkHours, formatWorkHours, isoToLocal, localToISO } from '@/lib/work-hours'; import type { Priority } from '@/lib/derive'; interface Props { versionId: string; requirementIds: string[]; versionDeadline?: string; onClose: () => void; onCreated?: () => void; } function defaultExpectedStart(): string { const d = new Date(); d.setHours(9, 0, 0, 0); return isoToLocal(d.toISOString()); } function defaultExpectedEnd(): string { const d = new Date(); d.setHours(18, 0, 0, 0); return isoToLocal(d.toISOString()); } export function DevTaskCreateModal({ versionId, requirementIds, versionDeadline, onClose, onCreated }: Props) { const { createTask, tasks } = useDevTaskStore(); const { categories } = useTaskCategoryStore(); const { requirements } = useRequirementStore(); const { members } = useMemberStore(); const user = useAuthStore((s) => s.user); const versionReqs = useMemo( () => requirements.filter((r) => requirementIds.includes(r.id)), [requirements, requirementIds], ); const versionTasks = useMemo( () => tasks.filter((t) => t.versionId === versionId || requirementIds.includes(t.requirementId)), [tasks, requirementIds, versionId], ); const [title, setTitle] = useState(''); const [requirementId, setRequirementId] = useState(versionReqs[0]?.id || ''); const [categoryId, setCategoryId] = useState(categories[0]?.id || ''); const [assigneeId, setAssigneeId] = useState(user?.name || ''); const [priority, setPriority] = useState(versionReqs[0]?.priority || 'P2'); const [priorityManuallySet, setPriorityManuallySet] = useState(false); const [expectedStartLocal, setExpectedStartLocal] = useState(defaultExpectedStart); const [expectedEndLocal, setExpectedEndLocal] = useState(defaultExpectedEnd); const [predecessorIds, setPredecessorIds] = useState([]); const [description, setDescription] = useState(''); const [overdueVersionReason, setOverdueVersionReason] = useState(''); const [prototypeNotes, setPrototypeNotes] = useState(''); useEffect(() => { if (categories[0] && !categoryId) setCategoryId(categories[0].id); }, [categories, categoryId]); const expectedStartISO = localToISO(expectedStartLocal); const expectedEndISO = localToISO(expectedEndLocal); const estimateHours = useMemo(() => { if (!expectedStartISO || !expectedEndISO) return 0; return calcWorkHours(expectedStartISO, expectedEndISO); }, [expectedStartISO, expectedEndISO]); const selectedReq = versionReqs.find((r) => r.id === requirementId); const effectivePriority = priorityManuallySet ? priority : (selectedReq?.priority || priority); const versionDeadlineISO = useMemo(() => { if (!versionDeadline) return null; const d = new Date(versionDeadline); if (isNaN(d.getTime())) return null; d.setHours(23, 59, 59, 999); return d.toISOString(); }, [versionDeadline]); const isOverdueVersion = !!(expectedEndISO && versionDeadlineISO && expectedEndISO > versionDeadlineISO); const startBeforeEnd = expectedStartISO && expectedEndISO && expectedStartISO < expectedEndISO; const canSubmit = title.trim() && requirementId && categoryId && assigneeId && !!startBeforeEnd && estimateHours > 0 && (!isOverdueVersion || overdueVersionReason.trim()); const handleSubmit = () => { if (!canSubmit) return; const reqRef = versionReqs.find((r) => r.id === requirementId); const references = [ ...(reqRef ? [{ type: 'requirement' as const, id: reqRef.code, label: `${reqRef.code} ${reqRef.title}` }] : []), ...prototypeNotes.split(/[,,\s]+/).map((s) => s.trim()).filter(Boolean).map((note) => ({ type: 'prototype_note' as const, id: note, label: note, })), ]; createTask({ versionId, requirementId, title: title.trim(), description: description.trim() || undefined, categoryId, assigneeId, reviewerId: undefined, priority: effectivePriority, expectedStartAt: expectedStartISO, expectedEndAt: expectedEndISO, estimateHours, actualStartAt: undefined, actualEndAt: undefined, status: 'todo', blockReason: undefined, blockedById: undefined, predecessorIds: predecessorIds.length > 0 ? predecessorIds : undefined, riskLevel: undefined, delayReason: undefined, overdueVersionReason: isOverdueVersion ? overdueVersionReason.trim() : undefined, references: references.length > 0 ? references : undefined, createdBy: user?.name || '系统', }); onCreated?.(); onClose(); }; return (
e.stopPropagation()}>

新建开发任务

setTitle(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" placeholder="例如:排班界面开发" />
{ setRequirementId(value); const r = versionReqs.find((x) => x.id === value); if (r && !priorityManuallySet) setPriority(r.priority); }} options={versionReqs.map((r) => ({ value: r.id, label: `${r.code} ${r.title}` }))} placeholder="选择所属需求" showAllOption={false} className="w-full" labelClassName="max-w-[calc(100%-20px)]" />
({ value: c.id, label: c.name }))} placeholder="选择任务类型" showAllOption={false} className="w-full" labelClassName="max-w-[calc(100%-20px)]" />
({ value: m.name, label: m.name }))} placeholder="选择负责人" showAllOption={false} className="w-full" labelClassName="max-w-[calc(100%-20px)]" />
{ setPriority(value as Priority); setPriorityManuallySet(true); }} options={(['P0', 'P1', 'P2', 'P3'] as Priority[]).map((p) => ({ value: p, label: p }))} showAllOption={false} className="w-full" labelClassName="max-w-[calc(100%-20px)]" />
{estimateHours > 0 ? formatWorkHours(estimateHours) : (startBeforeEnd ? '0h' : '请先选择有效起止时间')}

工作时段 9:00–12:00 + 13:00–18:00,跳过周末

{!startBeforeEnd && expectedStartLocal && expectedEndLocal && (
预计开始必须早于预计截止
)} {versionDeadline && (

版本截止:{versionDeadline}

)} {isOverdueVersion && (
任务预计截止超出版本截止日期({versionDeadline}),请说明原因
setOverdueVersionReason(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" />
)} {versionTasks.length > 0 && (
{versionTasks.map((t) => ( ))}
)}