flowstate/app/(app)/dashboard/modal/TaskModal.tsx
Chneemann 68183b770e
All checks were successful
Deploy Flowstate to VPS / deploy (push) Successful in 49s
refactor(dashboard): split task modal into modular TaskModalContent component
2026-08-26 09:28:58 +02:00

127 lines
4.6 KiB
TypeScript

/**
* @file app/(app)/dashboard/modal/TaskModal.tsx
* @description Client component orchestrating the task modal overlay, history sync, and action buttons.
*/
"use client";
import { useEffect } from "react";
import { Task } from "@/lib/types/task";
import { useRouter } from "next/navigation";
import { Pencil, Trash2 } from "lucide-react";
import TaskModalContent from "./TaskModalContent";
/**
* 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 the task detail modal container with URL state synchronization, keyboard event handling, and action triggers.
*
* @param {TaskModalProps} props - The component props.
* @returns {JSX.Element | null} The rendered modal overlay or null when no task is selected.
*/
export default function TaskModal({ task, onClose, onDelete }: TaskModalProps) {
const router = useRouter();
useEffect(() => {
/**
* Handles keyboard events to close the modal when the Escape key is pressed.
*
* @param {KeyboardEvent} e - The keyboard event instance.
*/
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") handleClose();
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
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 browser history without triggering Next.js routing, then executes the onClose callback.
*/
const handleClose = () => {
const params = new URLSearchParams(window.location.search);
params.delete("modal");
const query = params.toString();
window.history.replaceState(
{},
"",
query ? `${window.location.pathname}?${query}` : window.location.pathname,
);
onClose();
};
if (!task) return null;
return (
<div
className="fixed inset-0 z-49 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-in fade-in duration-300"
onClick={handleClose}
>
<div
className="bg-card border border-border rounded-3xl max-w-xl w-full max-h-[80vh] md:max-h-[90vh] shadow-2xl relative flex flex-col animate-in zoom-in-95 duration-200 overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
{/* Status Indikator */}
<div
className={`absolute top-0 left-0 right-0 h-1 z-10 ${task.status === "done" ? "bg-emerald-500" : task.priority === "high" ? "bg-destructive" : "bg-linear-to-r from-primary to-accent"}`}
/>
{/* Content */}
<TaskModalContent task={task} onClose={handleClose} />
{/* Footer Actions */}
{task.isCreator && (
<div className="flex items-center justify-end gap-3 px-8 py-4 bg-card border-t border-border/80 shrink-0">
<button
onClick={() => {
onDelete?.(task.id);
handleClose();
}}
className="inline-flex items-center space-x-2 px-4 py-2.5 text-sm font-medium bg-destructive-bg text-destructive border border-destructive-border hover:bg-destructive/20 rounded-xl transition-all cursor-pointer"
>
<Trash2 className="w-4 h-4" />
<span>Delete</span>
</button>
<button
onClick={() => router.push(`/tasks?task=edit&id=${task.id}`)}
className="inline-flex items-center space-x-2 px-5 py-2.5 text-sm font-semibold bg-primary text-background hover:bg-primary-hover rounded-xl transition-all shadow-lg shadow-primary/10 cursor-pointer"
>
<Pencil className="w-4 h-4" />
<span>Edit Task</span>
</button>
</div>
)}
</div>
</div>
);
}