'use client'; import { useState } from 'react'; import { X, AlertTriangle, Link2, ChevronRight, Bug as BugIcon, Trash2, ArrowRightLeft } from 'lucide-react'; import { TestCaseStatusBadge } from './TestCaseStatusBadge'; import { BugStatusBadge } from '@/components/bug/BugStatusBadge'; import { useTestCaseStore } from '@/stores/useTestCaseStore'; import { useBugStore } from '@/stores/useBugStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useMemberStore } from '@/stores/useMemberStore'; import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore'; import { CategoryChip } from '@/components/dev-task/CategoryChip'; import { TC_ALLOWED_TRANSITIONS, TEST_CASE_STATUS_LABEL, getTestCaseActualHours } from '@/lib/test-case'; import { formatWorkHours } from '@/lib/work-hours'; import { formatDateTime } from '@/lib/format'; import { BUG_SEVERITY_LABEL, BUG_SEVERITY_COLOR } from '@/lib/bug'; import type { TestCaseStatus } from '@/lib/test-case'; interface Props { testCaseId: string; onClose: () => void; onCreateBug?: (testCaseId: string) => void; contextLabel?: string; } export function TestCaseDetailDrawer({ testCaseId, onClose, onCreateBug, contextLabel }: Props) { const { testCases, changeStatus, deleteTestCase, updateTestCase } = useTestCaseStore(); const { bugs } = useBugStore(); const { requirements } = useRequirementStore(); const { members } = useMemberStore(); const { categories } = useTaskCategoryStore(); const [showTransfer, setShowTransfer] = useState(false); const [transferTo, setTransferTo] = useState(''); const tc = testCases.find((c) => c.id === testCaseId); if (!tc) return null; const requirement = requirements.find((r) => r.id === tc.requirementId); const category = categories.find((c) => c.id === tc.categoryId); const relatedBugs = bugs.filter((b) => b.testCaseId === tc.id); const nextStatuses = TC_ALLOWED_TRANSITIONS[tc.status]; const executorEstimateHours = typeof tc.estimateHours === 'number' && tc.estimateHours > 0 ? tc.estimateHours : undefined; const aiEstimateHours = typeof tc.aiEstimateHours === 'number' && tc.aiEstimateHours > 0 ? tc.aiEstimateHours : undefined; const actualHours = getTestCaseActualHours(tc); const [failReason, setFailReason] = useState(''); const [blockReason, setBlockReason] = useState(''); const [showFailInput, setShowFailInput] = useState(false); const [showBlockInput, setShowBlockInput] = useState(false); const handleTransition = (to: TestCaseStatus) => { if (to === 'failed') { setShowFailInput(true); return; } if (to === 'blocked') { setShowBlockInput(true); return; } changeStatus(tc.id, to); }; const confirmFail = () => { changeStatus(tc.id, 'failed', { failReason: failReason.trim() || undefined }); setShowFailInput(false); setFailReason(''); }; const confirmBlock = () => { changeStatus(tc.id, 'blocked', { blockReason: blockReason.trim() || undefined }); setShowBlockInput(false); setBlockReason(''); }; return (
e.stopPropagation()}> {contextLabel && (
{contextLabel}
)}
{tc.caseNo} {tc.title}
{tc.status !== 'passed' && ( )}
{/* 转交 */} {showTransfer && (
转交给:
)}
{/* 关联需求 */} {requirement && (
关联需求
{requirement.code} {requirement.title}
)} {/* 状态 & 操作 */}
状态 & 操作
{tc.executedAt && 执行于 {tc.executedAt}}
{nextStatuses.length > 0 && !showFailInput && !showBlockInput && (
{nextStatuses.map((s) => ( ))}
)} {showFailInput && (
setFailReason(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') confirmFail(); }} placeholder="不通过原因(可选)" className="flex-1 h-8 rounded-lg border border-[var(--line)] px-3 text-[12px] focus:border-red-400 focus:outline-none" autoFocus />
)} {showBlockInput && (
setBlockReason(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') confirmBlock(); }} placeholder="阻塞原因(可选)" className="flex-1 h-8 rounded-lg border border-[var(--line)] px-3 text-[12px] focus:border-orange-400 focus:outline-none" autoFocus />
)} {tc.failReason &&
不通过原因:{tc.failReason}
} {tc.blockReason &&
阻塞原因:{tc.blockReason}
}
{/* 基本信息 */}
基本信息
计划测试:{tc.plannedTestAt ? formatDateTime(tc.plannedTestAt) : '待排期'}
优先级:{tc.priority}
任务类型:
负责人:{tc.assigneeId || '-'}
AI 预估:{aiEstimateHours ? formatWorkHours(aiEstimateHours) : '—'}
执行预估:{executorEstimateHours ? formatWorkHours(executorEstimateHours) : '待负责人填写'}
创建人:{tc.createdBy}
创建日期:{tc.createdAt.slice(0, 10)}
{tc.startedAt &&
开始执行:{formatDateTime(tc.startedAt)}
} {tc.completedAt &&
执行完成:{formatDateTime(tc.completedAt)}
} {tc.startedAt &&
实际耗时:{formatWorkHours(actualHours)}
}
{/* 用例描述 */} {tc.description && (
测试步骤 & 预期

{tc.description}

)} {/* 关联 Bug + 提BUG按钮 */}
关联 Bug ({relatedBugs.length})
{tc.status === 'failed' && onCreateBug && ( )}
{relatedBugs.length === 0 ? (

暂无关联 Bug

) : (
{relatedBugs.map((bug) => (
{bug.bugNo} {bug.title} {BUG_SEVERITY_LABEL[bug.severity]}
))}
)}
); }