import React, { useState, useMemo } from 'react'; import { ChevronRightIcon, ChevronDownIcon, ArrowPathIcon, } from '@heroicons/react/24/outline'; import { useTranslation } from 'react-i18next'; import TaskItem from './TaskItem'; import { Project } from '../../entities/Project'; import { Task } from '../../entities/Task'; import { GroupedTasks } from '../../utils/tasksService'; interface GroupedTaskListProps { tasks: Task[]; groupedTasks?: GroupedTasks | null; onTaskUpdate: (task: Task) => Promise; onTaskCompletionToggle?: (task: Task) => void; onTaskCreate?: (task: Task) => void; onTaskDelete: (taskId: number) => void; projects: Project[]; hideProjectName?: boolean; onToggleToday?: (taskId: number) => Promise; showCompletedTasks?: boolean; searchQuery?: string; } interface TaskGroup { template: Task; instances: Task[]; } const GroupedTaskList: React.FC = ({ tasks, groupedTasks, onTaskUpdate, onTaskCompletionToggle, onTaskDelete, projects, hideProjectName = false, onToggleToday, showCompletedTasks = false, searchQuery = '', }) => { const { t } = useTranslation(); const [expandedRecurringGroups, setExpandedRecurringGroups] = useState< Set >(new Set()); // If we have day-based groupedTasks from API, use those instead of recurring groups const shouldUseDayGrouping = groupedTasks && Object.keys(groupedTasks).length > 0; // Group tasks by recurring template (legacy behavior) const { recurringGroups, standaloneTask } = useMemo(() => { if (shouldUseDayGrouping) { // For day grouping, we don't need recurring groups return { recurringGroups: [], standaloneTask: [] }; } // Filter completed tasks if needed const filteredTasks = showCompletedTasks ? tasks : tasks.filter((task) => { const isCompleted = task.status === 'done' || task.status === 'archived' || task.status === 2 || task.status === 3; return !isCompleted; }); const groups = new Map(); const standalone: Task[] = []; filteredTasks.forEach((task) => { if (task.recurring_parent_id) { // This is a recurring instance const parentId = task.recurring_parent_id; if (!groups.has(parentId)) { // Find the template task in the current results let template = filteredTasks.find((t) => t.id === parentId); // If template not found in results, create a placeholder using the instance data if (!template) { // Create a virtual template task based on the instance template = { ...task, id: parentId, recurring_parent_id: null, // This makes it the template due_date: null, // Templates don't have specific due dates name: task.name, // Keep the same name isVirtualTemplate: true, // Flag to identify virtual templates } as Task & { isVirtualTemplate?: boolean }; } groups.set(parentId, { template, instances: [] }); } const group = groups.get(parentId); if (group) { group.instances.push(task); } } else if ( task.recurrence_type && task.recurrence_type !== 'none' ) { // This is a recurring template - check if it has instances const instances = filteredTasks.filter( (t) => t.recurring_parent_id === task.id ); if (instances.length > 0) { groups.set(task.id!, { template: task, instances }); } else { // Template without instances, show as standalone standalone.push(task); } } else { // Regular task standalone.push(task); } }); return { recurringGroups: Array.from(groups.values()), standaloneTask: standalone, }; }, [tasks, showCompletedTasks, shouldUseDayGrouping]); // Filter grouped tasks for completed status and search query const filteredGroupedTasks = useMemo(() => { if (!shouldUseDayGrouping || !groupedTasks) return {}; const filtered: GroupedTasks = {}; Object.entries(groupedTasks).forEach(([groupName, groupTasks]) => { let filteredTasks = showCompletedTasks ? groupTasks : groupTasks.filter((task) => { const isCompleted = task.status === 'done' || task.status === 'archived' || task.status === 2 || task.status === 3; return !isCompleted; }); // Apply search filter if search query provided if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); filteredTasks = filteredTasks.filter( (task) => task.name.toLowerCase().includes(query) || task.note?.toLowerCase().includes(query) ); } if (filteredTasks.length > 0) { filtered[groupName] = filteredTasks; } }); return filtered; }, [groupedTasks, showCompletedTasks, shouldUseDayGrouping, searchQuery]); const toggleRecurringGroup = (templateId: number) => { setExpandedRecurringGroups((prev) => { const newSet = new Set(prev); if (newSet.has(templateId)) { newSet.delete(templateId); } else { newSet.add(templateId); } return newSet; }); }; const formatRecurrence = (recurrenceType: string) => { switch (recurrenceType) { case 'daily': return t('recurrence.daily', 'Daily'); case 'weekly': return t('recurrence.weekly', 'Weekly'); case 'monthly': return t('recurrence.monthly', 'Monthly'); default: return t('recurrence.recurring', 'Recurring'); } }; // Render day-based grouping if available if (shouldUseDayGrouping) { return (
{Object.keys(filteredGroupedTasks).length === 0 ? (

{t( 'tasks.noTasksAvailable', 'No tasks available.' )}

{t( 'tasks.blankSlateHint', 'Start by creating a new task or changing your filters.' )}

) : ( /* Responsive board layout */
{/* Mobile: Stack vertically, Desktop: Horizontal board */}
{Object.entries(filteredGroupedTasks).map( ([groupName, dayTasks]) => { return (
{/* Day column header */}

{groupName}

{/* Day column tasks */}
{dayTasks.map((task) => (
))} {/* Empty state for columns with no tasks */} {dayTasks.length === 0 && (

No tasks scheduled

)}
); } )}
)}
); } // Legacy: Render recurring task grouping return (
{/* Standalone tasks */} {standaloneTask.map((task) => (
))} {/* Grouped recurring tasks */} {recurringGroups.map((group) => { const isVirtualTemplate = (group.template as any) .isVirtualTemplate; const isExpanded = expandedRecurringGroups.has(group.template.id!) || isVirtualTemplate; // Auto-expand virtual templates return (
{/* Show template only if it's not virtual */} {!isVirtualTemplate && (
{/* Recurring instances count and expand button */} {group.instances.length > 0 && ( )}
)} {/* For virtual templates, show a simple header */} {isVirtualTemplate && group.instances.length > 0 && (
{group.template.name} -{' '} {formatRecurrence( group.template.recurrence_type! )} {group.instances.length} upcoming
)} {/* Expanded instances */} {isExpanded && group.instances.length > 0 && (
{formatRecurrence( group.template.recurrence_type! )}{' '} instances
{group.instances .sort( (a, b) => new Date( a.due_date || '' ).getTime() - new Date(b.due_date || '').getTime() ) .map((instance) => (
))}
)}
); })} {standaloneTask.length === 0 && recurringGroups.length === 0 && (

{t('tasks.noTasksAvailable', 'No tasks available.')}

{t( 'tasks.blankSlateHint', 'Start by creating a new task or changing your filters.' )}

)}
); }; export default GroupedTaskList;