import React, { useEffect, useState, useRef } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import TaskList from "./components/Task/TaskList"; import NewTask from "./NewTask"; import { Task } from "./entities/Task"; import { Project } from "./entities/Project"; import { getTitleAndIcon } from "./components/Task/getTitleAndIcon"; import { getDescription } from "./components/Task/getDescription"; import { TagIcon, XMarkIcon } from "@heroicons/react/24/solid"; // Import X icon for removing tag // Helper function to capitalize the first letter of a string const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); const Tasks: React.FC = () => { 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"); // State for sorting const dropdownRef = useRef(null); // Reference to the dropdown 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); // Extract tag from query params const tag = query.get("tag"); // Load orderBy from localStorage or use default 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); // Set the default to URL if not present navigate({ pathname: location.pathname, search: `?${params.toString()}`, }); } }, [location, navigate]); // Close dropdown if clicking outside 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]); // Fetch data when location changes useEffect(() => { const fetchData = async () => { setLoading(true); setError(null); try { // Fetch tasks with the selected tag if present 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 || []); } 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]); // Function to remove the tag from the URL const handleRemoveTag = () => { const params = new URLSearchParams(location.search); params.delete("tag"); // Remove tag from query params navigate({ pathname: location.pathname, search: `?${params.toString()}`, // Update the URL without the tag parameter }); }; // Function to create a new task const handleTaskCreate = async (taskData: Partial) => { try { const response = await fetch("/api/task", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(taskData), }); if (response.ok) { const newTask = await response.json(); setTasks((prevTasks) => [newTask, ...prevTasks]); } else { const errorData = await response.json(); console.error("Failed to create task:", errorData.error); setError("Failed to create task."); } } catch (error) { console.error("Error creating task:", error); setError("Error creating task."); } }; // Function to update an existing task 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 ) ); } } catch (error) { console.error("Error updating task:", error); setError("Error updating task."); } }; // Function to delete a 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."); } }; // Handle sorting changes const handleSortChange = (order: string) => { setOrderBy(order); localStorage.setItem("order_by", order); // Save the selected order to localStorage const params = new URLSearchParams(location.search); params.set("order_by", order); // Update or add the order_by param navigate({ pathname: location.pathname, search: `?${params.toString()}`, }); setDropdownOpen(false); // Close dropdown on selection }; // Get the description for the current task view const description = getDescription(query, projects); return (
{" "} {/* Center the content with padding */}
{" "} {/* Limit the width to 3xl (48rem) */} {/* Title and Icon */}

{title}

{/* If tag exists, display it as a styled button with an X to remove */} {tag && (
)}
{/* Sort Dropdown */}
{dropdownOpen && (
{[ "due_date:asc", "name:asc", "priority:desc", "status:desc", "created_at:desc", ].map((order) => ( ))}
)}
{/* Description */}

{description}

{loading ? (

Loading...

) : error ? (

{error}

) : ( <> {/* New Task Form */} handleTaskCreate({ name: taskName, status: "not_started" }) } /> {/* Task List */} {tasks.length > 0 ? ( ) : (

No tasks available.

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