/** * ProjectHierarchyTable - Leadership Overview Module * * Three-level hierarchical table: * Level 1: Project Name (Dark header) * Level 2: Product Name (Light header, indented) * Level 3: Mold data rows (Table format) * * Design: Dense, scannable, desktop-optimized with mobile scroll */ import { ProjectData } from '@/types/project'; import { groupProjectsByHierarchy, isDateStale, formatDateShort, ProjectGroup, ProductGroup, MoldItem } from '@/lib/hierarchyUtils'; import { ChevronDown, ChevronRight, Clock, AlertCircle, CheckCircle2 } from 'lucide-react'; import { useState } from 'react'; interface ProjectHierarchyTableProps { projects: ProjectData[]; } export default function ProjectHierarchyTable({ projects }: ProjectHierarchyTableProps) { const hierarchicalData = groupProjectsByHierarchy(projects); // Collapse state for each project const [collapsedProjects, setCollapsedProjects] = useState>(new Set()); const toggleProject = (projectName: string) => { setCollapsedProjects(prev => { const newSet = new Set(prev); if (newSet.has(projectName)) { newSet.delete(projectName); } else { newSet.add(projectName); } return newSet; }); }; return (
{/* Header */}

项目整体概览 / Project Overview

按项目和产品分组的宏观视图 · {projects.length} 个模具

{/* Table Container - No horizontal scroll, responsive reflow */}
{hierarchicalData.map(projectGroup => ( toggleProject(projectGroup.projectName)} /> ))}
); } // Level 1: Project Name Row function ProjectGroupRow({ projectGroup, isCollapsed, onToggle }: { projectGroup: ProjectGroup; isCollapsed: boolean; onToggle: () => void; }) { const totalMolds = projectGroup.products.reduce((sum, p) => sum + p.molds.length, 0); return (
{/* Project Header - Clickable to collapse */} {/* Products (Level 2) - Only show if not collapsed */} {!isCollapsed && projectGroup.products.map(productGroup => ( ))}
); } // Level 2: Product Name + Level 3: Mold Table function ProductGroupRow({ productGroup }: { productGroup: ProductGroup }) { return (
{/* Product Header */}
{productGroup.productName} ({productGroup.molds.length} 模具)
{/* Mold Data Table - Responsive: Table on desktop, Cards on mobile */}
{/* Table Header - Hidden on mobile, shown on desktop */} {/* CRITICAL: Must use grid-cols-12 to match data row layout exactly */}
{/* Col 1-2: Mold Number Header (2 cols) */}
模具编号
{/* Col 3-4: Update Date Header (2 cols) */}
更新日期
{/* Col 5-10: Progress Details Header (6 cols) - Add pl-4 for spacing */}
项目推进细节
{/* Col 11-12: Current Node Header (2 cols) - Right-aligned to match data */}
当前节点
{/* Data Rows - Stacked cards on mobile, table rows on desktop */}
{productGroup.molds.map((mold, index) => ( ))}
); } // Level 3: Individual Mold Row function MoldDataRow({ mold }: { mold: MoldItem }) { const isStale = isDateStale(mold.updateDate); const formattedDate = formatDateShort(mold.updateDate); // Truncate long text const truncateText = (text: string, maxLength: number = 80) => { if (!text || text === '-') return '-'; return text.length > maxLength ? text.substring(0, maxLength) + '...' : text; }; // Render current node: Show all, highlight critical const renderCurrentNode = (nodeValue: string) => { // Data cleaning: trim to remove invisible spaces const status = (nodeValue || '').toString().trim(); switch (status) { case '进行中': return (
进行中
); case '已超时': return (
已超时
); case '已完成': return (
已完成
); default: // Show all other status text (T1, PQR, PB2, etc.) in light gray return ( {status || '-'} ); } }; return (
{/* Mobile: Vertical Card Layout */}
{/* Top Row: Mold No (Left) + Current Node (Right) */}
{mold.moldNumber}
{renderCurrentNode(mold.currentNode)}
{/* Middle Row: Update Date */}
更新于: {formattedDate} {isStale && ( )}
{/* Bottom Row: Progress Details (Full width, wrapping allowed) */}
{mold.progressDetails}
{/* Desktop: CSS Grid 12-Column Layout */}
{/* Col 1-2: Mold Number (2 cols) */}
{mold.moldNumber}
{/* Col 3-4: Update Date (2 cols) */}
{formattedDate} {isStale && ( )}
{/* Col 5-10: Progress Details (6 cols = 50% width) - Add pl-4 to match header */}
{mold.progressDetails}
{/* Col 11-12: Current Node (2 cols, right-aligned) */}
{renderCurrentNode(mold.currentNode)}
); }