Files
btpxpress-frontend/hooks/useDashboard.ts

230 lines
8.8 KiB
TypeScript

/**
* Hook pour les données du dashboard - Version 2025 BTP Xpress
* Utilise UNIQUEMENT les données réelles du backend Quarkus
*/
import { useState, useEffect, useCallback } from 'react';
import { apiClient } from '../services/api-client';
import {
DashboardMetrics,
ChantierActif,
ActiviteRecente,
TacheUrgente,
DashboardPrincipalResponse,
DashboardChantiersResponse,
ChantierActifDTO
} from '../types/dashboard';
// Ré-exporter les types pour compatibilité
export type { DashboardMetrics, ChantierActif, ActiviteRecente, TacheUrgente };
interface DashboardData {
metrics: DashboardMetrics | null;
chantiersActifs: ChantierActif[];
activitesRecentes: ActiviteRecente[];
tachesUrgentes: TacheUrgente[];
loading: boolean;
error: string | null;
}
export const useDashboard = (periode: 'semaine' | 'mois' | 'trimestre' | 'annee' = 'mois') => {
const [data, setData] = useState<DashboardData>({
metrics: null,
chantiersActifs: [],
activitesRecentes: [],
tachesUrgentes: [],
loading: true,
error: null,
});
const [currentPeriode, setCurrentPeriode] = useState(periode);
const loadDashboardData = useCallback(async (abortController?: AbortController) => {
try {
setData(prev => ({ ...prev, loading: true, error: null }));
console.log('📊 Dashboard: Démarrage du chargement des données depuis le backend...');
console.log('🔗 API Base URL:', process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080');
// Vérifier si la requête a été annulée
if (abortController?.signal.aborted) {
console.log('📊 Dashboard: Requête annulée avant le début');
return;
}
// Charger les données depuis les endpoints réels du backend
const [dashboardPrincipalResponse, dashboardChantiersResponse] = await Promise.allSettled([
apiClient.get<DashboardPrincipalResponse>('/api/v1/dashboard'),
apiClient.get<DashboardChantiersResponse>('/api/v1/dashboard/chantiers')
]);
// Vérifier les erreurs
if (dashboardPrincipalResponse.status === 'rejected') {
console.error('❌ Erreur dashboard principal:', dashboardPrincipalResponse.reason);
throw new Error('Impossible de charger les métriques du dashboard');
}
if (dashboardChantiersResponse.status === 'rejected') {
console.error('❌ Erreur dashboard chantiers:', dashboardChantiersResponse.reason);
throw new Error('Impossible de charger les chantiers actifs');
}
const dashboardPrincipal = dashboardPrincipalResponse.value.data;
const dashboardChantiers = dashboardChantiersResponse.value.data;
console.log('✅ Dashboard principal:', dashboardPrincipal);
console.log('✅ Dashboard chantiers:', dashboardChantiers);
// Transformer les données backend vers le format attendu par le frontend
const metrics: DashboardMetrics = {
totalChantiers: dashboardPrincipal.chantiers.total,
chantiersActifs: dashboardPrincipal.chantiers.actifs,
chantiersEnRetard: dashboardChantiers.chantiersEnRetard.length,
chantiersTermines: (dashboardChantiers.statistiques as any)?.termines || 0,
totalEquipes: dashboardPrincipal.equipes.total,
equipesDisponibles: dashboardPrincipal.equipes.disponibles,
totalMateriel: dashboardPrincipal.materiel.total,
materielDisponible: dashboardPrincipal.materiel.disponible,
materielEnMaintenance: dashboardPrincipal.maintenance.enRetard + dashboardPrincipal.maintenance.planifiees,
totalDocuments: dashboardPrincipal.documents.total,
totalPhotos: 0, // TODO: Ajouter au backend si nécessaire
budgetTotal: dashboardChantiers.chantiersActifs.reduce((sum, c) => sum + c.budget, 0),
coutReel: dashboardChantiers.chantiersActifs.reduce((sum, c) => sum + c.coutReel, 0),
chiffreAffaires: dashboardChantiers.chantiersActifs.reduce((sum, c) => sum + c.budget, 0),
objectifCA: 0, // TODO: Ajouter au backend si nécessaire
tauxReussite: (dashboardChantiers.statistiques as any)?.tauxReussite || 0,
satisfactionClient: 0 // TODO: Ajouter au backend si nécessaire
};
// Transformer les chantiers actifs du backend vers le format frontend
const chantiersActifs: ChantierActif[] = dashboardChantiers.chantiersActifs.map((chantier: ChantierActifDTO) => ({
id: chantier.id,
nom: chantier.nom,
client: chantier.client,
avancement: chantier.avancement,
dateDebut: chantier.dateDebut,
dateFinPrevue: chantier.dateFinPrevue,
statut: mapStatutFromBackend(chantier.statut),
budget: chantier.budget,
coutReel: chantier.coutReel
}));
// Activités récentes et tâches urgentes - À implémenter avec des endpoints dédiés
const activitesRecentes: ActiviteRecente[] = [];
const tachesUrgentes: TacheUrgente[] = [];
console.log('✅ Données transformées:', {
metrics,
chantiersActifs: chantiersActifs.length,
activitesRecentes: activitesRecentes.length,
tachesUrgentes: tachesUrgentes.length
});
setData({
metrics,
chantiersActifs,
activitesRecentes,
tachesUrgentes,
loading: false,
error: null,
});
} catch (error: any) {
console.error('💥 Erreur lors du chargement du dashboard:', error);
const errorMessage = error.response?.status === 404
? 'Endpoints du dashboard non trouvés. Vérifiez que le backend est à jour.'
: error.response?.status === 401
? 'Non authentifié. Veuillez vous reconnecter.'
: error.response?.status === 500
? 'Erreur serveur. Vérifiez les logs du backend.'
: 'Erreur de communication avec le serveur. Vérifiez que le backend est démarré sur http://localhost:8080';
setData(prev => ({
...prev,
loading: false,
error: errorMessage,
}));
}
}, [currentPeriode]);
useEffect(() => {
const abortController = new AbortController();
// Vérifier si on a des tokens d'authentification stockés
if (typeof window !== 'undefined') {
const hasTokens = localStorage.getItem('accessToken');
const currentUrl = window.location.href;
const hasAuthCode = currentUrl.includes('code=') && currentUrl.includes('/dashboard');
console.log('📊 Dashboard Hook: État actuel', {
hasTokens: !!hasTokens,
hasAuthCode,
url: currentUrl
});
// Si on a des tokens, charger immédiatement même avec un code dans l'URL
if (hasTokens) {
console.log('📊 Dashboard Hook: Tokens trouvés, chargement immédiat des données...');
loadDashboardData(abortController);
return () => abortController.abort();
}
if (hasAuthCode && !hasTokens) {
console.log('📊 Dashboard Hook: Attente de la fin du traitement d\'authentification...');
// NE PAS charger les données tant que l'authentification n'est pas terminée
// La page dashboard appellera refresh() une fois l'authentification terminée
console.log('📊 Dashboard Hook: Chargement différé en attente de l\'authentification');
return () => {
abortController.abort();
};
}
}
// Charger par défaut seulement si on n'est pas en train de s'authentifier
if (typeof window === 'undefined' || !window.location.href.includes('code=')) {
console.log('📊 Dashboard Hook: Chargement par défaut...');
loadDashboardData(abortController);
} else {
console.log('📊 Dashboard Hook: Chargement différé (code d\'autorisation détecté)');
}
return () => abortController.abort();
}, [loadDashboardData]);
const refresh = useCallback(() => {
const abortController = new AbortController();
loadDashboardData(abortController);
return () => abortController.abort();
}, [loadDashboardData]);
const changePeriode = useCallback((nouvellePeriode: 'semaine' | 'mois' | 'trimestre' | 'annee') => {
setCurrentPeriode(nouvellePeriode);
}, []);
return {
...data,
refresh,
changePeriode,
periode: currentPeriode,
isLoading: data.loading,
};
};
// Fonctions utilitaires pour transformer les données backend
function mapStatutFromBackend(statut: string): 'EN_COURS' | 'EN_RETARD' | 'PLANIFIE' | 'TERMINE' {
switch (statut) {
case 'EN_COURS': return 'EN_COURS';
case 'PLANIFIE': return 'PLANIFIE';
case 'TERMINE': return 'TERMINE';
case 'EN_RETARD': return 'EN_RETARD';
case 'SUSPENDU': return 'EN_COURS'; // Mapper SUSPENDU vers EN_COURS pour l'affichage
case 'ANNULE': return 'TERMINE'; // Mapper ANNULE vers TERMINE pour l'affichage
default:
console.warn(`Statut inconnu reçu du backend: ${statut}`);
return 'EN_COURS';
}
}
export default useDashboard;