'use client'; import React, { useState, useEffect, useRef } from 'react'; import { useParams } from 'next/navigation'; import { Card } from 'primereact/card'; import { Checkbox } from 'primereact/checkbox'; import { Button } from 'primereact/button'; import { ProgressBar } from 'primereact/progressbar'; import { Badge } from 'primereact/badge'; import { Toast } from 'primereact/toast'; import { Accordion, AccordionTab } from 'primereact/accordion'; import { TreeTable } from 'primereact/treetable'; import { Column } from 'primereact/column'; import { Dialog } from 'primereact/dialog'; import { InputTextarea } from 'primereact/inputtextarea'; import { Avatar } from 'primereact/avatar'; import { apiClient } from '../../../../../services/api-client'; /** * Interface pour les tâches d'exécution avec état de completion */ interface TacheExecution { id: string; nom: string; description?: string; ordreExecution: number; dureeEstimeeMinutes?: number; critique: boolean; bloquante: boolean; priorite: 'BASSE' | 'NORMALE' | 'HAUTE'; niveauQualification?: string; nombreOperateursRequis: number; conditionsMeteo: string; outilsRequis?: string[]; materiauxRequis?: string[]; // État d'exécution terminee: boolean; dateCompletion?: Date; completeepar?: string; commentaires?: string; tempsRealise?: number; // en minutes difficulteRencontree?: 'AUCUNE' | 'FAIBLE' | 'MOYENNE' | 'ELEVEE'; } interface SousPhaseExecution { id: string; nom: string; description?: string; ordreExecution: number; taches: TacheExecution[]; // Calculé automatiquement avancement: number; // % basé sur les tâches terminées tachesTerminees: number; totalTaches: number; dureeRealisee: number; // en minutes dureeEstimee: number; // en minutes } interface PhaseExecution { id: string; nom: string; description?: string; ordreExecution: number; sousPhases: SousPhaseExecution[]; // Calculé automatiquement avancement: number; // % basé sur les sous-phases sousPhasesTerminees: number; totalSousPhases: number; } interface ChantierExecution { id: string; nom: string; client: string; dateDebut: Date; dateFinPrevue: Date; phases: PhaseExecution[]; // Avancement global calculé avancementGlobal: number; // % basé sur toutes les tâches totalTachesTerminees: number; totalTaches: number; } /** * Page d'exécution granulaire d'un chantier * Permet de cocher les tâches terminées pour un suivi précis de l'avancement */ const ExecutionGranulaireChantier = () => { const { id } = useParams(); const toast = useRef(null); // États principaux const [chantier, setChantier] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); // États pour les modals const [showCommentDialog, setShowCommentDialog] = useState(false); const [selectedTache, setSelectedTache] = useState(null); const [commentaires, setCommentaires] = useState(''); const [tempsRealise, setTempsRealise] = useState(0); const [difficulte, setDifficulte] = useState<'AUCUNE' | 'FAIBLE' | 'MOYENNE' | 'ELEVEE'>('AUCUNE'); // États pour la vue const [activeIndex, setActiveIndex] = useState(0); const [expandedKeys, setExpandedKeys] = useState({}); useEffect(() => { if (id) { loadChantierExecution(id as string); } }, [id]); /** * Charge les données d'exécution du chantier */ const loadChantierExecution = async (chantierId: string) => { try { setLoading(true); // Charger les informations de base du chantier const chantierResponse = await apiClient.get(`/chantiers/${chantierId}`); const chantierData = chantierResponse.data; // Charger les phases avec leurs sous-phases et tâches const phasesResponse = await apiClient.get(`/chantiers/${chantierId}/phases`); const phasesData = phasesResponse.data; // Pour chaque phase, charger les tâches d'exécution const phasesWithExecution = await Promise.all( phasesData.map(async (phase: any) => { const sousPhasesWithTaches = await Promise.all( phase.sousPhases?.map(async (sousPhase: any) => { // Charger les tâches et leur état d'exécution const tachesExecution = await loadTachesExecution(chantierId, sousPhase.id); // Calculer l'avancement de la sous-phase const tachesTerminees = tachesExecution.filter(t => t.terminee).length; const avancement = tachesExecution.length > 0 ? (tachesTerminees / tachesExecution.length) * 100 : 0; const dureeRealisee = tachesExecution .filter(t => t.terminee && t.tempsRealise) .reduce((sum, t) => sum + (t.tempsRealise || 0), 0); const dureeEstimee = tachesExecution .reduce((sum, t) => sum + (t.dureeEstimeeMinutes || 0), 0); return { ...sousPhase, taches: tachesExecution, avancement: Math.round(avancement), tachesTerminees, totalTaches: tachesExecution.length, dureeRealisee, dureeEstimee }; }) || [] ); // Calculer l'avancement de la phase const totalTaches = sousPhasesWithTaches.reduce((sum, sp) => sum + sp.totalTaches, 0); const totalTachesTerminees = sousPhasesWithTaches.reduce((sum, sp) => sum + sp.tachesTerminees, 0); const avancement = totalTaches > 0 ? (totalTachesTerminees / totalTaches) * 100 : 0; return { ...phase, sousPhases: sousPhasesWithTaches, avancement: Math.round(avancement), totalSousPhases: sousPhasesWithTaches.length, sousPhasesTerminees: sousPhasesWithTaches.filter(sp => sp.avancement === 100).length }; }) ); // Calculer l'avancement global du chantier const totalTaches = phasesWithExecution.reduce((sum, phase) => sum + phase.sousPhases.reduce((spSum, sp) => spSum + sp.totalTaches, 0), 0 ); const totalTachesTerminees = phasesWithExecution.reduce((sum, phase) => sum + phase.sousPhases.reduce((spSum, sp) => spSum + sp.tachesTerminees, 0), 0 ); const avancementGlobal = totalTaches > 0 ? (totalTachesTerminees / totalTaches) * 100 : 0; setChantier({ id: chantierData.id, nom: chantierData.nom, client: chantierData.client?.nom || chantierData.client, dateDebut: new Date(chantierData.dateDebut), dateFinPrevue: new Date(chantierData.dateFinPrevue), phases: phasesWithExecution, avancementGlobal: Math.round(avancementGlobal), totalTaches, totalTachesTerminees }); } catch (error) { console.error('Erreur lors du chargement:', error); showToast('error', 'Erreur', 'Impossible de charger les données d\'exécution'); } finally { setLoading(false); } }; /** * Charge les tâches d'exécution pour une sous-phase */ const loadTachesExecution = async (chantierId: string, sousPhaseId: string): Promise => { try { // Charger les templates de tâches const templatesResponse = await apiClient.get(`/tache-templates/by-sous-phase/${sousPhaseId}`); const templates = templatesResponse.data; // Charger les états d'exécution (s'ils existent) let etatsExecution = []; try { const etatsResponse = await apiClient.get(`/chantiers/${chantierId}/taches-execution/${sousPhaseId}`); etatsExecution = etatsResponse.data; } catch (error) { // Pas encore d'états d'exécution, on démarre avec toutes les tâches non terminées console.log('Aucun état d\'exécution trouvé, initialisation...'); } // Combiner templates avec états d'exécution return templates.map((template: any) => { const etat = etatsExecution.find((e: any) => e.tacheTemplateId === template.id); return { ...template, terminee: etat?.terminee || false, dateCompletion: etat?.dateCompletion ? new Date(etat.dateCompletion) : undefined, completeepar: etat?.completeepar, commentaires: etat?.commentaires, tempsRealise: etat?.tempsRealise, difficulteRencontree: etat?.difficulteRencontree || 'AUCUNE' }; }); } catch (error) { console.error('Erreur lors du chargement des tâches:', error); return []; } }; /** * Marque une tâche comme terminée ou non terminée */ const toggleTacheTerminee = async (tache: TacheExecution) => { if (!tache.terminee) { // Marquer comme terminée - ouvrir le dialog pour les détails setSelectedTache(tache); setCommentaires(tache.commentaires || ''); setTempsRealise(tache.tempsRealise || tache.dureeEstimeeMinutes || 0); setDifficulte(tache.difficulteRencontree || 'AUCUNE'); setShowCommentDialog(true); } else { // Marquer comme non terminée await saveTacheExecution(tache, false); } }; /** * Sauvegarde l'état d'exécution d'une tâche */ const saveTacheExecution = async (tache: TacheExecution, terminee: boolean, details?: any) => { try { setSaving(true); const executionData = { chantierID: id, tacheTemplateId: tache.id, terminee, dateCompletion: terminee ? new Date().toISOString() : null, completeepar: terminee ? 'CURRENT_USER' : null, // À remplacer par l'utilisateur connecté commentaires: details?.commentaires || '', tempsRealise: details?.tempsRealise || null, difficulteRencontree: details?.difficulte || 'AUCUNE' }; await apiClient.post(`/chantiers/${id}/taches-execution`, executionData); showToast('success', 'Succès', terminee ? 'Tâche marquée comme terminée' : 'Tâche marquée comme non terminée' ); // Recharger les données pour mettre à jour l'avancement await loadChantierExecution(id as string); } catch (error) { console.error('Erreur lors de la sauvegarde:', error); showToast('error', 'Erreur', 'Impossible de sauvegarder l\'état de la tâche'); } finally { setSaving(false); } }; /** * Confirme la completion d'une tâche avec les détails */ const confirmerCompletion = async () => { if (!selectedTache) return; await saveTacheExecution(selectedTache, true, { commentaires, tempsRealise, difficulte }); setShowCommentDialog(false); setSelectedTache(null); }; /** * Affiche un toast message */ const showToast = (severity: 'success' | 'info' | 'warn' | 'error', summary: string, detail: string) => { toast.current?.show({ severity, summary, detail, life: 3000 }); }; /** * Template pour l'affichage d'une tâche avec checkbox */ const renderTacheCheckbox = (tache: TacheExecution) => { const isDisabled = saving; return (
toggleTacheTerminee(tache)} disabled={isDisabled} />
{tache.nom}
{tache.description && (
{tache.description}
)}
{tache.critique && } {tache.bloquante && } {tache.priorite === 'HAUTE' && }
{tache.dureeEstimeeMinutes && (
Estimé: {Math.floor(tache.dureeEstimeeMinutes / 60)}h{tache.dureeEstimeeMinutes % 60}min
)} {tache.terminee && tache.tempsRealise && (
Réalisé: {Math.floor(tache.tempsRealise / 60)}h{tache.tempsRealise % 60}min
)}
); }; if (loading) { return (
Chargement de l'exécution du chantier...
); } if (!chantier) { return (
Chantier non trouvé

Impossible de charger les données d'exécution du chantier.

); } return (
{/* En-tête avec avancement global */}

{chantier.nom}

Client: {chantier.client} • Début: {chantier.dateDebut.toLocaleDateString('fr-FR')} • Fin prévue: {chantier.dateFinPrevue.toLocaleDateString('fr-FR')}
Avancement Global {chantier.avancementGlobal}%
{chantier.totalTachesTerminees} / {chantier.totalTaches} tâches terminées
{/* Phases avec sous-phases et tâches */}
setActiveIndex(e.index)}> {chantier.phases.map((phase, phaseIndex) => (
{phase.nom}
{phase.avancement}%
} >
{phase.sousPhases.map((sousPhase) => (
{sousPhase.nom}
{sousPhase.description && (

{sousPhase.description}

)}
{sousPhase.tachesTerminees} / {sousPhase.totalTaches} tâches terminées
{sousPhase.avancement}%
{sousPhase.taches.map((tache) => (
{renderTacheCheckbox(tache)}
))}
))}
))}
{/* Dialog de completion de tâche */} setShowCommentDialog(false)} style={{ width: '50vw' }} breakpoints={{ '960px': '75vw', '641px': '90vw' }} modal > {selectedTache && (
{selectedTache.nom}
{selectedTache.description && (

{selectedTache.description}

)}
setCommentaires(e.target.value)} rows={3} placeholder="Commentaires sur la réalisation de cette tâche..." />
setTempsRealise(parseInt(e.target.value) || 0)} min={0} /> {selectedTache.dureeEstimeeMinutes && ( Temps estimé: {selectedTache.dureeEstimeeMinutes} minutes )}
)}
); }; export default ExecutionGranulaireChantier;