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.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} />
))}
);
}