import React, { useState, useEffect, useRef, useCallback } from "react"; import { PriorityType, StatusType, Task, RecurrenceType } from "../../entities/Task"; import TaskActions from "./TaskActions"; import PriorityDropdown from "../Shared/PriorityDropdown"; import StatusDropdown from "../Shared/StatusDropdown"; import ConfirmDialog from "../Shared/ConfirmDialog"; import { useToast } from "../Shared/ToastContext"; import TagInput from "../Tag/TagInput"; import RecurrenceInput from "./RecurrenceInput"; import { Project } from "../../entities/Project"; import { useStore } from "../../store/useStore"; import { fetchTags } from '../../utils/tagsService'; import { fetchTaskById } from '../../utils/tasksService'; import { getTaskIntelligenceEnabled } from '../../utils/profileService'; import { analyzeTaskName, TaskAnalysis } from '../../utils/taskIntelligenceService'; import { useTranslation } from "react-i18next"; interface TaskModalProps { isOpen: boolean; onClose: () => void; task: Task; onSave: (task: Task) => void; onDelete: (taskId: number) => void; projects: Project[]; onCreateProject: (name: string) => Promise; onEditParentTask?: (parentTask: Task) => void; } const TaskModal: React.FC = ({ isOpen, onClose, task, onSave, onDelete, projects, onCreateProject, onEditParentTask, }) => { const [formData, setFormData] = useState(task); const [tags, setTags] = useState(task.tags?.map((tag) => tag.name) || []); const [filteredProjects, setFilteredProjects] = useState(projects || []); const [newProjectName, setNewProjectName] = useState(""); const [isCreatingProject, setIsCreatingProject] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false); const modalRef = useRef(null); const [isClosing, setIsClosing] = useState(false); const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [localAvailableTags, setLocalAvailableTags] = useState>([]); const [tagsLoaded, setTagsLoaded] = useState(false); const [tagsLoading, setTagsLoading] = useState(false); const [parentTask, setParentTask] = useState(null); const [parentTaskLoading, setParentTaskLoading] = useState(false); const [taskAnalysis, setTaskAnalysis] = useState(null); const [taskIntelligenceEnabled, setTaskIntelligenceEnabled] = useState(true); const { showSuccessToast, showErrorToast } = useToast(); const { t } = useTranslation(); useEffect(() => { setFormData(task); setTags(task.tags?.map((tag) => tag.name) || []); // Analyze task name and show helper when modal opens (only if intelligence is enabled) if (isOpen && task.name && taskIntelligenceEnabled) { const analysis = analyzeTaskName(task.name); setTaskAnalysis(analysis); } else { setTaskAnalysis(null); } // Safely find the current project, handling the case where projects might be undefined const currentProject = projects?.find((project) => project.id === task.project_id); setNewProjectName(currentProject ? currentProject.name : ''); // Fetch parent task if this is a child task const fetchParentTask = async () => { if (task.recurring_parent_id && isOpen) { setParentTaskLoading(true); try { const parent = await fetchTaskById(task.recurring_parent_id); setParentTask(parent); } catch (error) { console.error('Error fetching parent task:', error); setParentTask(null); } finally { setParentTaskLoading(false); } } else { setParentTask(null); } }; fetchParentTask(); }, [task, projects, isOpen, taskIntelligenceEnabled]); // Fetch task intelligence setting when modal opens useEffect(() => { const fetchTaskIntelligenceSetting = async () => { if (isOpen) { try { const enabled = await getTaskIntelligenceEnabled(); setTaskIntelligenceEnabled(enabled); } catch (error) { console.error('Error fetching task intelligence setting:', error); setTaskIntelligenceEnabled(true); // Default to enabled } } }; fetchTaskIntelligenceSetting(); }, [isOpen]); const handleEditParent = () => { if (parentTask && onEditParentTask) { onEditParentTask(parentTask); onClose(); // Close current modal } }; const handleParentRecurrenceChange = (field: string, value: any) => { // Update the parent task data in local state if (parentTask) { setParentTask({ ...parentTask, [field]: value }); } // Also update the form data to reflect the change setFormData(prev => ({ ...prev, [field]: value, update_parent_recurrence: true })); }; useEffect(() => { const loadTags = async () => { if (isOpen && !tagsLoaded) { setTagsLoading(true); try { const fetchedTags = await fetchTags(); setLocalAvailableTags(fetchedTags); setTagsLoaded(true); } catch (error: any) { console.error("Error fetching tags:", error); setTagsLoaded(true); // Mark as loaded even on error to prevent retry loop } finally { setTagsLoading(false); } } }; // Only load tags if modal is open if (isOpen) { loadTags(); } }, [isOpen, tagsLoaded]); const getPriorityString = (priority: PriorityType | number | undefined): PriorityType => { if (typeof priority === 'number') { const priorityNames: PriorityType[] = ['low', 'medium', 'high']; return priorityNames[priority] || 'medium'; } return priority || 'medium'; }; const getStatusString = (status: StatusType | number): StatusType => { if (typeof status === 'number') { const statusNames: StatusType[] = ['not_started', 'in_progress', 'done', 'archived']; return statusNames[status] || 'not_started'; } return status; }; const handleChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); // Analyze task name in real-time (only if intelligence is enabled) if (name === 'name' && taskIntelligenceEnabled) { const analysis = analyzeTaskName(value); setTaskAnalysis(analysis); } }; const handleRecurrenceChange = (field: string, value: any) => { setFormData((prev) => ({ ...prev, [field]: value })); }; const handleTagsChange = useCallback((newTags: string[]) => { setTags(newTags); setFormData((prev) => ({ ...prev, tags: newTags.map((name) => ({ name })), })); }, []); const handleProjectSearch = (e: React.ChangeEvent) => { const query = e.target.value.toLowerCase(); setNewProjectName(query); setDropdownOpen(true); setFilteredProjects( projects.filter((project) => project.name.toLowerCase().includes(query) ) ); }; const handleProjectSelection = (project: Project) => { setFormData({ ...formData, project_id: project.id }); setNewProjectName(project.name); setDropdownOpen(false); }; const handleCreateProject = async () => { if (newProjectName.trim() !== "") { setIsCreatingProject(true); try { const newProject = await onCreateProject(newProjectName); setFormData({ ...formData, project_id: newProject.id }); setFilteredProjects([...filteredProjects, newProject]); setNewProjectName(newProject.name); setDropdownOpen(false); showSuccessToast("Project created successfully!"); } catch (error) { showErrorToast("Failed to create project."); console.error("Error creating project:", error); } finally { setIsCreatingProject(false); } } }; const handleSubmit = () => { onSave({ ...formData, tags: tags.map((tag) => ({ name: tag })) }); showSuccessToast("Task updated successfully!"); handleClose(); }; const handleDeleteClick = () => { setShowConfirmDialog(true); }; const handleDeleteConfirm = () => { if (formData.id) { onDelete(formData.id); showSuccessToast("Task deleted successfully!"); setShowConfirmDialog(false); handleClose(); } }; const handleClose = () => { setIsClosing(true); setTimeout(() => { onClose(); setIsClosing(false); setTagsLoaded(false); // Reset tags loaded state for next modal open }, 300); }; useEffect(() => { setFilteredProjects(projects || []); }, [projects]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (modalRef.current && !modalRef.current.contains(event.target as Node)) { handleClose(); } }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isOpen]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { handleClose(); } }; if (isOpen) { document.addEventListener("keydown", handleKeyDown); } return () => { document.removeEventListener("keydown", handleKeyDown); }; }, [isOpen]); if (!isOpen) return null; return ( <>
{taskAnalysis && taskAnalysis.isVague && taskIntelligenceEnabled && (

{taskAnalysis.reason === 'short' && t('task.nameHelper.short', 'Make it more descriptive!')} {taskAnalysis.reason === 'no_verb' && t('task.nameHelper.noVerb', 'Add an action verb!')} {taskAnalysis.reason === 'vague_pattern' && t('task.nameHelper.vague', 'Be more specific!')}

{taskAnalysis.suggestion && (

{t(taskAnalysis.suggestion, taskAnalysis.suggestion)}

)}
)}
tag.name) || []} availableTags={localAvailableTags} />
{dropdownOpen && newProjectName && (
{filteredProjects.length > 0 ? ( filteredProjects.map((project) => ( )) ) : (
{t('forms.task.noMatchingProjects', 'No matching projects')}
)} {newProjectName && ( )}
)}
setFormData({ ...formData, status: value }) } />
setFormData({ ...formData, priority: value }) } />
{showConfirmDialog && ( setShowConfirmDialog(false)} /> )} ); }; export default TaskModal;