diff --git a/apps/web/components/test-case/TestCaseCreateModal.tsx b/apps/web/components/test-case/TestCaseCreateModal.tsx index cb37616..0ec68b0 100644 --- a/apps/web/components/test-case/TestCaseCreateModal.tsx +++ b/apps/web/components/test-case/TestCaseCreateModal.tsx @@ -1,12 +1,15 @@ 'use client'; -import { useState, useMemo } from 'react'; +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 type { Priority } from '@/lib/derive'; +import { getCategoriesByGroup, getDefaultCategoryByGroup } from '@/lib/task-category'; +import { clampTestCaseEstimateHours, getDefaultTestCaseEstimateHours } from '@/lib/ai-estimation-policy'; interface Props { versionId: string; @@ -18,30 +21,66 @@ export function TestCaseCreateModal({ versionId, requirementIds, onClose }: Prop 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 [assigneeId, setAssigneeId] = useState(user?.name || ''); const [description, setDescription] = useState(''); + const [prototypeNotes, setPrototypeNotes] = useState(''); - const canSubmit = title.trim(); + 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 canSubmit = title.trim() && categoryId && normalizedEstimateHours > 0; 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, title: title.trim(), description: description.trim() || undefined, + categoryId, priority, + estimateHours: normalizedEstimateHours, assigneeId: assigneeId || undefined, + references: references.length > 0 ? references : undefined, createdBy: user?.name || '系统', }); onClose(); @@ -73,6 +112,27 @@ export function TestCaseCreateModal({ versionId, requirementIds, onClose }: Prop {(['P0','P1','P2','P3'] as Priority[]).map((p) => )} +
+ + +
+
+ + 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" + /> +