'use client'; import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Search, FileText, Palette, ClipboardList, Code2, ClipboardCheck, Bug as BugIcon, ExternalLink, CheckCircle2, ChevronRight, ChevronDown, FolderOpen, Layers } from 'lucide-react'; import { useProductStore } from '@/stores/useProductStore'; import { useVersionPlanStore } from '@/stores/useVersionPlanStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useDevTaskStore } from '@/stores/useDevTaskStore'; import { useTestCaseStore } from '@/stores/useTestCaseStore'; import { useBugStore } from '@/stores/useBugStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { flattenVersions } from '@/lib/derive'; import { aggregateWorkItems, WORK_ITEM_TYPE_LABEL } from '@/lib/workspace-engine'; import type { WorkItem, WorkItemType } from '@/lib/workspace-engine'; import { DEV_TASK_STATUS_LABEL, DEV_TASK_STATUS_COLOR } 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'; type TabKey = 'all' | 'plan_research' | 'plan_product' | 'plan_ui' | 'devTask' | 'testCase' | 'bug'; const TABS: { key: TabKey; label: string; icon: any }[] = [ { key: 'all', label: '全部', icon: ClipboardList }, { key: 'plan_research', label: '调研', icon: Search }, { key: 'plan_product', label: '产品方案', icon: FileText }, { key: 'plan_ui', label: 'UI设计', icon: Palette }, { key: 'devTask', label: '开发任务', icon: Code2 }, { key: 'testCase', label: '测试用例', icon: ClipboardCheck }, { key: 'bug', label: 'Bug', icon: BugIcon }, ]; const PLAN_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 PLAN_STATUS_LABEL: Record = { pending: '未开始', in_progress: '进行中', completed: '已完成' }; export default function WorkspacePage() { const router = useRouter(); const { overview, fetchOverview } = useProductStore(); const { plans, fetchPlans, updatePlan } = useVersionPlanStore(); const { requirements, fetchRequirements } = useRequirementStore(); const { tasks: devTasks, fetchTasks, updateTask } = useDevTaskStore(); const { testCases, fetchTestCases, updateTestCase } = useTestCaseStore(); const { bugs, fetchBugs, updateBug } = useBugStore(); const user = useAuthStore((s) => s.user); const [activeTab, setActiveTab] = useState('all'); const [showCompleted, setShowCompleted] = useState(true); const [selectedVersionId, setSelectedVersionId] = useState(null); useEffect(() => { fetchOverview(); }, [fetchOverview]); useEffect(() => { fetchPlans(); }, [fetchPlans]); useEffect(() => { fetchRequirements(); }, [fetchRequirements]); useEffect(() => { fetchTasks(); }, [fetchTasks]); useEffect(() => { fetchTestCases(); }, [fetchTestCases]); useEffect(() => { fetchBugs(); }, [fetchBugs]); const userName = user?.name ?? ''; const allVersions = useMemo(() => flattenVersions(overview), [overview]); const versionMap = useMemo(() => { const map = new Map(); allVersions.forEach((v) => map.set(v.id, { id: v.id, name: v.name, productName: v.productName, projectName: v.projectName })); return map; }, [allVersions]); const requirementVersionMap = useMemo(() => { const map = new Map(); requirements.forEach((r) => { if (r.versionId) map.set(r.id, r.versionId); }); return map; }, [requirements]); const workItems = useMemo(() => aggregateWorkItems(userName, plans, devTasks, testCases, bugs, versionMap, requirementVersionMap), [userName, plans, devTasks, testCases, bugs, versionMap, requirementVersionMap] ); // 构建树:只显示跟自己有关的产品/项目/版本 const tree = useMemo(() => { const myVersionIds = new Set(workItems.map((i) => i.versionId).filter(Boolean)); const productMap = new Map }>(); allVersions.forEach((v) => { if (!myVersionIds.has(v.id)) return; if (!productMap.has(v.productName)) productMap.set(v.productName, { name: v.productName, projects: new Map() }); const prod = productMap.get(v.productName)!; if (!prod.projects.has(v.projectName)) prod.projects.set(v.projectName, { name: v.projectName, versions: [] }); const proj = prod.projects.get(v.projectName)!; const pending = workItems.filter((i) => i.versionId === v.id && !i.completed).length; if (!proj.versions.find((ver) => ver.id === v.id)) { proj.versions.push({ id: v.id, name: v.name, pendingCount: pending }); } }); return productMap; }, [allVersions, workItems]); const filteredItems = useMemo(() => { let items = workItems; if (selectedVersionId) items = items.filter((i) => i.versionId === selectedVersionId); if (activeTab !== 'all') items = items.filter((i) => i.type === activeTab); if (!showCompleted) items = items.filter((i) => !i.completed); return items.sort((a, b) => (a.completed !== b.completed ? (a.completed ? 1 : -1) : 0)); }, [workItems, activeTab, showCompleted, selectedVersionId]); // 待办数量按 tab 分(受 version filter 影响) const pendingByTab = useMemo(() => { const base = selectedVersionId ? workItems.filter((i) => i.versionId === selectedVersionId) : workItems; return { all: base.filter((i) => !i.completed).length, plan_research: base.filter((i) => i.type === 'plan_research' && !i.completed).length, plan_product: base.filter((i) => i.type === 'plan_product' && !i.completed).length, plan_ui: base.filter((i) => i.type === 'plan_ui' && !i.completed).length, devTask: base.filter((i) => i.type === 'devTask' && !i.completed).length, testCase: base.filter((i) => i.type === 'testCase' && !i.completed).length, bug: base.filter((i) => i.type === 'bug' && !i.completed).length, }; }, [workItems, selectedVersionId]); const pendingCount = pendingByTab.all; const completedCount = (selectedVersionId ? workItems.filter((i) => i.versionId === selectedVersionId) : workItems).filter((i) => i.completed).length; const handleItemAction = (item: WorkItem, action: string) => { if (item.type === 'plan_research' || item.type === 'plan_product' || item.type === 'plan_ui') { if (action === 'start') updatePlan(item.id, { status: 'in_progress' }); if (action === 'complete') updatePlan(item.id, { status: 'completed', completedAt: new Date().toISOString() }); } if (item.type === 'devTask') { if (action === 'start') updateTask(item.id, { status: 'in_progress' }); if (action === 'testing') updateTask(item.id, { status: 'testing' }); if (action === 'submit') updateTask(item.id, { status: 'submitted', completedAt: new Date().toISOString() }); } if (item.type === 'testCase') { if (action === 'pass') updateTestCase(item.id, { status: 'passed', completedAt: new Date().toISOString() }); if (action === 'fail') updateTestCase(item.id, { status: 'failed' }); } if (item.type === 'bug') { if (action === 'fix') updateBug(item.id, { status: 'fixing' }); if (action === 'fixed') updateBug(item.id, { status: 'fixed' }); if (action === 'close') updateBug(item.id, { status: 'closed', closedAt: new Date().toISOString() }); } }; return (
{/* 左侧第一列:产品/项目/版本树 */}

与我相关

{Array.from(tree.entries()).map(([prodName, prod]) => ( ))}
{/* 左侧第二列:环节分类 */}
待办 {pendingCount} 完成 {completedCount}
{/* 右侧:任务列表 */}

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

{filteredItems.length} 项 {selectedVersionId && ( {versionMap.get(selectedVersionId)?.name} )}
{filteredItems.length === 0 ? (

暂无相关任务

) : (
{filteredItems.map((item) => ( item.versionId && router.push(`/versions/${item.versionId}`)} onAction={(action) => handleItemAction(item, action)} /> ))}
)}
); } function ProductNode({ name, prod, selectedVersionId, onSelect }: { name: string; prod: { name: string; projects: Map }; selectedVersionId: string | null; onSelect: (id: string | null) => void; }) { const [expanded, setExpanded] = useState(true); return (
{expanded && Array.from(prod.projects.entries()).map(([projName, proj]) => ( ))}
); } function ProjectNode({ name, versions, selectedVersionId, onSelect }: { name: string; versions: { id: string; name: string; pendingCount: number }[]; selectedVersionId: string | null; onSelect: (id: string | null) => void; }) { const [expanded, setExpanded] = useState(true); return (
{expanded && versions.map((v) => ( ))}
); } function WorkItemCard({ item, onNavigate, onAction }: { item: WorkItem; onNavigate: () => void; onAction: (action: string) => void }) { const statusBadge = getStatusBadge(item); const actions = getActions(item); return (
{item.completed && } {WORK_ITEM_TYPE_LABEL[item.type]} {statusBadge} {item.extra?.taskNo && {item.extra.taskNo}} {item.extra?.caseNo && {item.extra.caseNo}} {item.extra?.bugNo && {item.extra.bugNo}} {item.title} {item.priority && {item.priority}} {item.extra?.severity && ( {BUG_SEVERITY_LABEL[item.extra.severity as keyof typeof BUG_SEVERITY_LABEL] || ''} )} {/* 操作按钮 */} {actions.length > 0 && (
{actions.map((a) => ( ))}
)}
{item.productName} / {item.projectName} / {item.extra?.dueDate && 截止 {item.extra.dueDate}} {item.extra?.startTime && {item.extra.startTime.slice(0, 16).replace('T', ' ')} → {item.extra.endTime?.slice(0, 16).replace('T', ' ')}}
); } function getActions(item: WorkItem): { key: string; label: string; style: string }[] { if (item.completed) return []; if (item.type === 'plan_research' || item.type === 'plan_product' || item.type === 'plan_ui') { if (item.status === 'pending') return [{ key: 'start', label: '开始', style: 'text-blue-600 hover:bg-blue-50 border border-blue-200' }]; if (item.status === 'in_progress') return [{ key: 'complete', label: '完成', style: 'text-emerald-600 hover:bg-emerald-50 border border-emerald-200' }]; } if (item.type === 'devTask') { if (item.status === 'todo') return [{ key: 'start', label: '开始', style: 'text-blue-600 hover:bg-blue-50 border border-blue-200' }]; if (item.status === 'in_progress') return [{ key: 'testing', label: '提测', style: 'text-purple-600 hover:bg-purple-50 border border-purple-200' }]; if (item.status === 'testing') return [{ key: 'submit', label: '完成', style: 'text-emerald-600 hover:bg-emerald-50 border border-emerald-200' }]; } if (item.type === 'testCase') { if (item.status === 'pending' || item.status === 'running' || item.status === 'failed') { return [ { key: 'pass', label: '通过', style: 'text-emerald-600 hover:bg-emerald-50 border border-emerald-200' }, { key: 'fail', label: '失败', style: 'text-red-600 hover:bg-red-50 border border-red-200' }, ]; } } if (item.type === 'bug') { if (item.status === 'open') return [{ key: 'fix', label: '修复中', style: 'text-blue-600 hover:bg-blue-50 border border-blue-200' }]; if (item.status === 'fixing') return [{ key: 'fixed', label: '已修复', style: 'text-purple-600 hover:bg-purple-50 border border-purple-200' }]; if (item.status === 'fixed' || item.status === 'verifying') return [{ key: 'close', label: '关闭', style: 'text-emerald-600 hover:bg-emerald-50 border border-emerald-200' }]; } return []; } function getStatusBadge(item: WorkItem) { if (item.type === 'devTask') { return ( {DEV_TASK_STATUS_LABEL[item.status as keyof typeof DEV_TASK_STATUS_LABEL] || item.status} ); } if (item.type === 'testCase') { return ( {TEST_CASE_STATUS_LABEL[item.status as keyof typeof TEST_CASE_STATUS_LABEL] || item.status} ); } if (item.type === 'bug') { return ( {BUG_STATUS_LABEL[item.status as keyof typeof BUG_STATUS_LABEL] || item.status} ); } return ( {PLAN_STATUS_LABEL[item.status] || item.status} ); }