'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 { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Steps } from 'primereact/steps'; import { devisService, clientService } from '../../../../services/api'; import type { Devis, Client } from '../../../../types/btp'; interface DevisLigne { id: string; designation: string; quantite: number; unite: string; prixUnitaire: number; montantHT: number; } const NouveauDevisPage = () => { const router = useRouter(); const toast = useRef(null); const [loading, setLoading] = useState(false); const [submitted, setSubmitted] = useState(false); const [activeIndex, setActiveIndex] = useState(0); const [clients, setClients] = useState([]); const [lignes, setLignes] = useState([]); const [devis, setDevis] = useState({ id: '', numero: '', dateEmission: new Date(), dateValidite: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), objet: '', description: '', montantHT: 0, montantTTC: 0, tauxTVA: 20, statut: 'BROUILLON', actif: true, client: null, chantier: null }); const [nouvelleLigne, setNouvelleLigne] = useState({ id: '', designation: '', quantite: 1, unite: 'u', prixUnitaire: 0, montantHT: 0 }); const [errors, setErrors] = useState>({}); const statuts = [ { label: 'Brouillon', value: 'BROUILLON' }, { label: 'Envoyé', value: 'ENVOYE' }, { label: 'Accepté', value: 'ACCEPTE' }, { label: 'Refusé', value: 'REFUSE' }, { label: 'Expiré', value: 'EXPIRE' } ]; const unites = [ { label: 'Unité', value: 'u' }, { label: 'Mètre', value: 'm' }, { label: 'Mètre carré', value: 'm²' }, { label: 'Mètre cube', value: 'm³' }, { label: 'Kilogramme', value: 'kg' }, { label: 'Heure', value: 'h' }, { label: 'Jour', value: 'j' }, { label: 'Forfait', value: 'forfait' } ]; const steps = [ { label: 'Informations générales' }, { label: 'Lignes de devis' }, { label: 'Montants et TVA' }, { label: 'Validation' } ]; useEffect(() => { loadClients(); }, []); useEffect(() => { // Recalcul automatique des montants const totalHT = lignes.reduce((sum, ligne) => sum + ligne.montantHT, 0); const totalTTC = totalHT * (1 + devis.tauxTVA / 100); setDevis(prev => ({ ...prev, montantHT: totalHT, montantTTC: totalTTC })); }, [lignes, devis.tauxTVA]); 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 validateStep = (step: number) => { const newErrors: Record = {}; switch (step) { case 0: // Informations générales if (!devis.objet.trim()) { newErrors.objet = 'L\'objet du devis est obligatoire'; } if (!devis.client) { newErrors.client = 'Le client est obligatoire'; } if (!devis.dateEmission) { newErrors.dateEmission = 'La date d\'émission est obligatoire'; } if (!devis.dateValidite) { newErrors.dateValidite = 'La date de validité est obligatoire'; } if (devis.dateEmission && devis.dateValidite && devis.dateEmission > devis.dateValidite) { newErrors.dateValidite = 'La date de validité doit être postérieure à la date d\'émission'; } break; case 1: // Lignes de devis if (lignes.length === 0) { newErrors.lignes = 'Au moins une ligne de devis est requise'; } break; case 2: // Montants et TVA if (!devis.tauxTVA || devis.tauxTVA < 0 || devis.tauxTVA > 100) { newErrors.tauxTVA = 'Le taux de TVA doit être compris entre 0 et 100%'; } 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 { const devisToSave = { ...devis, client: { id: devis.client }, // Envoyer seulement l'ID du client lignes: lignes }; // TODO: Implement when backend supports it toast.current?.show({ severity: 'warn', summary: 'Non implémenté', detail: 'La création de devis n\'est pas encore disponible', life: 3000 }); // Simulate success for demo setTimeout(() => { toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Devis créé avec succès (simulation)', life: 3000 }); setTimeout(() => { router.push('/devis'); }, 1000); }, 1000); } catch (error) { console.error('Erreur lors de la création:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de créer le devis', life: 3000 }); } finally { setLoading(false); } }; const handleCancel = () => { router.push('/devis'); }; const onInputChange = (e: React.ChangeEvent, name: string) => { const val = (e.target && e.target.value) || ''; let _devis = { ...devis }; (_devis as any)[name] = val; setDevis(_devis); if (errors[name]) { const newErrors = { ...errors }; delete newErrors[name]; setErrors(newErrors); } }; const onDateChange = (e: any, name: string) => { let _devis = { ...devis }; (_devis as any)[name] = e.value; setDevis(_devis); if (errors[name]) { const newErrors = { ...errors }; delete newErrors[name]; setErrors(newErrors); } }; const onNumberChange = (e: any, name: string) => { let _devis = { ...devis }; (_devis as any)[name] = e.value; setDevis(_devis); if (errors[name]) { const newErrors = { ...errors }; delete newErrors[name]; setErrors(newErrors); } }; const onDropdownChange = (e: any, name: string) => { let _devis = { ...devis }; (_devis as any)[name] = e.value; setDevis(_devis); if (errors[name]) { const newErrors = { ...errors }; delete newErrors[name]; setErrors(newErrors); } }; const onLigneInputChange = (e: React.ChangeEvent, name: string) => { const val = (e.target && e.target.value) || ''; let _ligne = { ...nouvelleLigne }; (_ligne as any)[name] = val; setNouvelleLigne(_ligne); }; const onLigneNumberChange = (e: any, name: string) => { let _ligne = { ...nouvelleLigne }; (_ligne as any)[name] = e.value; // Recalcul automatique du montant HT if (name === 'quantite' || name === 'prixUnitaire') { const quantite = name === 'quantite' ? e.value : _ligne.quantite; const prixUnitaire = name === 'prixUnitaire' ? e.value : _ligne.prixUnitaire; _ligne.montantHT = (quantite || 0) * (prixUnitaire || 0); } setNouvelleLigne(_ligne); }; const onLigneDropdownChange = (e: any, name: string) => { let _ligne = { ...nouvelleLigne }; (_ligne as any)[name] = e.value; setNouvelleLigne(_ligne); }; const ajouterLigne = () => { if (!nouvelleLigne.designation.trim()) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'La désignation est obligatoire', life: 3000 }); return; } const ligne: DevisLigne = { ...nouvelleLigne, id: Date.now().toString() // Simple ID pour la démo }; setLignes(prev => [...prev, ligne]); setNouvelleLigne({ id: '', designation: '', quantite: 1, unite: 'u', prixUnitaire: 0, montantHT: 0 }); if (errors.lignes) { const newErrors = { ...errors }; delete newErrors.lignes; setErrors(newErrors); } }; const supprimerLigne = (id: string) => { setLignes(prev => prev.filter(ligne => ligne.id !== id)); }; const renderStepContent = () => { switch (activeIndex) { case 0: return (
onInputChange(e, 'objet')} className={errors.objet ? 'p-invalid' : ''} placeholder="Objet du devis" /> {errors.objet && {errors.objet}}
onDropdownChange(e, 'client')} placeholder="Sélectionnez un client" className={errors.client ? 'p-invalid' : ''} filter /> {errors.client && {errors.client}}
onInputChange(e, 'description')} rows={4} placeholder="Description détaillée des travaux" />
onDateChange(e, 'dateEmission')} dateFormat="dd/mm/yy" showIcon className={errors.dateEmission ? 'p-invalid' : ''} /> {errors.dateEmission && {errors.dateEmission}}
onDateChange(e, 'dateValidite')} dateFormat="dd/mm/yy" showIcon className={errors.dateValidite ? 'p-invalid' : ''} minDate={devis.dateEmission} /> {errors.dateValidite && {errors.dateValidite}}
onDropdownChange(e, 'statut')} placeholder="Sélectionnez un statut" />
); case 1: return (

Lignes de devis

{/* Formulaire d'ajout de ligne */}
Ajouter une ligne
onLigneInputChange(e, 'designation')} placeholder="Description du poste" />
onLigneNumberChange(e, 'quantite')} min={0} maxFractionDigits={2} />
onLigneDropdownChange(e, 'unite')} />
onLigneNumberChange(e, 'prixUnitaire')} mode="currency" currency="EUR" locale="fr-FR" min={0} />
{/* Tableau des lignes */} `${rowData.quantite} ${rowData.unite}`} /> rowData.prixUnitaire.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })} /> rowData.montantHT.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })} /> (
); case 2: return (

Récapitulatif des montants

onNumberChange(e, 'tauxTVA')} suffix="%" min={0} max={100} className={errors.tauxTVA ? 'p-invalid' : ''} /> {errors.tauxTVA && {errors.tauxTVA}}
); case 3: return (

Récapitulatif du devis

{devis.objet}

{clients.find(c => c.value === devis.client)?.label}

{devis.description || 'Aucune description'}

{devis.dateEmission?.toLocaleDateString('fr-FR')}

{devis.dateValidite?.toLocaleDateString('fr-FR')}

`${rowData.quantite} ${rowData.unite}`} /> rowData.prixUnitaire.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })} /> rowData.montantHT.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })} />

{devis.montantHT?.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}

{(devis.montantTTC - devis.montantHT)?.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}

{devis.montantTTC?.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}

); default: return null; } }; return (

Nouveau Devis

{renderStepContent()}
); }; export default NouveauDevisPage;