import React, { useEffect, useState, useCallback } from "react"; import { format } from "date-fns"; import { el, enUS, es, ja, uk, de } from "date-fns/locale"; import { useTranslation } from "react-i18next"; import i18n from "i18next"; import { Link } from "react-router-dom"; import { ClipboardDocumentListIcon, ArrowPathIcon, CalendarDaysIcon, ClockIcon, InboxIcon, FolderIcon, ArchiveBoxIcon, } from "@heroicons/react/24/outline"; import { fetchTasks, updateTask, deleteTask } from "../../utils/tasksService"; import { fetchProjects } from "../../utils/projectsService"; import { loadInboxItemsToStore } from "../../utils/inboxService"; import { Task } from "../../entities/Task"; import { useStore } from "../../store/useStore"; import TaskList from "./TaskList"; import { Metrics } from "../../entities/Metrics"; const getLocale = (language: string) => { switch (language) { case 'el': return el; case 'es': return es; case 'jp': return ja; case 'ua': return uk; case 'de': return de; default: return enUS; } }; const TasksToday: React.FC = () => { const { t } = useTranslation(); // Don't use multiple separate useStore calls - combine them into one const store = useStore(); // Use local state for data instead of directly using store state // This prevents unnecessary re-renders from store updates const [localTasks, setLocalTasks] = useState([]); const [localProjects, setLocalProjects] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); // Metrics from the API const [metrics, setMetrics] = useState({ total_open_tasks: 0, tasks_pending_over_month: 0, tasks_in_progress_count: 0, tasks_in_progress: [], tasks_due_today: [], suggested_tasks: [], }); // Track mounting state to prevent state updates after unmount const isMounted = React.useRef(false); // Load data once on component mount useEffect(() => { isMounted.current = true; // Only fetch data once on mount const loadData = async () => { if (!isMounted.current) return; setIsLoading(true); setIsError(false); try { // Load inbox items to ensure the notification appears correctly loadInboxItemsToStore(); } catch (error) { console.error("Failed to load inbox items:", error); } try { // Load projects first const projectsData = await fetchProjects(); if (isMounted.current) { const safeProjectsData = Array.isArray(projectsData) ? projectsData : []; setLocalProjects(safeProjectsData); store.projectsStore.setProjects(safeProjectsData); } } catch (error) { if (isMounted.current) { setLocalProjects([]); setIsError(true); } } try { // Load tasks with metrics const { tasks: fetchedTasks, metrics: fetchedMetrics } = await fetchTasks("?type=today"); if (isMounted.current) { setLocalTasks(fetchedTasks); setMetrics(fetchedMetrics); // Also update the store store.tasksStore.setTasks(fetchedTasks); } } catch (error) { console.error("Failed to fetch tasks:", error); if (isMounted.current) { setIsError(true); } } finally { if (isMounted.current) { setIsLoading(false); } } }; loadData(); // Cleanup function to prevent state updates after unmount return () => { isMounted.current = false; }; }, []); // Empty dependency array - only run once on mount // Memoize task handlers to prevent recreating functions on each render const handleTaskUpdate = useCallback(async (updatedTask: Task): Promise => { if (!updatedTask.id || !isMounted.current) return; setIsLoading(true); try { await updateTask(updatedTask.id, updatedTask); // Refetch data to ensure consistency const { tasks: updatedTasks, metrics } = await fetchTasks("?type=today"); if (isMounted.current) { setLocalTasks(updatedTasks); setMetrics(metrics); // Update store store.tasksStore.setTasks(updatedTasks); } } catch (error) { console.error("Error updating task:", error); if (isMounted.current) { setIsError(true); } } finally { if (isMounted.current) { setIsLoading(false); } } }, [store.tasksStore]); const handleTaskDelete = useCallback(async (taskId: number): Promise => { if (!isMounted.current) return; setIsLoading(true); try { await deleteTask(taskId); // Refetch data to ensure consistency const { tasks: updatedTasks, metrics } = await fetchTasks("?type=today"); if (isMounted.current) { setLocalTasks(updatedTasks); setMetrics(metrics); // Update store store.tasksStore.setTasks(updatedTasks); } } catch (error) { console.error("Error deleting task:", error); if (isMounted.current) { setIsError(true); } } finally { if (isMounted.current) { setIsLoading(false); } } }, [store.tasksStore]); // Get inbox items count from store for the notification const inboxItemsCount = store.inboxStore.inboxItems.length; // Show loading state if (isLoading && localTasks.length === 0) { return (

{t('common.loading', 'Loading...')}

); } // Show error state if (isError && localTasks.length === 0) { return (

{t('errors.somethingWentWrong', 'Something went wrong')}

); } return (

{t('tasks.today')}

{format(new Date(), "PPP", { locale: getLocale(i18n.language) })}
{/* Task Metrics */}

{t('tasks.metrics', 'Tasks')}

{/* Left column */}

{t('tasks.backlog')}

{metrics.total_open_tasks}

{t('tasks.inProgress')}

{metrics.tasks_in_progress_count}

{/* Right column */}

{t('tasks.dueToday')}

{metrics.tasks_due_today.length}

{t('tasks.stale')}

{metrics.tasks_pending_over_month}

{/* Project Metrics */}

{t('projects.metrics', 'Projects')}

{t('projects.active')}

{Array.isArray(localProjects) ? localProjects.filter(project => project.active).length : 0}

{t('projects.inactive')}

{Array.isArray(localProjects) ? localProjects.filter(project => !project.active).length : 0}

{/* Inbox Notification */} {inboxItemsCount > 0 && (

{t('inbox.unprocessedItems', { count: inboxItemsCount, defaultValue: `You have ${inboxItemsCount} item(s) in your inbox.` })}

{t('inbox.processNow', 'Process them now')}

)} {metrics.tasks_due_today.length > 0 && ( <>

{t('tasks.dueToday')}

)} {metrics.tasks_in_progress.length > 0 && ( <>

{t('tasks.inProgress')}

)} {metrics.suggested_tasks.length > 0 && ( <>

{t('tasks.suggested')}

)} {metrics.tasks_due_today.length === 0 && metrics.tasks_in_progress.length === 0 && metrics.suggested_tasks.length === 0 && (

{t('tasks.noTasksAvailable')}

)}
); }; export default TasksToday;