import React from 'react'; import { type Task } from '../../types'; import type { ReorderTaskPayload } from '../lib/api'; import TaskCard from './TaskCard'; interface TaskColumnProps { title: string; tasks: Task[]; onTaskUpdate: (taskId: string, updates: Partial) => void; onEditTask: (task: Task) => void; onTaskReorder?: (payload: ReorderTaskPayload) => void; dragSourceStatus?: string | null; onDragStart?: () => void; onDragEnd?: () => void; onCleanup?: () => void; onDeleteTask?: (taskId: string) => void; onArchiveTask?: (taskId: string) => void; } const TaskColumn: React.FC = ({ title, tasks, onTaskUpdate, onEditTask, onTaskReorder, dragSourceStatus, onDragStart, onDragEnd, onCleanup, onDeleteTask, onArchiveTask }) => { const [isDragOver, setIsDragOver] = React.useState(false); const [draggedTaskId, setDraggedTaskId] = React.useState(null); const [dropPosition, setDropPosition] = React.useState<{ index: number; position: 'before' | 'after' } | null>(null); const getStatusBadgeClass = (status: string) => { const statusLower = status.toLowerCase(); if (statusLower.includes('done') || statusLower.includes('complete')) { return 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 transition-colors duration-200'; } if (statusLower.includes('progress') || statusLower.includes('doing')) { return 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 transition-colors duration-200'; } if (statusLower.includes('blocked') || statusLower.includes('stuck')) { return 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 transition-colors duration-200'; } return 'bg-stone-100 dark:bg-stone-900 text-stone-800 dark:text-stone-200 transition-colors duration-200'; }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); setDropPosition(null); const droppedTaskId = e.dataTransfer.getData('text/plain'); const sourceStatus = e.dataTransfer.getData('text/status'); if (!droppedTaskId) return; if (!onTaskReorder) { return; } const columnWithoutDropped = tasks.filter((task) => task.id !== droppedTaskId); let insertIndex = columnWithoutDropped.length; if (dropPosition) { const { index, position } = dropPosition; const baseIndex = position === 'before' ? index : index + 1; let count = 0; for (let i = 0; i < Math.min(baseIndex, tasks.length); i += 1) { if (tasks[i]?.id === droppedTaskId) { continue; } count += 1; } insertIndex = count; } const orderedTaskIds = columnWithoutDropped.map((task) => task.id); orderedTaskIds.splice(insertIndex, 0, droppedTaskId); const isSameColumn = sourceStatus === title; const isOrderUnchanged = isSameColumn && orderedTaskIds.length === tasks.length && orderedTaskIds.every((taskId, idx) => taskId === tasks[idx]?.id); if (isOrderUnchanged) { return; } onTaskReorder({ taskId: droppedTaskId, targetStatus: title, orderedTaskIds }); }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); }; const handleDragEnter = (e: React.DragEvent) => { e.preventDefault(); setIsDragOver(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); // Only set to false if we're leaving the column entirely if (!e.currentTarget.contains(e.relatedTarget as Node)) { setIsDragOver(false); setDropPosition(null); } }; const handleDragOverColumn = (e: React.DragEvent) => { e.preventDefault(); // Clear drop position if dragging in empty space const target = e.target as HTMLElement; if (target === e.currentTarget || target.classList.contains('space-y-3')) { setDropPosition(null); } }; return (

{title}

{tasks.length}
{tasks.map((task, index) => (
{ if (!onTaskReorder || !draggedTaskId || draggedTaskId === task.id) return; e.preventDefault(); const rect = e.currentTarget.getBoundingClientRect(); const y = e.clientY - rect.top; const height = rect.height; // Determine if we're in the top or bottom half if (y < height / 2) { setDropPosition({ index, position: 'before' }); } else { setDropPosition({ index, position: 'after' }); } }} > {/* Drop indicator for before this task */} {dropPosition?.index === index && dropPosition.position === 'before' && (
)} { setDraggedTaskId(task.id); onDragStart?.(); }} onDragEnd={() => { setDraggedTaskId(null); setDropPosition(null); onDragEnd?.(); }} onDelete={onDeleteTask} onArchive={onArchiveTask} status={title} /> {/* Drop indicator for after this task */} {dropPosition?.index === index && dropPosition.position === 'after' && (
)}
))} {/* Drop zone indicator - only show in different columns */} {isDragOver && dragSourceStatus !== title && (
Drop task here to change status
)} {tasks.length === 0 && !isDragOver && (
{dragSourceStatus && dragSourceStatus !== title ? `Drop here to move to ${title}` : `No tasks in ${title}`}
)} {/* Cleanup button for Done column */} {onCleanup && title.toLowerCase() === 'done' && tasks.length > 0 && (
)}
); }; export default TaskColumn;