diff --git a/app/(app)/dashboard/KanbanBoard.tsx b/app/(app)/dashboard/Board.tsx
similarity index 81%
rename from app/(app)/dashboard/KanbanBoard.tsx
rename to app/(app)/dashboard/Board.tsx
index b25c67c..362a9ba 100644
--- a/app/(app)/dashboard/KanbanBoard.tsx
+++ b/app/(app)/dashboard/Board.tsx
@@ -1,26 +1,26 @@
/**
- * @file dashboard/KanbanBoard.tsx
- * @description Client component wrapping the kanban columns grid, tracking individual task update/deletion states, and handling asynchronous mutations via API.
+ * @file dashboard/Board.tsx
+ * @description Client component wrapping the columns grid, tracking individual task update/deletion states, and handling asynchronous mutations via API.
*/
"use client";
import { useState, useTransition } from "react";
import { useRouter } from "next/navigation";
-import KanbanColumn from "./KanbanColumn";
-import { KANBAN_COLUMNS, Task, TaskStatus } from "@/types/tasks";
-import KanbanBoardHeader from "./components/KanbanBoardHeader";
+import Column from "./column/Column";
+import { COLUMNS, Task, TaskStatus } from "@/types/tasks";
+import Header from "./header/Header";
/**
- * Renders the responsive grid container of kanban columns, coordinating state tracking
+ * Renders the responsive grid container of columns, coordinating state tracking
* for active task updates/deletions, trash counts, and triggering mutation API requests.
*
* @param {Object} props - The component props.
* @param {Task[]} props.tasks - The array of task items displayed across the board.
* @param {number} props.trashCount - The count of items currently in the trash bin.
- * @returns {JSX.Element} The rendered kanban board component.
+ * @returns {JSX.Element} The rendered board component.
*/
-export default function KanbanBoard({
+export default function Board({
tasks,
trashCount,
}: {
@@ -100,14 +100,14 @@ export default function KanbanBoard({
return (
{/* Workspace Header */}
-
+
- {/* Kanban Columns Grid */}
+ {/* Columns Grid */}
- {KANBAN_COLUMNS.map((col) => {
+ {COLUMNS.map((col) => {
const columnTasks = tasks.filter((t) => t.status === col.id);
return (
-
} e - The drag event object.
- */
- const handleDragOver = (e: React.DragEvent) => {
- e.preventDefault();
- e.dataTransfer.dropEffect = "move";
- setIsDraggingOver(true);
- };
-
- /**
- * Resets the drag-over state when the dragged element leaves the column boundary.
- */
- const handleDragLeave = () => {
- setIsDraggingOver(false);
- };
-
- /**
- * Handles dropping a task card onto the column, extracting task metadata and triggering the move action.
- *
- * @param {React.DragEvent} e - The drop event object.
- */
- const handleDrop = (e: React.DragEvent) => {
- e.preventDefault();
- setIsDraggingOver(false);
-
- const taskId = e.dataTransfer.getData("text/plain");
- const sourceStatus = e.dataTransfer.getData("sourceStatus") as TaskStatus;
-
- if (!taskId || sourceStatus === props.id) return;
-
- props.onTaskMove?.(taskId, props.id);
- };
-
- return (
-
- {/* Column Header */}
-
-
-
-
- {props.title}
-
-
- {props.count}
-
-
-
-
-
-
-
- {/* Card List */}
-
- {props.tasks.length === 0 ? (
-
- No tasks
-
- ) : (
- props.tasks.map((task) => (
-
- ))
- )}
-
-
- );
-}
diff --git a/app/(app)/dashboard/KanbanCard.tsx b/app/(app)/dashboard/card/Card.tsx
similarity index 70%
rename from app/(app)/dashboard/KanbanCard.tsx
rename to app/(app)/dashboard/card/Card.tsx
index 6c286db..db0503a 100644
--- a/app/(app)/dashboard/KanbanCard.tsx
+++ b/app/(app)/dashboard/card/Card.tsx
@@ -1,30 +1,30 @@
/**
- * @file dashboard/KanbanCard.tsx
- * @description Client component rendering a single kanban card container with individual loading states and modular sub-components.
+ * @file dashboard/card/Card.tsx
+ * @description Client component rendering a single card container with individual loading states and modular sub-components.
*/
"use client";
import { Loader2 } from "lucide-react";
-import { KanbanCardProps, TaskStatus } from "@/types/tasks";
-import KanbanCardActions from "./components/KanbanCardActions";
-import KanbanCardAvatars from "./components/KanbanCardAvatars";
-import KanbanCardDueDate from "./components/KanbanCardDueDate";
-import KanbanCardPriority from "./components/KanbanCardPriority";
+import { CardProps, TaskStatus } from "@/types/tasks";
+import CardActions from "./CardActions";
+import CardAvatars from "./CardAvatars";
+import CardDueDate from "./CardDueDate";
+import CardPriority from "./CardPriority";
/**
- * Renders an interactive kanban card container handling drag-and-drop actions, loading states,
+ * Renders an interactive card container handling drag-and-drop actions, loading states,
* and assembling modular sub-components for priorities, due dates, avatars, and actions.
*
- * @param {KanbanCardProps} props - The component props containing the task object, updating status flag, and status change handler.
- * @returns {JSX.Element} The rendered kanban card component.
+ * @param {CardProps} props - The component props containing the task object, updating status flag, and status change handler.
+ * @returns {JSX.Element} The rendered card component.
*/
-export default function KanbanCard({
+export default function Card({
task,
isUpdating = false,
onStatusChange,
onDelete,
-}: KanbanCardProps) {
+}: CardProps) {
/**
* Initiates the drag action on a task card if not currently updating, storing its ID and status payload.
*
@@ -72,7 +72,7 @@ export default function KanbanCard({
{task.title}
-
+
{/* Card Description */}
@@ -82,14 +82,11 @@ export default function KanbanCard({
{/* --- Card Footer --- */}
-
+
-
-
+
onDelete?.(task.id)}
diff --git a/app/(app)/dashboard/components/KanbanCardActions.tsx b/app/(app)/dashboard/card/CardActions.tsx
similarity index 93%
rename from app/(app)/dashboard/components/KanbanCardActions.tsx
rename to app/(app)/dashboard/card/CardActions.tsx
index 43b26bf..05d23ac 100644
--- a/app/(app)/dashboard/components/KanbanCardActions.tsx
+++ b/app/(app)/dashboard/card/CardActions.tsx
@@ -1,17 +1,17 @@
/**
- * @file dashboard/components/KanbanCardActions.tsx
- * @description Client component rendering the mobile status transition and deletion dropdown for a kanban card.
+ * @file dashboard/card/CardActions.tsx
+ * @description Client component rendering the mobile status transition and deletion dropdown for a card.
*/
"use client";
import { useState, useEffect, useRef } from "react";
import { MoreHorizontal, CornerDownRight, Trash2 } from "lucide-react";
-import { KANBAN_COLUMNS, TaskStatus } from "@/types/tasks";
+import { COLUMNS, TaskStatus } from "@/types/tasks";
/**
* Renders a mobile-only action menu component allowing users to move a task
- * between different kanban columns or delete it entirely via a dropdown interface.
+ * between different 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.
@@ -19,7 +19,7 @@ import { KANBAN_COLUMNS, TaskStatus } from "@/types/tasks";
* @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({
+export default function CardActions({
currentStatus,
onMove,
onDelete,
@@ -94,7 +94,7 @@ export default function KanbanCardActions({
: "opacity-0 scale-10 pointer-events-none"
}`}
>
- {KANBAN_COLUMNS.map((col) => {
+ {COLUMNS.map((col) => {
if (col.id === currentStatus) return null;
return (
} e - The drag event object.
+ */
+ const handleDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = "move";
+ setIsDraggingOver(true);
+ };
+
+ /**
+ * Resets the drag-over highlight state when a dragged element leaves the column area.
+ */
+ const handleDragLeave = () => {
+ setIsDraggingOver(false);
+ };
+
+ /**
+ * Handles dropping a task card onto the column, extracting task data and triggering the move handler.
+ *
+ * @param {React.DragEvent} e - The drop event object.
+ */
+ const handleDrop = (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDraggingOver(false);
+
+ const taskId = e.dataTransfer.getData("text/plain");
+ const sourceStatus = e.dataTransfer.getData("sourceStatus") as TaskStatus;
+
+ if (!taskId || sourceStatus === props.id) return;
+
+ props.onTaskMove?.(taskId, props.id);
+ };
+
+ return (
+
+
+
+
+ {props.tasks.length === 0 ? (
+
+ ) : (
+ props.tasks.map((task) => (
+
+ ))
+ )}
+
+
+ );
+}
diff --git a/app/(app)/dashboard/column/ColumnEmptyState.tsx b/app/(app)/dashboard/column/ColumnEmptyState.tsx
new file mode 100644
index 0000000..21a2fb4
--- /dev/null
+++ b/app/(app)/dashboard/column/ColumnEmptyState.tsx
@@ -0,0 +1,19 @@
+/**
+ * @file dashboard/column/ColumnEmptyState.tsx
+ * @description Client component rendering a placeholder card when a specific column contains no tasks.
+ */
+
+"use client";
+
+/**
+ * Renders a dashed empty state container with placeholder text for empty columns.
+ *
+ * @returns {JSX.Element} The rendered column empty state component.
+ */
+export default function ColumnEmptyState() {
+ return (
+
+ No tasks
+
+ );
+}
diff --git a/app/(app)/dashboard/column/ColumnHeader.tsx b/app/(app)/dashboard/column/ColumnHeader.tsx
new file mode 100644
index 0000000..68cd2d1
--- /dev/null
+++ b/app/(app)/dashboard/column/ColumnHeader.tsx
@@ -0,0 +1,43 @@
+/**
+ * @file dashboard/column/ColumnHeader.tsx
+ * @description Component rendering the column header with title, color indicator, item count, and add button.
+ */
+
+"use client";
+
+import { Plus } from "lucide-react";
+
+/**
+ * Renders the header section of a column, displaying a color-coded status indicator,
+ * the column name, the total task count badge, and a button to add new tasks.
+ *
+ * @param {Object} props - The component props.
+ * @param {string} props.title - The title of the column.
+ * @param {string} [props.color] - Tailwind CSS color class for the status indicator dot.
+ * @param {number} props.count - The number of tasks currently inside this column.
+ * @returns {JSX.Element} The rendered column header component.
+ */
+export default function ColumnHeader({
+ title,
+ color = "bg-primary",
+ count,
+}: {
+ title: string;
+ color?: string;
+ count: number;
+}) {
+ return (
+
+
+
+
{title}
+
+ {count}
+
+
+
+
+
+
+ );
+}
diff --git a/app/(app)/dashboard/components/KanbanBoardHeader.tsx b/app/(app)/dashboard/components/KanbanBoardHeader.tsx
deleted file mode 100644
index 193b6c9..0000000
--- a/app/(app)/dashboard/components/KanbanBoardHeader.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * @file dashboard/components/KanbanBoardHeader.tsx
- * @description Component rendering the top title header with an integrated delete drop zone that appears during drag-and-drop interactions, plus a link to the trash bin view.
- */
-
-"use client";
-
-import { useState, useEffect } from "react";
-import { Plus, Sparkles, Trash2 } from "lucide-react";
-import Link from "next/link";
-
-/**
- * Renders the dashboard header featuring a workspace title, subtitle, new task action button,
- * a dynamic delete drop zone during drag operations, and a trash navigation link with a counter badge.
- *
- * @param {Object} props - The component props.
- * @param {(taskId: string) => void} props.onTaskDelete - Callback function invoked when a task is dropped into the delete zone.
- * @param {number} props.trashCount - The current count of items in the trash bin.
- * @returns {JSX.Element} The rendered kanban board header component.
- */
-export default function KanbanBoardHeader({
- onTaskDelete,
- trashCount,
-}: {
- onTaskDelete: (taskId: string) => void;
- trashCount: number;
-}) {
- 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 (
-
-
-
-
- Workspace
-
-
Dashboard
-
- Manage your tasks and keep track of your progress.
-
-
-
-
- {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
-
- ) : (
-
-
- New Task
-
- )}
-
- {/* Icon */}
-
-
- {/* Badge */}
- {trashCount > 0 && (
-
- {trashCount}
-
- )}
-
-
-
- );
-}
diff --git a/app/(app)/dashboard/header/DeleteDropZone.tsx b/app/(app)/dashboard/header/DeleteDropZone.tsx
new file mode 100644
index 0000000..995fdd5
--- /dev/null
+++ b/app/(app)/dashboard/header/DeleteDropZone.tsx
@@ -0,0 +1,71 @@
+/**
+ * @file dashboard/header/DeleteDropZone.tsx
+ * @description Client component rendering an interactive drop zone for deleting tasks during drag-and-drop.
+ */
+
+"use client";
+
+import { useState, useEffect } from "react";
+import { Trash2 } from "lucide-react";
+
+/**
+ * Renders a drop target zone that appears dynamically during drag-and-drop operations,
+ * allowing users to delete tasks by dragging them onto the designated area.
+ *
+ * @param {Object} props - The component props.
+ * @param {(taskId: string) => void} props.onTaskDelete - Callback triggered when a task is dropped into the delete zone.
+ * @returns {JSX.Element | null} The rendered delete drop zone component, or null if no drag operation is active.
+ */
+export default function DeleteDropZone({
+ 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);
+ };
+ }, []);
+
+ if (!isDragging) return null;
+
+ return (
+ {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = "move";
+ setIsOver(true);
+ }}
+ onDragLeave={() => setIsOver(false)}
+ onDrop={(e) => {
+ e.preventDefault();
+ setIsOver(false);
+ const taskId = e.dataTransfer.getData("text/plain");
+ if (taskId) {
+ onTaskDelete(taskId);
+ }
+ }}
+ className={`peer 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"
+ }`}
+ >
+
+ Drop to Delete
+
+ );
+}
diff --git a/app/(app)/dashboard/header/Header.tsx b/app/(app)/dashboard/header/Header.tsx
new file mode 100644
index 0000000..40d3c1a
--- /dev/null
+++ b/app/(app)/dashboard/header/Header.tsx
@@ -0,0 +1,49 @@
+/**
+ * @file dashboard/header/Header.tsx
+ * @description Client component rendering the dashboard top header section, including workspace info, deletion drop zones, task creation buttons, and trash links.
+ */
+
+"use client";
+
+import { Sparkles } from "lucide-react";
+import DeleteDropZone from "./DeleteDropZone";
+import TrashLink from "./TrashLink";
+import NewTaskButton from "./NewTaskButton";
+
+/**
+ * Renders the dashboard header section featuring title text, a task deletion drop zone,
+ * a new task action button, and a link to the trash bin.
+ *
+ * @param {HeaderProps} props - The component props.
+ * @returns {JSX.Element} The rendered dashboard header component.
+ */
+export default function Header({
+ onTaskDelete,
+ trashCount,
+}: {
+ onTaskDelete: (taskId: string) => void;
+ trashCount: number;
+}) {
+ return (
+
+
+
+
+ Workspace
+
+
Dashboard
+
+ Manage your tasks and keep track of your progress.
+
+
+
+
+
+ );
+}
diff --git a/app/(app)/dashboard/header/NewTaskButton.tsx b/app/(app)/dashboard/header/NewTaskButton.tsx
new file mode 100644
index 0000000..895974b
--- /dev/null
+++ b/app/(app)/dashboard/header/NewTaskButton.tsx
@@ -0,0 +1,22 @@
+/**
+ * @file dasboard/header/NewTaskButton.tsx
+ * @description Client component rendering an interactive trigger button for creating new tasks.
+ */
+
+"use client";
+
+import { Plus } from "lucide-react";
+
+/**
+ * Renders a stylized button component with an icon and active state animations to initiate task creation.
+ *
+ * @returns {JSX.Element} The rendered new task button component.
+ */
+export default function NewTaskButton() {
+ return (
+
+
+ New Task
+
+ );
+}
diff --git a/app/(app)/dashboard/header/TrashLink.tsx b/app/(app)/dashboard/header/TrashLink.tsx
new file mode 100644
index 0000000..3d46907
--- /dev/null
+++ b/app/(app)/dashboard/header/TrashLink.tsx
@@ -0,0 +1,36 @@
+/**
+ * @file dasboard/header/TrashLink.tsx
+ * @description Client component rendering a navigation link button to the trash view with a dynamic item counter badge.
+ */
+
+"use client";
+
+import Link from "next/link";
+import { Trash2 } from "lucide-react";
+
+/**
+ * Renders an interactive trash icon link featuring hover animations and an optional item count badge.
+ *
+ * @param {Object} props - The component props.
+ * @param {number} props.count - The number of items currently in the trash.
+ * @returns {JSX.Element} The rendered trash button component.
+ */
+export default function TrashLink({ count }: { count: number }) {
+ return (
+
+
+ {count > 0 && (
+
+ {count}
+
+ )}
+
+ );
+}
diff --git a/app/(app)/dashboard/page.tsx b/app/(app)/dashboard/page.tsx
index 5d4841c..bb55dfb 100644
--- a/app/(app)/dashboard/page.tsx
+++ b/app/(app)/dashboard/page.tsx
@@ -1,6 +1,6 @@
/**
* @file dashboard/page.tsx
- * @description Server component rendering the main dashboard page, handling authentication, fetching active user-related tasks and assignees, computing trash counts, and passing data to the kanban board container.
+ * @description Server component rendering the main dashboard page, handling authentication, fetching active user-related tasks and assignees, computing trash counts, and passing data to the board container.
*/
import { db } from "@/db";
@@ -8,13 +8,13 @@ import { tasksTable, taskAssigneesTable, usersTable } from "@/db/schema";
import { and, count, eq, inArray, isNotNull, isNull, or } from "drizzle-orm";
import { auth } from "@/auth";
import { redirect } from "next/navigation";
-import KanbanBoard from "./KanbanBoard";
+import Board from "./Board";
import { Task } from "@/types/tasks";
/**
* Renders the dashboard page component with user session validation,
* database queries for active tasks, team assignees, and soft-deleted trash counts,
- * before passing the structured dataset to the Kanban board container.
+ * before passing the structured dataset to the board container.
*
* @async
* @returns {Promise} The rendered dashboard page component.
@@ -95,7 +95,7 @@ export default async function Dashboard() {
return (
-
+
);
}
diff --git a/types/tasks.ts b/types/tasks.ts
index 5c396b4..fbeb282 100644
--- a/types/tasks.ts
+++ b/types/tasks.ts
@@ -1,6 +1,6 @@
/**
* @file types/tasks.ts
- * @description Type definitions, interfaces, and UI configuration mappings for task management and kanban views.
+ * @description Type definitions, interfaces, and UI configuration mappings for task management and views.
*/
import {
@@ -35,13 +35,13 @@ export interface TaskPriorityConfig {
className: string;
}
-export interface KanbanColumnConfig {
+export interface ColumnConfig {
id: TaskStatus;
title: string;
color: string;
}
-export interface KanbanColumnProps extends KanbanColumnConfig {
+export interface ColumnProps extends ColumnConfig {
count: number;
tasks: Task[];
updatingTaskIds?: Set;
@@ -49,7 +49,7 @@ export interface KanbanColumnProps extends KanbanColumnConfig {
onTaskDelete?: (taskId: string) => void;
}
-export interface KanbanCardProps {
+export interface CardProps {
task: Task;
isUpdating?: boolean;
onStatusChange?: (taskId: string, newStatus: TaskStatus) => void;
@@ -75,9 +75,9 @@ export const PRIORITY_CONFIG = {
},
} as const satisfies Record;
-export const KANBAN_COLUMNS = [
+export const COLUMNS = [
{ id: "todo", title: "To-do", color: "bg-zinc-400" },
{ id: "in_progress", title: "In Progress", color: "bg-indigo-500" },
{ id: "await_feedback", title: "Await Feedback", color: "bg-amber-500" },
{ id: "done", title: "Done", color: "bg-emerald-500" },
-] as const satisfies readonly KanbanColumnConfig[];
+] as const satisfies readonly ColumnConfig[];