import { useState } from 'react'; import type { Task, User, Status } from './data'; import { PRIORITY_COLORS, STATUS_COLORS, STATUS_LABELS } from './data'; import { Avatar, PriorityBadge, StatusBadge, ProgressBar } from './Shared'; function TaskCard({ task, onClick, onDragStart }: { task: Task; onClick: () => void; onDragStart: (e: React.DragEvent, task: Task) => void }) { const p = PRIORITY_COLORS[task.priority]; const due = new Date(task.dueDate + 'T00:00:00'); const overdue = due < new Date() && task.status !== 'done'; const commCount = task.comments.length; return (
onDragStart(e, task)} onDragEnd={e => (e.currentTarget as HTMLElement).style.opacity = '1'} onClick={onClick}>
{task.title}
{task.tags.slice(0, 2).map(t => {t})}
{task.subtasks.length > 0 && }
๐Ÿ“… {due.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} {commCount > 0 && ๐Ÿ’ฌ {commCount}}
); } function KanbanColumn({ status, statusLabel, tasks, color, onTaskClick, onAddTask, onDragStart, onDrop, isDragOver, onDragOver, onDragLeave }: { status: Status; statusLabel: string; tasks: Task[]; color: string; onTaskClick: (t: Task) => void; onAddTask: (s: Status) => void; onDragStart: (e: React.DragEvent, task: Task) => void; onDrop: (e: React.DragEvent, status: Status) => void; isDragOver: boolean; onDragOver: (e: React.DragEvent, status: Status) => void; onDragLeave: (e: React.DragEvent) => void; }) { return (
onDragOver(e, status)} onDragLeave={onDragLeave} onDrop={e => onDrop(e, status)}>
{statusLabel} {tasks.length}
{tasks.length === 0 ? (
{isDragOver ? 'โฌ‡ Drop task here' : 'No tasks here ยท Click + to add one'}
) : ( tasks.map(t => onTaskClick(t)} onDragStart={onDragStart} />) )}
); } interface KanbanProps { tasks: Task[]; currentUser: User; onTaskClick: (t: Task) => void; onAddTask: (s: Status) => void; filterUser: string | null; searchQuery: string; onMoveTask: (taskId: string, newStatus: Status) => void; } export function KanbanBoard({ tasks, currentUser, onTaskClick, onAddTask, filterUser, searchQuery, onMoveTask }: KanbanProps) { const [dragOverColumn, setDragOverColumn] = useState(null); let filtered = tasks; if (currentUser.role === 'employee') filtered = filtered.filter(t => t.assignee === currentUser.id); if (filterUser) filtered = filtered.filter(t => t.assignee === filterUser); if (searchQuery) filtered = filtered.filter(t => t.title.toLowerCase().includes(searchQuery.toLowerCase())); const handleDragStart = (e: React.DragEvent, task: Task) => { e.dataTransfer.setData('text/plain', task.id); e.dataTransfer.effectAllowed = 'move'; (e.currentTarget as HTMLElement).style.opacity = '0.4'; }; const handleDragOver = (e: React.DragEvent, status: Status) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setDragOverColumn(status); }; const handleDragLeave = (e: React.DragEvent) => { // Only clear if leaving the column entirely (not entering a child) const related = e.relatedTarget as HTMLElement | null; if (!related || !(e.currentTarget as HTMLElement).contains(related)) { setDragOverColumn(null); } }; const handleDrop = (e: React.DragEvent, newStatus: Status) => { e.preventDefault(); const taskId = e.dataTransfer.getData('text/plain'); const task = tasks.find(t => t.id === taskId); if (task && task.status !== newStatus) { onMoveTask(taskId, newStatus); } setDragOverColumn(null); }; const statuses: Status[] = ['todo', 'inprogress', 'review', 'done']; return (
{statuses.map(s => ( t.status === s)} onTaskClick={onTaskClick} onAddTask={onAddTask} onDragStart={handleDragStart} onDrop={handleDrop} isDragOver={dragOverColumn === s} onDragOver={handleDragOver} onDragLeave={handleDragLeave} /> ))}
); }