'use client'; export const dynamic = 'force-dynamic'; import React, { useState, useEffect, useRef } from 'react'; import { Card } from 'primereact/card'; import { Chart } from 'primereact/chart'; import { ProgressBar } from 'primereact/progressbar'; import { Tag } from 'primereact/tag'; import { Button } from 'primereact/button'; import { Toast } from 'primereact/toast'; import { DataView } from 'primereact/dataview'; import { Knob } from 'primereact/knob'; import { Timeline } from 'primereact/timeline'; import { Dropdown } from 'primereact/dropdown'; import { Calendar } from 'primereact/calendar'; import { Badge } from 'primereact/badge'; import { Panel } from 'primereact/panel'; import { Page } from '@/types'; import phaseChantierService from '@/services/phaseChantierService'; import { PhaseChantier } from '@/types/btp-extended'; const PhasesDashboardPage: Page = () => { const [phases, setPhases] = useState([]); const [loading, setLoading] = useState(true); const [selectedPeriod, setSelectedPeriod] = useState('semaine'); const [chartData, setChartData] = useState({}); const [avancementData, setAvancementData] = useState({}); const [budgetData, setBudgetData] = useState({}); const [timelineData, setTimelineData] = useState([]); const toast = useRef(null); const periodOptions = [ { label: 'Cette semaine', value: 'semaine' }, { label: 'Ce mois', value: 'mois' }, { label: 'Ce trimestre', value: 'trimestre' } ]; useEffect(() => { loadDashboardData(); }, [selectedPeriod]); const loadDashboardData = async () => { try { setLoading(true); // Charger les phases actives (exemple avec chantierId = 1) const data = await phaseChantierService.getByChantier(1); setPhases(data || []); // Préparer les données pour les graphiques prepareChartData(data || []); prepareTimelineData(data || []); } catch (error) { console.error('Erreur lors du chargement des données:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les données du tableau de bord', life: 3000 }); } finally { setLoading(false); } }; const prepareChartData = (data: PhaseChantier[]) => { const documentStyle = getComputedStyle(document.documentElement); // Graphique de répartition par statut const statutCounts = data.reduce((acc, phase) => { acc[phase.statut] = (acc[phase.statut] || 0) + 1; return acc; }, {} as Record); const statutData = { labels: Object.keys(statutCounts).map(statut => phaseChantierService.getStatutLabel(statut as any) ), datasets: [{ data: Object.values(statutCounts), backgroundColor: [ documentStyle.getPropertyValue('--blue-500'), documentStyle.getPropertyValue('--green-500'), documentStyle.getPropertyValue('--yellow-500'), documentStyle.getPropertyValue('--orange-500'), documentStyle.getPropertyValue('--red-500'), documentStyle.getPropertyValue('--gray-500'), ], hoverBackgroundColor: [ documentStyle.getPropertyValue('--blue-400'), documentStyle.getPropertyValue('--green-400'), documentStyle.getPropertyValue('--yellow-400'), documentStyle.getPropertyValue('--orange-400'), documentStyle.getPropertyValue('--red-400'), documentStyle.getPropertyValue('--gray-400'), ] }] }; // Graphique d'avancement moyen const avancementMoyen = data.length > 0 ? data.reduce((sum, phase) => sum + (phase.pourcentageAvancement || 0), 0) / data.length : 0; // Graphique budget vs coût const budgetVsCout = { labels: data.map(phase => phase.nom?.substring(0, 15) + '...'), datasets: [ { label: 'Budget prévu', backgroundColor: documentStyle.getPropertyValue('--blue-500'), borderColor: documentStyle.getPropertyValue('--blue-500'), data: data.map(phase => phase.budgetPrevu || 0) }, { label: 'Coût réel', backgroundColor: documentStyle.getPropertyValue('--red-500'), borderColor: documentStyle.getPropertyValue('--red-500'), data: data.map(phase => phase.coutReel || 0) } ] }; setChartData(statutData); setAvancementData({ value: avancementMoyen }); setBudgetData(budgetVsCout); }; const prepareTimelineData = (data: PhaseChantier[]) => { // Créer une timeline des prochaines échéances const prochaines = data .filter(phase => phase.dateFinPrevue && phase.statut !== 'TERMINEE') .sort((a, b) => new Date(a.dateFinPrevue!).getTime() - new Date(b.dateFinPrevue!).getTime()) .slice(0, 5) .map(phase => { const dateEcheance = new Date(phase.dateFinPrevue!); const maintenant = new Date(); const joursRestants = Math.ceil((dateEcheance.getTime() - maintenant.getTime()) / (1000 * 60 * 60 * 24)); return { phase: phase.nom, date: dateEcheance.toLocaleDateString('fr-FR'), joursRestants, statut: phase.statut, avancement: phase.pourcentageAvancement || 0, critique: phase.critique, color: joursRestants < 0 ? '#FF6B6B' : joursRestants < 7 ? '#FFA726' : '#66BB6A', icon: joursRestants < 0 ? 'pi pi-exclamation-triangle' : joursRestants < 7 ? 'pi pi-clock' : 'pi pi-calendar' }; }); setTimelineData(prochaines); }; const calculateKPIs = () => { const stats = phaseChantierService.calculateStatistiques(phases); const phasesEnRetard = phases.filter(phase => phaseChantierService.isEnRetard(phase)).length; const phasesCritiques = phases.filter(phase => phase.critique).length; const budgetUtilise = (stats.coutTotal / stats.budgetTotal) * 100; return { avancementGlobal: stats.avancementMoyen, respectDelais: ((phases.length - phasesEnRetard) / phases.length) * 100, budgetUtilise, phasesCritiques, stats }; }; const kpis = calculateKPIs(); const phaseItemTemplate = (phase: PhaseChantier) => { const retard = phaseChantierService.isEnRetard(phase); const avancement = phase.pourcentageAvancement || 0; return (
{phase.nom}

{phase.chantier?.nom || 'Chantier non défini'}

{phase.critique && ( )} {retard && ( )}
Avancement {avancement.toFixed(1)}%
Début prévu
{phase.dateDebutPrevue ? new Date(phase.dateDebutPrevue).toLocaleDateString('fr-FR') : 'Non défini' }
Fin prévue
{phase.dateFinPrevue ? new Date(phase.dateFinPrevue).toLocaleDateString('fr-FR') : 'Non défini' }
Budget
{new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', notation: 'compact' }).format(phase.budgetPrevu || 0)}
); }; const chartOptions = { plugins: { legend: { position: 'bottom' as const, labels: { usePointStyle: true, padding: 20 } } }, maintainAspectRatio: false }; const budgetChartOptions = { plugins: { legend: { position: 'top' as const } }, responsive: true, scales: { x: { ticks: { maxRotation: 45 } }, y: { beginAtZero: true, ticks: { callback: function(value: any) { return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', notation: 'compact' }).format(value); } } } } }; return (
{/* Header avec filtres */}

Tableau de Bord - Phases

setSelectedPeriod(e.value)} className="w-12rem" />
{/* KPIs principaux */}
Avancement Global

{kpis.avancementGlobal.toFixed(1)}% terminé

Respect des Délais

{kpis.respectDelais.toFixed(1)}% dans les temps

100 ? "#F44336" : "#FF9800"} rangeColor="#FFF3E0" />
Budget Utilisé

{kpis.budgetUtilise.toFixed(1)}% du budget

{kpis.phasesCritiques}
Phases Critiques

Nécessitent une attention

{/* Graphiques */}
{/* Timeline des prochaines échéances */}
( )} content={(item) => (
{item.phase}
{item.date} • {item.joursRestants >= 0 ? `${item.joursRestants} jours restants` : `${Math.abs(item.joursRestants)} jours de retard` }
{item.avancement.toFixed(1)}% {item.critique && ( )}
)} />
{/* Vue des phases actives */} p.statut === 'EN_COURS')} layout="grid" itemTemplate={phaseItemTemplate} paginator rows={6} emptyMessage="Aucune phase active" />
); }; export default PhasesDashboardPage;