// WelcomeView - Activity summary and project progress function WelcomeView({ onViewChange, onViewProject }) { const { user } = useAuth(); const { notifications, unreadCount, markAsRead, markAllAsRead, loading } = useNotifications(); // Fetch all projects (not limited) for widgets const [allProjects, setAllProjects] = React.useState([]); const [projectsLoading, setProjectsLoading] = React.useState(true); // Check if user is admin const isAdmin = user?.role === 'admin'; // Google Calendar state const [calendarAvailable, setCalendarAvailable] = React.useState(false); const [googleEvents, setGoogleEvents] = React.useState([]); // Function to load projects (includes archived for admin statistics) const loadProjects = React.useCallback(async () => { setProjectsLoading(true); try { const activeProjects = await ApiUtils.fetchProjects(); // For admin, also fetch archived projects for team statistics if (isAdmin) { try { const archivedProjects = await window.ApiProjects.fetchArchivedProjects(); setAllProjects([...activeProjects, ...archivedProjects]); } catch (err) { // If archived fetch fails, just use active projects console.error('Failed to fetch archived projects:', err); setAllProjects(activeProjects); } } else { setAllProjects(activeProjects); } } catch (err) { console.error('Failed to fetch projects:', err); } finally { setProjectsLoading(false); } }, [isAdmin]); // Initial load - check if we need to refresh due to changes made in other views React.useEffect(() => { const needsRefresh = localStorage.getItem('rosa_projects_need_refresh'); if (needsRefresh) { localStorage.removeItem('rosa_projects_need_refresh'); } loadProjects(); }, [loadProjects]); // Listen for project-related notifications and reload projects React.useEffect(() => { // Check if there's a new project-related notification const latestProjectNotification = notifications.find(n => (n.type === 'step_status_changed' || n.type === 'project_status_changed' || n.type === 'project_assigned' || n.type === 'project_unassigned') && !n.is_read ); if (latestProjectNotification) { // Reload projects when something project-related changes loadProjects(); } }, [notifications, loadProjects]); // Listen for custom project update events (triggered by ProjectTimeline component) React.useEffect(() => { const handleProjectStepUpdate = () => { loadProjects(); }; window.addEventListener('project-step-updated', handleProjectStepUpdate); return () => { window.removeEventListener('project-step-updated', handleProjectStepUpdate); }; }, [loadProjects]); // Also reload projects when the component becomes visible (user returns to this view) React.useEffect(() => { const handleVisibilityChange = () => { if (!document.hidden) { // Reload projects when tab becomes visible loadProjects(); } }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => document.removeEventListener('visibilitychange', handleVisibilityChange); }, [loadProjects]); // Fetch Google Calendar events for a specific month const fetchGoogleEvents = React.useCallback((month, year) => { // Admin uses their own calendar endpoint, non-admin uses shared endpoint const fetchFn = isAdmin ? ApiUtils.getCalendarEvents : ApiUtils.getSharedCalendarEvents; fetchFn(month, year) .then(data => { setGoogleEvents(data.events || []); }) .catch(error => { console.error('Error fetching Google Calendar events:', error); setGoogleEvents([]); }); }, [isAdmin]); // Check calendar status and fetch events for current month React.useEffect(() => { if (isAdmin) { // Admin checks their own calendar status ApiUtils.getCalendarStatus() .then(status => { setCalendarAvailable(status.connected); if (status.connected) { const now = new Date(); fetchGoogleEvents(now.getMonth() + 1, now.getFullYear()); } }) .catch(console.error); } else { // Non-admin checks if shared calendar is available ApiUtils.getSharedCalendarStatus() .then(status => { setCalendarAvailable(status.available); if (status.available) { const now = new Date(); fetchGoogleEvents(now.getMonth() + 1, now.getFullYear()); } }) .catch(console.error); } }, [isAdmin, fetchGoogleEvents]); // Handle month change from calendar widget const handleCalendarMonthChange = React.useCallback((month, year) => { if (calendarAvailable) { fetchGoogleEvents(month, year); } }, [calendarAvailable, fetchGoogleEvents]); // Calculate project progress const getProjectProgress = React.useCallback((project) => { if (!project.steps || project.steps.length === 0) return { completed: 0, total: 0, percent: 0 }; const completed = project.steps.filter(s => s.status === 'completato').length; const total = project.steps.length; return { completed, total, percent: Math.round((completed / total) * 100) }; }, []); // Filter non-archived projects for display widgets (Gantt, Calendar, etc.) const nonArchivedProjects = React.useMemo(() => { return allProjects.filter(p => !p.is_archived); }, [allProjects]); // Filter active projects for the progress widget (limited to 5) const activeProjects = React.useMemo(() => { return nonArchivedProjects .filter(p => p.status !== 'completato') .sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)) .slice(0, 5); }, [nonArchivedProjects]); // Get current step (first non-completed step) const getCurrentStep = (project) => { if (!project.steps || project.steps.length === 0) return null; const sortedSteps = [...project.steps].sort((a, b) => a.step_order - b.step_order); return sortedSteps.find(s => s.status !== 'completato') || sortedSteps[sortedSteps.length - 1]; }; // Time-of-day greeting const getGreeting = () => { const hour = new Date().getHours(); if (hour < 12) return 'Buongiorno'; if (hour < 18) return 'Buon pomeriggio'; return 'Buonasera'; }; // Calculate workload and deadline KPIs const workloadStats = React.useMemo(() => { const now = new Date(); now.setHours(0, 0, 0, 0); // Overdue projects (past due_date, not completed/cancelled) const overdue = nonArchivedProjects.filter(p => { if (!p.due_date || p.status === 'completato' || p.is_cancelled) return false; const due = new Date(p.due_date); due.setHours(23, 59, 59, 999); return due < now; }).length; // Due soon (within 7 days, not overdue) const dueSoon = nonArchivedProjects.filter(p => { if (!p.due_date || p.status === 'completato' || p.is_cancelled) return false; const due = new Date(p.due_date); due.setHours(0, 0, 0, 0); const daysUntil = Math.ceil((due - now) / (1000 * 60 * 60 * 24)); return daysUntil >= 0 && daysUntil <= 7; }).length; // My active projects (assigned to me, not completed/cancelled) const myActive = nonArchivedProjects.filter(p => { if (p.status === 'completato' || p.is_cancelled) return false; const isAssignedToMe = p.assignments?.some(a => a.user_id === user?.id) || p.my_assignment; return isAssignedToMe; }).length; // Completion rate (using all projects including archived) const myAllProjects = allProjects.filter(p => { const isAssignedToMe = p.assignments?.some(a => a.user_id === user?.id) || p.my_assignment; return isAssignedToMe; }); const myCompleted = myAllProjects.filter(p => p.status === 'completato' || (p.is_archived && !p.is_cancelled) ).length; const completionRate = myAllProjects.length > 0 ? Math.round((myCompleted / myAllProjects.length) * 100) : 0; return { overdue, dueSoon, myActive, completionRate, myTotal: myAllProjects.length }; }, [nonArchivedProjects, allProjects, user]); // Get notification icon by type const getNotificationIcon = (type) => { const iconMap = { project_assigned: ( ), project_unassigned: ( ), project_status_changed: ( ), step_status_changed: ( ), comment_added: ( ), }; return iconMap[type] || ( ); }; // Dynamic color for completion rate const getCompletionRateColors = (rate) => { if (rate >= 75) return { color: 'text-green-600', bg: 'bg-green-50', iconColor: 'text-green-500' }; if (rate >= 50) return { color: 'text-amber-600', bg: 'bg-amber-50', iconColor: 'text-amber-500' }; return { color: 'text-slate-600', bg: 'bg-slate-50', iconColor: 'text-slate-500' }; }; const completionColors = getCompletionRateColors(workloadStats.completionRate); const statCards = [ { label: 'Scadute', value: workloadStats.overdue, icon: ( ), color: 'text-red-600', bg: 'bg-red-50', iconColor: 'text-red-500' }, { label: 'In Scadenza', sublabel: 'prossimi 7 giorni', value: workloadStats.dueSoon, icon: ( ), color: 'text-amber-600', bg: 'bg-amber-50', iconColor: 'text-amber-500' }, { label: 'Le Mie Pratiche', sublabel: 'attive', value: workloadStats.myActive, icon: ( ), color: 'text-blue-600', bg: 'bg-blue-50', iconColor: 'text-blue-500' }, { label: 'Completate', sublabel: workloadStats.myTotal > 0 ? `${workloadStats.myTotal} totali` : null, value: `${workloadStats.completionRate}%`, icon: ( ), color: completionColors.color, bg: completionColors.bg, iconColor: completionColors.iconColor }, ]; return (
Ecco un riepilogo della tua attivitÃ
Nessuna attivita recente
) : ({notification.title}
{notification.message}
Nessuna pratica attiva
) : (