'use client'; import { useMemo, useState } from 'react'; import { X, Link2, ChevronRight, ArrowRightLeft } from 'lucide-react'; import { BugStatusBadge } from './BugStatusBadge'; import { ActivityLogPanel } from '@/components/ActivityLogPanel'; import { FilterSelect } from '@/components/FilterSelect'; import { useBugStore } from '@/stores/useBugStore'; import { useTestCaseStore } from '@/stores/useTestCaseStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useMemberStore } from '@/stores/useMemberStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { BUG_ALLOWED_TRANSITIONS, BUG_STATUS_LABEL, BUG_SEVERITY_LABEL, BUG_SEVERITY_COLOR } from '@/lib/bug'; import { formatDateTime } from '@/lib/format'; import { isMemberReference, resolveMemberDisplayName } from '@/lib/member-system'; import type { EntityActivityLogEntry } from '@/lib/entity-activity-log'; import type { BugStatus } from '@/lib/bug'; const LOG_ACTION_LABEL: Record = { create: '创建', status_change: '状态变更', transfer: '转交', resolve: '修复', }; interface Props { bugId: string; onClose: () => void; contextLabel?: string; readOnly?: boolean; } export function BugDetailDrawer({ bugId, onClose, contextLabel, readOnly = false }: Props) { const { bugs, changeStatus, transferBug } = useBugStore(); const { testCases } = useTestCaseStore(); const { requirements } = useRequirementStore(); const { members } = useMemberStore(); const user = useAuthStore((s) => s.user); const operator = user ? resolveMemberDisplayName(user.id, members) : '系统'; const bug = bugs.find((b) => b.id === bugId); if (!bug) return null; const tc = testCases.find((c) => c.id === bug.testCaseId); const requirement = tc ? requirements.find((r) => r.id === tc.requirementId) : null; const nextStatuses = BUG_ALLOWED_TRANSITIONS[bug.status]; const currentMember = user ? members.find((m) => m.id === user.id) : undefined; const assigneeName = resolveMemberDisplayName(bug.assigneeId, members); const reporterName = resolveMemberDisplayName(bug.reportedBy, members); const isCurrentAssignee = currentMember ? isMemberReference(bug.assigneeId, currentMember) : operator === assigneeName; const [resolution, setResolution] = useState(bug.resolution || ''); const [showResolutionInput, setShowResolutionInput] = useState(false); const [showTransfer, setShowTransfer] = useState(false); const [transferTo, setTransferTo] = useState(''); const [transferRemark, setTransferRemark] = useState(''); const [lightboxSrc, setLightboxSrc] = useState(null); const legacyLogEntries = useMemo(() => { return (bug.logs || []).map((log) => { const from = log.fromValue ? resolveMemberDisplayName(log.fromValue, members) : ''; const to = log.toValue ? resolveMemberDisplayName(log.toValue, members) : ''; const change = from && to ? `${from} → ${to}` : ''; const remark = log.remark ? `(${log.remark})` : ''; return { id: `bug-log-${log.id}`, occurredAt: log.createdAt, actorId: resolveMemberDisplayName(log.operator, members), label: LOG_ACTION_LABEL[log.action] || log.action, summary: [change, remark].filter(Boolean).join(' ') || bug.title, }; }); }, [bug.logs, bug.title, members]); const handleTransition = (to: BugStatus) => { if (readOnly) return; if (to === 'fixed') { setShowResolutionInput(true); return; } changeStatus(bug.id, to, operator); }; const confirmFix = () => { if (readOnly) return; changeStatus(bug.id, 'fixed', operator, { resolution: resolution.trim() || undefined }); setShowResolutionInput(false); }; const handleTransfer = () => { if (readOnly) return; if (!transferTo) return; transferBug(bug.id, transferTo, operator, transferRemark.trim() || undefined); setShowTransfer(false); setTransferTo(''); setTransferRemark(''); }; return (
e.stopPropagation()}> {contextLabel && (
{contextLabel}
)}
{bug.bugNo} {bug.title}
{/* 关联链路 */}
关联链路
{tc && (
{tc.caseNo} {tc.title}
)} {requirement && (
{requirement.code} {requirement.title}
)}
{/* 状态 & 操作 */}
状态 & 操作
{BUG_SEVERITY_LABEL[bug.severity]} {bug.priority}
{nextStatuses.length > 0 && !showResolutionInput && isCurrentAssignee && !readOnly && (
{nextStatuses.map((s) => ( ))} {bug.status !== 'closed' && bug.status !== 'rejected' && ( )}
)} {nextStatuses.length > 0 && !showResolutionInput && !isCurrentAssignee && !readOnly && (
当前修复人为 {assigneeName},仅修复人可操作
)} {showResolutionInput && !readOnly && (
setResolution(e.target.value)} placeholder="修复说明" className="h-8 w-full rounded-lg border border-[var(--line)] px-3 text-[12px] focus:border-[var(--accent)] focus:outline-none" autoFocus />
)} {showTransfer && !readOnly && (
转交给:
setTransferTo(value === 'all' ? '' : value)} options={members.filter((m) => !isMemberReference(bug.assigneeId, m)).map((m) => ({ value: m.name, label: m.name }))} allLabel="选择接收人" className="w-full" labelClassName="max-w-[calc(100%-20px)]" /> setTransferRemark(e.target.value)} placeholder="转交备注(可选)" className="h-8 w-full rounded-lg border border-[var(--line)] px-3 text-[12px] focus:border-[var(--accent)] focus:outline-none" />
)}
{/* 基本信息 */}
基本信息
计划修复:{bug.plannedFixAt ? formatDateTime(bug.plannedFixAt) : '待排期'}
提交人:{reporterName}
修复人:{assigneeName}
提交:{formatDateTime(bug.createdAt)}
{bug.resolvedAt &&
修复:{formatDateTime(bug.resolvedAt)}
} {bug.closedAt &&
关闭:{formatDateTime(bug.closedAt)}
}
{/* 描述 */}
Bug 描述

{bug.description}

{/* 截图 */} {bug.images && bug.images.length > 0 && (
截图附件
{bug.images.map((src, i) => ( {`截图${i setLightboxSrc(src)} /> ))}
)} {/* 修复说明 */} {bug.resolution && (
修复说明

{bug.resolution}

)}
{/* Lightbox */} {lightboxSrc && (
{ e.stopPropagation(); setLightboxSrc(null); }}> 大图预览 e.stopPropagation()} />
)}
); }