"use client"; import { useState } from "react"; import { DndContext, DragOverlay, PointerSensor, KeyboardSensor, closestCenter, useSensor, useSensors, type DragEndEvent, type DragStartEvent, } from "@dnd-kit/core"; import { SortableContext, arrayMove, horizontalListSortingStrategy } from "@dnd-kit/sortable"; import { BoardProvider, useBoard } from "@/components/board/board-context"; import { CategoryLane } from "@/components/board/category-lane"; import { AddCategoryLane } from "@/components/board/add-category-lane"; import { GroupCardOverlay } from "@/components/board/group-card-overlay"; import { EmptyState } from "@/components/board/empty-state"; import type { CategoryDTO, GroupDTO } from "@/types/board"; function Board() { const { categories, reorderLanes, reorderGroups, moveGroup } = useBoard(); const [draggedGroup, setDraggedGroup] = useState(null); const sensors = useSensors( // A short activation distance lets a plain click (checkbox, notes icon, // todo text, ...) fire normally -- drag only kicks in once the pointer // has actually moved, which is what makes the whole card grabbable // without swallowing clicks on its interactive children. useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), useSensor(KeyboardSensor) ); function findCategory(categoryId: string): CategoryDTO | undefined { return categories.find((c) => c.id === categoryId); } function handleDragStart(event: DragStartEvent) { const data = event.active.data.current; if (data?.type === "group") { const category = findCategory(data.categoryId as string); const group = category?.groups.find((g) => g.id === event.active.id); setDraggedGroup(group ?? null); } } function handleDragEnd(event: DragEndEvent) { const { active, over } = event; setDraggedGroup(null); if (!over) return; const activeData = active.data.current; const overData = over.data.current; if (!activeData) return; if (activeData.type === "category") { if (overData?.type !== "category" || active.id === over.id) return; const oldIndex = categories.findIndex((c) => c.id === active.id); const newIndex = categories.findIndex((c) => c.id === over.id); if (oldIndex === -1 || newIndex === -1) return; const reordered = arrayMove(categories, oldIndex, newIndex); reorderLanes(reordered.map((c) => c.id)); return; } if (activeData.type === "group") { const groupId = active.id as string; const sourceCategoryId = activeData.categoryId as string; const sourceCategory = findCategory(sourceCategoryId); if (!sourceCategory) return; let destCategoryId: string; let destIndex: number; if (overData?.type === "group") { destCategoryId = overData.categoryId as string; const destCategory = findCategory(destCategoryId); destIndex = destCategory ? destCategory.groups.findIndex((g) => g.id === over.id) : 0; } else if (overData?.type === "category-dropzone") { destCategoryId = overData.categoryId as string; const destCategory = findCategory(destCategoryId); destIndex = destCategory ? destCategory.groups.length : 0; } else { return; } if (sourceCategoryId === destCategoryId) { const oldIndex = sourceCategory.groups.findIndex((g) => g.id === groupId); if (oldIndex === -1 || oldIndex === destIndex) return; const reordered = arrayMove(sourceCategory.groups, oldIndex, destIndex); reorderGroups(sourceCategoryId, reordered.map((g) => g.id)); return; } const sourceIds = sourceCategory.groups.filter((g) => g.id !== groupId).map((g) => g.id); const destCategory = findCategory(destCategoryId); const targetIds = destCategory ? destCategory.groups.map((g) => g.id) : []; const clampedIndex = Math.min(Math.max(destIndex, 0), targetIds.length); targetIds.splice(clampedIndex, 0, groupId); moveGroup(groupId, sourceCategoryId, destCategoryId, targetIds, sourceIds); } } const categoryIds = categories.map((c) => c.id); return (

Home

{categories.length === 0 ? (
) : (
{categories.map((category) => ( ))}
)}
{draggedGroup ? : null}
); } export function KanbanBoard({ initialCategories }: { initialCategories: CategoryDTO[] }) { return ( ); }