'use client'; import { useState, useEffect, useCallback } from 'react'; import { tasksClient, TaskFilters, CreateTaskData } from '@/clients/tasks-client'; import { updateTaskStatus, createTask as createTaskAction } from '@/actions/tasks'; import { Task, TaskStats, TaskStatus } from '@/lib/types'; interface UseTasksState { tasks: Task[]; stats: TaskStats; loading: boolean; error: string | null; syncing: boolean; // Pour indiquer les opérations optimistes en cours } interface UseTasksActions { refreshTasks: () => Promise; createTask: (data: CreateTaskData) => Promise; updateTaskOptimistic: (taskId: string, status: TaskStatus) => Promise; setFilters: (filters: TaskFilters) => void; } /** * Hook pour la gestion des tâches */ export function useTasks( initialFilters?: TaskFilters, initialData?: { tasks: Task[]; stats?: TaskStats } ): UseTasksState & UseTasksActions { const [state, setState] = useState({ tasks: initialData?.tasks || [], stats: initialData?.stats || { total: 0, completed: 0, inProgress: 0, todo: 0, backlog: 0, cancelled: 0, freeze: 0, archived: 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 result = await createTaskAction({ title: data.title, description: data.description, status: data.status, priority: data.priority, tags: data.tags }); if (result.success) { // Rafraîchir la liste après création await refreshTasks(); return result.data as Task; } else { throw new Error(result.error || 'Erreur lors de la création'); } } catch (error) { setState(prev => ({ ...prev, loading: false, error: error instanceof Error ? error.message : 'Erreur lors de la création' })); return null; } }, [refreshTasks]); // Note: updateTask et deleteTask ont été migrés vers Server Actions // Voir /src/actions/tasks.ts pour updateTaskTitle, updateTaskStatus, deleteTask /** * Met à jour le statut d'une tâche de manière optimiste (pour drag & drop) */ const updateTaskOptimistic = useCallback(async (taskId: string, status: TaskStatus): Promise => { // 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, status }; 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, backlog: updatedTasks.filter(t => t.status === 'backlog').length, cancelled: updatedTasks.filter(t => t.status === 'cancelled').length, freeze: updatedTasks.filter(t => t.status === 'freeze').length, archived: updatedTasks.filter(t => t.status === 'archived').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 Server Action en arrière-plan try { const result = await updateTaskStatus(taskId, status); // Si l'action réussit, la revalidation automatique se charge du reste if (result.success) { setState(prev => ({ ...prev, syncing: false })); return result.data as Task; } else { throw new Error(result.error || 'Erreur lors de la mise à jour'); } } 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, backlog: currentTasks.filter(t => t.status === 'backlog').length, cancelled: currentTasks.filter(t => t.status === 'cancelled').length, freeze: currentTasks.filter(t => t.status === 'freeze').length, archived: currentTasks.filter(t => t.status === 'archived').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]); // Note: deleteTask a été migré vers Server Actions // Utilisez directement deleteTask depuis /src/actions/tasks.ts dans les composants // 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, updateTaskOptimistic, setFilters }; }