'use client'; import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Search, FileText, Palette, Layout, ClipboardList, Check, ExternalLink, Link2, FileUp, Code2, ClipboardCheck, Bug as BugIcon } from 'lucide-react'; import { useProductStore } from '@/stores/useProductStore'; import { useVersionPlanStore } from '@/stores/useVersionPlanStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useDevTaskStore } from '@/stores/useDevTaskStore'; import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore'; import { useTestCaseStore } from '@/stores/useTestCaseStore'; import { useBugStore } from '@/stores/useBugStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { flattenVersions } from '@/lib/derive'; import { calcPlanProgress, calcLinkedReqProgress } from '@/lib/version-plan'; import { DEV_TASK_STATUS_LABEL, DEV_TASK_STATUS_COLOR, formatHours } from '@/lib/dev-task'; import { TEST_CASE_STATUS_LABEL, TEST_CASE_STATUS_COLOR } from '@/lib/test-case'; import { BUG_STATUS_LABEL, BUG_STATUS_COLOR, BUG_SEVERITY_LABEL, BUG_SEVERITY_COLOR } from '@/lib/bug'; import type { PlanTask, VersionPlan } from '@/lib/version-plan'; import type { DevTask } from '@/lib/dev-task'; type TabKey = 'all' | 'research' | 'product' | 'ui' | 'devTask' | 'testCase' | 'bug'; const TABS: { key: TabKey; label: string; icon: any }[] = [ { key: 'all', label: '全部待办', icon: ClipboardList }, { key: 'research', label: '调研', icon: Search }, { key: 'product', label: '产品方案', icon: FileText }, { key: 'ui', label: 'UI设计', icon: Palette }, { key: 'devTask', label: '开发任务', icon: Code2 }, { key: 'testCase', label: '测试用例', icon: ClipboardCheck }, { key: 'bug', label: 'Bug', icon: BugIcon }, ]; export default function WorkspacePage() { const router = useRouter(); const { overview, fetchOverview } = useProductStore(); const { plans, fetchPlans, updatePlan, completePlan } = useVersionPlanStore(); const { requirements, fetchRequirements } = useRequirementStore(); const { tasks: devTasks, fetchTasks } = useDevTaskStore(); const { categories, fetchCategories } = useTaskCategoryStore(); const { testCases, fetchTestCases } = useTestCaseStore(); const { bugs, fetchBugs } = useBugStore(); const user = useAuthStore((s) => s.user); const [activeTab, setActiveTab] = useState('all'); const [completingPlan, setCompletingPlan] = useState(null); useEffect(() => { fetchOverview(); }, [fetchOverview]); useEffect(() => { fetchPlans(); }, [fetchPlans]); useEffect(() => { fetchRequirements(); }, [fetchRequirements]); useEffect(() => { fetchTasks(); }, [fetchTasks]); useEffect(() => { fetchCategories(); }, [fetchCategories]); useEffect(() => { fetchTestCases(); }, [fetchTestCases]); useEffect(() => { fetchBugs(); }, [fetchBugs]); const userName = user?.name ?? ''; const allVersions = useMemo(() => flattenVersions(overview), [overview]); // 我负责的所有未完成计划 const myPlans = useMemo(() => plans.filter((p) => p.owner === userName && p.status !== 'completed'), [plans, userName] ); // 我负责的所有未完成开发任务 const myDevTasks = useMemo(() => devTasks.filter((t) => t.assigneeId === userName && t.status !== 'submitted'), [devTasks, userName] ); // 我负责的待执行/执行中/失败测试用例 const myTestCases = useMemo(() => testCases.filter((c) => c.assigneeId === userName && (c.status === 'pending' || c.status === 'running' || c.status === 'failed')), [testCases, userName] ); // 我负责的未关闭 Bug const myBugs = useMemo(() => bugs.filter((b) => b.assigneeId === userName && b.status !== 'closed' && b.status !== 'rejected'), [bugs, userName] ); const counts = { all: myPlans.length + myDevTasks.length + myTestCases.length + myBugs.length, research: myPlans.filter((p) => p.type === 'research').length, product: myPlans.filter((p) => p.type === 'product').length, ui: myPlans.filter((p) => p.type === 'ui').length, devTask: myDevTasks.length, testCase: myTestCases.length, bug: myBugs.length, }; const filtered = activeTab === 'all' ? myPlans : (activeTab === 'devTask' || activeTab === 'testCase' || activeTab === 'bug') ? [] : myPlans.filter((p) => p.type === activeTab); const today = new Date().toISOString().slice(0, 10); const toggleTask = (plan: VersionPlan, task: PlanTask) => { const next: PlanTask['status'] = task.status === 'pending' ? 'in_progress' : task.status === 'in_progress' ? 'completed' : 'pending'; const updatedTasks = (plan.tasks || []).map((t) => t.id === task.id ? { ...t, status: next } : t); updatePlan(plan.id, { tasks: updatedTasks }); }; return (
{/* 左侧:分组 */}

与我相关

{/* 右侧:待办列表 */}

{TABS.find((t) => t.key === activeTab)?.label}

{activeTab === 'devTask' ? myDevTasks.length : activeTab === 'testCase' ? myTestCases.length : activeTab === 'bug' ? myBugs.length : filtered.length} 项
{activeTab === 'devTask' ? ( myDevTasks.length === 0 ? (

暂无开发任务

) : ( myDevTasks.map((task) => { const cat = categories.find((c) => c.id === task.categoryId); const req = requirements.find((r) => r.id === task.requirementId); return (
{DEV_TASK_STATUS_LABEL[task.status]} {task.isBlocked && 阻塞} {task.taskNo} {task.title}
{task.priority}
{req && {req.code} {req.title}} {cat && {cat.name}} 预计 {formatHours(task.estimateHours)} {task.actualHours > 0 && 已投入 {task.actualHours}h} {task.dueDate && 截止 {task.dueDate}}
); }) ) ) : activeTab === 'testCase' ? ( myTestCases.length === 0 ? (

暂无测试用例

) : ( myTestCases.map((tc) => (
{TEST_CASE_STATUS_LABEL[tc.status]} {tc.caseNo} {tc.title}
{tc.priority}
)) ) ) : activeTab === 'bug' ? ( myBugs.length === 0 ? (

暂无 Bug

) : ( myBugs.map((bug) => (
{BUG_STATUS_LABEL[bug.status]} {BUG_SEVERITY_LABEL[bug.severity]} {bug.bugNo} {bug.title}
{bug.priority}
)) ) ) : filtered.length === 0 ? (

暂无待办事项

) : ( filtered.map((plan) => { const version = allVersions.find((v) => v.id === plan.versionId); const isActive = plan.startTime <= today; const typeLabel = plan.type === 'research' ? '调研' : plan.type === 'product' ? '产品方案' : 'UI设计'; const linkedReqs = (plan.linkedRequirementIds || []).map((id) => requirements.find((r) => r.id === id)).filter(Boolean) as { id: string; code: string; title: string }[]; return ( toggleTask(plan, task)} onToggleReq={(reqId) => { const current = plan.completedRequirementIds || []; const next = current.includes(reqId) ? current.filter((id) => id !== reqId) : [...current, reqId]; updatePlan(plan.id, { completedRequirementIds: next }); }} onAddTask={(title) => { const newTask: PlanTask = { id: `task-${Date.now()}`, title, status: 'pending' }; updatePlan(plan.id, { tasks: [...(plan.tasks || []), newTask] }); }} onComplete={() => setCompletingPlan(plan)} onJumpVersion={() => version && router.push(`/versions/${version.id}`)} onUpdate={(data) => updatePlan(plan.id, data)} /> ); }) )}
{completingPlan && ( setCompletingPlan(null)} onSubmit={(result) => { completePlan(completingPlan.id, result); setCompletingPlan(null); }} /> )}
); } function PlanCard({ plan, versionName, versionInfo, versionId, typeLabel, isActive, linkedReqs, onToggleTask, onToggleReq, onAddTask, onComplete, onJumpVersion, onUpdate }: { plan: VersionPlan; versionName?: string; versionInfo: string; versionId?: string; typeLabel: string; isActive: boolean; linkedReqs: { id: string; code: string; title: string }[]; onToggleTask: (task: PlanTask) => void; onToggleReq: (reqId: string) => void; onAddTask: (title: string) => void; onComplete: () => void; onJumpVersion: () => void; onUpdate: (data: Partial) => void; }) { const [newTaskTitle, setNewTaskTitle] = useState(''); const isResearch = plan.type === 'research'; const progress = isResearch ? calcPlanProgress(plan.tasks) : calcLinkedReqProgress(plan.linkedRequirementIds, plan.completedRequirementIds); const allReqsDone = !isResearch && plan.linkedRequirementIds && plan.linkedRequirementIds.length > 0 && progress === 100; return (
{isActive ? '进行中' : '未开始'} {typeLabel} {plan.title}
{(allReqsDone || (isResearch && plan.tasks && plan.tasks.length > 0)) && ( )}
{versionInfo} / {plan.startTime.slice(0, 10)} → {plan.endTime.slice(0, 10)} {progress > 0 && {progress}%}
{/* 进度条 */} {((isResearch && plan.tasks && plan.tasks.length > 0) || (!isResearch && plan.linkedRequirementIds && plan.linkedRequirementIds.length > 0)) && (
)} {/* 调研:任务清单 */} {isResearch && (
{(plan.tasks || []).map((task) => (
{task.title} {task.status === 'completed' ? '已完成' : task.status === 'in_progress' ? '进行中' : '未开始'}
))}
setNewTaskTitle(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && newTaskTitle.trim()) { onAddTask(newTaskTitle.trim()); setNewTaskTitle(''); } }} placeholder="添加任务,回车确认" className="flex-1 h-7 rounded-lg border border-[var(--line)] bg-[var(--bg)] px-3 text-[12px] focus:border-[var(--accent)] focus:outline-none" />
)} {/* 产品方案/UI:关联需求清单 */} {!isResearch && linkedReqs.length > 0 && (
{linkedReqs.map((req) => { const isDone = (plan.completedRequirementIds || []).includes(req.id); return (
{req.code} {req.title}
); })} {allReqsDone && (
所有需求已完成,请提交原型成果
)}
)} {!isResearch && linkedReqs.length === 0 && (
未关联需求,关联
)}
); } function CompleteModal({ onClose, onSubmit }: { onClose: () => void; onSubmit: (result: { resultType: 'link' | 'file'; resultUrl?: string; resultFileName?: string; resultFileData?: string }) => void; }) { const [resultType, setResultType] = useState<'link' | 'file'>('link'); const [url, setUrl] = useState(''); const [fileName, setFileName] = useState(''); const [fileData, setFileData] = useState(''); 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 canSubmit = resultType === 'link' ? url.trim().length > 0 : fileData.length > 0; return (
e.stopPropagation()}>

提交成果

{resultType === 'link' ? ( setUrl(e.target.value)} placeholder="https://..." 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" /> ) : (
{fileName &&

已选:{fileName}

}
)}
); }