173 lines
5.4 KiB
TypeScript
Executable File
173 lines
5.4 KiB
TypeScript
Executable File
'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<T> {
|
|
data: T | null;
|
|
loading: boolean;
|
|
error: any;
|
|
execute: (...args: any[]) => Promise<T | null>;
|
|
retry: () => Promise<T | null>;
|
|
reset: () => void;
|
|
toast: React.RefObject<Toast>;
|
|
}
|
|
|
|
export function useApiCall<T = any>(
|
|
apiFunction: (...args: any[]) => Promise<T>,
|
|
options: UseApiCallOptions = {}
|
|
): UseApiCallReturn<T> {
|
|
const [data, setData] = useState<T | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<any>(null);
|
|
const toast = useRef<Toast>(null);
|
|
const lastArgsRef = useRef<any[]>([]);
|
|
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<T | null> => {
|
|
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<T | null> => {
|
|
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; |