'use client'; export const dynamic = 'force-dynamic'; import React, { useState, useEffect, useRef } from 'react'; import { Card } from 'primereact/card'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Button } from 'primereact/button'; import { Tag } from 'primereact/tag'; import { ProgressBar } from 'primereact/progressbar'; import { Dialog } from 'primereact/dialog'; import { InputTextarea } from 'primereact/inputtextarea'; import { Dropdown } from 'primereact/dropdown'; import { Timeline } from 'primereact/timeline'; import { Toast } from 'primereact/toast'; import { Divider } from 'primereact/divider'; import { Badge } from 'primereact/badge'; import { Knob } from 'primereact/knob'; import chantierService from '../../../../services/chantierService'; /** * Page Workflow Chantiers BTP Express * Gestion complète du cycle de vie des chantiers avec transitions d'état */ const WorkflowChantiers = () => { const [chantiers, setChantiers] = useState([]); const [loading, setLoading] = useState(true); const [workflowDialog, setWorkflowDialog] = useState(false); const [selectedChantier, setSelectedChantier] = useState(null); const [nouveauStatut, setNouveauStatut] = useState(''); const [commentaire, setCommentaire] = useState(''); const [historique, setHistorique] = useState([]); const [metriques, setMetriques] = useState({}); const toast = useRef(null); // Définition des transitions workflow BTP const workflowTransitions = { 'PLANIFIE': [ { label: 'Démarrer le chantier', value: 'EN_COURS', icon: 'pi-play', color: 'success' }, { label: 'Annuler', value: 'ANNULE', icon: 'pi-times', color: 'danger' } ], 'EN_COURS': [ { label: 'Terminer le chantier', value: 'TERMINE', icon: 'pi-check', color: 'success' }, { label: 'Suspendre', value: 'SUSPENDU', icon: 'pi-pause', color: 'warning' }, { label: 'Annuler', value: 'ANNULE', icon: 'pi-times', color: 'danger' } ], 'SUSPENDU': [ { label: 'Reprendre', value: 'EN_COURS', icon: 'pi-play', color: 'info' }, { label: 'Annuler définitivement', value: 'ANNULE', icon: 'pi-times', color: 'danger' } ], 'TERMINE': [], // Statut final 'ANNULE': [] // Statut final }; const statutsConfig = { 'PLANIFIE': { color: 'info', icon: 'pi-calendar', label: 'Planifié' }, 'EN_COURS': { color: 'success', icon: 'pi-cog', label: 'En cours' }, 'SUSPENDU': { color: 'warning', icon: 'pi-pause', label: 'Suspendu' }, 'TERMINE': { color: 'success', icon: 'pi-check-circle', label: 'Terminé' }, 'ANNULE': { color: 'danger', icon: 'pi-times-circle', label: 'Annulé' } }; useEffect(() => { loadChantiers(); loadMetriques(); }, []); const loadChantiers = async () => { try { setLoading(true); // Simulation données chantiers avec workflow const mockChantiers = [ { id: '1', nom: 'Rénovation Villa Rousseau', client: 'Claire Rousseau', statut: 'EN_COURS' as any, avancement: 65, dateDebut: '2025-01-15', dateFinPrevue: '2025-03-20', montantPrevu: 85000, montantReel: 72500, equipe: 'Équipe Rénovation A', phases: [ { nom: 'Démolition', statut: 'TERMINE' as any, avancement: 100 }, { nom: 'Gros œuvre', statut: 'EN_COURS' as any, avancement: 80 }, { nom: 'Second œuvre', statut: 'PLANIFIE', avancement: 0 }, { nom: 'Finitions', statut: 'PLANIFIE', avancement: 0 } ], alertes: ['Retard de 3 jours sur livraison matériaux'], derniereMiseAJour: new Date() }, { id: '2', nom: 'Extension Maison Martin', client: 'Sophie Martin', statut: 'PLANIFIE', avancement: 0, dateDebut: '2025-02-10', dateFinPrevue: '2025-05-15', montantPrevu: 45000, montantReel: 0, equipe: 'Équipe Extension B', phases: [ { nom: 'Fondations', statut: 'PLANIFIE', avancement: 0 }, { nom: 'Élévation', statut: 'PLANIFIE', avancement: 0 }, { nom: 'Couverture', statut: 'PLANIFIE', avancement: 0 }, { nom: 'Aménagement', statut: 'PLANIFIE', avancement: 0 } ], alertes: [], derniereMiseAJour: new Date() }, { id: '3', nom: 'Réfection Toiture Dupont', client: 'Jean Dupont', statut: 'SUSPENDU' as any, avancement: 30, dateDebut: '2025-01-08', dateFinPrevue: '2025-02-28', montantPrevu: 28000, montantReel: 12000, equipe: 'Équipe Couverture C', phases: [ { nom: 'Dépose ancienne toiture', statut: 'TERMINE' as any, avancement: 100 }, { nom: 'Charpente', statut: 'SUSPENDU' as any, avancement: 60 }, { nom: 'Couverture neuve', statut: 'PLANIFIE', avancement: 0 }, { nom: 'Isolation', statut: 'PLANIFIE', avancement: 0 } ], alertes: ['Chantier suspendu - Problème météorologique', 'Attente validation assurance'], derniereMiseAJour: new Date() } ]; setChantiers(mockChantiers); } catch (error) { console.error('Erreur chargement chantiers:', error); } finally { setLoading(false); } }; const loadMetriques = () => { setMetriques({ totalChantiers: 12, enCours: 5, planifies: 4, terminesRecemment: 2, suspendus: 1, tauxReussite: 92, delaiMoyen: -2 // Négatif = en avance }); }; const ouvrirWorkflow = (chantier: any) => { setSelectedChantier(chantier); setNouveauStatut(''); setCommentaire(''); // Charger historique du chantier const mockHistorique = [ { date: new Date('2025-01-15T08:00:00'), statut: 'EN_COURS' as any, utilisateur: 'M. Laurent', commentaire: 'Démarrage chantier - Équipe mobilisée', automatique: false }, { date: new Date('2025-01-20T14:30:00'), statut: 'EN_COURS' as any, utilisateur: 'Système', commentaire: 'Phase démolition terminée automatiquement', automatique: true }, { date: new Date('2025-01-25T10:15:00'), statut: 'EN_COURS' as any, utilisateur: 'Mme Petit', commentaire: 'Avancement gros œuvre - 50% réalisé', automatique: false } ]; setHistorique(mockHistorique); setWorkflowDialog(true); }; const executerTransition = async () => { if (!selectedChantier || !nouveauStatut) return; try { // Simuler appel API pour changer le statut const chantierMisAJour = { ...selectedChantier, statut: nouveauStatut, derniereMiseAJour: new Date() }; // Logique métier spécifique selon le statut if (nouveauStatut === 'EN_COURS') { chantierMisAJour.dateDebutReel = new Date(); } else if (nouveauStatut === 'TERMINE') { chantierMisAJour.dateFinReelle = new Date(); chantierMisAJour.avancement = 100; } // Mettre à jour la liste const chantiersUpdated = chantiers.map(c => c.id === selectedChantier.id ? chantierMisAJour : c ); setChantiers(chantiersUpdated); // Ajouter à l'historique const nouvelleEntree = { date: new Date(), statut: nouveauStatut, utilisateur: 'Utilisateur actuel', commentaire: commentaire || `Changement vers ${statutsConfig[nouveauStatut as keyof typeof statutsConfig]?.label}`, automatique: false }; setHistorique([nouvelleEntree, ...historique]); toast.current?.show({ severity: 'success', summary: 'Transition réussie', detail: `Chantier "${selectedChantier.nom}" passé en statut ${statutsConfig[nouveauStatut as keyof typeof statutsConfig]?.label}`, life: 4000 }); setWorkflowDialog(false); loadMetriques(); // Recalculer les métriques } catch (error) { console.error('Erreur transition:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible d\'effectuer la transition', life: 3000 }); } }; const statutBodyTemplate = (rowData: any) => { const config = statutsConfig[rowData.statut as keyof typeof statutsConfig]; return (
); }; const avancementBodyTemplate = (rowData: any) => { return (
= 100 ? '#10B981' : rowData.avancement >= 50 ? '#F59E0B' : '#3B82F6'} /> {rowData.avancement}%
); }; const alertesBodyTemplate = (rowData: any) => { if (!rowData.alertes || rowData.alertes.length === 0) { return -; } return (
); }; const actionsBodyTemplate = (rowData: any) => { const transitionsPossibles = workflowTransitions[rowData.statut as keyof typeof workflowTransitions] || []; return (
); }; const phasesBodyTemplate = (rowData: any) => { const phasesTerminees = rowData.phases?.filter((p: any) => p.statut === 'TERMINE').length || 0; const totalPhases = rowData.phases?.length || 0; return (
{phasesTerminees}/{totalPhases} 0 ? (phasesTerminees / totalPhases) * 100 : 0} style={{ width: '60px', height: '6px' }} />
); }; return (
{/* Métriques Workflow */}
{metriques.totalChantiers}
Total chantiers
{metriques.enCours}
En cours
{metriques.planifies}
Planifiés
{metriques.suspendus}
Suspendus
Taux réussite
{metriques.delaiMoyen > 0 ? `+${metriques.delaiMoyen}` : metriques.delaiMoyen}j
Délai moyen
{/* Tableau chantiers avec workflow */}
new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', notation: 'compact' }).format(rowData.montantPrevu)} style={{ minWidth: '100px' }} />
{/* Dialog Workflow */} setWorkflowDialog(false)} maximizable > {selectedChantier && (
{/* Informations chantier */}
Client: {selectedChantier.client}
Statut actuel:
Avancement:
Équipe: {selectedChantier.equipe}
Budget: {new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(selectedChantier.montantPrevu)}
{/* Transitions disponibles */}
{workflowTransitions[selectedChantier.statut as keyof typeof workflowTransitions]?.map((transition) => (
)}
{/* Historique */}
(
{item.automatique && }
{item.date.toLocaleString('fr-FR')}
{item.utilisateur}
{item.commentaire}
)} />
)} ); }; export default WorkflowChantiers;