import { useState, useCallback } from "react"; import { useFlashOnIncrease } from "../hooks/useFlashOnIncrease"; import type { Task, TaskDetail, TaskCreateInput, Column as ColumnType } from "@kb/core"; import { COLUMN_LABELS, COLUMN_DESCRIPTIONS } from "@kb/core"; import { TaskCard } from "./TaskCard"; import { WorktreeGroup } from "./WorktreeGroup"; import { QuickEntryBox } from "./QuickEntryBox"; import { groupByWorktree } from "../utils/worktreeGrouping"; import type { ToastType } from "../hooks/useToast"; import { ChevronDown, ChevronUp } from "lucide-react"; interface ColumnProps { column: ColumnType; tasks: Task[]; allTasks: Task[]; maxConcurrent: number; onMoveTask: (id: string, column: ColumnType) => Promise; onOpenDetail: (task: TaskDetail) => void; addToast: (message: string, type?: ToastType) => void; onQuickCreate?: (description: string) => Promise; onNewTask?: () => void; autoMerge?: boolean; onToggleAutoMerge?: () => void; globalPaused?: boolean; onUpdateTask?: ( id: string, updates: { title?: string; description?: string; dependencies?: string[] } ) => Promise; onArchiveTask?: (id: string) => Promise; onUnarchiveTask?: (id: string) => Promise; collapsed?: boolean; onToggleCollapse?: () => void; } export function Column({ column, tasks, allTasks, maxConcurrent, onMoveTask, onOpenDetail, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onArchiveTask, onUnarchiveTask, collapsed, onToggleCollapse }: ColumnProps) { const [dragOver, setDragOver] = useState(false); const countFlashing = useFlashOnIncrease(tasks.length); // Archived column is collapsed by default - don't show drag state when collapsed const isArchived = column === "archived"; const isCollapsed = isArchived && collapsed; const handleDragOver = useCallback((e: React.DragEvent) => { // Don't allow dropping into archived column via drag-drop if (isArchived) return; e.preventDefault(); e.dataTransfer.dropEffect = "move"; setDragOver(true); }, [isArchived]); const handleDragLeave = useCallback((e: React.DragEvent) => { const el = e.currentTarget as HTMLElement; if (!el.contains(e.relatedTarget as Node)) { setDragOver(false); } }, []); const handleDrop = useCallback(async (e: React.DragEvent) => { e.preventDefault(); setDragOver(false); const taskId = e.dataTransfer.getData("text/plain"); if (!taskId) return; try { await onMoveTask(taskId, column); } catch (err: any) { addToast(err.message, "error"); } }, [column, onMoveTask, addToast]); return (

{COLUMN_LABELS[column]}

{tasks.length} {column === "in-review" && onToggleAutoMerge && ( )} {onNewTask && ( )} {isArchived && onToggleCollapse && ( )}
{!isCollapsed &&

{COLUMN_DESCRIPTIONS[column]}

} {!isCollapsed && (
{column === "triage" && onQuickCreate && ( )} {column === "in-progress" ? ( (() => { const groups = groupByWorktree(tasks, allTasks, maxConcurrent); return groups.length === 0 ? (
No tasks
) : ( groups.map((group) => ( )) ); })() ) : tasks.length === 0 ? (
No tasks
) : ( tasks.map((task) => ( )) )}
)}
); }