'use client'; import { useState } from 'react'; import { X, Check, Link2, FileUp, ExternalLink, Play, ArrowRightLeft } from 'lucide-react'; import { useVersionPlanStore } from '@/stores/useVersionPlanStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useMemberStore } from '@/stores/useMemberStore'; import { calcPlanProgress, calcLinkedReqProgress } from '@/lib/version-plan'; import { formatDateTime } from '@/lib/format'; import type { PlanTask, VersionPlan } from '@/lib/version-plan'; import { canEditPlanRequirementCoverage, canTogglePlanChecklist, getPlanCompletionState } from '@/lib/version-plan-workflow'; interface Props { planId: string; onClose: () => void; contextLabel?: string; } const STATUS_STYLE: Record = { pending: 'bg-zinc-100 text-zinc-600', in_progress: 'bg-blue-50 text-blue-600', completed: 'bg-emerald-50 text-emerald-600' }; const STATUS_LABEL: Record = { pending: '未开始', in_progress: '进行中', completed: '已完成' }; const TYPE_LABEL: Record = { research: '调研', product: '产品方案', ui: 'UI设计' }; export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) { const { plans, updatePlan, completePlan } = useVersionPlanStore(); const { requirements } = useRequirementStore(); const { members } = useMemberStore(); const [showTransfer, setShowTransfer] = useState(false); const [transferTo, setTransferTo] = useState(''); const [resultType, setResultType] = useState<'link' | 'file'>('link'); const [resultTitle, setResultTitle] = useState(''); const [resultUrl, setResultUrl] = useState(''); const [fileName, setFileName] = useState(''); const [fileData, setFileData] = useState(''); const [showComplete, setShowComplete] = useState(false); const plan = plans.find((p) => p.id === planId); if (!plan) return null; const completionState = getPlanCompletionState(plan); const isResearch = plan.type === 'research'; const progress = isResearch ? calcPlanProgress(plan.tasks) : calcLinkedReqProgress(plan.linkedRequirementIds, plan.completedRequirementIds); const linkedReqs = (plan.linkedRequirementIds || []).map((id) => requirements.find((r) => r.id === id)).filter(Boolean) as { id: string; code: string; title: string }[]; const canToggle = canTogglePlanChecklist(plan); const canEditCoverage = canEditPlanRequirementCoverage(plan); const handleToggleTask = (task: PlanTask) => { if (!canToggle) return; const nextStatus = task.status === 'completed' ? 'pending' : 'completed'; const updatedTasks = (plan.tasks || []).map((t) => t.id === task.id ? { ...t, status: nextStatus as PlanTask['status'] } : t); updatePlan(plan.id, { tasks: updatedTasks }); }; const handleToggleReq = (reqId: string) => { if (!canEditCoverage) return; const current = plan.completedRequirementIds || []; const next = current.includes(reqId) ? current.filter((id) => id !== reqId) : [...current, reqId]; updatePlan(plan.id, { completedRequirementIds: next }); }; const handleFile = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setFileName(file.name); const reader = new FileReader(); reader.onload = () => setFileData(reader.result as string); reader.readAsDataURL(file); }; const handleSubmitResult = () => { const url = resultType === 'link' ? resultUrl.trim() : fileData; const title = resultTitle.trim(); if (!url || !title) return; const response = completePlan(plan.id, { resultType, resultTitle: title, resultUrl: url, resultFileName: fileName || undefined, resultFileData: resultType === 'file' ? fileData : undefined }); if (response && typeof response === 'object' && 'ok' in response && !response.ok) { alert(response.message || '计划未满足完成条件'); return; } setShowComplete(false); }; const handleTransfer = () => { if (!transferTo) return; updatePlan(plan.id, { owner: transferTo }); setShowTransfer(false); setTransferTo(''); }; return (
e.stopPropagation()}> {/* Context */} {contextLabel && (
{contextLabel}
)} {/* Header */}
{TYPE_LABEL[plan.type]} {STATUS_LABEL[plan.status]}
{/* Body */}

{plan.title}

{/* Info */}
负责人
{plan.owner}
进度
{progress}%
计划时间
{formatDateTime(plan.startTime)} → {formatDateTime(plan.endTime)}
{plan.actualStartAt && (
实际开始
{formatDateTime(plan.actualStartAt)}
)} {plan.completedAt && (
完成时间
{formatDateTime(plan.completedAt)}
)}
{/* Progress Bar */}
{/* Research Tasks */} {isResearch && plan.tasks && plan.tasks.length > 0 && (
任务清单
{plan.tasks.map((task) => (
{task.title}
))}
)} {/* Product/UI: Linked Requirements */} {linkedReqs.length > 0 && (
关联需求
{linkedReqs.map((req) => { const isDone = (plan.completedRequirementIds || []).includes(req.id); return (
{req.code} {req.title}
); })} {plan.status === 'in_progress' && !completionState.canSubmitResult && (

还不能提交成果:{completionState.missingReasons.join('、')}

)}
)} {/* Result */} {plan.status === 'completed' && plan.resultUrl && (
成果
)} {plan.remark && (
备注
{plan.remark}
)} {/* Transfer Section */} {showTransfer && (
转交给
)} {/* Complete with result */} {showComplete && (
提交成果
setResultTitle(e.target.value)} placeholder="成果标题(必填,如 v1.0 产品方案)" className="h-8 w-full rounded-lg border border-[var(--line)] px-2 text-[12px] focus:border-[var(--accent)] focus:outline-none" />
{resultType === 'link' ? ( setResultUrl(e.target.value)} placeholder="https://..." className="h-8 w-full rounded-lg border border-[var(--line)] px-2 text-[12px] focus:border-[var(--accent)] focus:outline-none" /> ) : (
{fileName &&

{fileName}

}
)}
)}
{/* Footer Actions */} {plan.status !== 'completed' && ( <>
{plan.status === 'pending' && ( )} {plan.status === 'in_progress' && ( )}
{plan.status === 'in_progress' && !completionState.canSubmitResult && (
还不能提交成果:{completionState.missingReasons.join('、')}
)} )}
); }