'use client'; /** * Assistant de génération automatique de phases pour chantiers BTP * Wizard en 3 étapes: Sélection template -> Personnalisation -> Prévisualisation & Génération */ import React, { useState, useRef, useEffect } from 'react'; import { Dialog } from 'primereact/dialog'; import { Steps } from 'primereact/steps'; import { Button } from 'primereact/button'; import { Card } from 'primereact/card'; import { Toast } from 'primereact/toast'; import { ProgressBar } from 'primereact/progressbar'; import { Divider } from 'primereact/divider'; import { Badge } from 'primereact/badge'; import { Tag } from 'primereact/tag'; import TemplateSelectionStep from './wizard/TemplateSelectionStep'; import CustomizationStep from './wizard/CustomizationStep'; import PreviewGenerationStep from './wizard/PreviewGenerationStep'; import typeChantierService from '../../services/typeChantierService'; import phaseService from '../../services/phaseService'; export interface PhaseTemplate { id: string; nom: string; description: string; ordre: number; dureeEstimee: number; budgetEstime: number; competencesRequises: string[]; prerequis: string[]; sousPhases: SousPhaseTemplate[]; categorieMetier: 'GROS_OEUVRE' | 'SECOND_OEUVRE' | 'FINITIONS' | 'EQUIPEMENTS' | 'AMENAGEMENTS'; obligatoire: boolean; personnalisable: boolean; } export interface SousPhaseTemplate { id: string; nom: string; description: string; ordre: number; dureeEstimee: number; budgetEstime: number; competencesRequises: string[]; obligatoire: boolean; } export interface TypeChantierTemplate { id: string; nom: string; description: string; categorie: string; phases: PhaseTemplate[]; dureeGlobaleEstimee: number; budgetGlobalEstime: number; nombreTotalPhases: number; complexiteMetier: 'SIMPLE' | 'MOYENNE' | 'COMPLEXE' | 'EXPERT'; tags: string[]; } export interface WizardConfiguration { typeChantier: TypeChantierTemplate | null; phasesSelectionnees: PhaseTemplate[]; configurationsPersonnalisees: Record; budgetGlobal: number; dureeGlobale: number; dateDebutSouhaitee: Date | null; optionsAvancees: { integrerPlanning: boolean; calculerBudgetAuto: boolean; appliquerMarges: boolean; taux: { margeCommerciale: number; alea: number; tva: number; }; }; } interface PhaseGenerationWizardProps { visible: boolean; onHide: () => void; chantier: Chantier; onGenerated: (phases: any[]) => void; } const PhaseGenerationWizard: React.FC = ({ visible, onHide, chantier, onGenerated }) => { const toast = useRef(null); // États du wizard const [activeIndex, setActiveIndex] = useState(0); const [isGenerating, setIsGenerating] = useState(false); const [generationProgress, setGenerationProgress] = useState(0); // Configuration du wizard initialisée avec les données du chantier const [configuration, setConfiguration] = useState(() => ({ typeChantier: null, phasesSelectionnees: [], configurationsPersonnalisees: {}, budgetGlobal: chantier?.montantPrevu || 0, dureeGlobale: chantier?.dateDebut && chantier?.dateFinPrevue ? Math.ceil((new Date(chantier.dateFinPrevue).getTime() - new Date(chantier.dateDebut).getTime()) / (1000 * 60 * 60 * 24)) : 0, dateDebutSouhaitee: chantier?.dateDebut ? new Date(chantier.dateDebut) : null, optionsAvancees: { integrerPlanning: true, calculerBudgetAuto: true, appliquerMarges: true, taux: { margeCommerciale: 15, alea: 10, tva: 20 } } })); // Données chargées const [templatesTypes, setTemplatesTypes] = useState([]); const [loading, setLoading] = useState(false); // Étapes du wizard const wizardSteps = [ { label: 'Sélection Template', icon: 'pi pi-th-large', description: 'Choisir le type de chantier et template de phases' }, { label: 'Personnalisation', icon: 'pi pi-cog', description: 'Adapter les phases et paramètres à vos besoins' }, { label: 'Génération', icon: 'pi pi-check-circle', description: 'Prévisualiser et générer les phases' } ]; // Charger les templates au montage useEffect(() => { if (visible) { loadTemplatesTypes(); } }, [visible]); // Mettre à jour la configuration quand les données du chantier changent useEffect(() => { if (chantier && visible) { setConfiguration(prev => ({ ...prev, budgetGlobal: chantier.montantPrevu || 0, dureeGlobale: chantier.dateDebut && chantier.dateFinPrevue ? Math.ceil((new Date(chantier.dateFinPrevue).getTime() - new Date(chantier.dateDebut).getTime()) / (1000 * 60 * 60 * 24)) : 0, dateDebutSouhaitee: chantier.dateDebut ? new Date(chantier.dateDebut) : null })); } }, [chantier, visible]); const loadTemplatesTypes = async () => { try { setLoading(true); const templates = await typeChantierService.getAllTemplates(); setTemplatesTypes(templates); } catch (error) { console.error('Erreur lors du chargement des templates:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les templates de chantiers', life: 3000 }); } finally { setLoading(false); } }; // Navigation du wizard const nextStep = () => { if (activeIndex < wizardSteps.length - 1) { setActiveIndex(activeIndex + 1); } }; const prevStep = () => { if (activeIndex > 0) { setActiveIndex(activeIndex - 1); } }; const canProceedToNext = (): boolean => { switch (activeIndex) { case 0: return configuration.typeChantier !== null; case 1: return configuration.phasesSelectionnees.length > 0; case 2: return true; default: return false; } }; // Génération des phases const generatePhases = async () => { if (!configuration.typeChantier) { toast.current?.show({ severity: 'warn', summary: 'Configuration incomplète', detail: 'Veuillez sélectionner un type de chantier', life: 3000 }); return; } try { setIsGenerating(true); setGenerationProgress(0); // Simulation du processus de génération avec étapes const etapes = [ { label: 'Validation de la configuration', delay: 500 }, { label: 'Génération des phases principales', delay: 800 }, { label: 'Création des sous-phases', delay: 600 }, { label: 'Calcul des budgets automatiques', delay: 700 }, { label: 'Intégration au planning', delay: 400 }, { label: 'Sauvegarde en base', delay: 500 } ]; for (let i = 0; i < etapes.length; i++) { const etape = etapes[i]; toast.current?.show({ severity: 'info', summary: `Étape ${i + 1}/${etapes.length}`, detail: etape.label, life: 2000 }); await new Promise(resolve => setTimeout(resolve, etape.delay)); setGenerationProgress(((i + 1) / etapes.length) * 100); } // Appel au service pour générer les phases avec données du chantier try { const phasesGenerees = await phaseService.generateFromTemplate( parseInt(chantier.id.toString()), configuration.typeChantier.id, { phasesSelectionnees: configuration.phasesSelectionnees, configurationsPersonnalisees: configuration.configurationsPersonnalisees, optionsAvancees: configuration.optionsAvancees, dateDebutSouhaitee: configuration.dateDebutSouhaitee || chantier.dateDebut, dureeGlobale: configuration.dureeGlobale, // Données du chantier pour cohérence chantierData: { budgetTotal: chantier.montantPrevu, typeChantier: chantier.typeChantier, dateDebut: chantier.dateDebut, dateFinPrevue: chantier.dateFinPrevue, surface: chantier.surface, adresse: chantier.adresse } } ); toast.current?.show({ severity: 'success', summary: 'Génération réussie', detail: `${phasesGenerees.length} phases ont été générées avec succès`, life: 5000 }); onGenerated(phasesGenerees); onHide(); } catch (serviceError) { console.warn('Service non disponible, génération simulée:', serviceError); // Génération simulée de phases avec données du chantier const baseDate = chantier.dateDebut ? new Date(chantier.dateDebut) : new Date(); let currentDate = new Date(baseDate); const phasesSimulees = configuration.phasesSelectionnees.map((phase, index) => { const dateDebut = new Date(currentDate); const dateFin = new Date(dateDebut.getTime() + phase.dureeEstimee * 24 * 60 * 60 * 1000); currentDate = new Date(dateFin.getTime() + 24 * 60 * 60 * 1000); // Jour suivant pour la phase suivante // Calculer budget proportionnel si budget total défini let budgetProportionnel = phase.budgetEstime; if (chantier.montantPrevu && configuration.budgetGlobal) { const ratioPhase = phase.budgetEstime / configuration.budgetGlobal; budgetProportionnel = chantier.montantPrevu * ratioPhase; } return { id: `sim_${Date.now()}_${index}`, nom: phase.nom, description: phase.description, chantierId: chantier.id.toString(), dateDebutPrevue: dateDebut.toISOString(), dateFinPrevue: dateFin.toISOString(), dureeEstimeeHeures: phase.dureeEstimee * 8, budgetPrevu: budgetProportionnel, coutReel: 0, statut: 'PLANIFIEE', priorite: phase.categorieMetier === 'GROS_OEUVRE' ? 'CRITIQUE' : 'MOYENNE', critique: phase.obligatoire, ordreExecution: phase.ordre, phaseParent: null, prerequisPhases: [], competencesRequises: phase.competencesRequises, materielsNecessaires: [], fournisseursRecommandes: [], dateCreation: new Date().toISOString(), dateModification: new Date().toISOString(), creePar: 'wizard', modifiePar: 'wizard' }; }); toast.current?.show({ severity: 'success', summary: 'Génération simulée réussie', detail: `${phasesSimulees.length} phases ont été générées (mode simulation)`, life: 5000 }); onGenerated(phasesSimulees); onHide(); } } catch (error) { console.error('Erreur lors de la génération:', error); toast.current?.show({ severity: 'error', summary: 'Erreur de génération', detail: 'Impossible de générer les phases automatiquement', life: 3000 }); } finally { setIsGenerating(false); setGenerationProgress(0); } }; // Reset du wizard avec données du chantier const resetWizard = () => { setActiveIndex(0); setConfiguration({ typeChantier: null, phasesSelectionnees: [], configurationsPersonnalisees: {}, budgetGlobal: chantier?.montantPrevu || 0, dureeGlobale: chantier?.dateDebut && chantier?.dateFinPrevue ? Math.ceil((new Date(chantier.dateFinPrevue).getTime() - new Date(chantier.dateDebut).getTime()) / (1000 * 60 * 60 * 24)) : 0, dateDebutSouhaitee: chantier?.dateDebut ? new Date(chantier.dateDebut) : null, optionsAvancees: { integrerPlanning: true, calculerBudgetAuto: true, appliquerMarges: true, taux: { margeCommerciale: 15, alea: 10, tva: 20 } } }); setGenerationProgress(0); setIsGenerating(false); }; // Template du header const headerTemplate = () => (

Assistant de Génération de Phases

Chantier: {chantier?.nom} {chantier?.typeChantier && ( ({chantier.typeChantier}) )}

{chantier && (

Budget: {chantier.montantPrevu?.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' }) || 'Non défini'} | Période: {chantier.dateDebut ? new Date(chantier.dateDebut).toLocaleDateString('fr-FR') : 'Non défini'} → {chantier.dateFinPrevue ? new Date(chantier.dateFinPrevue).toLocaleDateString('fr-FR') : 'Non défini'}

)}
{configuration.typeChantier && (
)}
); // Template du footer const footerTemplate = () => (
{configuration.phasesSelectionnees.length > 0 && ( )}
); // Rendu conditionnel des étapes const renderCurrentStep = () => { switch (activeIndex) { case 0: return ( ); case 1: return ( ); case 2: return ( ); default: return null; } }; return ( <> { if (!isGenerating) { resetWizard(); onHide(); } }} header={headerTemplate} footer={footerTemplate} style={{ width: '90vw', maxWidth: '1200px' }} modal closable={!isGenerating} className="p-dialog-maximized-responsive" >
{/* Barre de progression de génération */} {isGenerating && (
Génération en cours...
{generationProgress.toFixed(0)}% terminé
)} {/* Steps navigation */}
{ if (!isGenerating && e.index <= activeIndex) { setActiveIndex(e.index); } }} readOnly={isGenerating} />
{/* Contenu de l'étape courante */}
{renderCurrentStep()}
); }; export default PhaseGenerationWizard;