- Added `visibleStatuses` prop to `KanbanBoard`, `PrioritySwimlanesBoard`, `SwimlanesBase`, and `SwimlanesBoard` for improved column visibility control. - Updated `KanbanBoardContainer` to derive `visibleStatuses` from `useColumnVisibility`, allowing dynamic filtering of displayed statuses. - Refactored `KanbanFilters` to accept `hiddenStatuses` and `onToggleStatusVisibility` props, enabling better integration with column visibility management. - Cleaned up visibility logic across components to ensure consistent behavior and user experience.
178 lines
5.4 KiB
TypeScript
178 lines
5.4 KiB
TypeScript
'use client';
|
|
|
|
import { Task, TaskStatus } from '@/lib/types';
|
|
import { KanbanColumn } from './Column';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { CreateTaskForm } from '@/components/forms/CreateTaskForm';
|
|
import { CreateTaskData } from '@/clients/tasks-client';
|
|
import { useMemo, useState } from 'react';
|
|
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
|
|
import { getAllStatuses } from '@/lib/status-config';
|
|
import {
|
|
DndContext,
|
|
DragEndEvent,
|
|
DragOverlay,
|
|
DragStartEvent,
|
|
PointerSensor,
|
|
useSensor,
|
|
useSensors,
|
|
} from '@dnd-kit/core';
|
|
import { TaskCard } from './TaskCard';
|
|
|
|
interface KanbanBoardProps {
|
|
tasks: Task[];
|
|
onCreateTask?: (data: CreateTaskData) => Promise<Task | null>;
|
|
onDeleteTask?: (taskId: string) => Promise<void>;
|
|
onEditTask?: (task: Task) => void;
|
|
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
|
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
|
|
loading?: boolean;
|
|
compactView?: boolean;
|
|
visibleStatuses?: TaskStatus[];
|
|
}
|
|
|
|
export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, onUpdateStatus, loading = false, compactView = false, visibleStatuses }: KanbanBoardProps) {
|
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
|
|
|
// Gestion de la visibilité des colonnes (utilise les props si disponibles)
|
|
const { getVisibleStatuses } = useColumnVisibility();
|
|
|
|
// Configuration des capteurs pour le drag & drop
|
|
const sensors = useSensors(
|
|
useSensor(PointerSensor, {
|
|
activationConstraint: {
|
|
distance: 8, // Évite les clics accidentels
|
|
},
|
|
})
|
|
);
|
|
// Organiser les tâches par statut
|
|
const tasksByStatus = useMemo(() => {
|
|
const grouped = tasks.reduce((acc, task) => {
|
|
if (!acc[task.status]) {
|
|
acc[task.status] = [];
|
|
}
|
|
acc[task.status].push(task);
|
|
return acc;
|
|
}, {} as Record<TaskStatus, Task[]>);
|
|
|
|
return grouped;
|
|
}, [tasks]);
|
|
|
|
// Configuration des colonnes basée sur la config centralisée
|
|
const allColumns = useMemo(() => {
|
|
return getAllStatuses().map(statusConfig => ({
|
|
id: statusConfig.key,
|
|
tasks: tasksByStatus[statusConfig.key] || []
|
|
}));
|
|
}, [tasksByStatus]);
|
|
|
|
// Filtrer les colonnes visibles
|
|
const visibleColumns = visibleStatuses ?
|
|
allColumns.filter(column => visibleStatuses.includes(column.id)) :
|
|
getVisibleStatuses(allColumns);
|
|
|
|
const handleCreateTask = async (data: CreateTaskData) => {
|
|
if (onCreateTask) {
|
|
await onCreateTask(data);
|
|
}
|
|
};
|
|
|
|
// Gestion du début du drag
|
|
const handleDragStart = (event: DragStartEvent) => {
|
|
const { active } = event;
|
|
const task = tasks.find(t => t.id === active.id);
|
|
setActiveTask(task || null);
|
|
};
|
|
|
|
// Gestion de la fin du drag
|
|
const handleDragEnd = async (event: DragEndEvent) => {
|
|
const { active, over } = event;
|
|
setActiveTask(null);
|
|
|
|
if (!over || !onUpdateStatus) return;
|
|
|
|
const taskId = active.id as string;
|
|
const newStatus = over.id as TaskStatus;
|
|
|
|
// Trouver la tâche actuelle
|
|
const task = tasks.find(t => t.id === taskId);
|
|
if (!task || task.status === newStatus) return;
|
|
|
|
// Mettre à jour le statut
|
|
await onUpdateStatus(taskId, newStatus);
|
|
};
|
|
|
|
return (
|
|
<DndContext
|
|
id="kanban-board"
|
|
sensors={sensors}
|
|
onDragStart={handleDragStart}
|
|
onDragEnd={handleDragEnd}
|
|
>
|
|
<div className="h-full flex flex-col bg-slate-950">
|
|
{/* Header avec bouton nouvelle tâche */}
|
|
<div className="flex justify-between items-center p-6 pb-0">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
|
|
<h2 className="text-lg font-mono font-bold text-slate-100 uppercase tracking-wider">
|
|
Kanban Board
|
|
</h2>
|
|
</div>
|
|
|
|
{onCreateTask && (
|
|
<Button
|
|
variant="primary"
|
|
onClick={() => setIsCreateModalOpen(true)}
|
|
disabled={loading}
|
|
>
|
|
+ Nouvelle tâche
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
|
|
{/* Board tech dark */}
|
|
<div className="flex-1 flex gap-6 overflow-x-auto p-6">
|
|
{visibleColumns.map((column) => (
|
|
<KanbanColumn
|
|
key={column.id}
|
|
id={column.id}
|
|
tasks={column.tasks}
|
|
onCreateTask={onCreateTask ? handleCreateTask : undefined}
|
|
onDeleteTask={onDeleteTask}
|
|
onEditTask={onEditTask}
|
|
onUpdateTitle={onUpdateTitle}
|
|
compactView={compactView}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Modal de création */}
|
|
{onCreateTask && (
|
|
<CreateTaskForm
|
|
isOpen={isCreateModalOpen}
|
|
onClose={() => setIsCreateModalOpen(false)}
|
|
onSubmit={handleCreateTask}
|
|
loading={loading}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Overlay pour le drag & drop */}
|
|
<DragOverlay>
|
|
{activeTask ? (
|
|
<div className="rotate-3 opacity-90">
|
|
<TaskCard
|
|
task={activeTask}
|
|
onDelete={undefined}
|
|
onEdit={undefined}
|
|
onUpdateTitle={undefined}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
</DragOverlay>
|
|
</DndContext>
|
|
);
|
|
}
|