'use client'; import { useEffect, useState, useMemo } from 'react'; import { X } from 'lucide-react'; import { useTestCaseStore } from '@/stores/useTestCaseStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useMemberStore } from '@/stores/useMemberStore'; import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { WorkDateTimePicker } from '@/components/WorkDateTimePicker'; import type { Priority } from '@/lib/derive'; import { getCategoriesByGroup, getDefaultCategoryByGroup } from '@/lib/task-category'; import { clampTestCaseEstimateHours, getDefaultTestCaseEstimateHours } from '@/lib/ai-estimation-policy'; import { isoToLocal, localToISO } from '@/lib/work-hours'; interface Props { versionId: string; requirementIds: string[]; roundNo?: number; onClose: () => void; } function defaultPlannedTestLocal(): string { const d = new Date(); d.setHours(9, 0, 0, 0); return isoToLocal(d.toISOString()); } function defaultPlannedEndLocal(): string { const d = new Date(); d.setHours(10, 0, 0, 0); return isoToLocal(d.toISOString()); } export function TestCaseCreateModal({ versionId, requirementIds, roundNo, onClose }: Props) { const { createTestCase } = useTestCaseStore(); const { requirements } = useRequirementStore(); const { members } = useMemberStore(); const { categories } = useTaskCategoryStore(); const user = useAuthStore((s) => s.user); const versionReqs = useMemo( () => requirements.filter((r) => requirementIds.includes(r.id)), [requirements, requirementIds], ); const testCategories = useMemo(() => getCategoriesByGroup(categories, 'testing'), [categories]); const [title, setTitle] = useState(''); const [requirementId, setRequirementId] = useState(versionReqs[0]?.id || ''); const [priority, setPriority] = useState(versionReqs[0]?.priority || 'P2'); const [categoryId, setCategoryId] = useState(getDefaultCategoryByGroup(categories, 'testing').id); const selectedCategory = useMemo( () => categories.find((category) => category.id === categoryId) ?? testCategories[0], [categories, categoryId, testCategories], ); const [estimateHours, setEstimateHours] = useState(0.5); const [plannedTestLocal, setPlannedTestLocal] = useState(defaultPlannedTestLocal); const [plannedEndLocal, setPlannedEndLocal] = useState(defaultPlannedEndLocal); const [assigneeId, setAssigneeId] = useState(user?.name || ''); const [description, setDescription] = useState(''); const [prototypeNotes, setPrototypeNotes] = useState(''); useEffect(() => { if (testCategories.length === 0) return; if (testCategories.some((category) => category.id === categoryId)) return; const next = testCategories[0]; setCategoryId(next.id); setEstimateHours(getDefaultTestCaseEstimateHours(next.code)); }, [categoryId, testCategories]); const handleCategoryChange = (nextCategoryId: string) => { setCategoryId(nextCategoryId); const category = categories.find((c) => c.id === nextCategoryId) ?? testCategories.find((c) => c.id === nextCategoryId); setEstimateHours(getDefaultTestCaseEstimateHours(category?.code)); }; const normalizedEstimateHours = clampTestCaseEstimateHours(selectedCategory?.code, estimateHours); const plannedTestAt = localToISO(plannedTestLocal); const plannedEndAt = localToISO(plannedEndLocal); const hasValidPlan = Boolean(plannedTestAt && plannedEndAt && plannedEndAt > plannedTestAt); const canSubmit = title.trim() && categoryId && normalizedEstimateHours > 0 && hasValidPlan; 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, })), ]; createTestCase({ versionId, requirementId: requirementId || undefined, roundNo: roundNo ?? 1, title: title.trim(), description: description.trim() || undefined, categoryId, priority, estimateHours: normalizedEstimateHours, plannedTestAt, plannedEndAt, assigneeId: assigneeId || undefined, references: references.length > 0 ? references : undefined, createdBy: user?.name || '系统', }); 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="例如:正常排班" />
setEstimateHours(Number(e.target.value))} onBlur={() => setEstimateHours(normalizedEstimateHours)} 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" />
{!hasValidPlan && plannedTestLocal && plannedEndLocal && (
计划开始必须早于计划结束
)}