Files
towercontrol/components/kanban/Column.tsx
Julien Froidefond 9193305550 feat: implement drag & drop functionality using @dnd-kit
- Added drag & drop capabilities to the Kanban board with @dnd-kit for task status updates.
- Integrated DndContext in `KanbanBoard` and utilized `useDroppable` in `KanbanColumn` for drop zones.
- Enhanced `TaskCard` with draggable features and visual feedback during dragging.
- Updated `TODO.md` to reflect the completion of drag & drop tasks and optimistically update task statuses.
- Introduced optimistic updates in `useTasks` for smoother user experience during drag & drop operations.
2025-09-14 09:08:06 +02:00

137 lines
5.0 KiB
TypeScript

import { Task, TaskStatus } from '@/lib/types';
import { TaskCard } from './TaskCard';
import { QuickAddTask } from './QuickAddTask';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { CreateTaskData } from '@/clients/tasks-client';
import { useState } from 'react';
import { useDroppable } from '@dnd-kit/core';
interface KanbanColumnProps {
id: TaskStatus;
title: string;
color: string;
tasks: Task[];
onCreateTask?: (data: CreateTaskData) => Promise<void>;
onDeleteTask?: (taskId: string) => Promise<void>;
onEditTask?: (task: Task) => void;
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
}
export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle }: KanbanColumnProps) {
const [showQuickAdd, setShowQuickAdd] = useState(false);
// Configuration de la zone droppable
const { setNodeRef, isOver } = useDroppable({
id: id,
});
// Couleurs tech/cyberpunk
const techStyles = {
gray: {
border: 'border-slate-700',
glow: 'shadow-slate-500/20',
accent: 'text-slate-400',
badge: 'bg-slate-800 text-slate-300 border border-slate-600'
},
blue: {
border: 'border-cyan-500/30',
glow: 'shadow-cyan-500/20',
accent: 'text-cyan-400',
badge: 'bg-cyan-950 text-cyan-300 border border-cyan-500/30'
},
green: {
border: 'border-emerald-500/30',
glow: 'shadow-emerald-500/20',
accent: 'text-emerald-400',
badge: 'bg-emerald-950 text-emerald-300 border border-emerald-500/30'
},
red: {
border: 'border-red-500/30',
glow: 'shadow-red-500/20',
accent: 'text-red-400',
badge: 'bg-red-950 text-red-300 border border-red-500/30'
}
};
const style = techStyles[color as keyof typeof techStyles];
// Icônes tech
const techIcons = {
todo: '⚡',
in_progress: '🔄',
done: '✓',
cancelled: '✕'
};
const badgeVariant = color === 'green' ? 'success' : color === 'blue' ? 'primary' : color === 'red' ? 'danger' : 'default';
return (
<div className="flex-shrink-0 w-80 h-full">
<Card
ref={setNodeRef}
variant="elevated"
className={`h-full flex flex-col transition-all duration-200 ${
isOver ? 'ring-2 ring-cyan-400/50 bg-slate-800/60' : ''
}`}
>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`w-2 h-2 rounded-full ${style.accent.replace('text-', 'bg-')} animate-pulse`}></div>
<h3 className={`font-mono text-sm font-bold ${style.accent} uppercase tracking-wider`}>
{title}
</h3>
</div>
<div className="flex items-center gap-2">
<Badge variant={badgeVariant} size="sm">
{String(tasks.length).padStart(2, '0')}
</Badge>
{onCreateTask && (
<button
onClick={() => setShowQuickAdd(true)}
className={`w-5 h-5 rounded-full border border-dashed ${style.border} ${style.accent} hover:bg-slate-800/50 transition-colors flex items-center justify-center text-xs font-mono`}
title="Ajouter une tâche rapide"
>
+
</button>
)}
</div>
</div>
</CardHeader>
<CardContent className="flex-1 p-4 h-[calc(100vh-220px)] overflow-y-auto">
<div className="space-y-3">
{/* Quick Add Task */}
{showQuickAdd && onCreateTask && (
<QuickAddTask
status={id}
onSubmit={async (data) => {
await onCreateTask(data);
// Ne pas fermer automatiquement pour permettre la création en série
}}
onCancel={() => setShowQuickAdd(false)}
/>
)}
{tasks.length === 0 && !showQuickAdd ? (
<div className="text-center py-20">
<div className={`w-16 h-16 mx-auto mb-4 rounded-full bg-slate-800 border-2 border-dashed ${style.border} flex items-center justify-center`}>
<span className={`text-2xl ${style.accent} opacity-50`}>{techIcons[id]}</span>
</div>
<p className="text-xs font-mono text-slate-500 uppercase tracking-wide">NO DATA</p>
<div className="mt-2 flex justify-center">
<div className={`w-8 h-0.5 ${style.accent.replace('text-', 'bg-')} opacity-30`}></div>
</div>
</div>
) : (
tasks.map((task) => (
<TaskCard key={task.id} task={task} onDelete={onDeleteTask} onEdit={onEditTask} onUpdateTitle={onUpdateTitle} />
))
)}
</div>
</CardContent>
</Card>
</div>
);
}