'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 { PlanDetailDrawer } from '@/components/version/PlanDetailDrawer'; import { DevTaskDetailDrawer } from '@/components/dev-task/DevTaskDetailDrawer'; import { TestCaseDetailDrawer } from '@/components/test-case/TestCaseDetailDrawer'; import { BugDetailDrawer } from '@/components/bug/BugDetailDrawer'; 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 } = useVersionPlanStore(); const { requirements, fetchRequirements } = useRequirementStore(); const { tasks: devTasks, fetchTasks } = useDevTaskStore(); const { testCases, fetchTestCases } = useTestCaseStore(); const { bugs, fetchBugs } = useBugStore(); const user = useAuthStore((s) => s.user); const [activeTab, setActiveTab] = useState('all'); const [showCompleted, setShowCompleted] = useState(true); const [selectedVersionId, setSelectedVersionId] = useState(null); const [drawerItem, setDrawerItem] = 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; 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}`)} onClick={() => setDrawerItem(item)} /> ))}
)}
{/* Detail Drawers */} {drawerItem && (drawerItem.type === 'plan_research' || drawerItem.type === 'plan_product' || drawerItem.type === 'plan_ui') && ( setDrawerItem(null)} /> )} {drawerItem && drawerItem.type === 'devTask' && ( t.id)} onClose={() => setDrawerItem(null)} /> )} {drawerItem && drawerItem.type === 'testCase' && ( setDrawerItem(null)} /> )} {drawerItem && drawerItem.type === 'bug' && ( setDrawerItem(null)} /> )}
); } 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, onClick }: { item: WorkItem; onNavigate: () => void; onClick: () => void }) { const statusBadge = getStatusBadge(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] || ''} )}
{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 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} ); }