"use client"; import { PlusIcon } from "@radix-ui/react-icons"; import { useMemo, useState } from "react"; import { Column, TaskCard as Task } from "@/types"; import ColumnContainer from "@/components/kanban/column"; import { DndContext, DragEndEvent, DragOverEvent, DragOverlay, DragStartEvent, PointerSensor, useSensor, useSensors, } from "@dnd-kit/core"; import { SortableContext, arrayMove } from "@dnd-kit/sortable"; import { createPortal } from "react-dom"; import TaskCard from "@/components/kanban/task"; const defaultCols: Column[] = [ { id: "todo", title: "Todo", }, { id: "doing", title: "Work in progress", }, { id: "done", title: "Done", }, ]; const defaultTasks: Task[] = [ { id: "1", status: "todo", name: "List admin APIs for dashboard", }, { id: "2", status: "todo", name: "Develop user registration functionality with OTP delivered on SMS after email confirmation and phone number confirmation", }, { id: "3", status: "doing", name: "Conduct security testing", }, { id: "4", status: "doing", name: "Analyze competitors", }, { id: "5", status: "done", name: "Create UI kit documentation", }, { id: "6", status: "done", name: "Dev meeting", }, { id: "7", status: "done", name: "Deliver dashboard prototype", }, { id: "8", status: "todo", name: "Optimize application performance", }, { id: "9", status: "todo", name: "Implement data validation", }, { id: "10", status: "todo", name: "Design database schema", }, { id: "11", status: "todo", name: "Integrate SSL web certificates into workflow", }, { id: "12", status: "doing", name: "Implement error logging and monitoring", }, { id: "13", status: "doing", name: "Design and implement responsive UI", }, ]; function KanbanBoard() { const [columns, setColumns] = useState(defaultCols); const columnsId = useMemo(() => columns.map((col) => col.id), [columns]); const [tasks, setTasks] = useState(defaultTasks); const [activeColumn, setActiveColumn] = useState(null); const [activeTask, setActiveTask] = useState(null); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 10, }, }) ); return (
{columns.map((col) => ( task.status === col.id)} /> ))}
{createPortal( {activeColumn && ( task.status === activeColumn.id)} /> )} {activeTask && ( )} , document.body )}
); function createTask(status: string) { const newTask: Task = { id: String(generateId()), status, name: `Task ${tasks.length + 1}`, }; setTasks([...tasks, newTask]); } function deleteTask(id: string) { const newTasks = tasks.filter((task) => task.id !== id); setTasks(newTasks); } function updateTask(id: string, name: string) { const newTasks = tasks.map((task) => { if (task.id !== id) return task; return { ...task, name }; }); setTasks(newTasks); } function createNewColumn() { const columnToAdd: Column = { id: String(generateId()), title: `Column ${columns.length + 1}`, }; setColumns([...columns, columnToAdd]); } function deleteColumn(id: string) { const filteredColumns = columns.filter((col) => col.id !== id); setColumns(filteredColumns); const newTasks = tasks.filter((t) => t.status !== id); setTasks(newTasks); } function updateColumn(id: string, title: string) { const newColumns = columns.map((col) => { if (col.id !== id) return col; return { ...col, title }; }); setColumns(newColumns); } function onDragStart(event: DragStartEvent) { if (event.active.data.current?.type === "Column") { setActiveColumn(event.active.data.current.column); return; } if (event.active.data.current?.type === "Task") { setActiveTask(event.active.data.current.task); return; } } function onDragEnd(event: DragEndEvent) { setActiveColumn(null); setActiveTask(null); const { active, over } = event; if (!over) return; const activeId = active.id; const overId = over.id; if (activeId === overId) return; const isActiveAColumn = active.data.current?.type === "Column"; if (!isActiveAColumn) return; console.log("DRAG END"); setColumns((columns) => { const activeColumnIndex = columns.findIndex((col) => col.id === activeId); const overColumnIndex = columns.findIndex((col) => col.id === overId); return arrayMove(columns, activeColumnIndex, overColumnIndex); }); } function onDragOver(event: DragOverEvent) { const { active, over } = event; if (!over) return; const activeId = active.id; const overId = over.id; if (activeId === overId) return; const isActiveATask = active.data.current?.type === "Task"; const isOverATask = over.data.current?.type === "Task"; if (!isActiveATask) return; // Im dropping a Task over another Task if (isActiveATask && isOverATask) { setTasks((tasks) => { const activeIndex = tasks.findIndex((t) => t.id === activeId); const overIndex = tasks.findIndex((t) => t.id === overId); if (tasks[activeIndex].status != tasks[overIndex].status) { // Fix introduced after video recording tasks[activeIndex].status = tasks[overIndex].status; return arrayMove(tasks, activeIndex, overIndex - 1); } return arrayMove(tasks, activeIndex, overIndex); }); } const isOverAColumn = over.data.current?.type === "Column"; // Im dropping a Task over a column if (isActiveATask && isOverAColumn) { setTasks((tasks) => { const activeIndex = tasks.findIndex((t) => t.id === activeId); tasks[activeIndex].status = String(overId); console.log("DROPPING TASK OVER COLUMN", { activeIndex }); return arrayMove(tasks, activeIndex, activeIndex); }); } } } function generateId() { /* Generate a random number between 0 and 10000 */ return Math.floor(Math.random() * 10001); } export default KanbanBoard;