import React, { useEffect, useState, useRef } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import TaskList from "./Task/TaskList"; import NewTask from "./Task/NewTask"; import { Task } from "../entities/Task"; import { Project } from "../entities/Project"; import { getTitleAndIcon } from "./Task/getTitleAndIcon"; import { getDescription } from "./Task/getDescription"; import { createTask, toggleTaskToday } from "../utils/tasksService"; import { useToast } from "./Shared/ToastContext"; import { TagIcon, XMarkIcon, ChevronDownIcon, ChevronDoubleDownIcon, MagnifyingGlassIcon, } from "@heroicons/react/24/solid"; const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); // Helper function to get search placeholder by language const getSearchPlaceholder = (language: string): string => { const placeholders: Record = { en: 'Search tasks...', el: 'Αναζήτηση εργασιών...', es: 'Buscar tareas...', de: 'Aufgaben suchen...', jp: 'タスクを検索...', ua: 'Пошук завдань...' }; return placeholders[language] || 'Search tasks...'; }; const Tasks: React.FC = () => { const { t, i18n } = useTranslation(); const { showSuccessToast } = useToast(); const [tasks, setTasks] = useState([]); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [dropdownOpen, setDropdownOpen] = useState(false); const [orderBy, setOrderBy] = useState("due_date:asc"); const [taskSearchQuery, setTaskSearchQuery] = useState(""); const dropdownRef = useRef(null); const location = useLocation(); const navigate = useNavigate(); const query = new URLSearchParams(location.search); const { title: stateTitle, icon: stateIcon } = location.state || {}; const { title, icon } = stateTitle && stateIcon ? { title: stateTitle, icon: stateIcon } : getTitleAndIcon(query, projects, t); const IconComponent = typeof icon === "string" ? React.createElement(icon) : icon; const tag = query.get("tag"); const status = query.get("status"); useEffect(() => { const savedOrderBy = localStorage.getItem("order_by") || "due_date:asc"; setOrderBy(savedOrderBy); const params = new URLSearchParams(location.search); if (!params.get("order_by")) { params.set("order_by", savedOrderBy); navigate({ pathname: location.pathname, search: `?${params.toString()}`, }, { replace: true }); } }, [location.pathname]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setDropdownOpen(false); } }; if (dropdownOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [dropdownOpen]); useEffect(() => { const fetchData = async () => { setLoading(true); setError(null); try { const tagId = query.get("tag"); const [tasksResponse, projectsResponse] = await Promise.all([ fetch(`/api/tasks${location.search}${tagId ? `&tag=${tagId}` : ""}`), fetch("/api/projects"), ]); if (tasksResponse.ok) { const tasksData = await tasksResponse.json(); setTasks(tasksData.tasks || []); } else { throw new Error("Failed to fetch tasks."); } if (projectsResponse.ok) { const projectsData = await projectsResponse.json(); setProjects(projectsData?.projects || []); } else { throw new Error("Failed to fetch projects."); } } catch (error) { setError((error as Error).message); } finally { setLoading(false); } }; fetchData(); }, [location]); const handleRemoveTag = () => { const params = new URLSearchParams(location.search); params.delete("tag"); navigate({ pathname: location.pathname, search: `?${params.toString()}`, }); }; const handleTaskCreate = async (taskData: Partial) => { try { const newTask = await createTask(taskData as Task); // Add the new task optimistically to avoid race conditions setTasks((prevTasks) => [newTask, ...prevTasks]); // Show success toast with task link const taskLink = ( {t('task.created', 'Task')} {newTask.name} {t('task.createdSuccessfully', 'created successfully!')} ); showSuccessToast(taskLink); } catch (error) { console.error("Error creating task:", error); setError("Error creating task."); throw error; // Re-throw to allow proper error handling } }; const handleTaskUpdate = async (updatedTask: Task) => { try { const response = await fetch(`/api/task/${updatedTask.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(updatedTask), }); if (response.ok) { setTasks((prevTasks) => prevTasks.map((task) => task.id === updatedTask.id ? updatedTask : task ) ); } else { const errorData = await response.json(); console.error("Failed to update task:", errorData.error); setError("Failed to update task."); } } catch (error) { console.error("Error updating task:", error); setError("Error updating task."); } }; const handleTaskDelete = async (taskId: number) => { try { const response = await fetch(`/api/task/${taskId}`, { method: "DELETE", }); if (response.ok) { setTasks((prevTasks) => prevTasks.filter((task) => task.id !== taskId)); } else { const errorData = await response.json(); console.error("Failed to delete task:", errorData.error); setError("Failed to delete task."); } } catch (error) { console.error("Error deleting task:", error); setError("Error deleting task."); } }; const handleToggleToday = async (taskId: number): Promise => { try { const updatedTask = await toggleTaskToday(taskId); setTasks((prevTasks) => prevTasks.map((task) => task.id === taskId ? updatedTask : task ) ); } catch (error) { console.error("Error toggling task today status:", error); setError("Error toggling task today status."); } }; const handleSortChange = (order: string) => { setOrderBy(order); localStorage.setItem("order_by", order); const params = new URLSearchParams(location.search); params.set("order_by", order); navigate({ pathname: location.pathname, search: `?${params.toString()}`, }, { replace: true }); setDropdownOpen(false); }; const description = getDescription(query, projects, t); const isNewTaskAllowed = () => { return status !== "done"; }; const filteredTasks = tasks.filter((task) => task.name.toLowerCase().includes(taskSearchQuery.toLowerCase()) ); return (
{IconComponent && }

{title}

{tag && (
)}
{dropdownOpen && (
{[ "due_date:asc", "name:asc", "priority:desc", "status:desc", "created_at:desc", ].map((order) => ( ))}
)}

{description}

setTaskSearchQuery(e.target.value)} className="w-full bg-transparent border-none focus:ring-0 focus:outline-none dark:text-white" />
{loading ? (

{t('common.loading', 'Loading...')}

) : error ? (

{error}

) : ( <> {/* New Task Form */} {isNewTaskAllowed() && ( await handleTaskCreate({ name: taskName, status: "not_started" }) } /> )} {filteredTasks.length > 0 ? ( ) : (

{t('tasks.noTasksAvailable', 'Δεν υπάρχουν διαθέσιμες εργασίες.')}

)} )}
); }; export default Tasks;