'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 { Dialog } from 'primereact/dialog'; import { InputText } from 'primereact/inputtext'; import { Dropdown } from 'primereact/dropdown'; import { InputTextarea } from 'primereact/inputtextarea'; import { InputNumber } from 'primereact/inputnumber'; import { Checkbox } from 'primereact/checkbox'; import { Toast } from 'primereact/toast'; import { ConfirmDialog, confirmDialog } from 'primereact/confirmdialog'; import { TreeTable } from 'primereact/treetable'; import { Card } from 'primereact/card'; import { Badge } from 'primereact/badge'; import { ProgressBar } from 'primereact/progressbar'; import { apiClient } from '../../../../services/api-client'; /** * Interface pour les templates de tâches */ interface TacheTemplate { id: string; nom: string; description?: string; ordreExecution: number; dureeEstimeeMinutes?: number; critique: boolean; bloquante: boolean; priorite: 'BASSE' | 'NORMALE' | 'HAUTE'; niveauQualification?: 'MANOEUVRE' | 'OUVRIER_SPECIALISE' | 'OUVRIER_QUALIFIE' | 'COMPAGNON' | 'CHEF_EQUIPE' | 'TECHNICIEN' | 'EXPERT'; nombreOperateursRequis: number; outilsRequis?: string[]; materiauxRequis?: string[]; conditionsMeteo: 'TOUS_TEMPS' | 'TEMPS_SEC' | 'PAS_DE_VENT_FORT' | 'TEMPERATURE_POSITIVE' | 'PAS_DE_PLUIE' | 'INTERIEUR_UNIQUEMENT'; sousPhaseParent: { id: string; nom: string; }; actif: boolean; } interface SousPhaseTemplate { id: string; nom: string; description?: string; ordreExecution: number; taches: TacheTemplate[]; statistiques?: { totalTaches: number; tachesCritiques: number; tachesBloquantes: number; dureeEstimeeMinutes: number; }; } interface PhaseTemplate { id: string; nom: string; description?: string; ordreExecution: number; sousPhases: SousPhaseTemplate[]; } /** * Page de gestion granulaire des templates de tâches * Permet la gestion complète de la hiérarchie Phase → Sous-phase → Tâche */ const GestionTachesTemplates = () => { const toast = useRef(null); // États pour les données const [phases, setPhases] = useState([]); const [selectedPhase, setSelectedPhase] = useState(null); const [selectedSousPhase, setSelectedSousPhase] = useState(null); const [taches, setTaches] = useState([]); // États pour les modals const [showTacheDialog, setShowTacheDialog] = useState(false); const [showSousPhaseDialog, setShowSousPhaseDialog] = useState(false); const [editingTache, setEditingTache] = useState(null); // États pour les formulaires const [formTache, setFormTache] = useState>({ nom: '', description: '', critique: false, bloquante: false, priorite: 'NORMALE', nombreOperateursRequis: 1, conditionsMeteo: 'TOUS_TEMPS', actif: true }); // États pour l'interface const [loading, setLoading] = useState(false); const [expandedRows, setExpandedRows] = useState({}); // Options pour les dropdowns const prioriteOptions = [ { label: 'Basse', value: 'BASSE' }, { label: 'Normale', value: 'NORMALE' }, { label: 'Haute', value: 'HAUTE' } ]; const qualificationOptions = [ { label: 'Manœuvre', value: 'MANOEUVRE' }, { label: 'Ouvrier spécialisé', value: 'OUVRIER_SPECIALISE' }, { label: 'Ouvrier qualifié', value: 'OUVRIER_QUALIFIE' }, { label: 'Compagnon', value: 'COMPAGNON' }, { label: 'Chef d\'équipe', value: 'CHEF_EQUIPE' }, { label: 'Technicien', value: 'TECHNICIEN' }, { label: 'Expert', value: 'EXPERT' } ]; const meteoOptions = [ { label: 'Tous temps', value: 'TOUS_TEMPS' }, { label: 'Temps sec uniquement', value: 'TEMPS_SEC' }, { label: 'Pas de vent fort', value: 'PAS_DE_VENT_FORT' }, { label: 'Température positive', value: 'TEMPERATURE_POSITIVE' }, { label: 'Pas de pluie', value: 'PAS_DE_PLUIE' }, { label: 'Intérieur uniquement', value: 'INTERIEUR_UNIQUEMENT' } ]; // Chargement des données au montage useEffect(() => { loadPhaseTemplates(); }, []); // Chargement des tâches quand une sous-phase est sélectionnée useEffect(() => { if (selectedSousPhase) { loadTachesForSousPhase(selectedSousPhase.id); } }, [selectedSousPhase]); /** * Charge tous les templates de phases pour MAISON_INDIVIDUELLE */ const loadPhaseTemplates = async () => { try { setLoading(true); const response = await apiClient.get('/phase-templates/by-type/MAISON_INDIVIDUELLE'); // Pour chaque phase, charger ses sous-phases et tâches const phasesWithDetails = await Promise.all( response.data.map(async (phase: PhaseTemplate) => { const sousPhases = await loadSousPhasesForPhase(phase.id); return { ...phase, sousPhases }; }) ); setPhases(phasesWithDetails); if (phasesWithDetails.length > 0) { setSelectedPhase(phasesWithDetails[0]); } } catch (error) { showToast('error', 'Erreur', 'Impossible de charger les templates de phases'); } finally { setLoading(false); } }; /** * Charge les sous-phases pour une phase donnée */ const loadSousPhasesForPhase = async (phaseId: string): Promise => { try { const response = await apiClient.get(`/sous-phase-templates/by-phase/${phaseId}`); // Pour chaque sous-phase, charger ses tâches et statistiques const sousPhasesWithTaches = await Promise.all( response.data.map(async (sousPhase: SousPhaseTemplate) => { const [taches, statistiques] = await Promise.all([ loadTachesForSousPhase(sousPhase.id), loadStatistiquesForSousPhase(sousPhase.id) ]); return { ...sousPhase, taches, statistiques }; }) ); return sousPhasesWithTaches; } catch (error) { console.error('Erreur lors du chargement des sous-phases:', error); return []; } }; /** * Charge les tâches pour une sous-phase donnée */ const loadTachesForSousPhase = async (sousPhaseId: string): Promise => { try { const response = await apiClient.get(`/tache-templates/by-sous-phase/${sousPhaseId}`); setTaches(response.data); return response.data; } catch (error) { console.error('Erreur lors du chargement des tâches:', error); return []; } }; /** * Charge les statistiques pour une sous-phase donnée */ const loadStatistiquesForSousPhase = async (sousPhaseId: string) => { try { const response = await apiClient.get(`/tache-templates/stats/by-sous-phase/${sousPhaseId}`); return response.data; } catch (error) { console.error('Erreur lors du chargement des statistiques:', error); return null; } }; /** * Affiche un toast message */ const showToast = (severity: 'success' | 'info' | 'warn' | 'error', summary: string, detail: string) => { toast.current?.show({ severity, summary, detail, life: 3000 }); }; /** * Ouvre le dialog de création/édition d'une tâche */ const openTacheDialog = (tache?: TacheTemplate) => { if (!selectedSousPhase) { showToast('warn', 'Attention', 'Veuillez d\'abord sélectionner une sous-phase'); return; } if (tache) { setEditingTache(tache); setFormTache({ nom: tache.nom, description: tache.description, dureeEstimeeMinutes: tache.dureeEstimeeMinutes, critique: tache.critique, bloquante: tache.bloquante, priorite: tache.priorite, niveauQualification: tache.niveauQualification, nombreOperateursRequis: tache.nombreOperateursRequis, conditionsMeteo: tache.conditionsMeteo, outilsRequis: tache.outilsRequis, materiauxRequis: tache.materiauxRequis }); } else { setEditingTache(null); setFormTache({ nom: '', description: '', critique: false, bloquante: false, priorite: 'NORMALE', nombreOperateursRequis: 1, conditionsMeteo: 'TOUS_TEMPS', actif: true }); } setShowTacheDialog(true); }; /** * Sauvegarde une tâche (création ou modification) */ const saveTache = async () => { if (!selectedSousPhase || !formTache.nom?.trim()) { showToast('warn', 'Attention', 'Le nom de la tâche est obligatoire'); return; } try { const tacheData = { ...formTache, sousPhaseParent: { id: selectedSousPhase.id } }; if (editingTache) { await apiClient.put(`/tache-templates/${editingTache.id}`, tacheData); showToast('success', 'Succès', 'Tâche modifiée avec succès'); } else { await apiClient.post('/tache-templates', tacheData); showToast('success', 'Succès', 'Tâche créée avec succès'); } setShowTacheDialog(false); await loadTachesForSousPhase(selectedSousPhase.id); await loadPhaseTemplates(); // Recharger pour mettre à jour les statistiques } catch (error) { showToast('error', 'Erreur', 'Impossible de sauvegarder la tâche'); } }; /** * Supprime une tâche avec confirmation */ const deleteTache = (tache: TacheTemplate) => { confirmDialog({ message: `Êtes-vous sûr de vouloir supprimer la tâche "${tache.nom}" ?`, header: 'Confirmation de suppression', icon: 'pi pi-exclamation-triangle', acceptLabel: 'Oui', rejectLabel: 'Non', accept: async () => { try { await apiClient.delete(`/tache-templates/${tache.id}`); showToast('success', 'Succès', 'Tâche supprimée avec succès'); await loadTachesForSousPhase(selectedSousPhase!.id); await loadPhaseTemplates(); } catch (error) { showToast('error', 'Erreur', 'Impossible de supprimer la tâche'); } } }); }; /** * Template pour l'affichage des actions dans le tableau */ const actionBodyTemplate = (rowData: TacheTemplate) => { return (
); }; /** * Template pour l'affichage de la priorité */ const prioriteBodyTemplate = (rowData: TacheTemplate) => { const severity = rowData.priorite === 'HAUTE' ? 'danger' : rowData.priorite === 'NORMALE' ? 'info' : 'success'; return ; }; /** * Template pour l'affichage de la durée */ const dureeBodyTemplate = (rowData: TacheTemplate) => { if (!rowData.dureeEstimeeMinutes) return '-'; const heures = Math.floor(rowData.dureeEstimeeMinutes / 60); const minutes = rowData.dureeEstimeeMinutes % 60; if (heures > 0) { return `${heures}h${minutes > 0 ? ` ${minutes}min` : ''}`; } return `${minutes}min`; }; /** * Template pour l'affichage des indicateurs critique/bloquante */ const indicateursBodyTemplate = (rowData: TacheTemplate) => { return (
{rowData.critique && ( )} {rowData.bloquante && ( )}
); }; /** * Template pour l'affichage des statistiques d'une sous-phase */ const renderSousPhaseStats = (sousPhase: SousPhaseTemplate) => { const stats = sousPhase.statistiques; if (!stats) return null; const pourcentageCritiques = stats.totalTaches > 0 ? (stats.tachesCritiques / stats.totalTaches * 100) : 0; const dureeHeures = stats.dureeEstimeeMinutes / 60; return (
{stats.totalTaches} tâches {stats.tachesCritiques} critiques {stats.tachesBloquantes} bloquantes {dureeHeures.toFixed(1)}h
); }; return (
{/* Header */}
Gestion Granulaire des Tâches

Système de tracking détaillé : Phase → Sous-phase → Tâche

{/* Sélection de Phase et Sous-phase */}
{/* Phases */}
{phases.map((phase) => (
{ setSelectedPhase(phase); setSelectedSousPhase(null); setTaches([]); }} >
{phase.nom}
{phase.sousPhases?.length || 0} sous-phases
))}
{/* Sous-phases */}
{selectedPhase ? (
{selectedPhase.sousPhases?.map((sousPhase) => (
setSelectedSousPhase(sousPhase)} >
{sousPhase.nom}
{sousPhase.description && (
{sousPhase.description}
)}
{renderSousPhaseStats(sousPhase)}
))}
) : (

Sélectionnez une phase pour voir ses sous-phases

)}
{/* Tableau des Tâches */}
{selectedSousPhase ? ( ) : (

Sélectionnez une sous-phase pour voir ses tâches

)}
{/* Dialog de Création/Édition de Tâche */} setShowTacheDialog(false)} style={{ width: '60vw' }} breakpoints={{ '960px': '75vw', '641px': '90vw' }} modal >
setFormTache({ ...formTache, nom: e.target.value })} placeholder="Nom de la tâche..." />
setFormTache({ ...formTache, dureeEstimeeMinutes: e.value || 0 })} min={0} placeholder="Durée en minutes" />
setFormTache({ ...formTache, description: e.target.value })} rows={3} placeholder="Description détaillée de la tâche..." />
setFormTache({ ...formTache, priorite: e.value })} />
setFormTache({ ...formTache, nombreOperateursRequis: e.value || 1 })} min={1} max={10} />
setFormTache({ ...formTache, niveauQualification: e.value })} placeholder="Sélectionner..." />
setFormTache({ ...formTache, conditionsMeteo: e.value })} />
setFormTache({ ...formTache, critique: e.checked })} />
setFormTache({ ...formTache, bloquante: e.checked })} />
); }; export default GestionTachesTemplates;