// ProjectGanttChart - Gantt chart visualization for active projects function ProjectGanttChart({ projects, onProjectClick }) { const [viewMode, setViewMode] = React.useState('month'); // 'week', 'month', 'quarter' const [collapsed, setCollapsed] = React.useState(false); const containerRef = React.useRef(null); const today = new Date(); today.setHours(0, 0, 0, 0); // Filter projects that have at least a start_date const projectsWithDates = projects.filter(p => p.start_date); if (projectsWithDates.length === 0) { return null; } // Calculate date range based on all projects const calculateDateRange = () => { let minDate = new Date(); let maxDate = new Date(); projectsWithDates.forEach(project => { const startDate = new Date(project.start_date); const endDate = project.due_date ? new Date(project.due_date) : new Date(startDate.getTime() + 30 * 24 * 60 * 60 * 1000); if (startDate < minDate) minDate = startDate; if (endDate > maxDate) maxDate = endDate; }); // Add padding minDate.setDate(minDate.getDate() - 7); maxDate.setDate(maxDate.getDate() + 14); return { minDate, maxDate }; }; const { minDate, maxDate } = calculateDateRange(); // Generate time periods based on view mode const generatePeriods = () => { const periods = []; let current = new Date(minDate); if (viewMode === 'week') { current.setDate(current.getDate() - current.getDay() + 1); while (current <= maxDate) { const weekStart = new Date(current); const weekEnd = new Date(current); weekEnd.setDate(weekEnd.getDate() + 6); periods.push({ start: weekStart, end: weekEnd, label: `${weekStart.getDate()} ${weekStart.toLocaleDateString('it-IT', { month: 'short' })}` }); current.setDate(current.getDate() + 7); } } else if (viewMode === 'month') { current.setDate(1); while (current <= maxDate) { const monthStart = new Date(current); const monthEnd = new Date(current.getFullYear(), current.getMonth() + 1, 0); periods.push({ start: monthStart, end: monthEnd, label: current.toLocaleDateString('it-IT', { month: 'short', year: '2-digit' }) }); current.setMonth(current.getMonth() + 1); } } else { current.setDate(1); current.setMonth(Math.floor(current.getMonth() / 3) * 3); while (current <= maxDate) { const quarterStart = new Date(current); const quarterEnd = new Date(current.getFullYear(), current.getMonth() + 3, 0); const quarterNum = Math.floor(current.getMonth() / 3) + 1; periods.push({ start: quarterStart, end: quarterEnd, label: `Q${quarterNum} ${current.getFullYear()}` }); current.setMonth(current.getMonth() + 3); } } return periods; }; const periods = generatePeriods(); const totalDays = Math.ceil((maxDate - minDate) / (1000 * 60 * 60 * 24)); const getProjectBar = (project) => { const startDate = new Date(project.start_date); const endDate = project.due_date ? new Date(project.due_date) : new Date(startDate.getTime() + 14 * 24 * 60 * 60 * 1000); const startOffset = Math.max(0, Math.ceil((startDate - minDate) / (1000 * 60 * 60 * 24))); const duration = Math.max(1, Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24))); const leftPercent = (startOffset / totalDays) * 100; const widthPercent = (duration / totalDays) * 100; const isOverdue = project.due_date && new Date(project.due_date) < today && project.status !== 'completed'; const daysUntilDue = project.due_date ? Math.ceil((new Date(project.due_date) - today) / (1000 * 60 * 60 * 24)) : null; const isNearingDeadline = daysUntilDue !== null && daysUntilDue > 0 && daysUntilDue <= 7; return { leftPercent, widthPercent, isOverdue, isNearingDeadline, daysUntilDue }; }; const getBarColor = (project, isOverdue, isNearingDeadline) => { if (project.status === 'completed') return 'bg-green-500'; if (isOverdue) return 'bg-red-500'; if (isNearingDeadline) return 'bg-amber-500'; // Normal projects use priority-based colors (distinct from warning colors) switch (project.priority) { case 'high': return 'bg-purple-500'; case 'medium': return 'bg-blue-500'; default: return 'bg-slate-400'; } }; const todayOffset = Math.ceil((today - minDate) / (1000 * 60 * 60 * 24)); const todayPercent = (todayOffset / totalDays) * 100; const sortedProjects = [...projectsWithDates].sort((a, b) => new Date(a.start_date) - new Date(b.start_date) ); return (