import { useState, useMemo, useCallback } from 'react'; import { DragDropContext, Droppable, Draggable, type DropResult } from '@hello-pangea/dnd'; import { trpc } from '@/lib/trpc'; import { Badge } from '@/components/ui/badge'; import { TaskRow, type TaskItem } from '@/components/tasks/TaskRow'; import { NewTaskDialog } from '@/components/tasks/NewTaskDialog'; import { EditTaskDialog } from '@/components/tasks/EditTaskDialog'; import { TaskDetailDialog } from '@/components/tasks/TaskDetailDialog'; const COLUMNS = [ { id: 'todo', label: 'To Do' }, { id: 'in_progress', label: 'In Progress' }, { id: 'done', label: 'Completed' }, ] as const; type ColumnId = (typeof COLUMNS)[number]['id']; type KanbanBoardProps = { projectId: string; newTaskOpen: boolean; onNewTaskOpenChange: (open: boolean) => void; }; export function KanbanBoard({ projectId, newTaskOpen, onNewTaskOpenChange }: KanbanBoardProps) { const { data: tasksList } = trpc.tasks.list.useQuery({ projectId }); const utils = trpc.useUtils(); const updateTask = trpc.tasks.update.useMutation({ onSuccess: () => void utils.tasks.list.invalidate(), }); const deleteTask = trpc.tasks.delete.useMutation({ onSuccess: () => void utils.tasks.list.invalidate(), }); // Edit / view task dialog state const [editTask, setEditTask] = useState(null); const [viewTask, setViewTask] = useState(null); // Group tasks by status const columns = useMemo(() => { const tasks = tasksList ?? []; const grouped: Record = { todo: [], in_progress: [], done: [], }; for (const task of tasks) { const status = (task.status ?? 'todo') as ColumnId; if (status in grouped) { grouped[status].push(task); } else { grouped.todo.push(task); } } return grouped; }, [tasksList]); const handleDragEnd = useCallback( (result: DropResult) => { const { destination, source, draggableId } = result; if (!destination) return; if (destination.droppableId === source.droppableId) return; updateTask.mutate({ id: draggableId, status: destination.droppableId, }); }, [updateTask], ); const handleToggle = useCallback( (taskId: string, currentStatus: string | null) => { const nextStatus = currentStatus === 'todo' ? 'in_progress' : currentStatus === 'in_progress' ? 'done' : 'todo'; updateTask.mutate({ id: taskId, status: nextStatus }); }, [updateTask], ); return ( <>
{COLUMNS.map((col) => (
{/* Column header */}
{col.label} {columns[col.id].length}
{/* Droppable column */} {(provided, snapshot) => (
{columns[col.id].map((task, index) => ( {(dragProvided) => (
deleteTask.mutate({ id })} onClick={setViewTask} hideBreadcrumb />
)}
))} {provided.placeholder}
)}
))}
{ if (!open) setEditTask(null); }} /> { if (!open) setViewTask(null); }} onEdit={(task) => { setViewTask(null); setEditTask(task); }} onDelete={(id) => { deleteTask.mutate({ id }); setViewTask(null); }} /> ); }