'use client'; export const dynamic = 'force-dynamic'; import React, { useState, useEffect, useRef } from 'react'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Button } from 'primereact/button'; import { Toast } from 'primereact/toast'; import { Card } from 'primereact/card'; import { Tag } from 'primereact/tag'; import { Badge } from 'primereact/badge'; import { ProgressBar } from 'primereact/progressbar'; import { Toolbar } from 'primereact/toolbar'; import { Panel } from 'primereact/panel'; import { Chart } from 'primereact/chart'; import { Timeline } from 'primereact/timeline'; import { Message } from 'primereact/message'; import { Page } from '@/types'; import phaseChantierService from '@/services/phaseChantierService'; import { PhaseChantier } from '@/types/btp-extended'; const PhasesEnRetardPage: Page = () => { const [phasesEnRetard, setPhasesEnRetard] = useState([]); const [loading, setLoading] = useState(true); const [statistiques, setStatistiques] = useState({ total: 0, retardMoyen: 0, impactBudget: 0, phasesUrgentes: 0 }); const [chartData, setChartData] = useState({}); const [chartOptions, setChartOptions] = useState({}); const toast = useRef(null); useEffect(() => { loadPhasesEnRetard(); initChart(); }, []); const loadPhasesEnRetard = async () => { try { setLoading(true); const data = await phaseChantierService.getEnRetard(); setPhasesEnRetard(data || []); // Calculer les statistiques if (data && data.length > 0) { const retards = data.map(phase => phaseChantierService.calculateRetard(phase)); const retardMoyen = retards.reduce((a, b) => a + b, 0) / retards.length; const impactBudget = data.reduce((total, phase) => { const ecart = (phase.coutReel || 0) - (phase.budgetPrevu || 0); return total + (ecart > 0 ? ecart : 0); }, 0); const phasesUrgentes = data.filter(phase => phaseChantierService.calculateRetard(phase) > 30 ).length; setStatistiques({ total: data.length, retardMoyen: Math.round(retardMoyen), impactBudget, phasesUrgentes }); } } catch (error) { console.error('Erreur lors du chargement des phases en retard:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les phases en retard', life: 3000 }); } finally { setLoading(false); } }; const initChart = () => { const documentStyle = getComputedStyle(document.documentElement); const textColor = documentStyle.getPropertyValue('--text-color'); const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary'); const surfaceBorder = documentStyle.getPropertyValue('--surface-border'); const data = { labels: ['1-7 jours', '8-15 jours', '16-30 jours', '> 30 jours'], datasets: [ { label: 'Phases en retard', backgroundColor: ['#FFF3CD', '#FCF8E3', '#F8D7DA', '#D32F2F'], borderColor: ['#856404', '#856404', '#721C24', '#B71C1C'], data: [0, 0, 0, 0] // Sera calculé dynamiquement } ] }; const options = { maintainAspectRatio: false, aspectRatio: 0.6, plugins: { legend: { labels: { fontColor: textColor } } }, scales: { x: { ticks: { color: textColorSecondary }, grid: { color: surfaceBorder } }, y: { ticks: { color: textColorSecondary }, grid: { color: surfaceBorder } } } }; setChartData(data); setChartOptions(options); }; const actionBodyTemplate = (rowData: PhaseChantier) => { return (
); }; const retardBodyTemplate = (rowData: PhaseChantier) => { const retard = phaseChantierService.calculateRetard(rowData); let severity: 'info' | 'warning' | 'danger' = 'info'; if (retard > 30) severity = 'danger'; else if (retard > 15) severity = 'warning'; return ( ); }; const impactBodyTemplate = (rowData: PhaseChantier) => { const budgetPrevu = rowData.budgetPrevu || 0; const coutReel = rowData.coutReel || 0; const ecart = coutReel - budgetPrevu; if (ecart <= 0) return ; const pourcentageEcart = budgetPrevu > 0 ? (ecart / budgetPrevu * 100) : 0; return (
+{pourcentageEcart.toFixed(1)}%
); }; const prioriteBodyTemplate = (rowData: PhaseChantier) => { const retard = phaseChantierService.calculateRetard(rowData); const critique = rowData.critique; let priorite = 'Normale'; let severity: 'info' | 'warning' | 'danger' = 'info'; if (critique && retard > 15) { priorite = 'URGENTE'; severity = 'danger'; } else if (retard > 30) { priorite = 'Très haute'; severity = 'danger'; } else if (retard > 15) { priorite = 'Haute'; severity = 'warning'; } return ; }; const responsableBodyTemplate = (rowData: PhaseChantier) => { return rowData.responsable ? `${rowData.responsable.prenom} ${rowData.responsable.nom}` : 'Non assigné'; }; const relancerPhase = async (phase: PhaseChantier) => { try { if (phase.id) { await phaseChantierService.resume(Number(phase.id)); await loadPhasesEnRetard(); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Phase relancée avec succès', life: 3000 }); } } catch (error) { toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Erreur lors de la relance', life: 3000 }); } }; const replanifierPhase = (phase: PhaseChantier) => { // TODO: Ouvrir un dialog de replanification toast.current?.show({ severity: 'info', summary: 'Fonctionnalité', detail: 'Replanification à implémenter', life: 3000 }); }; const escaladerPhase = (phase: PhaseChantier) => { // TODO: Implémenter l'escalade (notification aux responsables) toast.current?.show({ severity: 'warn', summary: 'Escalade', detail: `Phase ${phase.nom} escaladée vers la direction`, life: 3000 }); }; const exportData = () => { // TODO: Implémenter l'export des données toast.current?.show({ severity: 'info', summary: 'Export', detail: 'Export en cours...', life: 3000 }); }; const leftToolbarTemplate = () => { return (
); }; const rightToolbarTemplate = () => { return (
); }; // Timeline des actions recommandées const actionsRecommandees = [ { status: 'Immédiat', date: 'Aujourd\'hui', icon: 'pi pi-exclamation-triangle', color: '#FF6B6B', description: `${statistiques.phasesUrgentes} phases critiques à traiter en urgence` }, { status: 'Cette semaine', date: '7 jours', icon: 'pi pi-calendar', color: '#4ECDC4', description: 'Replanification des phases avec retard modéré' }, { status: 'Ce mois', date: '30 jours', icon: 'pi pi-chart-line', color: '#45B7D1', description: 'Analyse des causes et mise en place d\'actions préventives' } ]; return (
{/* Alerte si phases critiques */} {statistiques.phasesUrgentes > 0 && ( )} {/* Statistiques de retard */}
{statistiques.total}
Phases en retard
{statistiques.retardMoyen}j
Retard moyen
{new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', notation: 'compact' }).format(statistiques.impactBudget)}
Impact budget
{statistiques.phasesUrgentes}
Phases urgentes
{/* Liste des phases en retard */}
rowData.chantier?.nom || 'N/A'} sortable style={{ minWidth: '10rem' }} />
{/* Actions recommandées */}
( )} content={(item) => (
{item.status}
{item.date}
{item.description}
)} />
); }; export default PhasesEnRetardPage;