import { DndContext, closestCorners, PointerSensor, TouchSensor, useSensor, useSensors, type DragEndEvent, useDroppable, useDraggable, } from '@dnd-kit/core'; import { format, parseISO } from 'date-fns'; import type { ProjectTask } from '@/types'; import { Badge } from '@/components/ui/badge'; const COLUMNS: { id: string; label: string; color: string }[] = [ { id: 'pending', label: 'Pending', color: 'text-gray-400' }, { id: 'in_progress', label: 'In Progress', color: 'text-blue-400' }, { id: 'blocked', label: 'Blocked', color: 'text-red-400' }, { id: 'on_hold', label: 'On Hold', color: 'text-orange-400' }, { id: 'review', label: 'Review', color: 'text-yellow-400' }, { id: 'completed', label: 'Completed', color: 'text-green-400' }, ]; const priorityColors: Record = { none: 'bg-gray-500/20 text-gray-400', low: 'bg-green-500/20 text-green-400', medium: 'bg-yellow-500/20 text-yellow-400', high: 'bg-red-500/20 text-red-400', }; interface KanbanBoardProps { tasks: ProjectTask[]; selectedTaskId: number | null; kanbanParentTask?: ProjectTask | null; onSelectTask: (taskId: number) => void; onStatusChange: (taskId: number, status: string) => void; onBackToAllTasks?: () => void; } function KanbanColumn({ column, tasks, selectedTaskId, onSelectTask, }: { column: (typeof COLUMNS)[0]; tasks: ProjectTask[]; selectedTaskId: number | null; onSelectTask: (taskId: number) => void; }) { const { setNodeRef, isOver } = useDroppable({ id: column.id }); return (
{/* Column header */}
{column.label} {tasks.length}
{/* Cards */}
{tasks.map((task) => ( onSelectTask(task.id)} /> ))}
); } function KanbanCard({ task, isSelected, onSelect, }: { task: ProjectTask; isSelected: boolean; onSelect: () => void; }) { const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({ id: task.id, data: { task }, }); const style = transform ? { transform: `translate(${transform.x}px, ${transform.y}px)`, opacity: isDragging ? 0.5 : 1, } : undefined; const completedSubtasks = task.subtasks?.filter((s) => s.status === 'completed').length ?? 0; const totalSubtasks = task.subtasks?.length ?? 0; return (

{task.title}

{task.priority} {task.due_date && ( {format(parseISO(task.due_date), 'MMM d')} )} {totalSubtasks > 0 && ( {completedSubtasks}/{totalSubtasks} )}
); } export default function KanbanBoard({ tasks, selectedTaskId, kanbanParentTask, onSelectTask, onStatusChange, onBackToAllTasks, }: KanbanBoardProps) { const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }) , useSensor(TouchSensor, { activationConstraint: { delay: 200, tolerance: 8 } }) ); // Subtask view is driven by kanbanParentTask (decoupled from selected task) const isSubtaskView = kanbanParentTask != null && (kanbanParentTask.subtasks?.length ?? 0) > 0; const activeTasks: ProjectTask[] = isSubtaskView ? (kanbanParentTask.subtasks ?? []) : tasks; const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (!over) return; const taskId = active.id as number; const newStatus = over.id as string; const task = activeTasks.find((t) => t.id === taskId); if (task && task.status !== newStatus && COLUMNS.some((c) => c.id === newStatus)) { onStatusChange(taskId, newStatus); } }; const tasksByStatus = COLUMNS.map((col) => ({ column: col, tasks: activeTasks.filter((t) => t.status === col.id), })); return (
{/* Subtask view header */} {isSubtaskView && kanbanParentTask && (
/ Subtasks of: {kanbanParentTask.title}
)}
{tasksByStatus.map(({ column, tasks: colTasks }) => ( ))}
); }