/** * Hierarchy Grouping Utilities for Leadership Overview * * Transforms flat project array into hierarchical structure: * Project Name -> Product Name -> Mold Items */ import { ProjectData } from '@/types/project'; export interface MoldItem { moldNumber: string; updateDate: string; progressDetails: string; currentNode: string; fullProject: ProjectData; } export interface ProductGroup { productName: string; molds: MoldItem[]; } export interface ProjectGroup { projectName: string; products: ProductGroup[]; } /** * Group flat projects array into hierarchical structure */ export function groupProjectsByHierarchy(projects: ProjectData[]): ProjectGroup[] { const projectMap = new Map>(); // First pass: organize data projects.forEach(project => { const projectName = project.identity.projectName || 'Unknown Project'; const productName = project.identity.productName || 'Unknown Product'; const moldNumber = project.identity.moldNumber || 'N/A'; if (!projectMap.has(projectName)) { projectMap.set(projectName, new Map()); } const productMap = projectMap.get(projectName)!; if (!productMap.has(productName)) { productMap.set(productName, []); } productMap.get(productName)!.push({ moldNumber, updateDate: project.details.detailDate, progressDetails: project.details.detailProgress, currentNode: project.milestones.currentNode, fullProject: project }); }); // Second pass: convert to array structure const result: ProjectGroup[] = []; projectMap.forEach((productMap, projectName) => { const products: ProductGroup[] = []; productMap.forEach((molds, productName) => { products.push({ productName, molds: molds.sort((a, b) => a.moldNumber.localeCompare(b.moldNumber)) }); }); result.push({ projectName, products: products.sort((a, b) => a.productName.localeCompare(b.productName)) }); }); return result.sort((a, b) => a.projectName.localeCompare(b.projectName)); } /** * Check if date is older than 7 days */ export function isDateStale(dateStr: string): boolean { if (!dateStr || dateStr === '-') return false; try { // Handle Excel date format (e.g., "1/2/26" or "2026-01-02") const date = new Date(dateStr); if (isNaN(date.getTime())) return false; const now = new Date(); const diffTime = now.getTime() - date.getTime(); const diffDays = diffTime / (1000 * 60 * 60 * 24); return diffDays > 7; } catch { return false; } } /** * Format date for display (e.g., "1月2日") */ export function formatDateShort(dateStr: string): string { if (!dateStr || dateStr === '-') return '-'; try { const date = new Date(dateStr); if (isNaN(date.getTime())) return dateStr; const month = date.getMonth() + 1; const day = date.getDate(); return `${month}月${day}日`; } catch { return dateStr; } } /** * Get badge color class based on current node */ export function getNodeBadgeClass(node: string): string { const normalizedNode = node?.toUpperCase() || ''; // Completed stages if (normalizedNode.includes('MP') || normalizedNode.includes('完成')) { return 'bg-green-100 text-green-700 border-green-300'; } // In-progress stages if (normalizedNode.includes('PQR') || normalizedNode.includes('T1') || normalizedNode.includes('VMP') || normalizedNode.includes('进行中')) { return 'bg-blue-100 text-blue-700 border-blue-300'; } // Early stages if (normalizedNode.includes('KICK OFF') || normalizedNode.includes('G/L')) { return 'bg-slate-100 text-slate-700 border-slate-300'; } // Default return 'bg-slate-100 text-slate-600 border-slate-300'; }