'use client'; import React, { useState, useEffect, useRef } from 'react'; import { useParams, 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 { Dropdown } from 'primereact/dropdown'; import { Calendar } from 'primereact/calendar'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Toast } from 'primereact/toast'; import { ProgressSpinner } from 'primereact/progressspinner'; import { Toolbar } from 'primereact/toolbar'; import { Dialog } from 'primereact/dialog'; import { Divider } from 'primereact/divider'; import { factureService, clientService } from '../../../../../services/api'; import { formatCurrency } from '../../../../../utils/formatters'; import type { Facture, LigneFacture, Client } from '../../../../../types/btp'; const FactureEditPage = () => { const params = useParams(); const router = useRouter(); const toast = useRef(null); const [facture, setFacture] = useState>({ numero: '', objet: '', description: '', type: 'FACTURE', statut: 'BROUILLON', dateEmission: new Date(), dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // +30 jours tauxTVA: 20, lignes: [] }); const [clients, setClients] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [showLigneDialog, setShowLigneDialog] = useState(false); const [editingLigne, setEditingLigne] = useState(null); const [editingIndex, setEditingIndex] = useState(-1); const [ligneForm, setLigneForm] = useState>({ designation: '', quantite: 1, unite: 'unité', prixUnitaire: 0, montantHT: 0 }); const factureId = params.id as string; const isNew = factureId === 'nouveau'; const typeOptions = [ { label: 'Facture', value: 'FACTURE' }, { label: 'Acompte', value: 'ACOMPTE' }, { label: 'Facture de situation', value: 'SITUATION' }, { label: 'Facture de solde', value: 'SOLDE' } ]; const statutOptions = [ { label: 'Brouillon', value: 'BROUILLON' }, { label: 'Envoyée', value: 'ENVOYEE' }, { label: 'Payée', value: 'PAYEE' }, { label: 'Partiellement payée', value: 'PARTIELLEMENT_PAYEE' }, { label: 'En retard', value: 'EN_RETARD' } ]; const uniteOptions = [ { label: 'Unité', value: 'unité' }, { label: 'Mètre', value: 'm' }, { label: 'Mètre carré', value: 'm²' }, { label: 'Mètre cube', value: 'm³' }, { label: 'Heure', value: 'h' }, { label: 'Jour', value: 'jour' }, { label: 'Forfait', value: 'forfait' }, { label: 'Kilogramme', value: 'kg' }, { label: 'Tonne', value: 't' } ]; useEffect(() => { loadData(); }, [factureId]); useEffect(() => { calculateMontants(); }, [facture.lignes, facture.tauxTVA]); const loadData = async () => { try { setLoading(true); // Charger les clients const clientsResponse = await clientService.getAll(); setClients(clientsResponse.data); if (!isNew) { // Charger la facture existante const factureResponse = await factureService.getById(factureId); setFacture(factureResponse.data); } else { // Générer un nouveau numéro const numeroResponse = await factureService.generateNumero(); setFacture(prev => ({ ...prev, numero: numeroResponse.data.numero })); } } catch (error) { console.error('Erreur lors du chargement:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les données' }); } finally { setLoading(false); } }; const calculateMontants = () => { if (!facture.lignes) return; const montantHT = facture.lignes.reduce((total, ligne) => total + (ligne.montantHT || 0), 0); const montantTVA = montantHT * (facture.tauxTVA || 0) / 100; const montantTTC = montantHT + montantTVA; setFacture(prev => ({ ...prev, montantHT, montantTTC })); }; const handleSave = async () => { try { setSaving(true); if (!facture.numero || !facture.objet || !facture.client) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez remplir tous les champs obligatoires' }); return; } if (isNew) { await factureService.create(facture); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Facture créée avec succès' }); } else { await factureService.update(factureId, facture); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Facture modifiée avec succès' }); } router.push('/factures'); } catch (error) { console.error('Erreur lors de la sauvegarde:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Erreur lors de la sauvegarde' }); } finally { setSaving(false); } }; const handleAddLigne = () => { setEditingLigne(null); setEditingIndex(-1); setLigneForm({ designation: '', quantite: 1, unite: 'unité', prixUnitaire: 0, montantHT: 0 }); setShowLigneDialog(true); }; const handleEditLigne = (ligne: LigneFacture, index: number) => { setEditingLigne(ligne); setEditingIndex(index); setLigneForm({ ...ligne }); setShowLigneDialog(true); }; const handleSaveLigne = () => { if (!ligneForm.designation || !ligneForm.quantite || !ligneForm.prixUnitaire) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez remplir tous les champs de la ligne' }); return; } const montantHT = (ligneForm.quantite || 0) * (ligneForm.prixUnitaire || 0); const nouvelleLigne: LigneFacture = { ...ligneForm, montantHT } as LigneFacture; const nouvellesLignes = [...(facture.lignes || [])]; if (editingIndex >= 0) { nouvellesLignes[editingIndex] = nouvelleLigne; } else { nouvellesLignes.push(nouvelleLigne); } setFacture(prev => ({ ...prev, lignes: nouvellesLignes })); setShowLigneDialog(false); }; const handleDeleteLigne = (index: number) => { const nouvellesLignes = facture.lignes?.filter((_, i) => i !== index) || []; setFacture(prev => ({ ...prev, lignes: nouvellesLignes })); }; const toolbarStartTemplate = () => (
); const toolbarEndTemplate = () => (
); if (loading) { return (
); } return (
{/* Informations générales */}
setFacture(prev => ({ ...prev, numero: e.target.value }))} className="w-full" disabled={!isNew} />
setFacture(prev => ({ ...prev, type: e.value }))} className="w-full" />
setFacture(prev => ({ ...prev, objet: e.target.value }))} className="w-full" />
setFacture(prev => ({ ...prev, description: e.target.value }))} className="w-full" rows={3} />
({ label: client.nom, value: client }))} onChange={(e) => setFacture(prev => ({ ...prev, client: e.value }))} className="w-full" placeholder="Sélectionner un client" filter />
setFacture(prev => ({ ...prev, statut: e.value }))} className="w-full" />
setFacture(prev => ({ ...prev, dateEmission: e.value || new Date() }))} className="w-full" dateFormat="dd/mm/yy" />
setFacture(prev => ({ ...prev, dateEcheance: e.value || new Date() }))} className="w-full" dateFormat="dd/mm/yy" />
setFacture(prev => ({ ...prev, tauxTVA: e.value || 0 }))} className="w-full" suffix="%" min={0} max={100} />
{/* Lignes de la facture */}
Lignes de facturation
rowData.quantite?.toLocaleString('fr-FR')} /> formatCurrency(rowData.prixUnitaire)} /> formatCurrency(rowData.montantHT)} /> (
)} />
{/* Totaux */}
Montant HT: {formatCurrency(facture.montantHT || 0)}
TVA ({facture.tauxTVA}%): {formatCurrency(((facture.montantHT || 0) * (facture.tauxTVA || 0)) / 100)}
Montant TTC: {formatCurrency(facture.montantTTC || 0)}
{/* Dialog d'ajout/modification de ligne */} setShowLigneDialog(false)} style={{ width: '600px' }} footer={
} >
setLigneForm(prev => ({ ...prev, designation: e.target.value }))} className="w-full" />
{ const quantite = e.value || 0; const montantHT = quantite * (ligneForm.prixUnitaire || 0); setLigneForm(prev => ({ ...prev, quantite, montantHT })); }} className="w-full" min={0} maxFractionDigits={2} />
setLigneForm(prev => ({ ...prev, unite: e.value }))} className="w-full" />
{ const prixUnitaire = e.value || 0; const montantHT = (ligneForm.quantite || 0) * prixUnitaire; setLigneForm(prev => ({ ...prev, prixUnitaire, montantHT })); }} className="w-full" mode="currency" currency="EUR" locale="fr-FR" min={0} />
{formatCurrency(ligneForm.montantHT || 0)}
); }; export default FactureEditPage;