Files
btpxpress-frontend/app/(main)/chantiers/nouveau/page.tsx

1244 lines
62 KiB
TypeScript

'use client';
import React, { useState, useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
import { Card } from 'primereact/card';
import { Button } from 'primereact/button';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber';
import { Calendar } from 'primereact/calendar';
import { Dropdown } from 'primereact/dropdown';
import { Toast } from 'primereact/toast';
import { Divider } from 'primereact/divider';
import { Checkbox } from 'primereact/checkbox';
import { Steps } from 'primereact/steps';
import { Panel } from 'primereact/panel';
import { Badge } from 'primereact/badge';
import { Tag } from 'primereact/tag';
import { Timeline } from 'primereact/timeline';
import { ProgressBar } from 'primereact/progressbar';
import { Accordion, AccordionTab } from 'primereact/accordion';
import PhasesTimelinePreview from '../../../../components/phases/PhasesTimelinePreview';
import { chantierService, clientService } from '../../../../services/api';
import type { Chantier, Client } from '../../../../types/btp';
import type { TypeChantier } from '../../../../types/chantier-templates';
import type { ChantierFormData, ChantierPreview } from '../../../../types/chantier-form';
const NouveauChantierPage = () => {
const router = useRouter();
const toast = useRef<Toast>(null);
const [loading, setLoading] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [activeIndex, setActiveIndex] = useState(0);
const [clients, setClients] = useState<any[]>([]);
const [chantierTypes, setChantierTypes] = useState<any[]>([]);
const [preview, setPreview] = useState<ChantierPreview | null>(null);
const [calculatingPreview, setCalculatingPreview] = useState(false);
const [chantierForm, setChantierForm] = useState<ChantierFormData>({
nom: '',
description: '',
adresse: '',
codePostal: '',
ville: '',
dateDebut: new Date().toISOString().split('T')[0],
dateFinPrevue: '',
montantPrevu: 0,
clientId: '',
// Nouveaux champs avancés
typeChantier: 'MAISON_INDIVIDUELLE' as TypeChantier,
surface: 0,
nombreNiveaux: 1,
autoGenererPhases: true,
inclureSousPhases: true,
ajusterDelais: true,
// Spécificités
specificites: [],
contraintes: '',
accesPMR: false,
performanceEnergetique: 'STANDARD',
// Informations techniques
natureTerrain: 'PLAT',
presenceReseaux: true,
accessibiliteChantier: 'FACILE',
stockagePossible: true,
// Réglementation
permisRequis: true,
numeroPermis: '',
dateObtentionPermis: '',
controleUrbanismeRequis: false,
// Options de planification
margeSecurite: 5,
periodesTravailRestreintes: [],
// Équipes et ressources
equipePrefereId: '',
materielsRequis: [],
// Champs manquants pour compatibilité
statut: 'PLANIFIE',
actif: true
});
const [errors, setErrors] = useState<Record<string, string>>({});
const statuts = [
{ label: 'Planifié', value: 'PLANIFIE' },
{ label: 'En cours', value: 'EN_COURS' },
{ label: 'Terminé', value: 'TERMINE' },
{ label: 'Annulé', value: 'ANNULE' },
{ label: 'Suspendu', value: 'SUSPENDU' }
];
const steps = [
{ label: 'Informations générales' },
{ label: 'Type de chantier' },
{ label: 'Configuration technique' },
{ label: 'Localisation' },
{ label: 'Planification' },
{ label: 'Options avancées' },
{ label: 'Prévisualisation' },
{ label: 'Validation' }
];
useEffect(() => {
loadClients();
loadChantierTypes();
}, []);
// Effet pour calculer la prévisualisation quand le type change
useEffect(() => {
if (chantierForm.typeChantier && chantierForm.dateDebut) {
calculatePreview();
}
}, [chantierForm.typeChantier, chantierForm.dateDebut, chantierForm.surface, chantierForm.nombreNiveaux]);
const loadClients = async () => {
try {
const data = await clientService.getAll();
setClients(data.map(client => ({
label: `${client.prenom} ${client.nom}${client.entreprise ? ' - ' + client.entreprise : ''}`,
value: client.id,
client: client
})));
} catch (error) {
console.error('Erreur lors du chargement des clients:', error);
}
};
const loadChantierTypes = async () => {
try {
// Utiliser le service API standardisé avec authentification
const { typeChantierService } = await import('../../../../services/api');
const typesParCategorie = await typeChantierService.getByCategorie();
console.log('Types récupérés depuis l\'API:', typesParCategorie);
// Convertir en format attendu par PrimeReact
const groupedTypes: any[] = [];
Object.keys(typesParCategorie).forEach(categorie => {
const categoryTypes = typesParCategorie[categorie];
groupedTypes.push({
label: categorie,
items: categoryTypes.map((type: any) => ({
label: type.nom,
value: type.code
}))
});
});
setChantierTypes(groupedTypes);
} catch (error) {
console.error('❌ API TypeChantier non disponible. Le backend doit être redémarré.', error);
// Afficher un message d'erreur à l'utilisateur
if (toast.current) {
toast.current.show({
severity: 'error',
summary: 'API non disponible',
detail: 'Les endpoints TypeChantier ne sont pas encore exposés. Redémarrez le backend.',
life: 5000
});
}
setChantierTypes([]);
}
};
const calculatePreview = async () => {
if (!chantierForm.typeChantier || !chantierForm.dateDebut) return;
setCalculatingPreview(true);
try {
let typeChantier: any = null;
try {
// Essayer d'utiliser le service API standardisé avec authentification
const { typeChantierService } = await import('../../../../services/api');
typeChantier = await typeChantierService.getByCode(chantierForm.typeChantier);
console.log('Type de chantier récupéré depuis l\'API:', typeChantier);
} catch (apiError) {
console.warn('Impossible de récupérer les données du type de chantier depuis l\'API, utilisation des données par défaut');
// Données de fallback pour les types de chantier courants
const typeChantierDefaults: Record<string, any> = {
'MAISON_INDIVIDUELLE': {
code: 'MAISON_INDIVIDUELLE',
nom: 'Maison individuelle',
dureeMoyenneJours: 120,
coutMoyenM2: 1200,
description: 'Construction de maison individuelle'
},
'IMMEUBLE_COLLECTIF': {
code: 'IMMEUBLE_COLLECTIF',
nom: 'Immeuble collectif',
dureeMoyenneJours: 365,
coutMoyenM2: 1500,
description: 'Construction d\'immeuble résidentiel collectif'
},
'RENOVATION': {
code: 'RENOVATION',
nom: 'Rénovation',
dureeMoyenneJours: 90,
coutMoyenM2: 800,
description: 'Travaux de rénovation'
}
};
typeChantier = typeChantierDefaults[chantierForm.typeChantier] || {
code: chantierForm.typeChantier,
nom: chantierForm.typeChantier,
dureeMoyenneJours: 180,
coutMoyenM2: 1000,
description: 'Type de chantier personnalisé'
};
console.log('Utilisation des données par défaut:', typeChantier);
}
const dateDebut = new Date(chantierForm.dateDebut);
const dateFinEstimee = new Date(dateDebut.getTime() + (typeChantier.dureeMoyenneJours || 180) * 24 * 60 * 60 * 1000);
const previewData: ChantierPreview = {
typeChantier: chantierForm.typeChantier,
nom: chantierForm.nom || typeChantier.nom,
dureeEstimee: typeChantier.dureeMoyenneJours || 180,
dateFinEstimee: dateFinEstimee,
complexite: {
niveau: (typeChantier.dureeMoyenneJours || 180) > 300 ? 'COMPLEXE' : 'SIMPLE',
score: (typeChantier.dureeMoyenneJours || 180) > 300 ? 75 : 25,
facteurs: []
},
phasesCount: 8, // Valeur par défaut en attendant les templates
sousePhasesCount: 24, // Valeur par défaut en attendant les templates
specificites: [],
reglementations: []
};
setPreview(previewData);
// Auto-calculer la date de fin
if (!chantierForm.dateFinPrevue) {
setChantierForm(prev => ({
...prev,
dateFinPrevue: dateFinEstimee.toISOString().split('T')[0]
}));
}
} catch (error) {
console.error('❌ Impossible de calculer la prévisualisation - API TypeChantier non disponible:', error);
if (toast.current) {
toast.current.show({
severity: 'warn',
summary: 'Prévisualisation indisponible',
detail: 'Impossible de calculer la prévisualisation sans l\'API TypeChantier',
life: 3000
});
}
} finally {
setCalculatingPreview(false);
}
};
const validateStep = (step: number) => {
const newErrors: Record<string, string> = {};
switch (step) {
case 0: // Informations générales
if (!chantierForm.nom.trim()) {
newErrors.nom = 'Le nom du chantier est obligatoire';
}
if (!chantierForm.clientId) {
newErrors.clientId = 'Le client est obligatoire';
}
break;
case 1: // Type de chantier
if (!chantierForm.typeChantier) {
newErrors.typeChantier = 'Le type de chantier est obligatoire';
}
break;
case 2: // Configuration technique
if (chantierForm.surface && chantierForm.surface <= 0) {
newErrors.surface = 'La surface doit être supérieure à 0';
}
break;
case 3: // Localisation
if (!chantierForm.adresse.trim()) {
newErrors.adresse = 'L\'adresse est obligatoire';
}
break;
case 4: // Planification
if (!chantierForm.dateDebut) {
newErrors.dateDebut = 'La date de début est obligatoire';
}
if (!chantierForm.dateFinPrevue) {
newErrors.dateFinPrevue = 'La date de fin prévue est obligatoire';
}
if (chantierForm.dateDebut && chantierForm.dateFinPrevue &&
new Date(chantierForm.dateDebut) > new Date(chantierForm.dateFinPrevue)) {
newErrors.dateFinPrevue = 'La date de fin doit être postérieure à la date de début';
}
break;
case 5: // Options avancées
if (chantierForm.montantPrevu && chantierForm.montantPrevu <= 0) {
newErrors.montantPrevu = 'Le montant prévu doit être supérieur à 0';
}
break;
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const nextStep = () => {
if (validateStep(activeIndex)) {
setActiveIndex(prev => Math.min(prev + 1, steps.length - 1));
}
};
const prevStep = () => {
setActiveIndex(prev => Math.max(prev - 1, 0));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSubmitted(true);
if (!validateStep(activeIndex)) {
toast.current?.show({
severity: 'error',
summary: 'Erreur',
detail: 'Veuillez corriger les erreurs du formulaire',
life: 3000
});
return;
}
setLoading(true);
try {
// Préparer les données pour l'envoi
const chantierToSave: any = {
nom: chantierForm.nom,
description: chantierForm.description || '',
adresse: chantierForm.adresse,
dateDebut: chantierForm.dateDebut,
dateFinPrevue: chantierForm.dateFinPrevue || null,
statut: chantierForm.statut || 'PLANIFIE',
montantPrevu: Number(chantierForm.montantPrevu) || 0,
actif: chantierForm.actif,
clientId: chantierForm.clientId,
// Nouveaux champs avancés
typeChantier: chantierForm.typeChantier,
surface: chantierForm.surface,
nombreNiveaux: chantierForm.nombreNiveaux,
dureeEstimeeJours: preview?.dureeEstimee,
complexite: preview?.complexite?.niveau
};
console.log('Données à envoyer:', chantierToSave);
await chantierService.create(chantierToSave);
toast.current?.show({
severity: 'success',
summary: 'Succès',
detail: 'Chantier créé avec succès',
life: 3000
});
setTimeout(() => {
router.push('/chantiers');
}, 1000);
} catch (error: any) {
console.error('Erreur lors de la création:', error);
// Extraire le message d'erreur du backend
let errorMessage = 'Impossible de créer le chantier';
if (error.response?.data?.message) {
errorMessage = error.response.data.message;
} else if (error.response?.data?.error) {
errorMessage = error.response.data.error;
} else if (error.response?.data) {
errorMessage = JSON.stringify(error.response.data);
}
toast.current?.show({
severity: 'error',
summary: 'Erreur',
detail: errorMessage,
life: 5000
});
} finally {
setLoading(false);
}
};
const handleCancel = () => {
router.push('/chantiers');
};
const onInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, name: string) => {
const val = (e.target && e.target.value) || '';
setChantierForm(prev => ({ ...prev, [name]: val }));
// Clear error when user starts typing
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
const onDateChange = (e: any, name: string) => {
setChantierForm(prev => ({ ...prev, [name]: e.value }));
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
const onNumberChange = (e: any, name: string) => {
setChantierForm(prev => ({ ...prev, [name]: e.value }));
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
const onDropdownChange = (e: any, name: string) => {
setChantierForm(prev => ({ ...prev, [name]: e.value }));
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
const onCheckboxChange = (e: any, name: string) => {
setChantierForm(prev => ({ ...prev, [name]: e.checked }));
};
const renderStepContent = () => {
switch (activeIndex) {
case 0: // Informations générales
return (
<div className="formgrid grid">
<div className="field col-12">
<label htmlFor="nom" className="font-bold">
Nom du chantier <span className="text-red-500">*</span>
</label>
<InputText
id="nom"
value={chantierForm.nom}
onChange={(e) => onInputChange(e, 'nom')}
className={errors.nom ? 'p-invalid' : ''}
placeholder="Nom du chantier"
/>
{errors.nom && <small className="p-error">{errors.nom}</small>}
</div>
<div className="field col-12">
<label htmlFor="clientId" className="font-bold">
Client <span className="text-red-500">*</span>
</label>
<Dropdown
id="clientId"
value={chantierForm.clientId}
options={clients}
onChange={(e) => onDropdownChange(e, 'clientId')}
placeholder="Sélectionnez un client"
className={errors.clientId ? 'p-invalid' : ''}
filter
/>
{errors.clientId && <small className="p-error">{errors.clientId}</small>}
</div>
<div className="field col-12">
<label htmlFor="description" className="font-bold">Description</label>
<InputTextarea
id="description"
value={chantierForm.description}
onChange={(e) => onInputChange(e, 'description')}
rows={4}
placeholder="Description détaillée du chantier"
/>
</div>
</div>
);
case 1: // Type de chantier
return (
<div className="formgrid grid">
<div className="field col-12">
<label htmlFor="typeChantier" className="font-bold">
Type de chantier <span className="text-red-500">*</span>
</label>
<Dropdown
id="typeChantier"
value={chantierForm.typeChantier}
options={chantierTypes}
optionLabel="label"
optionValue="value"
optionGroupLabel="label"
optionGroupChildren="items"
onChange={(e) => onDropdownChange(e, 'typeChantier')}
placeholder="Sélectionnez le type de chantier"
className={errors.typeChantier ? 'p-invalid' : ''}
filter
/>
{errors.typeChantier && <small className="p-error">{errors.typeChantier}</small>}
</div>
{/* Options de génération automatique */}
<div className="field col-12">
<div className="formgrid grid">
<div className="field col-12 md:col-4">
<div className="flex align-items-center">
<Checkbox
id="autoGenererPhases"
checked={chantierForm.autoGenererPhases}
onChange={(e) => onCheckboxChange(e, 'autoGenererPhases')}
/>
<label htmlFor="autoGenererPhases" className="ml-2 font-bold">
Générer automatiquement les phases
</label>
</div>
<small className="text-600">
Les phases seront créées selon le type de chantier
</small>
</div>
<div className="field col-12 md:col-4">
<div className="flex align-items-center">
<Checkbox
id="inclureSousPhases"
checked={chantierForm.inclureSousPhases}
onChange={(e) => onCheckboxChange(e, 'inclureSousPhases')}
disabled={!chantierForm.autoGenererPhases}
/>
<label htmlFor="inclureSousPhases" className="ml-2 font-bold">
Inclure les sous-phases
</label>
</div>
<small className="text-600">
Détailler chaque phase principale
</small>
</div>
<div className="field col-12 md:col-4">
<div className="flex align-items-center">
<Checkbox
id="ajusterDelais"
checked={chantierForm.ajusterDelais}
onChange={(e) => onCheckboxChange(e, 'ajusterDelais')}
disabled={!chantierForm.autoGenererPhases}
/>
<label htmlFor="ajusterDelais" className="ml-2 font-bold">
Ajuster les délais
</label>
</div>
<small className="text-600">
Adapter selon la complexité
</small>
</div>
</div>
</div>
{/* Prévisualisation des phases */}
{chantierForm.typeChantier && chantierForm.autoGenererPhases && preview && (
<div className="field col-12">
<Card title="Prévisualisation des phases" className="mt-4">
<div className="grid">
<div className="col-12 md:col-6">
<div className="text-center p-3 border-1 surface-border border-round">
<div className="text-xl font-bold text-primary">{preview.phasesCount}</div>
<div className="text-600">Phases principales</div>
</div>
</div>
<div className="col-12 md:col-6">
<div className="text-center p-3 border-1 surface-border border-round">
<div className="text-xl font-bold text-primary">{preview.sousePhasesCount}</div>
<div className="text-600">Sous-phases détaillées</div>
</div>
</div>
<div className="col-12 md:col-6">
<div className="text-center p-3 border-1 surface-border border-round">
<div className="text-xl font-bold text-orange-500">{preview.dureeEstimee}</div>
<div className="text-600">Jours estimés</div>
</div>
</div>
<div className="col-12 md:col-6">
<div className="text-center p-3 border-1 surface-border border-round">
<Tag
value={preview.complexite.niveau}
severity={
preview.complexite.niveau === 'SIMPLE' ? 'success' :
preview.complexite.niveau === 'MOYEN' ? 'warning' :
preview.complexite.niveau === 'COMPLEXE' ? 'danger' : 'danger'
}
/>
<div className="text-600 mt-1">Complexité estimée</div>
</div>
</div>
</div>
{calculatingPreview && (
<div className="flex align-items-center justify-content-center mt-3">
<ProgressBar mode="indeterminate" style={{ height: '4px' }} />
<span className="ml-2 text-600">Calcul en cours...</span>
</div>
)}
</Card>
</div>
)}
</div>
);
case 2: // Configuration technique
return (
<div className="formgrid grid">
<div className="field col-12 md:col-6">
<label htmlFor="surface" className="font-bold">
Surface (m²)
</label>
<InputNumber
id="surface"
value={chantierForm.surface}
onValueChange={(e) => onNumberChange(e, 'surface')}
mode="decimal"
locale="fr-FR"
minFractionDigits={0}
maxFractionDigits={2}
className={errors.surface ? 'p-invalid' : ''}
placeholder="Surface en m²"
/>
{errors.surface && <small className="p-error">{errors.surface}</small>}
</div>
<div className="field col-12 md:col-6">
<label htmlFor="nombreNiveaux" className="font-bold">
Nombre de niveaux
</label>
<InputNumber
id="nombreNiveaux"
value={chantierForm.nombreNiveaux}
onValueChange={(e) => onNumberChange(e, 'nombreNiveaux')}
min={1}
max={20}
placeholder="Nombre de niveaux"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="natureTerrain" className="font-bold">Nature du terrain</label>
<Dropdown
id="natureTerrain"
value={chantierForm.natureTerrain}
options={[
{ label: 'Terrain plat', value: 'PLAT' },
{ label: 'Pente légère', value: 'PENTE_LEGERE' },
{ label: 'Pente forte', value: 'PENTE_FORTE' },
{ label: 'Terrain rocheux', value: 'ROCHEUX' },
{ label: 'Terrain humide', value: 'HUMIDE' },
{ label: 'Terrain instable', value: 'INSTABLE' }
]}
onChange={(e) => onDropdownChange(e, 'natureTerrain')}
placeholder="Sélectionnez la nature du terrain"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="accessibiliteChantier" className="font-bold">Accessibilité du chantier</label>
<Dropdown
id="accessibiliteChantier"
value={chantierForm.accessibiliteChantier}
options={[
{ label: 'Facile - Accès direct, terrain plat', value: 'FACILE' },
{ label: 'Moyenne - Quelques contraintes d\'accès', value: 'MOYENNE' },
{ label: 'Difficile - Accès restreint, terrain complexe', value: 'DIFFICILE' }
]}
onChange={(e) => onDropdownChange(e, 'accessibiliteChantier')}
placeholder="Sélectionnez l'accessibilité"
/>
</div>
<div className="field col-12">
<div className="formgrid grid">
<div className="field col-12 md:col-4">
<div className="flex align-items-center">
<Checkbox
id="presenceReseaux"
checked={chantierForm.presenceReseaux}
onChange={(e) => onCheckboxChange(e, 'presenceReseaux')}
/>
<label htmlFor="presenceReseaux" className="ml-2 font-bold">
Réseaux existants
</label>
</div>
<small className="text-600">
Électricité, eau, gaz présents
</small>
</div>
<div className="field col-12 md:col-4">
<div className="flex align-items-center">
<Checkbox
id="stockagePossible"
checked={chantierForm.stockagePossible}
onChange={(e) => onCheckboxChange(e, 'stockagePossible')}
/>
<label htmlFor="stockagePossible" className="ml-2 font-bold">
Stockage possible
</label>
</div>
<small className="text-600">
Espace pour matériaux disponible
</small>
</div>
<div className="field col-12 md:col-4">
<div className="flex align-items-center">
<Checkbox
id="accesPMR"
checked={chantierForm.accesPMR}
onChange={(e) => onCheckboxChange(e, 'accesPMR')}
/>
<label htmlFor="accesPMR" className="ml-2 font-bold">
Accès PMR requis
</label>
</div>
<small className="text-600">
Accessibilité personnes handicapées
</small>
</div>
</div>
</div>
<div className="field col-12">
<label htmlFor="performanceEnergetique" className="font-bold">Performance énergétique</label>
<Dropdown
id="performanceEnergetique"
value={chantierForm.performanceEnergetique}
options={[
{ label: 'Standard (réglementation minimale)', value: 'STANDARD' },
{ label: 'RT 2012', value: 'RT2012' },
{ label: 'RT 2020', value: 'RT2020' },
{ label: 'RE 2020 (obligatoire neuf)', value: 'RE2020' },
{ label: 'Bâtiment passif', value: 'PASSIF' },
{ label: 'Bâtiment à énergie positive', value: 'POSITIF' }
]}
onChange={(e) => onDropdownChange(e, 'performanceEnergetique')}
placeholder="Sélectionnez la performance énergétique"
/>
</div>
</div>
);
case 3: // Localisation
return (
<div className="formgrid grid">
<div className="field col-12">
<label htmlFor="adresse" className="font-bold">
Adresse du chantier <span className="text-red-500">*</span>
</label>
<InputTextarea
id="adresse"
value={chantierForm.adresse}
onChange={(e) => onInputChange(e, 'adresse')}
rows={3}
className={errors.adresse ? 'p-invalid' : ''}
placeholder="Adresse complète du chantier"
/>
{errors.adresse && <small className="p-error">{errors.adresse}</small>}
</div>
<div className="field col-12 md:col-6">
<label htmlFor="codePostal" className="font-bold">Code postal</label>
<InputText
id="codePostal"
value={chantierForm.codePostal}
onChange={(e) => onInputChange(e, 'codePostal')}
placeholder="Code postal"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="ville" className="font-bold">Ville</label>
<InputText
id="ville"
value={chantierForm.ville}
onChange={(e) => onInputChange(e, 'ville')}
placeholder="Ville"
/>
</div>
</div>
);
case 4: // Planification
return (
<div className="formgrid grid">
<div className="field col-12 md:col-6">
<label htmlFor="dateDebut" className="font-bold">
Date de début <span className="text-red-500">*</span>
</label>
<Calendar
id="dateDebut"
value={chantierForm.dateDebut ? new Date(chantierForm.dateDebut) : null}
onChange={(e) => onDateChange(e, 'dateDebut')}
dateFormat="dd/mm/yy"
showIcon
className={errors.dateDebut ? 'p-invalid' : ''}
/>
{errors.dateDebut && <small className="p-error">{errors.dateDebut}</small>}
</div>
<div className="field col-12 md:col-6">
<label htmlFor="dateFinPrevue" className="font-bold">
Date de fin prévue
</label>
<Calendar
id="dateFinPrevue"
value={chantierForm.dateFinPrevue ? new Date(chantierForm.dateFinPrevue) : null}
onChange={(e) => onDateChange(e, 'dateFinPrevue')}
dateFormat="dd/mm/yy"
showIcon
className={errors.dateFinPrevue ? 'p-invalid' : ''}
minDate={chantierForm.dateDebut ? new Date(chantierForm.dateDebut) : undefined}
/>
{errors.dateFinPrevue && <small className="p-error">{errors.dateFinPrevue}</small>}
{preview?.dateFinEstimee && (
<small className="text-600">
Date calculée automatiquement : {preview.dateFinEstimee.toLocaleDateString('fr-FR')}
</small>
)}
</div>
<div className="field col-12 md:col-6">
<label htmlFor="statut" className="font-bold">Statut initial</label>
<Dropdown
id="statut"
value={chantierForm.statut}
options={statuts}
onChange={(e) => onDropdownChange(e, 'statut')}
placeholder="Sélectionnez un statut"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="margeSecurite" className="font-bold">Marge de sécurité (jours)</label>
<InputNumber
id="margeSecurite"
value={chantierForm.margeSecurite}
onValueChange={(e) => onNumberChange(e, 'margeSecurite')}
min={0}
max={30}
placeholder="Jours de marge"
/>
<small className="text-600">
Délai supplémentaire pour imprévus
</small>
</div>
{/* Prévisualisation du planning */}
{preview && (
<div className="field col-12">
<Card title="Planning estimé" className="mt-3">
<Timeline
value={[
{
status: 'Début projet',
date: chantierForm.dateDebut,
icon: 'pi pi-play',
color: '#9C27B0'
},
{
status: 'Fin estimée',
date: preview.dateFinEstimee.toLocaleDateString('fr-FR'),
icon: 'pi pi-check',
color: '#673AB7'
}
]}
opposite={(item) => item.status}
content={(item) => <small className="text-color-secondary">{item.date}</small>}
/>
<div className="mt-3 p-3 surface-100 border-round">
<div className="flex align-items-center justify-content-between">
<span className="font-semibold">Durée totale estimée:</span>
<Badge value={`${preview.dureeEstimee} jours`} severity="info" />
</div>
</div>
</Card>
</div>
)}
</div>
);
case 5: // Options avancées
return (
<div className="formgrid grid">
<div className="field col-12">
<label htmlFor="montantPrevu" className="font-bold">
Montant prévu
</label>
<InputNumber
id="montantPrevu"
value={chantierForm.montantPrevu}
onValueChange={(e) => onNumberChange(e, 'montantPrevu')}
mode="currency"
currency="EUR"
locale="fr-FR"
className={errors.montantPrevu ? 'p-invalid' : ''}
/>
{errors.montantPrevu && <small className="p-error">{errors.montantPrevu}</small>}
</div>
<div className="field col-12">
<label htmlFor="contraintes" className="font-bold">Contraintes particulières</label>
<InputTextarea
id="contraintes"
value={chantierForm.contraintes}
onChange={(e) => onInputChange(e, 'contraintes')}
rows={3}
placeholder="Décrivez les contraintes particulières du chantier"
/>
</div>
<div className="field col-12">
<div className="formgrid grid">
<div className="field col-12 md:col-6">
<div className="flex align-items-center">
<Checkbox
id="permisRequis"
checked={chantierForm.permisRequis}
onChange={(e) => onCheckboxChange(e, 'permisRequis')}
/>
<label htmlFor="permisRequis" className="ml-2 font-bold">
Permis de construire requis
</label>
</div>
</div>
<div className="field col-12 md:col-6">
<div className="flex align-items-center">
<Checkbox
id="controleUrbanismeRequis"
checked={chantierForm.controleUrbanismeRequis}
onChange={(e) => onCheckboxChange(e, 'controleUrbanismeRequis')}
/>
<label htmlFor="controleUrbanismeRequis" className="ml-2 font-bold">
Contrôle urbanisme requis
</label>
</div>
</div>
</div>
</div>
{chantierForm.permisRequis && (
<div className="field col-12">
<div className="formgrid grid">
<div className="field col-12 md:col-6">
<label htmlFor="numeroPermis" className="font-bold">Numéro de permis</label>
<InputText
id="numeroPermis"
value={chantierForm.numeroPermis}
onChange={(e) => onInputChange(e, 'numeroPermis')}
placeholder="Numéro du permis de construire"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="dateObtentionPermis" className="font-bold">Date d'obtention</label>
<Calendar
id="dateObtentionPermis"
value={chantierForm.dateObtentionPermis ? new Date(chantierForm.dateObtentionPermis) : null}
onChange={(e) => onDateChange(e, 'dateObtentionPermis')}
dateFormat="dd/mm/yy"
showIcon
/>
</div>
</div>
</div>
)}
<div className="field col-12">
<div className="flex align-items-center">
<Checkbox
id="actif"
checked={chantierForm.actif}
onChange={(e) => onCheckboxChange(e, 'actif')}
/>
<label htmlFor="actif" className="ml-2 font-bold">
Chantier actif
</label>
</div>
<small className="text-600">
Un chantier inactif n'apparaîtra pas dans les listes de sélection
</small>
</div>
</div>
);
case 6: // Prévisualisation
return (
<div className="formgrid grid">
<div className="field col-12">
<h3 className="text-primary">Récapitulatif du projet</h3>
<Divider />
</div>
{/* Informations générales dans un layout plus compact */}
<div className="field col-12">
<Card title="Informations générales" className="mb-3">
<div className="grid">
<div className="col-12 md:col-6">
<div className="flex flex-column gap-2">
<div>
<span className="font-semibold text-600">Nom du chantier: </span>
<span>{chantierForm.nom}</span>
</div>
<div>
<span className="font-semibold text-600">Client: </span>
<span>{clients.find(c => c.value === chantierForm.clientId)?.label}</span>
</div>
<div>
<span className="font-semibold text-600">Type de chantier: </span>
<span>{chantierTypes.find(group =>
group.items?.find((item: any) => item.value === chantierForm.typeChantier)
)?.items?.find((item: any) => item.value === chantierForm.typeChantier)?.label}</span>
</div>
</div>
</div>
<div className="col-12 md:col-6">
<div className="flex flex-column gap-2">
<div>
<span className="font-semibold text-600">Surface: </span>
<span>{chantierForm.surface ? `${chantierForm.surface}` : 'Non spécifiée'}</span>
</div>
<div>
<span className="font-semibold text-600">Date de début: </span>
<span>{chantierForm.dateDebut}</span>
</div>
<div>
<span className="font-semibold text-600">Montant prévu: </span>
<span>{chantierForm.montantPrevu?.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' }) || 'Non spécifié'}</span>
</div>
</div>
</div>
</div>
</Card>
</div>
{/* Planning détaillé des phases */}
{chantierForm.typeChantier && chantierForm.autoGenererPhases && (
<div className="field col-12">
<PhasesTimelinePreview
typeChantier={chantierForm.typeChantier}
dateDebut={chantierForm.dateDebut ? new Date(chantierForm.dateDebut) : new Date()}
surface={chantierForm.surface}
nombreNiveaux={chantierForm.nombreNiveaux}
inclureSousPhases={chantierForm.inclureSousPhases}
ajusterDelais={chantierForm.ajusterDelais}
margeSecurite={chantierForm.margeSecurite}
compact={false}
showDetails={true}
/>
</div>
)}
{/* Configuration et options */}
<div className="field col-12">
<Card title="Configuration" className="mb-3">
<div className="grid">
<div className="col-12 md:col-4">
<div className="flex align-items-center gap-2 mb-2">
<i className={`pi ${chantierForm.autoGenererPhases ? 'pi-check text-green-500' : 'pi-times text-red-500'}`}></i>
<span>Génération automatique des phases</span>
</div>
</div>
<div className="col-12 md:col-4">
<div className="flex align-items-center gap-2 mb-2">
<i className={`pi ${chantierForm.inclureSousPhases ? 'pi-check text-green-500' : 'pi-times text-red-500'}`}></i>
<span>Sous-phases incluses</span>
</div>
</div>
<div className="col-12 md:col-4">
<div className="flex align-items-center gap-2 mb-2">
<i className={`pi ${chantierForm.ajusterDelais ? 'pi-check text-green-500' : 'pi-times text-red-500'}`}></i>
<span>Ajustement des délais</span>
</div>
</div>
</div>
</Card>
</div>
</div>
);
case 7: // Validation
return (
<div className="formgrid grid">
<div className="field col-12">
<div className="surface-card p-4 border-round shadow-2">
<div className="flex align-items-center mb-3">
<i className="pi pi-check-circle text-4xl text-green-500 mr-3"></i>
<div>
<h3 className="m-0 text-green-600">Projet prêt à être créé</h3>
<p className="text-600 m-0">Vérifiez les informations avant de finaliser</p>
</div>
</div>
<div className="grid">
<div className="col-12 md:col-6">
<div className="border-1 surface-border border-round p-3">
<h4 className="mt-0 mb-2">Informations générales</h4>
<ul className="list-none p-0 m-0">
<li className="pb-2"> Nom du chantier défini</li>
<li className="pb-2"> Client sélectionné</li>
<li className="pb-2"> Type de chantier choisi</li>
<li className="pb-2"> Localisation renseignée</li>
</ul>
</div>
</div>
<div className="col-12 md:col-6">
<div className="border-1 surface-border border-round p-3">
<h4 className="mt-0 mb-2">Configuration</h4>
<ul className="list-none p-0 m-0">
<li className="pb-2"> Planification configurée</li>
{chantierForm.autoGenererPhases && <li className="pb-2"> Phases automatiques activées</li>}
{chantierForm.inclureSousPhases && <li className="pb-2"> Sous-phases incluses</li>}
{chantierForm.montantPrevu && <li className="pb-2"> Budget défini</li>}
</ul>
</div>
</div>
</div>
<Divider />
<div className="text-center">
<p className="text-600 mb-3">
En cliquant sur "Créer le chantier", le projet sera enregistré et
{chantierForm.autoGenererPhases ? ' les phases seront générées automatiquement' : ' vous pourrez ajouter les phases manuellement'}.
</p>
<div className="flex justify-content-center gap-2">
<i className="pi pi-info-circle text-blue-500"></i>
<small className="text-600">
Vous pourrez modifier ces informations après la création
</small>
</div>
</div>
</div>
</div>
</div>
);
default:
return null;
}
};
return (
<div className="grid">
<div className="col-12">
<Card>
<Toast ref={toast} />
<div className="flex justify-content-between align-items-center mb-4">
<h2>Nouveau Chantier</h2>
<Button
icon="pi pi-arrow-left"
label="Retour"
className="p-button-text"
onClick={handleCancel}
/>
</div>
<Steps model={steps} activeIndex={activeIndex} className="mb-4" />
<form onSubmit={handleSubmit} className="p-fluid">
{renderStepContent()}
<Divider />
<div className="flex justify-content-between">
<Button
type="button"
label="Précédent"
icon="pi pi-chevron-left"
className="p-button-text"
onClick={prevStep}
disabled={activeIndex === 0}
/>
<div className="flex gap-2">
<Button
type="button"
label="Annuler"
icon="pi pi-times"
className="p-button-text"
onClick={handleCancel}
disabled={loading}
/>
{activeIndex < steps.length - 1 ? (
<Button
type="button"
label="Suivant"
icon="pi pi-chevron-right"
iconPos="right"
onClick={nextStep}
/>
) : (
<Button
type="submit"
label="Créer le chantier"
icon="pi pi-check"
loading={loading}
disabled={loading}
/>
)}
</div>
</div>
</form>
</Card>
</div>
</div>
);
};
export default NouveauChantierPage;