229 lines
6.4 KiB
TypeScript
229 lines
6.4 KiB
TypeScript
/**
|
|
* Service centralisé de gestion d'erreurs pour BTP Xpress
|
|
*/
|
|
|
|
import { Toast } from 'primereact/toast';
|
|
import { AxiosError } from 'axios';
|
|
|
|
export interface ErrorDetails {
|
|
code?: string;
|
|
message: string;
|
|
field?: string;
|
|
severity: 'error' | 'warn' | 'info';
|
|
}
|
|
|
|
export interface ApiErrorResponse {
|
|
error: string;
|
|
message?: string;
|
|
details?: ErrorDetails[];
|
|
timestamp?: string;
|
|
path?: string;
|
|
}
|
|
|
|
export class ErrorHandler {
|
|
private static toast: React.RefObject<Toast> | null = null;
|
|
|
|
static setToast(toastRef: React.RefObject<Toast>) {
|
|
this.toast = toastRef;
|
|
}
|
|
|
|
/**
|
|
* Gère les erreurs API avec messages appropriés
|
|
*/
|
|
static handleApiError(error: unknown, context?: string): void {
|
|
console.error(`Erreur API ${context ? `(${context})` : ''}:`, error);
|
|
|
|
if (error instanceof AxiosError) {
|
|
const response = error.response;
|
|
|
|
if (response) {
|
|
const status = response.status;
|
|
const data = response.data as ApiErrorResponse;
|
|
|
|
switch (status) {
|
|
case 400:
|
|
this.showError('Données invalides', data.message || data.error || 'Vérifiez les informations saisies');
|
|
break;
|
|
case 401:
|
|
this.showError('Non autorisé', 'Veuillez vous reconnecter');
|
|
// Ne pas rediriger si on est en train de traiter un code d'autorisation
|
|
if (typeof window !== 'undefined') {
|
|
const currentUrl = window.location.href;
|
|
const hasAuthCode = currentUrl.includes('code=') && currentUrl.includes('/dashboard');
|
|
|
|
if (!hasAuthCode) {
|
|
// Rediriger vers la page de connexion
|
|
window.location.href = '/api/auth/login';
|
|
} else {
|
|
console.log('🔄 ErrorHandler: Erreur 401 ignorée car authentification en cours...');
|
|
}
|
|
}
|
|
break;
|
|
case 403:
|
|
this.showError('Accès refusé', 'Vous n\'avez pas les permissions nécessaires');
|
|
break;
|
|
case 404:
|
|
this.showError('Ressource non trouvée', data.message || 'L\'élément demandé n\'existe pas');
|
|
break;
|
|
case 409:
|
|
this.showError('Conflit', data.message || 'Cette opération entre en conflit avec l\'état actuel');
|
|
break;
|
|
case 422:
|
|
this.handleValidationErrors(data);
|
|
break;
|
|
case 500:
|
|
this.showError('Erreur serveur', 'Une erreur interne s\'est produite. Veuillez réessayer plus tard.');
|
|
break;
|
|
case 503:
|
|
this.showError('Service indisponible', 'Le service est temporairement indisponible');
|
|
break;
|
|
default:
|
|
this.showError('Erreur réseau', `Erreur ${status}: ${data.message || data.error || 'Erreur inconnue'}`);
|
|
}
|
|
} else if (error.request) {
|
|
this.showError('Erreur de connexion', 'Impossible de contacter le serveur. Vérifiez votre connexion internet.');
|
|
} else {
|
|
this.showError('Erreur', error.message || 'Une erreur inattendue s\'est produite');
|
|
}
|
|
} else if (error instanceof Error) {
|
|
this.showError('Erreur', error.message);
|
|
} else {
|
|
this.showError('Erreur', 'Une erreur inconnue s\'est produite');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gère les erreurs de validation (422)
|
|
*/
|
|
private static handleValidationErrors(data: ApiErrorResponse): void {
|
|
if (data.details && data.details.length > 0) {
|
|
const messages = data.details.map(detail =>
|
|
detail.field ? `${detail.field}: ${detail.message}` : detail.message
|
|
);
|
|
this.showError('Erreurs de validation', messages.join('\n'));
|
|
} else {
|
|
this.showError('Erreur de validation', data.message || data.error || 'Données invalides');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Affiche un message d'erreur
|
|
*/
|
|
static showError(summary: string, detail: string): void {
|
|
if (this.toast?.current) {
|
|
this.toast.current.show({
|
|
severity: 'error',
|
|
summary,
|
|
detail,
|
|
life: 5000
|
|
});
|
|
} else {
|
|
console.error(`${summary}: ${detail}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Affiche un message d'avertissement
|
|
*/
|
|
static showWarning(summary: string, detail: string): void {
|
|
if (this.toast?.current) {
|
|
this.toast.current.show({
|
|
severity: 'warn',
|
|
summary,
|
|
detail,
|
|
life: 4000
|
|
});
|
|
} else {
|
|
console.warn(`${summary}: ${detail}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Affiche un message de succès
|
|
*/
|
|
static showSuccess(summary: string, detail: string): void {
|
|
if (this.toast?.current) {
|
|
this.toast.current.show({
|
|
severity: 'success',
|
|
summary,
|
|
detail,
|
|
life: 3000
|
|
});
|
|
} else {
|
|
console.log(`${summary}: ${detail}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Affiche un message d'information
|
|
*/
|
|
static showInfo(summary: string, detail: string): void {
|
|
if (this.toast?.current) {
|
|
this.toast.current.show({
|
|
severity: 'info',
|
|
summary,
|
|
detail,
|
|
life: 3000
|
|
});
|
|
} else {
|
|
console.info(`${summary}: ${detail}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valide les champs obligatoires
|
|
*/
|
|
static validateRequired(fields: Record<string, any>): string[] {
|
|
const errors: string[] = [];
|
|
|
|
Object.entries(fields).forEach(([fieldName, value]) => {
|
|
if (value === null || value === undefined ||
|
|
(typeof value === 'string' && value.trim() === '') ||
|
|
(Array.isArray(value) && value.length === 0)) {
|
|
errors.push(`Le champ "${fieldName}" est obligatoire`);
|
|
}
|
|
});
|
|
|
|
return errors;
|
|
}
|
|
|
|
/**
|
|
* Valide un email
|
|
*/
|
|
static validateEmail(email: string): boolean {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email);
|
|
}
|
|
|
|
/**
|
|
* Valide un numéro de téléphone français
|
|
*/
|
|
static validatePhoneNumber(phone: string): boolean {
|
|
const phoneRegex = /^(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}$/;
|
|
return phoneRegex.test(phone);
|
|
}
|
|
|
|
/**
|
|
* Valide un SIRET
|
|
*/
|
|
static validateSiret(siret: string): boolean {
|
|
if (!siret || siret.length !== 14) return false;
|
|
|
|
const digits = siret.replace(/\s/g, '').split('').map(Number);
|
|
if (digits.some(isNaN)) return false;
|
|
|
|
// Algorithme de validation SIRET
|
|
let sum = 0;
|
|
for (let i = 0; i < 14; i++) {
|
|
let digit = digits[i];
|
|
if (i % 2 === 1) {
|
|
digit *= 2;
|
|
if (digit > 9) digit -= 9;
|
|
}
|
|
sum += digit;
|
|
}
|
|
|
|
return sum % 10 === 0;
|
|
}
|
|
}
|