'use client'; import { useState } from 'react'; import { Plus, Pencil, Trash2, X, Check, ExternalLink, FileUp, Link2 } from 'lucide-react'; import type { VersionPlan } from '@/lib/version-plan'; import { calcPlanDuration, formatDuration, calcTotalDuration } from '@/lib/version-plan'; interface Props { plans: VersionPlan[]; versionId: string; versionDeadline?: string; currentUserName: string; planType: 'research' | 'product' | 'ui'; linkedRequirements?: { id: string; title: string; code: string; productOwner?: string }[]; onCreate: (data: Omit) => void; onUpdate: (id: string, data: Partial) => void; onComplete: (id: string, result: { resultType: 'link' | 'file'; resultUrl?: string; resultFileName?: string; resultFileData?: string }) => void; onDelete: (id: string) => void; } const TYPE_LABEL = { research: '调研', product: '产品方案', ui: 'UI设计' }; const STATUS_STYLE = { pending: 'bg-zinc-100 text-zinc-600', in_progress: 'bg-blue-50 text-blue-600 border-blue-200', completed: 'bg-green-50 text-green-700 border-green-200', }; const STATUS_LABEL = { pending: '未开始', in_progress: '进行中', completed: '已完成' }; export function PlanTab({ plans, versionId, versionDeadline, currentUserName, planType, linkedRequirements, onCreate, onUpdate, onComplete, onDelete }: Props) { const [showCreateModal, setShowCreateModal] = useState(false); const [editingPlan, setEditingPlan] = useState(null); const [completingPlan, setCompletingPlan] = useState(null); const typePlans = plans.filter((p) => p.versionId === versionId && p.type === planType); const totalDuration = calcTotalDuration(typePlans); return (
{typePlans.length} 个{TYPE_LABEL[planType]}计划 总耗时:{totalDuration} {versionDeadline && 版本截止:{versionDeadline}}
{typePlans.length === 0 ? (
暂无{TYPE_LABEL[planType]}计划
) : (
{typePlans.map((plan) => { const dur = plan.completedAt ? calcPlanDuration(plan.startTime, plan.completedAt) : calcPlanDuration(plan.startTime, plan.endTime); const durText = formatDuration(dur.days, dur.hours); return (
{plan.title} {STATUS_LABEL[plan.status]}
负责人:{plan.owner} 计划:{plan.startTime.slice(0, 10)} → {plan.endTime.slice(0, 10)} {plan.completedAt && 完成:{plan.completedAt.slice(0, 10)}} 耗时 {durText}
{plan.overdueReason && (
超期原因:{plan.overdueReason}
)} {plan.linkedRequirementIds && plan.linkedRequirementIds.length > 0 && linkedRequirements && (
{plan.linkedRequirementIds.map((rid) => { const req = linkedRequirements.find((r) => r.id === rid); return req ? ( {req.code} {req.title.slice(0, 12)}{req.title.length > 12 ? '...' : ''} {req.productOwner && ({req.productOwner})} ) : null; })}
)} {plan.status === 'completed' && plan.resultUrl && (
{plan.resultType === 'link' ? : } {plan.resultFileName || '查看成果'}
)}
{plan.status !== 'completed' && ( <> )}
); })}
)} {(showCreateModal || editingPlan) && ( { setShowCreateModal(false); setEditingPlan(null); }} onSubmit={(data) => { if (editingPlan) onUpdate(editingPlan.id, data); else onCreate(data as any); setShowCreateModal(false); setEditingPlan(null); }} /> )} {completingPlan && ( setCompletingPlan(null)} onSubmit={(result) => { onComplete(completingPlan.id, result); setCompletingPlan(null); }} /> )}
); } function PlanFormModal({ initial, planType, versionId, versionDeadline, currentUserName, linkedRequirements, onClose, onSubmit }: { initial: VersionPlan | null; planType: 'research' | 'product' | 'ui'; versionId: string; versionDeadline?: string; currentUserName: string; linkedRequirements?: { id: string; title: string; code: string }[]; onClose: () => void; onSubmit: (data: any) => void; }) { const now = new Date().toISOString().slice(0, 10); const [title, setTitle] = useState(initial?.title ?? ''); const [owner] = useState(initial?.owner ?? currentUserName); const [startTime, setStartTime] = useState(initial?.startTime?.slice(0, 10) ?? now); const [endTime, setEndTime] = useState(initial?.endTime?.slice(0, 10) ?? ''); const [remark, setRemark] = useState(initial?.remark ?? ''); const [overdueReason, setOverdueReason] = useState(initial?.overdueReason ?? ''); const [selectedReqs, setSelectedReqs] = useState>(new Set(initial?.linkedRequirementIds ?? [])); const showReqSelect = planType === 'product' || planType === 'ui'; const isOverdue = !!(versionDeadline && endTime && new Date(endTime) > new Date(versionDeadline)); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!title.trim() || !endTime) return; if (isOverdue && !overdueReason.trim()) return; onSubmit({ versionId, type: planType, title: title.trim(), owner: owner.trim() || currentUserName, startTime, endTime, status: initial?.status ?? 'pending', linkedRequirementIds: showReqSelect ? Array.from(selectedReqs) : undefined, remark: remark.trim() || undefined, overdueReason: isOverdue ? overdueReason.trim() : undefined, addedBy: currentUserName, }); }; return (
e.stopPropagation()}>

{initial ? '编辑' : '新建'}{TYPE_LABEL[planType]}计划

setTitle(e.target.value)} required className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none" />
setStartTime(e.target.value)} className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none" />
setEndTime(e.target.value)} required className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none" />
{versionDeadline && (
版本截止日期:{versionDeadline}
)} {isOverdue && (
⚠ 结束日期超出版本截止日期,请说明原因