diff --git a/src/components/KanbanBoard.tsx b/src/components/KanbanBoard.tsx new file mode 100644 index 0000000..c98681d --- /dev/null +++ b/src/components/KanbanBoard.tsx @@ -0,0 +1,171 @@ +'use client'; + +import { useApp, type Task } from './AppProvider'; +import { useState, useCallback } from 'react'; +import { format, isToday, isTomorrow, isPast, parseISO } from 'date-fns'; + +interface KanbanTask extends Task { + projectColor?: string; +} + +export default function KanbanBoard() { + const { tasks, projects, setSelectedTask, setShowEditTaskModal, setEditingTask, refreshTasks } = useApp(); + const [draggedTask, setDraggedTask] = useState(null); + + // Add project info + const kanbanTasks: KanbanTask[] = tasks.map(task => { + const project = projects.find(p => p.id === task.projectId); + return { ...task, projectColor: project?.color }; + }); + + const todoTasks = kanbanTasks.filter(t => t.status === 'todo'); + const inProgressTasks = kanbanTasks.filter(t => t.status === 'in_progress'); + const doneTasks = kanbanTasks.filter(t => t.status === 'done'); + + const columns: { key: string; title: string; tasks: KanbanTask[]; icon: string; color: string }[] = [ + { key: 'todo', title: 'To Do', tasks: todoTasks, icon: '📋', color: 'border-gray-300' }, + { key: 'in_progress', title: 'In Progress', tasks: inProgressTasks, icon: '🔄', color: 'border-blue-300' }, + { key: 'done', title: 'Done', tasks: doneTasks, icon: '✅', color: 'border-green-300' }, + ]; + + const handleDragStart = (taskId: string) => { + setDraggedTask(taskId); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + }; + + const handleDrop = async (status: string, e: React.DragEvent) => { + e.preventDefault(); + if (!draggedTask) return; + + try { + await fetch(`/api/tasks/${draggedTask}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status }), + }); + refreshTasks(); + // Refresh kanban data + const res = await fetch(`/api/kanban`); + const data = await res.json(); + } catch (error) { + console.error('Failed to update task status:', error); + } + setDraggedTask(null); + }; + + const handleTaskClick = (task: KanbanTask) => { + setSelectedTask(task); + setEditingTask(task); + setShowEditTaskModal(true); + }; + + const getDueDateInfo = (dueDate: string | null) => { + if (!dueDate) return null; + const date = parseISO(dueDate); + if (isToday(date)) return { label: 'Today', className: 'text-[var(--priority-urgent)] font-medium' }; + if (isTomorrow(date)) return { label: 'Tomorrow', className: 'text-[var(--priority-high)]' }; + if (isPast(date)) return { label: format(date, 'MMM d'), className: 'text-[var(--priority-urgent)] font-medium' }; + return { label: format(date, 'MMM d'), className: 'text-text-secondary' }; + }; + + const priorityConfig = { + urgent: { bg: 'bg-[var(--priority-urgent)]/10', text: 'text-[var(--priority-urgent)]' }, + high: { bg: 'bg-[var(--priority-high)]/10', text: 'text-[var(--priority-high)]' }, + medium: { bg: 'bg-[var(--priority-medium)]/10', text: 'text-[var(--priority-medium)]' }, + low: { bg: 'bg-[var(--priority-low)]/10', text: 'text-[var(--priority-low)]' }, + }; + + return ( +
+
+ {columns.map((column) => ( +
handleDrop(column.key, e)} + > + {/* Column header */} +
+
+
+ {column.icon} + {column.title} + + {column.tasks.length} + +
+
+
+ + {/* Tasks */} +
+ {column.tasks.map((task) => { + const dueInfo = getDueDateInfo(task.dueDate); + const prio = priorityConfig[task.priority]; + + return ( +
handleDragStart(task.id)} + onClick={() => handleTaskClick(task)} + className={`p-3 bg-card border border-border rounded-lg cursor-grab hover:shadow-md transition-all ${ + draggedTask === task.id ? 'opacity-50 scale-95' : '' + } ${task.completed ? 'opacity-60' : ''}`} + > + {/* Project indicator */} + {task.projectColor && ( +
+ +
+ )} + + {/* Title */} +

+ {task.title} +

+ + {/* Description preview */} + {task.description && ( +

{task.description}

+ )} + + {/* Footer */} +
+ + {task.priority} + + + {dueInfo && ( + + {dueInfo.label} + + )} + + {task.recurrenceRule !== 'none' && ( + 🔄 + )} +
+
+ ); + })} + + {column.tasks.length === 0 && ( +
+ Drop tasks here +
+ )} +
+
+ ))} +
+
+ ); +}