import React, { useEffect, useState } from 'react'; import { useParams, Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { PencilSquareIcon, TrashIcon, TagIcon, FolderIcon, CalendarIcon, ExclamationTriangleIcon, ArrowPathIcon, ListBulletIcon, } from '@heroicons/react/24/outline'; import ConfirmDialog from '../Shared/ConfirmDialog'; import TaskModal from './TaskModal'; import { Task } from '../../entities/Task'; import { Project } from '../../entities/Project'; import { fetchTaskByUuid, updateTask, deleteTask, fetchSubtasks, toggleTaskCompletion, } from '../../utils/tasksService'; import { createProject } from '../../utils/projectsService'; import { useStore } from '../../store/useStore'; import { useToast } from '../Shared/ToastContext'; import TaskPriorityIcon from './TaskPriorityIcon'; import LoadingScreen from '../Shared/LoadingScreen'; import MarkdownRenderer from '../Shared/MarkdownRenderer'; import TaskTimeline from './TaskTimeline'; const TaskDetails: React.FC = () => { const { uuid } = useParams<{ uuid: string }>(); const navigate = useNavigate(); const { t } = useTranslation(); const { showSuccessToast, showErrorToast } = useToast(); const projects = useStore((state) => state.projectsStore.projects); // Local state const [task, setTask] = useState(null); const [subtasks, setSubtasks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isTaskModalOpen, setIsTaskModalOpen] = useState(false); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [taskToDelete, setTaskToDelete] = useState(null); // Date and recurrence formatting functions (from TaskHeader) const formatDueDate = (dueDate: string) => { const today = new Date().toISOString().split('T')[0]; const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000) .toISOString() .split('T')[0]; const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000) .toISOString() .split('T')[0]; if (dueDate === today) return t('dateIndicators.today', 'TODAY'); if (dueDate === tomorrow) return t('dateIndicators.tomorrow', 'TOMORROW'); if (dueDate === yesterday) return t('dateIndicators.yesterday', 'YESTERDAY'); return new Date(dueDate).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', }); }; 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'); case 'monthly_weekday': return t('recurrence.monthlyWeekday', 'Monthly'); case 'monthly_last_day': return t('recurrence.monthlyLastDay', 'Monthly'); default: return t('recurrence.recurring', 'Recurring'); } }; useEffect(() => { const fetchTaskData = async () => { if (!uuid) { setError('No task UUID provided'); setLoading(false); return; } try { setLoading(true); const taskData = await fetchTaskByUuid(uuid); setTask(taskData); // Load subtasks if this task has any if (taskData.id) { try { const subtasksData = await fetchSubtasks(taskData.id); setSubtasks(subtasksData); } catch (subtaskError) { console.warn('Error loading subtasks:', subtaskError); // Don't fail the whole page if subtasks fail to load } } } catch (fetchError) { setError('Task not found'); console.error('Error fetching task:', fetchError); } finally { setLoading(false); } }; fetchTaskData(); }, [uuid]); const handleEdit = () => { setIsTaskModalOpen(true); }; const handleToggleCompletion = async () => { if (!task?.id) return; try { const updatedTask = await toggleTaskCompletion(task.id); setTask(updatedTask); const statusMessage = updatedTask.status === 'done' || updatedTask.status === 2 ? t('task.completedSuccess', 'Task marked as completed') : t('task.reopenedSuccess', 'Task reopened'); showSuccessToast(statusMessage); } catch (error) { console.error('Error toggling task completion:', error); showErrorToast( t('task.toggleError', 'Failed to update task status') ); } }; const handleTaskUpdate = async (updatedTask: Task) => { try { if (task?.id) { const updated = await updateTask(task.id, updatedTask); setTask(updated); showSuccessToast( t('task.updateSuccess', 'Task updated successfully') ); // Reload subtasks in case they changed if (updated.id) { try { const subtasksData = await fetchSubtasks(updated.id); setSubtasks(subtasksData); } catch (subtaskError) { console.warn('Error reloading subtasks:', subtaskError); } } } setIsTaskModalOpen(false); } catch (error) { console.error('Error updating task:', error); showErrorToast(t('task.updateError', 'Failed to update task')); } }; const handleDeleteClick = () => { if (task) { setTaskToDelete(task); setIsConfirmDialogOpen(true); } }; const handleDeleteConfirm = async () => { if (taskToDelete?.id) { try { await deleteTask(taskToDelete.id); showSuccessToast( t('task.deleteSuccess', 'Task deleted successfully') ); navigate('/today'); // Navigate back to today view after deletion } catch (error) { console.error('Error deleting task:', error); showErrorToast(t('task.deleteError', 'Failed to delete task')); } } setIsConfirmDialogOpen(false); setTaskToDelete(null); }; const handleCreateProject = async (name: string): Promise => { try { return await createProject({ name }); } catch (error) { console.error('Error creating project:', error); throw error; } }; const handleSubtaskClick = (subtask: Task) => { if (subtask.uuid) { navigate(`/task/${subtask.uuid}`); } }; if (loading) { return ; } if (error || !task) { return (

{error || t('task.notFound', 'Task Not Found')}

{t( 'task.notFoundDescription', 'The task you are looking for does not exist or has been deleted.' )}

); } return (
{/* Header Section with Title and Action Buttons */}

{task.name}

{/* Project, tags, due date, and recurrence under title */} {(task.Project || (task.tags && task.tags.length > 0) || task.due_date || (task.recurrence_type && task.recurrence_type !== 'none')) && (
{task.Project && (
{task.Project.name}
)} {task.Project && task.tags && task.tags.length > 0 && ( )} {task.tags && task.tags.length > 0 && (
{task.tags.map((tag, index) => ( {tag.name} {index < task.tags!.length - 1 && ', '} ))}
)} {(task.Project || (task.tags && task.tags.length > 0)) && task.due_date && ( )} {task.due_date && (
{formatDueDate(task.due_date)}
)} {(task.Project || (task.tags && task.tags.length > 0) || task.due_date) && task.recurrence_type && task.recurrence_type !== 'none' && ( )} {task.recurrence_type && task.recurrence_type !== 'none' && (
{formatRecurrence( task.recurrence_type )}
)}
)}
{/* Content - Two column layout */}
{/* Left Column - Notes and Subtasks */}
{/* Notes Section - Always Visible */}

{t('task.notes', 'Notes')}

{task.note ? (
) : (
{t('task.noNotes', 'No notes added yet')}
)}
{/* Subtasks Section - Always Visible */}

{t('task.subtasks', 'Subtasks')}

{subtasks.length > 0 ? (
{subtasks.map((subtask) => (
handleSubtaskClick( subtask ) } className={`rounded-lg shadow-sm bg-white dark:bg-gray-900 border-2 cursor-pointer transition-all duration-200 ${ subtask.status === 'in_progress' || subtask.status === 1 ? 'border-green-400/60 dark:border-green-500/60' : 'border-gray-50 dark:border-gray-800' }`} >
{ if ( subtask.id ) { try { await toggleTaskCompletion( subtask.id ); // Reload subtasks after toggling completion if ( task?.id ) { const subtasksData = await fetchSubtasks( task.id ); setSubtasks( subtasksData ); } } catch (error) { console.error( 'Error toggling subtask completion:', error ); } } }} />
{subtask.name}
))}
) : (
{t('task.noSubtasks', 'No subtasks yet')}
)}
{/* Right Column - Recent Activity */}

{t('task.recentActivity', 'Recent Activity')}

{/* End of main content sections */} {/* Task Modal for Editing */} setIsTaskModalOpen(false)} onSave={handleTaskUpdate} onDelete={async (taskId: number) => { await deleteTask(taskId); navigate('/today'); }} projects={projects} onCreateProject={handleCreateProject} showToast={false} initialSubtasks={subtasks} /> {/* Confirm Delete Dialog */} {isConfirmDialogOpen && taskToDelete && ( { setIsConfirmDialogOpen(false); setTaskToDelete(null); }} /> )}
); }; export default TaskDetails;