| |
| |
| |
| |
| |
| |
|
|
| 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[]; |
| } |
|
|
| |
| |
| |
| export function groupProjectsByHierarchy(projects: ProjectData[]): ProjectGroup[] { |
| const projectMap = new Map<string, Map<string, MoldItem[]>>(); |
| |
| |
| 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 |
| }); |
| }); |
| |
| |
| 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)); |
| } |
|
|
| |
| |
| |
| export function isDateStale(dateStr: string): boolean { |
| if (!dateStr || dateStr === '-') return false; |
| |
| try { |
| |
| 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; |
| } |
| } |
|
|
| |
| |
| |
| 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; |
| } |
| } |
|
|
| |
| |
| |
| export function getNodeBadgeClass(node: string): string { |
| const normalizedNode = node?.toUpperCase() || ''; |
| |
| |
| if (normalizedNode.includes('MP') || normalizedNode.includes('完成')) { |
| return 'bg-green-100 text-green-700 border-green-300'; |
| } |
| |
| |
| if (normalizedNode.includes('PQR') || |
| normalizedNode.includes('T1') || |
| normalizedNode.includes('VMP') || |
| normalizedNode.includes('进行中')) { |
| return 'bg-blue-100 text-blue-700 border-blue-300'; |
| } |
| |
| |
| if (normalizedNode.includes('KICK OFF') || normalizedNode.includes('G/L')) { |
| return 'bg-slate-100 text-slate-700 border-slate-300'; |
| } |
| |
| |
| return 'bg-slate-100 text-slate-600 border-slate-300'; |
| } |
|
|