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.
This commit is contained in:
@@ -15,12 +15,14 @@ interface UseTasksState {
|
||||
};
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
syncing: boolean; // Pour indiquer les opérations optimistes en cours
|
||||
}
|
||||
|
||||
interface UseTasksActions {
|
||||
refreshTasks: () => Promise<void>;
|
||||
createTask: (data: CreateTaskData) => Promise<Task | null>;
|
||||
updateTask: (data: UpdateTaskData) => Promise<Task | null>;
|
||||
updateTaskOptimistic: (data: UpdateTaskData) => Promise<Task | null>;
|
||||
deleteTask: (taskId: string) => Promise<void>;
|
||||
setFilters: (filters: TaskFilters) => void;
|
||||
}
|
||||
@@ -42,7 +44,8 @@ export function useTasks(
|
||||
completionRate: 0
|
||||
},
|
||||
loading: false,
|
||||
error: null
|
||||
error: null,
|
||||
syncing: false
|
||||
});
|
||||
|
||||
const [filters, setFilters] = useState<TaskFilters>(initialFilters || {});
|
||||
@@ -117,6 +120,87 @@ export function useTasks(
|
||||
}
|
||||
}, [refreshTasks]);
|
||||
|
||||
/**
|
||||
* Met à jour une tâche de manière optimiste (pour drag & drop)
|
||||
*/
|
||||
const updateTaskOptimistic = useCallback(async (data: UpdateTaskData): Promise<Task | null> => {
|
||||
const { taskId, ...updates } = data;
|
||||
|
||||
// 1. Sauvegarder l'état actuel pour rollback
|
||||
const currentTasks = state.tasks;
|
||||
const taskToUpdate = currentTasks.find(t => t.id === taskId);
|
||||
|
||||
if (!taskToUpdate) {
|
||||
console.error('Tâche non trouvée pour mise à jour optimiste:', taskId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. Mise à jour optimiste immédiate de l'état local
|
||||
const updatedTask = { ...taskToUpdate, ...updates };
|
||||
const updatedTasks = currentTasks.map(task =>
|
||||
task.id === taskId ? updatedTask : task
|
||||
);
|
||||
|
||||
// Recalculer les stats
|
||||
const newStats = {
|
||||
total: updatedTasks.length,
|
||||
completed: updatedTasks.filter(t => t.status === 'done').length,
|
||||
inProgress: updatedTasks.filter(t => t.status === 'in_progress').length,
|
||||
todo: updatedTasks.filter(t => t.status === 'todo').length,
|
||||
completionRate: updatedTasks.length > 0
|
||||
? Math.round((updatedTasks.filter(t => t.status === 'done').length / updatedTasks.length) * 100)
|
||||
: 0
|
||||
};
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
tasks: updatedTasks,
|
||||
stats: newStats,
|
||||
error: null,
|
||||
syncing: true // Indiquer qu'une synchronisation est en cours
|
||||
}));
|
||||
|
||||
// 3. Appel API en arrière-plan
|
||||
try {
|
||||
const response = await tasksClient.updateTask(data);
|
||||
|
||||
// Si l'API retourne des données différentes, on met à jour
|
||||
if (response.data) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
tasks: prev.tasks.map(task =>
|
||||
task.id === taskId ? response.data : task
|
||||
),
|
||||
syncing: false // Synchronisation terminée
|
||||
}));
|
||||
} else {
|
||||
setState(prev => ({ ...prev, syncing: false }));
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// 4. Rollback en cas d'erreur
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
tasks: currentTasks,
|
||||
stats: {
|
||||
total: currentTasks.length,
|
||||
completed: currentTasks.filter(t => t.status === 'done').length,
|
||||
inProgress: currentTasks.filter(t => t.status === 'in_progress').length,
|
||||
todo: currentTasks.filter(t => t.status === 'todo').length,
|
||||
completionRate: currentTasks.length > 0
|
||||
? Math.round((currentTasks.filter(t => t.status === 'done').length / currentTasks.length) * 100)
|
||||
: 0
|
||||
},
|
||||
error: error instanceof Error ? error.message : 'Erreur lors de la mise à jour',
|
||||
syncing: false // Arrêter l'indicateur de synchronisation
|
||||
}));
|
||||
|
||||
console.error('Erreur lors de la mise à jour optimiste:', error);
|
||||
return null;
|
||||
}
|
||||
}, [state.tasks]);
|
||||
|
||||
/**
|
||||
* Supprime une tâche
|
||||
*/
|
||||
@@ -148,6 +232,7 @@ export function useTasks(
|
||||
refreshTasks,
|
||||
createTask,
|
||||
updateTask,
|
||||
updateTaskOptimistic,
|
||||
deleteTask,
|
||||
setFilters
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user