diff --git a/app/(app)/dashboard/KanbanBoard.tsx b/app/(app)/dashboard/KanbanBoard.tsx index 08be5e8..baed71d 100644 --- a/app/(app)/dashboard/KanbanBoard.tsx +++ b/app/(app)/dashboard/KanbanBoard.tsx @@ -1,6 +1,6 @@ /** * @file dashboard/KanbanBoard.tsx - * @description Client component wrapping the kanban columns grid, tracking individual task update states, and handling asynchronous status mutations via API. + * @description Client component wrapping the kanban columns grid, tracking individual task update/deletion states, and handling asynchronous mutations via API. */ "use client"; @@ -13,7 +13,7 @@ import KanbanBoardHeader from "./components/KanbanBoardHeader"; /** * Renders the responsive grid container of kanban columns, coordinating state tracking - * for active task updates and triggering status mutation API requests. + * for active task updates/deletions and triggering mutation API requests. * * @param {Object} props - The component props. * @param {Task[]} props.tasks - The array of task items displayed across the board. @@ -61,10 +61,39 @@ export default function KanbanBoard({ tasks }: { tasks: Task[] }) { }); }; + /** + * Deletes a specific task by sending a DELETE request to the API, + * managing loading states, and refreshing the router upon success. + * + * @param {string} taskId - The unique identifier of the task to delete. + */ + const deleteTask = (taskId: string) => { + setUpdatingTaskIds((prev) => new Set(prev).add(taskId)); + + startTransition(async () => { + try { + const response = await fetch(`/api/tasks/${taskId}`, { + method: "DELETE", + }); + + if (!response.ok) throw new Error("Failed to delete task"); + await router.refresh(); + } catch (error) { + console.error("Error during task deletion:", error); + } finally { + setUpdatingTaskIds((prev) => { + const next = new Set(prev); + next.delete(taskId); + return next; + }); + } + }); + }; + return (
{/* Workspace Header */} - + {/* Kanban Columns Grid */}
@@ -80,6 +109,7 @@ export default function KanbanBoard({ tasks }: { tasks: Task[] }) { color={col.color} updatingTaskIds={updatingTaskIds} onTaskMove={updateTaskStatus} + onTaskDelete={deleteTask} /> ); })} diff --git a/app/(app)/dashboard/KanbanCard.tsx b/app/(app)/dashboard/KanbanCard.tsx index 716a755..6c286db 100644 --- a/app/(app)/dashboard/KanbanCard.tsx +++ b/app/(app)/dashboard/KanbanCard.tsx @@ -23,6 +23,7 @@ export default function KanbanCard({ task, isUpdating = false, onStatusChange, + onDelete, }: KanbanCardProps) { /** * Initiates the drag action on a task card if not currently updating, storing its ID and status payload. @@ -88,7 +89,11 @@ export default function KanbanCard({ creator={task.creator} assignees={task.assignees} /> - + onDelete?.(task.id)} + />
diff --git a/app/(app)/dashboard/KanbanColumn.tsx b/app/(app)/dashboard/KanbanColumn.tsx index 2190a9a..a36f816 100644 --- a/app/(app)/dashboard/KanbanColumn.tsx +++ b/app/(app)/dashboard/KanbanColumn.tsx @@ -97,6 +97,7 @@ export default function KanbanColumn(props: KanbanColumnProps) { task={task} isUpdating={props.updatingTaskIds?.has(task.id)} onStatusChange={props.onTaskMove} + onDelete={props.onTaskDelete} /> )) )} diff --git a/app/(app)/dashboard/components/KanbanBoardHeader.tsx b/app/(app)/dashboard/components/KanbanBoardHeader.tsx index 7906d9d..918f7bd 100644 --- a/app/(app)/dashboard/components/KanbanBoardHeader.tsx +++ b/app/(app)/dashboard/components/KanbanBoardHeader.tsx @@ -1,16 +1,45 @@ /** * @file dashboard/components/KanbanBoardHeader.tsx - * @description Component rendering the top title header and action buttons for the dashboard view. + * @description Component rendering the top title header with an integrated delete drop zone that appears during drag-and-drop interactions. */ -import { Plus, Sparkles } from "lucide-react"; +"use client"; + +import { useState, useEffect } from "react"; +import { Plus, Sparkles, Trash2 } from "lucide-react"; /** - * Renders the dashboard header section including title, subtitle, workspace indicator, and action triggers. + * Renders the dashboard header featuring a workspace title, subtitle, new task action button, + * and a dynamic drop zone for deleting tasks during drag operations. * - * @returns {JSX.Element} The rendered dashboard header component. + * @param {Object} props - The component props. + * @param {(taskId: string) => void} props.onTaskDelete - Callback function invoked when a task is dropped into the delete zone. + * @returns {JSX.Element} The rendered kanban board header component. */ -export default function KanbanBoardHeader() { +export default function KanbanBoardHeader({ + onTaskDelete, +}: { + onTaskDelete: (taskId: string) => void; +}) { + const [isDragging, setIsDragging] = useState(false); + const [isOver, setIsOver] = useState(false); + + useEffect(() => { + const handleDragStart = () => setIsDragging(true); + const handleDragEnd = () => { + setIsDragging(false); + setIsOver(false); + }; + + window.addEventListener("dragstart", handleDragStart); + window.addEventListener("dragend", handleDragEnd); + + return () => { + window.removeEventListener("dragstart", handleDragStart); + window.removeEventListener("dragend", handleDragEnd); + }; + }, []); + return (
@@ -25,10 +54,38 @@ export default function KanbanBoardHeader() {
- + {isDragging ? ( +
{ + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + setIsOver(true); + }} + onDragLeave={() => setIsOver(false)} + onDrop={(e) => { + e.preventDefault(); + setIsOver(false); + setIsDragging(false); + const taskId = e.dataTransfer.getData("text/plain"); + if (taskId) { + onTaskDelete(taskId); + } + }} + className={`flex items-center gap-2 px-4 py-2.5 rounded-xl font-medium text-sm border-2 border-dashed cursor-pointer select-none transition-all duration-300 ease-out transform ${ + isOver + ? "bg-destructive border-destructive scale-110 shadow-lg animate-pulse" + : "bg-destructive/10 border-destructive/40 text-destructive scale-100 opacity-90 hover:opacity-100 animate-in fade-in zoom-in-95 duration-200" + }`} + > + + Drop to Delete +
+ ) : ( + + )}
); diff --git a/app/(app)/dashboard/components/KanbanCardActions.tsx b/app/(app)/dashboard/components/KanbanCardActions.tsx index 667077d..43b26bf 100644 --- a/app/(app)/dashboard/components/KanbanCardActions.tsx +++ b/app/(app)/dashboard/components/KanbanCardActions.tsx @@ -1,29 +1,32 @@ /** * @file dashboard/components/KanbanCardActions.tsx - * @description Client component rendering the mobile status transition dropdown for a kanban card. + * @description Client component rendering the mobile status transition and deletion dropdown for a kanban card. */ "use client"; import { useState, useEffect, useRef } from "react"; -import { MoreHorizontal, CornerDownRight } from "lucide-react"; +import { MoreHorizontal, CornerDownRight, Trash2 } from "lucide-react"; import { KANBAN_COLUMNS, TaskStatus } from "@/types/tasks"; /** * Renders a mobile-only action menu component allowing users to move a task - * between different kanban columns via a dropdown interface. + * between different kanban columns or delete it entirely via a dropdown interface. * * @param {Object} props - The component props. * @param {TaskStatus} props.currentStatus - The current status category of the task. * @param {(newStatus: TaskStatus) => void} props.onMove - Callback function triggered when a new status column is selected. + * @param {() => void} props.onDelete - Callback function triggered when the delete action is selected. * @returns {JSX.Element} The rendered mobile card actions component. */ export default function KanbanCardActions({ currentStatus, onMove, + onDelete, }: { currentStatus: TaskStatus; onMove: (newStatus: TaskStatus) => void; + onDelete: () => void; }) { const [showMobileActions, setShowMobileActions] = useState(false); const dropdownRef = useRef(null); @@ -59,6 +62,17 @@ export default function KanbanCardActions({ setShowMobileActions(false); }; + /** + * Handles clicking the delete action item to trigger task deletion. + * + * @param {React.MouseEvent} e - The mouse event object. + */ + const handleDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onDelete(); + setShowMobileActions(false); + }; + return (
); diff --git a/types/tasks.ts b/types/tasks.ts index 750237d..5c396b4 100644 --- a/types/tasks.ts +++ b/types/tasks.ts @@ -46,12 +46,14 @@ export interface KanbanColumnProps extends KanbanColumnConfig { tasks: Task[]; updatingTaskIds?: Set; onTaskMove?: (taskId: string, targetStatus: TaskStatus) => void; + onTaskDelete?: (taskId: string) => void; } export interface KanbanCardProps { task: Task; isUpdating?: boolean; onStatusChange?: (taskId: string, newStatus: TaskStatus) => void; + onDelete?: (taskId: string) => void; } // ==========================================