'use client'; import { useState, useMemo, useRef } from 'react'; import { X, ImagePlus, Trash2 } from 'lucide-react'; import { useBugStore } from '@/stores/useBugStore'; import { useTestCaseStore } from '@/stores/useTestCaseStore'; import { useDevTaskStore } from '@/stores/useDevTaskStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useMemberStore } from '@/stores/useMemberStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { resolveMemberDisplayName } from '@/lib/member-system'; import { FilterSelect } from '@/components/FilterSelect'; import { WorkDateTimePicker } from '@/components/WorkDateTimePicker'; import type { Priority } from '@/lib/derive'; import type { BugSeverity } from '@/lib/bug'; import { isoToLocal, localToISO } from '@/lib/work-hours'; interface Props { testCaseId: string; onClose: () => void; } function defaultPlannedFixLocal(): string { const d = new Date(); d.setHours(18, 0, 0, 0); return isoToLocal(d.toISOString()); } export function BugCreateModal({ testCaseId, onClose }: Props) { const { createBug } = useBugStore(); const { testCases } = useTestCaseStore(); const { tasks: devTasks } = useDevTaskStore(); const { requirements } = useRequirementStore(); const { members } = useMemberStore(); const user = useAuthStore((s) => s.user); const tc = testCases.find((c) => c.id === testCaseId); const requirement = requirements.find((r) => r.id === tc?.requirementId); const defaultAssignee = useMemo(() => { if (!tc) return ''; const reqTasks = devTasks.filter((t) => t.requirementId === tc.requirementId); const inProgress = reqTasks.filter((t) => t.status === 'in_progress').sort((a, b) => b.createdAt.localeCompare(a.createdAt)); if (inProgress.length > 0) return inProgress[0].assigneeId; const latest = reqTasks.sort((a, b) => b.createdAt.localeCompare(a.createdAt)); return latest.length > 0 ? latest[0].assigneeId : ''; }, [tc, devTasks]); const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [severity, setSeverity] = useState('major'); const [priority, setPriority] = useState(tc?.priority || 'P1'); const [assigneeId, setAssigneeId] = useState(defaultAssignee); const [plannedFixLocal, setPlannedFixLocal] = useState(defaultPlannedFixLocal); const [images, setImages] = useState([]); const fileRef = useRef(null); const plannedFixAt = localToISO(plannedFixLocal); const canSubmit = title.trim() && description.trim() && assigneeId && Boolean(plannedFixAt); const handleFiles = (files: FileList | null) => { if (!files) return; Array.from(files).forEach((file) => { if (!file.type.startsWith('image/')) return; const reader = new FileReader(); reader.onload = () => { setImages((prev) => [...prev, reader.result as string]); }; reader.readAsDataURL(file); }); }; const handleSubmit = () => { if (!canSubmit) return; const operator = user ? resolveMemberDisplayName(user.id, members) : '系统'; createBug({ versionId: tc?.versionId || '', testCaseId, requirementId: tc?.requirementId || undefined, title: title.trim(), description: description.trim(), severity, priority, reportedBy: operator, assigneeId, plannedFixAt, images: images.length > 0 ? images : undefined, }, operator); onClose(); }; return (
e.stopPropagation()}>

提交 Bug

关联用例:{tc?.caseNo} {tc?.title}
{requirement &&
关联需求:{requirement.code} {requirement.title}
}
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="简要描述问题" />