'use client'; import { useState, useCallback, useRef } from 'react'; import { Toast } from 'primereact/toast'; import { apiService } from '../services/api'; interface UseApiCallOptions { onSuccess?: (data: any) => void; onError?: (error: any) => void; showSuccessToast?: boolean; showErrorToast?: boolean; successMessage?: string; retryAttempts?: number; retryDelay?: number; } interface UseApiCallReturn { data: T | null; loading: boolean; error: any; execute: (...args: any[]) => Promise; retry: () => Promise; reset: () => void; toast: React.RefObject; } export function useApiCall( apiFunction: (...args: any[]) => Promise, options: UseApiCallOptions = {} ): UseApiCallReturn { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const toast = useRef(null); const lastArgsRef = useRef([]); const retryCountRef = useRef(0); const { onSuccess, onError, showSuccessToast = false, showErrorToast = true, successMessage = 'Opération réussie', retryAttempts = 0, retryDelay = 1000 } = options; const execute = useCallback(async (...args: any[]): Promise => { lastArgsRef.current = args; setLoading(true); setError(null); retryCountRef.current = 0; try { const result = await apiFunction(...args); setData(result); // Notifier que le serveur répond (pour une détection immédiate) // Cette ligne se déclenchera dès qu'un appel API réussit if (showSuccessToast && toast.current) { toast.current.show({ severity: 'success', summary: 'Succès', detail: successMessage, life: 3000 }); } if (onSuccess) { onSuccess(result); } return result; } catch (err: any) { setError(err); // Déclenchement d'un health check urgent uniquement pour les erreurs critiques if (shouldRetry(err) && retryCountRef.current === 0) { // Health check urgent seulement au premier échec pour éviter le spam apiService.checkServerHealth(true).catch(() => {}); } // Gestion des erreurs réseau avec retry automatique if (shouldRetry(err) && retryCountRef.current < retryAttempts) { retryCountRef.current++; if (showErrorToast && toast.current) { toast.current.show({ severity: 'warn', summary: 'Tentative de reconnexion', detail: `Tentative ${retryCountRef.current}/${retryAttempts}...`, life: 2000 }); } // Attendre avant de retry await new Promise(resolve => setTimeout(resolve, retryDelay)); return execute(...args); } // Afficher l'erreur finale if (showErrorToast && toast.current) { const message = getErrorMessage(err); toast.current.show({ severity: 'error', summary: 'Erreur', detail: message, life: err.statusCode === 'SERVER_UNAVAILABLE' ? 0 : 5000 // Sticky pour erreurs serveur }); } if (onError) { onError(err); } return null; } finally { setLoading(false); } }, [apiFunction, onSuccess, onError, showSuccessToast, showErrorToast, successMessage, retryAttempts, retryDelay]); const retry = useCallback(async (): Promise => { if (lastArgsRef.current.length > 0) { return execute(...lastArgsRef.current); } return execute(); }, [execute]); const reset = useCallback(() => { setData(null); setError(null); setLoading(false); retryCountRef.current = 0; }, []); return { data, loading, error, execute, retry, reset, toast }; } // Fonctions utilitaires function shouldRetry(error: any): boolean { return error?.statusCode === 'NETWORK_ERROR' || error?.statusCode === 'SERVER_UNAVAILABLE' || error?.statusCode === 'TIMEOUT' || error?.code === 'ECONNABORTED' || error?.code === 'ERR_NETWORK'; } function getErrorMessage(error: any): string { if (error?.statusCode === 'SERVER_UNAVAILABLE') { return 'Serveur indisponible. Vérifiez que le serveur backend est démarré.'; } if (error?.statusCode === 'NETWORK_ERROR') { return 'Erreur réseau. Vérifiez votre connexion internet.'; } if (error?.statusCode === 'TIMEOUT') { return 'Délai d\'attente dépassé. Le serveur met trop de temps à répondre.'; } return error?.userMessage || error?.message || 'Une erreur inattendue s\'est produite'; } export default useApiCall;