// DeadlinesCalendarWidget - Monthly calendar showing project deadlines and Google events function DeadlinesCalendarWidget({ projects, onViewChange, googleEvents = [], onMonthChange }) { const [currentDate, setCurrentDate] = React.useState(new Date()); const [selectedDay, setSelectedDay] = React.useState(null); // Italian month names const monthNames = [ 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre' ]; // Italian weekday abbreviations (starting Monday) const weekDays = ['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom']; // Generate calendar days for current month const calendarDays = React.useMemo(() => { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); // Get day of week (0=Sun, adjust for Monday start) let startPadding = firstDay.getDay() - 1; if (startPadding < 0) startPadding = 6; // Sunday becomes 6 const daysInMonth = lastDay.getDate(); const days = []; // Add padding for previous month for (let i = startPadding - 1; i >= 0; i--) { const prevDate = new Date(year, month, -i); days.push({ date: prevDate, isCurrentMonth: false }); } // Current month days for (let d = 1; d <= daysInMonth; d++) { days.push({ date: new Date(year, month, d), isCurrentMonth: true }); } // Pad to complete grid (6 rows x 7 days = 42) const remaining = 42 - days.length; for (let i = 1; i <= remaining; i++) { days.push({ date: new Date(year, month + 1, i), isCurrentMonth: false }); } return days; }, [currentDate]); // Get projects for a specific date const getProjectsForDate = React.useCallback((date) => { const dateStr = date.toISOString().split('T')[0]; return projects.filter(p => p.due_date === dateStr && p.status !== 'completato' && !p.is_archived ); }, [projects]); // Get Google events for a specific date const getGoogleEventsForDate = React.useCallback((date) => { const dateStr = date.toISOString().split('T')[0]; return googleEvents.filter(event => { // Handle all-day events (start is just a date) if (event.all_day) { return event.start === dateStr; } // Handle timed events (start is a datetime string) const eventDate = event.start?.split('T')[0]; return eventDate === dateStr; }); }, [googleEvents]); // Check if date is today const isToday = (date) => { const today = new Date(); return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear(); }; // Navigate months const goToPrevMonth = () => { const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1); setCurrentDate(newDate); setSelectedDay(null); if (onMonthChange) { onMonthChange(newDate.getMonth() + 1, newDate.getFullYear()); } }; const goToNextMonth = () => { const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1); setCurrentDate(newDate); setSelectedDay(null); if (onMonthChange) { onMonthChange(newDate.getMonth() + 1, newDate.getFullYear()); } }; const goToToday = () => { const newDate = new Date(); setCurrentDate(newDate); setSelectedDay(null); if (onMonthChange) { onMonthChange(newDate.getMonth() + 1, newDate.getFullYear()); } }; // Get dot color based on priority const getDotColor = (priority) => { switch (priority) { case 'high': return 'bg-red-500'; case 'medium': return 'bg-amber-500'; case 'low': return 'bg-blue-500'; default: return 'bg-slate-400'; } }; // Render dots for projects and Google events on a day const renderDots = (dayProjects, dayGoogleEvents) => { const totalItems = dayProjects.length + dayGoogleEvents.length; if (totalItems === 0) return null; const maxDots = 3; const dots = []; // Add project dots first dayProjects.slice(0, maxDots).forEach((p, i) => { dots.push( ); }); // Add Google event dots (blue-400 color) const remainingSlots = maxDots - dots.length; dayGoogleEvents.slice(0, remainingSlots).forEach((e, i) => { dots.push( ); }); const extraCount = totalItems - maxDots; return (
{dots} {extraCount > 0 && ( +{extraCount} )}
); }; // Handle day click const handleDayClick = (day, hasItems) => { if (hasItems) { setSelectedDay(selectedDay?.getTime() === day.date.getTime() ? null : day.date); } }; // Get items for selected day const selectedDayProjects = selectedDay ? getProjectsForDate(selectedDay) : []; const selectedDayGoogleEvents = selectedDay ? getGoogleEventsForDate(selectedDay) : []; return ( {/* Header */}

Calendario Scadenze

{/* Month Navigation */}
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
{/* Weekday Headers */}
{weekDays.map(day => (
{day}
))}
{/* Calendar Grid */}
{calendarDays.map((day, index) => { const dayProjects = getProjectsForDate(day.date); const dayGoogleEvents = getGoogleEventsForDate(day.date); const hasItems = dayProjects.length > 0 || dayGoogleEvents.length > 0; const isSelected = selectedDay?.getTime() === day.date.getTime(); return (
handleDayClick(day, hasItems)} > {day.date.getDate()} {day.isCurrentMonth && renderDots(dayProjects, dayGoogleEvents)}
); })}
{/* Selected Day Details */} {selectedDay && (selectedDayProjects.length > 0 || selectedDayGoogleEvents.length > 0) && (
{selectedDay.getDate()} {monthNames[selectedDay.getMonth()]}
{/* Project deadlines */} {selectedDayProjects.map(project => (
onViewChange('projects')} > {project.name}
))} {/* Google Calendar events */} {selectedDayGoogleEvents.map(event => (
{event.summary} {!event.all_day && event.start && ( {new Date(event.start).toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' })} {event.end && ` - ${new Date(event.end).toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' })}`} )}
))}
)} {/* Legend */}
Alta
Media
Bassa
{googleEvents.length > 0 && (
Google Calendar
)}
); } window.DeadlinesCalendarWidget = DeadlinesCalendarWidget;