/** * @file app/(app)/dashboard/modal/TaskModal.tsx * @description Client component rendering a detailed modal overlay for viewing task metadata, status, assignees, and quick actions with ESC key support. */ "use client"; import { useEffect } from "react"; import { Task, PRIORITY_CONFIG } from "@/lib/types/task"; import { getFullName, getStatusColor } from "@/lib/utils/user"; import { X, CalendarDays, AlertCircle, User, Users, FileText, Pencil, Trash2, } from "lucide-react"; import { useRouter } from "next/navigation"; /** * Properties for the TaskModal component. * * @interface TaskModalProps * @property {Task | null} task - The selected task object to display, or null if hidden. * @property {() => void} onClose - Callback handler to close the modal dialog. * @property {(taskId: string) => void} [onDelete] - Optional callback function triggered when deleting the task. */ interface TaskModalProps { task: Task | null; onClose: () => void; onDelete?: (taskId: string) => void; } /** * Renders a full task detail modal with status indicators, priority details, description, assignees, and creator-only action buttons. * Supports ESC key navigation and updates URL query parameters dynamically. * * @param {TaskModalProps} props - The component props. * @returns {JSX.Element | null} The rendered modal component or null when no task is selected. */ export default function TaskModal({ task, onClose, onDelete }: TaskModalProps) { const router = useRouter(); useEffect(() => { /** * Attaches a global keydown event listener to close the modal when the Escape key is pressed. * * @param {KeyboardEvent} e - The keyboard event object. */ const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { handleClose(); } }; window.addEventListener("keydown", handleKeyDown); return () => { window.removeEventListener("keydown", handleKeyDown); }; }, [onClose]); useEffect(() => { /** * Updates the URL search parameters to include `modal=task-detail` when a task is selected, * maintaining modal state in the browser history without triggering a full page re-render. */ if (task) { const params = new URLSearchParams(window.location.search); if (params.get("modal") !== "task-detail") { params.set("modal", "task-detail"); window.history.replaceState( {}, "", `${window.location.pathname}?${params.toString()}`, ); } } }, [task]); /** * Removes modal query parameters from the browser location history without causing a Next.js soft navigation, then triggers onClose. */ const handleClose = () => { const params = new URLSearchParams(window.location.search); params.delete("modal"); const newQuery = params.toString(); window.history.replaceState( {}, "", newQuery ? `${window.location.pathname}?${newQuery}` : window.location.pathname, ); onClose(); }; if (!task) return null; const priorityConfig = task.priority && PRIORITY_CONFIG[task.priority]; const isOverdue = task.dueDate && new Date(task.dueDate) < new Date() && task.status !== "done"; return (
e.stopPropagation()} > {/* Dynamic Status Indicator Strip */}
{/* --- SCROLLABLE CONTENT AREA --- */}
{/* Header / Status, Priority & Title */}
{task.status} {priorityConfig && ( {priorityConfig.label} )}

{task.title}

{/* Description Section */}
Description
{task.description || "No description provided for this task."}
{/* Meta Grid (Creator & Due Date) */}
{/* Creator */}

Creator

{getFullName(task.creator.firstName, task.creator.lastName)}

{/* Due Date */}
{isOverdue ? ( ) : ( )}

{isOverdue ? "Overdue Due Date" : "Due Date"}

{task.dueDate ? `${new Date(task.dueDate).toLocaleDateString("de-DE", { day: "2-digit", month: "short", year: "numeric", })} (${new Date(task.dueDate).toLocaleTimeString( "de-DE", { hour: "2-digit", minute: "2-digit", }, )})` : "No due date"}

{/* Assignees Section */}
Assignees ({task.assignees.length})
{task.assignees.length > 0 ? ( task.assignees.map((assignee) => (
{getFullName(assignee.firstName, assignee.lastName)}
)) ) : (

No assignees assigned to this task.

)}
{/* Footer Actions */} {task.isCreator && (
)}
); }