import { useMemo, useCallback } from 'react' import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors, closestCorners, } from '@dnd-kit/core' import type { DragEndEvent, DragStartEvent } from '@dnd-kit/core' import { useState } from 'react' import { Loader2 } from 'lucide-react' import { useStartTask, usePauseTask, useResumeTask, useCompleteTask, } from '../api/tasks.ts' import { WorkTaskStatus } from '../types/index.ts' import type { WorkTask } from '../types/index.ts' import { COLUMN_CONFIG } from '../lib/constants.ts' import KanbanColumn from './KanbanColumn.tsx' import TaskCard from './TaskCard.tsx' interface KanbanBoardProps { tasks: WorkTask[] isLoading: boolean onTaskClick: (id: number) => void } export default function KanbanBoard({ tasks, isLoading, onTaskClick }: KanbanBoardProps) { const startTask = useStartTask() const pauseTask = usePauseTask() const resumeTask = useResumeTask() const completeTask = useCompleteTask() const [activeTask, setActiveTask] = useState(null) const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }) ) // Filter to top-level tasks only and group by status const columns = useMemo(() => { const topLevel = tasks.filter((t) => t.parentTaskId === null) return COLUMN_CONFIG.map((col) => ({ ...col, tasks: topLevel.filter((t) => t.status === col.status), })) }, [tasks]) const handleDragStart = useCallback( (event: DragStartEvent) => { const draggedId = Number(event.active.id) const task = tasks.find((t) => t.id === draggedId) ?? null setActiveTask(task) }, [tasks] ) const handleDragEnd = useCallback( (event: DragEndEvent) => { setActiveTask(null) const { active, over } = event if (!over) return const taskId = Number(active.id) const task = tasks.find((t) => t.id === taskId) if (!task) return // Determine target status from the droppable column ID let targetStatus: string | null = null const overId = String(over.id) if (overId.startsWith('column-')) { targetStatus = overId.replace('column-', '') } else { // Dropped over another card - find which column it belongs to const overTaskId = Number(over.id) const overTask = tasks.find((t) => t.id === overTaskId) if (overTask) { targetStatus = overTask.status } } if (targetStatus === null || targetStatus === task.status) return // Map transitions to API calls switch (targetStatus) { case WorkTaskStatus.Active: // Works for both Pending->Active and Paused->Active if (task.status === WorkTaskStatus.Paused) { resumeTask.mutate({ id: taskId }) } else { startTask.mutate(taskId) } break case WorkTaskStatus.Paused: if (task.status === WorkTaskStatus.Active) { pauseTask.mutate({ id: taskId }) } break case WorkTaskStatus.Completed: completeTask.mutate(taskId) break case WorkTaskStatus.Pending: // Transition back to Pending is not supported break } }, [tasks, startTask, pauseTask, resumeTask, completeTask] ) if (isLoading) { return (
) } return (
{columns.map((col) => ( {} : undefined} /> ))}
{activeTask ? (
{}} />
) : null}
) }