'use client'; import { useState, useEffect, useCallback } from 'react'; import { tasksClient, TaskFilters, CreateTaskData, UpdateTaskData } from '@/clients/tasks-client'; import { Task } from '@/lib/types'; interface UseTasksState { tasks: Task[]; stats: { total: number; completed: number; inProgress: number; todo: number; cancelled: number; freeze: number; completionRate: number; }; loading: boolean; error: string | null; syncing: boolean; // Pour indiquer les opérations optimistes en cours } interface UseTasksActions { refreshTasks: () => Promise; createTask: (data: CreateTaskData) => Promise; updateTask: (data: UpdateTaskData) => Promise; updateTaskOptimistic: (data: UpdateTaskData) => Promise; deleteTask: (taskId: string) => Promise; setFilters: (filters: TaskFilters) => void; } /** * Hook pour la gestion des tâches */ export function useTasks( initialFilters?: TaskFilters, initialData?: { tasks: Task[]; stats: UseTasksState['stats'] } ): UseTasksState & UseTasksActions { const [state, setState] = useState({ tasks: initialData?.tasks || [], stats: initialData?.stats || { total: 0, completed: 0, inProgress: 0, todo: 0, cancelled: 0, freeze: 0, completionRate: 0 }, loading: false, error: null, syncing: false }); const [filters, setFilters] = useState(initialFilters || {}); /** * Récupère les tâches depuis l'API */ const refreshTasks = useCallback(async () => { setState(prev => ({ ...prev, loading: true, error: null })); try { const response = await tasksClient.getTasks({ ...filters, limit: undefined }); setState(prev => ({ ...prev, tasks: response.data, stats: response.stats, loading: false })); } catch (error) { setState(prev => ({ ...prev, loading: false, error: error instanceof Error ? error.message : 'Erreur inconnue' })); } }, [filters]); /** * Crée une nouvelle tâche */ const createTask = useCallback(async (data: CreateTaskData): Promise => { setState(prev => ({ ...prev, loading: true, error: null })); try { const response = await tasksClient.createTask(data); // Rafraîchir la liste après création await refreshTasks(); return response.data; } catch (error) { setState(prev => ({ ...prev, loading: false, error: error instanceof Error ? error.message : 'Erreur lors de la création' })); return null; } }, [refreshTasks]); /** * Met à jour une tâche */ const updateTask = useCallback(async (data: UpdateTaskData): Promise => { setState(prev => ({ ...prev, loading: true, error: null })); try { const response = await tasksClient.updateTask(data); // Rafraîchir la liste après mise à jour await refreshTasks(); return response.data; } catch (error) { setState(prev => ({ ...prev, loading: false, error: error instanceof Error ? error.message : 'Erreur lors de la mise à jour' })); return null; } }, [refreshTasks]); /** * Met à jour une tâche de manière optimiste (pour drag & drop) */ const updateTaskOptimistic = useCallback(async (data: UpdateTaskData): Promise => { 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, cancelled: updatedTasks.filter(t => t.status === 'cancelled').length, freeze: updatedTasks.filter(t => t.status === 'freeze').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, cancelled: currentTasks.filter(t => t.status === 'cancelled').length, freeze: currentTasks.filter(t => t.status === 'freeze').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 */ const deleteTask = useCallback(async (taskId: string): Promise => { setState(prev => ({ ...prev, loading: true, error: null })); try { await tasksClient.deleteTask(taskId); // Rafraîchir la liste après suppression await refreshTasks(); } catch (error) { setState(prev => ({ ...prev, loading: false, error: error instanceof Error ? error.message : 'Erreur lors de la suppression' })); throw error; // Re-throw pour que l'UI puisse gérer l'erreur } }, [refreshTasks]); // Charger les tâches au montage seulement si pas de données initiales useEffect(() => { if (!initialData?.tasks?.length) { refreshTasks(); } }, [refreshTasks, initialData?.tasks?.length]); return { ...state, refreshTasks, createTask, updateTask, updateTaskOptimistic, deleteTask, setFilters }; }