import React, { useState, useRef } from 'react'; import { PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; import { useTranslation } from 'react-i18next'; import { Task } from '../../../entities/Task'; import TaskPriorityIcon from '../TaskPriorityIcon'; import { toggleTaskCompletion } from '../../../utils/tasksService'; interface TaskSubtasksSectionProps { parentTaskId: number; subtasks: Task[]; onSubtasksChange: (subtasks: Task[]) => void; onSubtaskUpdate?: (subtask: Task) => Promise; } const TaskSubtasksSection: React.FC = ({ parentTaskId, subtasks, onSubtasksChange, onSubtaskUpdate, }) => { const [newSubtaskName, setNewSubtaskName] = useState(''); const [isLoading] = useState(false); const [editingIndex, setEditingIndex] = useState(null); const [editingName, setEditingName] = useState(''); const { t } = useTranslation(); const subtasksSectionRef = useRef(null); const addInputRef = useRef(null); const scrollToBottom = () => { setTimeout(() => { // Find the modal's scrollable container const modalScrollContainer = document.querySelector( '.absolute.inset-0.overflow-y-auto' ); if (modalScrollContainer) { modalScrollContainer.scrollTo({ top: modalScrollContainer.scrollHeight, behavior: 'smooth', }); } }, 100); }; const handleCreateSubtask = () => { if (!newSubtaskName.trim()) return; const newSubtask: Task = { name: newSubtaskName.trim(), status: 'not_started', priority: 'low', today: false, parent_task_id: parentTaskId, // Set the parent task ID immediately // Mark as new for backend processing isNew: true, // Also keep for UI purposes _isNew: true, completed_at: null, } as Task; onSubtasksChange([...subtasks, newSubtask]); setNewSubtaskName(''); // Only scroll when adding new subtask, not when toggling completion scrollToBottom(); }; const handleDeleteSubtask = (index: number) => { const updatedSubtasks = subtasks.filter((_, i) => i !== index); onSubtasksChange(updatedSubtasks); }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); handleCreateSubtask(); } }; const handleEditSubtask = (index: number) => { setEditingIndex(index); setEditingName(subtasks[index].name); }; const handleSaveEdit = () => { if (!editingName.trim() || editingIndex === null) return; const updatedSubtasks = subtasks.map((subtask, index) => { if (index === editingIndex) { const isNameChanged = subtask.name !== editingName.trim(); const isNew = (subtask as any)._isNew || (subtask as any).isNew || false; const isEdited = !isNew && isNameChanged; return { ...subtask, name: editingName.trim(), // Backend flags isNew: isNew, isEdited: isEdited, // UI flags _isNew: isNew, _isEdited: isEdited, }; } return subtask; }); onSubtasksChange(updatedSubtasks); setEditingIndex(null); setEditingName(''); }; const handleCancelEdit = () => { setEditingIndex(null); setEditingName(''); }; const handleToggleNewSubtaskCompletion = (index: number) => { const updatedSubtasks = subtasks.map((subtask, i) => { if (i === index) { const isDone = subtask.status === 'done' || subtask.status === 2; const newStatus = isDone ? ('not_started' as const) : ('done' as const); const hasId = subtask.id && !((subtask as any)._isNew || (subtask as any).isNew); return { ...subtask, status: newStatus, completed_at: isDone ? null : new Date().toISOString(), // Mark for backend update if it has an ID (existing subtask) _statusChanged: hasId, }; } return subtask; }); onSubtasksChange(updatedSubtasks); }; return (
{/* Existing Subtasks */} {isLoading ? (
{t('loading.subtasks', 'Loading subtasks...')}
) : subtasks.length > 0 ? (
{subtasks.map((subtask, index) => (
{editingIndex === index ? (
{ if ( subtask.id && onSubtaskUpdate && !( (subtask as any) ._isNew || (subtask as any).isNew ) ) { // Existing subtask - use API for immediate toggle, then update callback try { const updatedSubtask = await toggleTaskCompletion( subtask.id ); await onSubtaskUpdate( updatedSubtask ); } catch (error) { console.error( 'Error toggling subtask completion:', error ); } } else { // New subtask or no callback - handle locally handleToggleNewSubtaskCompletion( index ); } }} />
setEditingName(e.target.value) } onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleSaveEdit(); } else if (e.key === 'Escape') { handleCancelEdit(); } }} onBlur={handleSaveEdit} className="flex-1 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-600 dark:text-white overflow-hidden" autoFocus />
) : (
{ if ( subtask.id && onSubtaskUpdate && !( (subtask as any) ._isNew || (subtask as any) .isNew ) ) { // Existing subtask - use API for immediate toggle, then update callback try { const updatedSubtask = await toggleTaskCompletion( subtask.id ); await onSubtaskUpdate( updatedSubtask ); } catch (error) { console.error( 'Error toggling subtask completion:', error ); } } else { // New subtask or no callback - handle locally handleToggleNewSubtaskCompletion( index ); } }} />
handleEditSubtask(index) } title={t( 'actions.clickToEdit', 'Click to edit' )} > {subtask.name} {(subtask as any)._isNew && ( (new) )} {(subtask as any)._isEdited && ( (edited) )}
)}
))}
) : (
{t('subtasks.noSubtasks', 'No subtasks yet')}
)} {/* Add New Subtask */}
setNewSubtaskName(e.target.value)} onKeyDown={handleKeyPress} placeholder={t('subtasks.placeholder', 'Add a subtask...')} className="flex-1 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white overflow-hidden" />
); }; export default TaskSubtasksSection;