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 { ClipboardDocumentListIcon, ArrowPathIcon, CalendarDaysIcon, FolderIcon, CheckCircleIcon, ArrowUpIcon, ArrowDownIcon, ChevronDownIcon, ChevronRightIcon, Cog6ToothIcon, } from "@heroicons/react/24/outline"; import { fetchTasks, updateTask, deleteTask } from "../../utils/tasksService"; import { fetchProjects } from "../../utils/projectsService"; import { Task } from "../../entities/Task"; import { useStore } from "../../store/useStore"; import TaskList from "./TaskList"; import TodayPlan from "./TodayPlan"; import { Metrics } from "../../entities/Metrics"; import ProductivityAssistant from "../Productivity/ProductivityAssistant"; import NextTaskSuggestion from "./NextTaskSuggestion"; import WeeklyCompletionChart from "./WeeklyCompletionChart"; import TodaySettingsDropdown from "./TodaySettingsDropdown"; import { getProductivityAssistantEnabled, getNextTaskSuggestionEnabled } from "../../utils/profileService"; import { toggleTaskToday } from "../../utils/tasksService"; 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); const [dailyQuote, setDailyQuote] = useState(''); const [productivityAssistantEnabled, setProductivityAssistantEnabled] = useState(true); const [isSettingsDropdownOpen, setIsSettingsDropdownOpen] = useState(false); const [todaySettings, setTodaySettings] = useState({ showMetrics: false, showProductivity: false, showIntelligence: false, showDueToday: true, showCompleted: true, showProgressBar: true, // Always enabled showDailyQuote: true, }); const [nextTaskSuggestionEnabled, setNextTaskSuggestionEnabled] = useState(true); const [showNextTaskSuggestion, setShowNextTaskSuggestion] = useState(() => { const stored = sessionStorage.getItem('hideNextTaskSuggestion'); return stored !== 'true'; }); const [isSuggestedCollapsed, setIsSuggestedCollapsed] = useState(() => { const stored = localStorage.getItem('suggestedTasksCollapsed'); return stored === 'true'; }); const [isCompletedCollapsed, setIsCompletedCollapsed] = useState(() => { const stored = localStorage.getItem('completedTasksCollapsed'); return stored === 'true'; }); // 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: [], tasks_completed_today: [], weekly_completions: [], }); // Helper function to get completion trend vs average const getCompletionTrend = () => { const todayCount = metrics.tasks_completed_today.length; // Calculate average: sum of all completed tasks divided by 7 days // The average represents the daily average across the week if (metrics.weekly_completions.length === 0) { return { direction: 'same', difference: 0, percentage: 0, todayCount, averageCount: 0 }; } // Sum all completed tasks from the weekly data const totalCompletedTasks = metrics.weekly_completions.reduce((sum, completion) => sum + completion.count, 0); // Average is total completed tasks divided by 7 const averageCount = totalCompletedTasks / 7; // Calculate percentage change vs average let percentage = 0; if (averageCount > 0) { percentage = Math.round(((todayCount - averageCount) / averageCount) * 100); } else if (todayCount > 0) { // If average was 0 but today has completions, it's a 100%+ increase percentage = 100; } if (todayCount > averageCount) { return { direction: 'up', difference: Math.round((todayCount - averageCount) * 10) / 10, // Round to 1 decimal percentage: Math.abs(percentage), todayCount, averageCount: Math.round(averageCount * 10) / 10 // Round to 1 decimal }; } else if (todayCount < averageCount) { return { direction: 'down', difference: Math.round((averageCount - todayCount) * 10) / 10, // Round to 1 decimal percentage: Math.abs(percentage), todayCount, averageCount: Math.round(averageCount * 10) / 10 // Round to 1 decimal }; } else { return { direction: 'same', difference: 0, percentage: 0, todayCount, averageCount: Math.round(averageCount * 10) / 10 // Round to 1 decimal }; } }; // Track mounting state to prevent state updates after unmount const isMounted = React.useRef(false); // Function to handle next task suggestion dismissal const handleCloseNextTaskSuggestion = () => { setShowNextTaskSuggestion(false); sessionStorage.setItem('hideNextTaskSuggestion', 'true'); }; // Toggle functions for collapsible sections const toggleSuggestedCollapsed = () => { const newState = !isSuggestedCollapsed; setIsSuggestedCollapsed(newState); localStorage.setItem('suggestedTasksCollapsed', newState.toString()); }; const toggleCompletedCollapsed = () => { const newState = !isCompletedCollapsed; setIsCompletedCollapsed(newState); localStorage.setItem('completedTasksCollapsed', newState.toString()); }; // 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 productivity assistant setting const isEnabled = await getProductivityAssistantEnabled(); if (isMounted.current) { setProductivityAssistantEnabled(isEnabled); } } catch (error) { console.error("Failed to load productivity assistant setting:", error); } try { // Load next task suggestion setting const isNextTaskEnabled = await getNextTaskSuggestionEnabled(); if (isMounted.current) { setNextTaskSuggestionEnabled(isNextTaskEnabled); } } catch (error) { console.error("Failed to load next task suggestion setting:", 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) { console.error('Projects loading error:', 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); } } // Load daily quote from translations try { const response = await fetch(`/locales/${i18n.language}/quotes.json`); if (response.ok) { const data = await response.json(); if (isMounted.current && data.quotes && data.quotes.length > 0) { // Get a random quote from the translated quotes const randomIndex = Math.floor(Math.random() * data.quotes.length); setDailyQuote(data.quotes[randomIndex]); } } else { // Fallback to English if language file doesn't exist const fallbackResponse = await fetch('/locales/en/quotes.json'); if (fallbackResponse.ok) { const fallbackData = await fallbackResponse.json(); if (isMounted.current && fallbackData.quotes && fallbackData.quotes.length > 0) { const randomIndex = Math.floor(Math.random() * fallbackData.quotes.length); setDailyQuote(fallbackData.quotes[randomIndex]); } } } } catch (error) { console.error("Failed to load daily quote:", error); // Ultimate fallback if (isMounted.current) { setDailyQuote("Focus on progress, not perfection."); } } // Load user settings try { const response = await fetch('/api/profile', { credentials: 'include', }); if (response.ok) { const userData = await response.json(); if (isMounted.current) { // Parse today_settings if it's a string, or use the object directly let settings; if (userData.today_settings) { if (typeof userData.today_settings === 'string') { try { settings = JSON.parse(userData.today_settings); } catch (error) { console.error('Error parsing today_settings:', error); settings = null; } } else { settings = userData.today_settings; } } // Use parsed settings or fall back to defaults settings = settings || { showMetrics: false, showProductivity: false, showIntelligence: false, showDueToday: true, showCompleted: true, showProgressBar: true, // Always enabled showDailyQuote: true, }; // Ensure progress bar is always enabled settings.showProgressBar = true; setTodaySettings(settings); } } } catch (error) { console.error("Failed to load user settings:", error); } }; 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]); const handleToggleToday = useCallback(async (taskId: number): Promise => { if (!isMounted.current) return; try { await toggleTaskToday(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 toggling task today status:", error); if (isMounted.current) { setIsError(true); } } }, [store.tasksStore]); // Calculate today's progress for the progress bar const getTodayProgress = () => { const todayTasks = metrics.today_plan_tasks || []; const completedToday = metrics.tasks_completed_today.length; const totalTodayTasks = todayTasks.length + completedToday; return { completed: completedToday, total: totalTodayTasks, percentage: totalTodayTasks === 0 ? 0 : Math.round((completedToday / totalTodayTasks) * 100) }; }; const todayProgress = getTodayProgress(); // Handle settings change const handleSettingsChange = (newSettings: typeof todaySettings) => { setTodaySettings(newSettings); }; // 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 (
{/* Today Header with Icons on the Right */}

{t('tasks.today')}

{format(new Date(), "PPP", { locale: getLocale(i18n.language) })}
{/* Today Navigation Icons */}
setIsSettingsDropdownOpen(false)} settings={todaySettings} onSettingsChange={handleSettingsChange} />
{/* Today Progress Bar - integrated with header */} {todaySettings.showProgressBar && todayProgress.total > 0 && (
{/* Daily Quote */} {todaySettings.showDailyQuote && dailyQuote && (

{dailyQuote}

)}
)}
{/* Metrics Section - Conditionally Rendered */} {todaySettings.showMetrics && (
{/* Combined Task & Project Metrics */}

{t('dashboard.overview')}

{t('tasks.backlog')}

{metrics.total_open_tasks}

{t('tasks.inProgress')}

{metrics.tasks_in_progress_count}

{t('tasks.dueToday')}

{metrics.tasks_due_today.length}

{t('tasks.completedToday', 'Completed Today')}

{(() => { const trend = getCompletionTrend(); const getTooltipText = () => { if (trend.direction === 'same') { return t('dashboard.sameAsAverage', 'Same as average'); } else if (trend.direction === 'up') { return t('dashboard.betterThanAverage', '{{percentage}}% more than average', { percentage: trend.percentage }); } else { return t('dashboard.worseThanAverage', '{{percentage}}% less than average', { percentage: trend.percentage }); } }; return ( <> {(trend.direction === 'up' || trend.direction === 'down') && (
{trend.direction === 'up' && ( )} {trend.direction === 'down' && ( )}
{getTooltipText()}
)}

{metrics.tasks_completed_today.length}

); })()}

{t('projects.active')}

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

{/* Weekly Completion Chart */}
)} {/* Productivity Assistant - Conditionally Rendered */} {todaySettings.showProductivity && productivityAssistantEnabled && ( )} {/* Today Plan */} {/* Intelligence - Conditionally Rendered - Appears after Today Plan */} {todaySettings.showIntelligence && (
{/* Next Task Suggestion */} {nextTaskSuggestionEnabled && showNextTaskSuggestion && ( )} {/* Suggested Tasks */} {metrics.suggested_tasks.length > 0 && (

{t('tasks.suggested')}

{metrics.suggested_tasks.length} {isSuggestedCollapsed ? ( ) : ( )}
{!isSuggestedCollapsed && ( )}
)}
)} {/* Due Today Tasks - Conditionally Rendered */} {todaySettings.showDueToday && metrics.tasks_due_today.length > 0 && (

{t('tasks.dueToday')}

)} {/* Completed Tasks - Conditionally Rendered */} {todaySettings.showCompleted && metrics.tasks_completed_today.length > 0 && (

{t('tasks.completedToday')}

{metrics.tasks_completed_today.length} {isCompletedCollapsed ? ( ) : ( )}
{!isCompletedCollapsed && ( )}
)} {metrics.tasks_due_today.length === 0 && metrics.tasks_in_progress.length === 0 && metrics.suggested_tasks.length === 0 && (

{t('tasks.noTasksAvailable')}

)}
); }; export default TasksToday;