import React, { useState, useEffect } from 'react'; import { Task } from '../../entities/Task'; import { Project } from '../../entities/Project'; import TaskHeader from './TaskHeader'; // Import SubtasksDisplay component from TaskHeader interface SubtasksDisplayProps { showSubtasks: boolean; loadingSubtasks: boolean; subtasks: Task[]; onTaskClick: (e: React.MouseEvent, task: Task) => void; onTaskUpdate: (task: Task) => Promise; loadSubtasks: () => Promise; } const SubtasksDisplay: React.FC = ({ showSubtasks, loadingSubtasks, subtasks, onTaskClick, onTaskUpdate, loadSubtasks, }) => { const { t } = useTranslation(); if (!showSubtasks) return null; return (
{loadingSubtasks ? (
{t('loading.subtasks', 'Loading subtasks...')}
) : subtasks.length > 0 ? ( subtasks.map((subtask) => (
{ e.stopPropagation(); onTaskClick(e, subtask); }} >
{subtask.status === 'done' || subtask.status === 2 || subtask.status === 'archived' || subtask.status === 3 ? (
{ e.stopPropagation(); if (subtask.id) { try { const updatedSubtask = await toggleTaskCompletion( subtask.id ); await onTaskUpdate( updatedSubtask ); // Refresh subtasks to show updated status await loadSubtasks(); } catch (error) { console.error( 'Error toggling subtask completion:', error ); } } }} >
) : (
{ e.stopPropagation(); if (subtask.id) { try { const updatedSubtask = await toggleTaskCompletion( subtask.id ); await onTaskUpdate( updatedSubtask ); // Refresh subtasks to show updated status await loadSubtasks(); } catch (error) { console.error( 'Error toggling subtask completion:', error ); } } }} /> )} {subtask.name}
{/* Right side status indicators removed */}
)) ) : (
{t('subtasks.noSubtasks', 'No subtasks found')}
)}
); }; import TaskModal from './TaskModal'; import { toggleTaskCompletion, fetchSubtasks, fetchTaskById, } from '../../utils/tasksService'; import { isTaskOverdue } from '../../utils/dateUtils'; import { useTranslation } from 'react-i18next'; interface TaskItemProps { task: Task; onTaskUpdate: (task: Task) => Promise; onTaskDelete: (taskId: number) => void; projects: Project[]; hideProjectName?: boolean; onToggleToday?: (taskId: number) => Promise; } const TaskItem: React.FC = ({ task, onTaskUpdate, onTaskDelete, projects, hideProjectName = false, onToggleToday, }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [subtaskModalOpen, setSubtaskModalOpen] = useState(false); const [selectedSubtask, setSelectedSubtask] = useState(null); const [projectList, setProjectList] = useState(projects); const [parentTaskModalOpen, setParentTaskModalOpen] = useState(false); const [parentTask, setParentTask] = useState(null); // Subtasks state const [showSubtasks, setShowSubtasks] = useState(false); const [subtasks, setSubtasks] = useState([]); const [loadingSubtasks, setLoadingSubtasks] = useState(false); const [hasSubtasks, setHasSubtasks] = useState(false); // Calculate completion percentage const calculateCompletionPercentage = () => { if (subtasks.length === 0) return 0; const completedCount = subtasks.filter( (subtask) => subtask.status === 'done' || subtask.status === 2 || subtask.status === 'archived' || subtask.status === 3 ).length; return Math.round((completedCount / subtasks.length) * 100); }; const completionPercentage = calculateCompletionPercentage(); // Helper function to check if task has subtasks const checkSubtasks = async () => { if (!task.id) return; try { const subtasksData = await fetchSubtasks(task.id); setHasSubtasks(subtasksData.length > 0); } catch (error) { console.error('Error fetching subtasks:', error); setHasSubtasks(false); } }; // Check if task has subtasks on mount useEffect(() => { checkSubtasks(); }, [task.id]); const loadSubtasks = async () => { if (!task.id) return; setLoadingSubtasks(true); try { const subtasksData = await fetchSubtasks(task.id); setSubtasks(subtasksData); } catch (error) { console.error('Failed to load subtasks:', error); setSubtasks([]); } finally { setLoadingSubtasks(false); } }; // Reload subtasks when showSubtasks changes to true useEffect(() => { if (showSubtasks && subtasks.length === 0) { loadSubtasks(); } }, [showSubtasks, subtasks.length]); const handleSubtasksToggle = async (e: React.MouseEvent) => { e.stopPropagation(); if (!showSubtasks && subtasks.length === 0) { await loadSubtasks(); } setShowSubtasks(!showSubtasks); }; const handleTaskClick = () => { setIsModalOpen(true); }; const handleSubtaskClick = async (subtask: Task) => { // If subtask has a parent_task_id, open the parent task with subtasks focus if (subtask.parent_task_id) { try { const parentTask = await fetchTaskById(subtask.parent_task_id); setParentTask(parentTask); setParentTaskModalOpen(true); } catch (error) { console.error('Error fetching parent task:', error); // Fall back to opening the subtask itself setSelectedSubtask(subtask); setSubtaskModalOpen(true); } } else { // If no parent_task_id, open the subtask itself setSelectedSubtask(subtask); setSubtaskModalOpen(true); } }; const handleSubtaskSave = async (updatedSubtask: Task) => { await onTaskUpdate(updatedSubtask); setSubtaskModalOpen(false); setSelectedSubtask(null); // Refresh subtasks check after saving await checkSubtasks(); }; const handleSubtaskDelete = async () => { if (selectedSubtask && selectedSubtask.id) { await onTaskDelete(selectedSubtask.id); setSubtaskModalOpen(false); setSelectedSubtask(null); // Refresh subtasks check after deleting await checkSubtasks(); } }; const handleParentTaskSave = async (updatedParentTask: Task) => { await onTaskUpdate(updatedParentTask); setParentTaskModalOpen(false); setParentTask(null); // Refresh subtasks check after saving await checkSubtasks(); }; const handleParentTaskDelete = async () => { if (parentTask && parentTask.id) { await onTaskDelete(parentTask.id); setParentTaskModalOpen(false); setParentTask(null); // Refresh subtasks check after deleting await checkSubtasks(); } }; const handleSave = async (updatedTask: Task) => { await onTaskUpdate(updatedTask); setIsModalOpen(false); // Refresh subtasks check after saving await checkSubtasks(); }; const handleDelete = async () => { if (task.id) { await onTaskDelete(task.id); } }; const handleToggleCompletion = async () => { if (task.id) { try { const updatedTask = await toggleTaskCompletion(task.id); await onTaskUpdate(updatedTask); } catch (error) { console.error('Error toggling task completion:', error); } } }; const handleCreateProject = async (name: string): Promise => { try { const response = await fetch('/api/project', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name, active: true }), }); if (!response.ok) { throw new Error('Failed to create project'); } const newProject = await response.json(); setProjectList((prevProjects) => [...prevProjects, newProject]); return newProject; } catch (error) { console.error('Error creating project:', error); throw error; } }; // Use the project from the task's included data if available, otherwise find from projectList let project = task.Project || projectList.find((p) => p.id === task.project_id); // If project exists but doesn't have an ID, add the ID from task.project_id if (project && !project.id && task.project_id) { project = { ...project, id: task.project_id }; } // Check if task is in progress to apply pulsing border animation const isInProgress = task.status === 'in_progress' || task.status === 1; // Check if task is overdue (created yesterday or earlier and not completed) const isOverdue = isTaskOverdue(task); return ( <>
{/* Progress bar at bottom of parent task */} {subtasks.length > 0 && (
)}
{/* Hide subtasks display for archived tasks */} {!(task.status === 'archived' || task.status === 3) && ( { e.stopPropagation(); handleSubtaskClick(task); }} onTaskUpdate={onTaskUpdate} loadSubtasks={loadSubtasks} /> )} setIsModalOpen(false)} task={task} onSave={handleSave} onDelete={handleDelete} projects={projectList} onCreateProject={handleCreateProject} /> {selectedSubtask && ( { setSubtaskModalOpen(false); setSelectedSubtask(null); }} task={selectedSubtask} onSave={handleSubtaskSave} onDelete={handleSubtaskDelete} projects={projectList} onCreateProject={handleCreateProject} /> )} {parentTask && ( { setParentTaskModalOpen(false); setParentTask(null); }} task={parentTask} onSave={handleParentTaskSave} onDelete={handleParentTaskDelete} projects={projectList} onCreateProject={handleCreateProject} autoFocusSubtasks={true} /> )} ); }; export default TaskItem;