Initial commit

This commit is contained in:
dahoud
2025-10-01 01:39:07 +00:00
commit b430bf3b96
826 changed files with 255287 additions and 0 deletions

View File

@@ -0,0 +1,998 @@
'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 { Dialog } from 'primereact/dialog';
import { Steps } from 'primereact/steps';
import { RadioButton } from 'primereact/radiobutton';
import { clientService, chantierService, devisService } from '../../../../services/api';
import { formatCurrency } from '../../../../utils/formatters';
import type { Facture, LigneFacture, Client, Chantier, Devis } from '../../../../types/btp';
interface LigneFactureFormData {
designation: string;
description: string;
quantite: number;
unite: string;
prixUnitaire: number;
montantLigne: number;
ordre: number;
}
const NouvelleFacturePage = () => {
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 [chantiers, setChantiers] = useState<any[]>([]);
const [devisList, setDevisList] = useState<any[]>([]);
const [ligneDialog, setLigneDialog] = useState(false);
const [creationMode, setCreationMode] = useState<'manual' | 'from_devis'>('manual');
const [facture, setFacture] = useState<Facture>({
id: '',
numero: '',
objet: '',
description: '',
dateEmission: new Date(),
dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // +30 jours
datePaiement: null,
statut: 'BROUILLON',
montantHT: 0,
tauxTVA: 20,
montantTVA: 0,
montantTTC: 0,
montantPaye: 0,
conditionsPaiement: 'Paiement à 30 jours fin de mois',
typeFacture: 'FACTURE',
actif: true,
client: null,
chantier: null,
devis: null,
lignes: []
});
const [selectedDevis, setSelectedDevis] = useState<Devis | null>(null);
const [currentLigne, setCurrentLigne] = useState<LigneFactureFormData>({
designation: '',
description: '',
quantite: 1,
unite: 'h',
prixUnitaire: 0,
montantLigne: 0,
ordre: 1
});
const [editingLigne, setEditingLigne] = useState<number | null>(null);
const [errors, setErrors] = useState<Record<string, string>>({});
const unites = [
{ label: 'Heures', value: 'h' },
{ label: 'Jours', value: 'j' },
{ label: 'Mètres', value: 'm' },
{ label: 'Mètres carrés', value: 'm²' },
{ label: 'Mètres cubes', value: 'm³' },
{ label: 'Unités', value: 'u' },
{ label: 'Forfait', value: 'forfait' },
{ label: 'Lots', value: 'lot' }
];
const statuts = [
{ label: 'Brouillon', value: 'BROUILLON' },
{ label: 'Émise', value: 'ENVOYEE' },
{ label: 'Payée', value: 'PAYEE' }
];
const typesFacture = [
{ label: 'Facture complète', value: 'FACTURE' },
{ label: 'Facture d\'acompte', value: 'ACOMPTE' },
{ label: 'Avoir', value: 'AVOIR' }
];
const steps = [
{ label: 'Mode de création' },
{ label: 'Informations générales' },
{ label: 'Client et références' },
{ label: 'Prestations' },
{ label: 'Conditions' },
{ label: 'Validation' }
];
useEffect(() => {
loadClients();
generateNumero();
}, []);
useEffect(() => {
if (facture.client) {
loadChantiersByClient(facture.client as string);
loadDevisByClient(facture.client as string);
} else {
setChantiers([]);
setDevisList([]);
}
}, [facture.client]);
useEffect(() => {
calculateTotals();
}, [facture.lignes, facture.tauxTVA]);
useEffect(() => {
if (selectedDevis && creationMode === 'from_devis') {
importFromDevis();
}
}, [selectedDevis]);
const generateNumero = () => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const time = String(now.getHours()).padStart(2, '0') + String(now.getMinutes()).padStart(2, '0');
const numero = `FACT-${year}${month}${day}-${time}`;
setFacture(prev => ({ ...prev, numero }));
};
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 loadChantiersByClient = async (clientId: string) => {
try {
const data = await chantierService.getByClient(clientId);
setChantiers(data.map(chantier => ({
label: chantier.nom,
value: chantier.id,
chantier: chantier
})));
} catch (error) {
console.error('Erreur lors du chargement des chantiers:', error);
setChantiers([]);
}
};
const loadDevisByClient = async (clientId: string) => {
try {
const data = await devisService.getAll();
// Filtrer les devis acceptés du client
const clientDevis = data.filter(devis =>
devis.client?.id === clientId && devis.statut === 'ACCEPTE'
);
setDevisList(clientDevis.map(devis => ({
label: `${devis.numero} - ${devis.objet} (${formatCurrency(devis.montantTTC || 0)})`,
value: devis.id,
devis: devis
})));
} catch (error) {
console.error('Erreur lors du chargement des devis:', error);
setDevisList([]);
}
};
const importFromDevis = () => {
if (!selectedDevis) return;
setFacture(prev => ({
...prev,
objet: `Facture - ${selectedDevis.objet}`,
description: selectedDevis.description || '',
montantHT: selectedDevis.montantHT || 0,
tauxTVA: selectedDevis.tauxTVA || 20,
montantTVA: selectedDevis.montantTVA || 0,
montantTTC: selectedDevis.montantTTC || 0,
client: selectedDevis.client?.id || null,
devis: selectedDevis.id,
lignes: selectedDevis.lignes?.map(ligneDevis => ({
id: '',
designation: ligneDevis.designation,
description: ligneDevis.description || '',
quantite: ligneDevis.quantite,
unite: ligneDevis.unite,
prixUnitaire: ligneDevis.prixUnitaire,
montantLigne: ligneDevis.montantLigne || 0,
ordre: ligneDevis.ordre,
dateCreation: new Date().toISOString(),
dateModification: new Date().toISOString(),
facture: {} as Facture
})) || []
}));
};
const calculateTotals = () => {
const montantHT = facture.lignes?.reduce((sum, ligne) => sum + (ligne.montantLigne || 0), 0) || 0;
const montantTVA = montantHT * (facture.tauxTVA / 100);
const montantTTC = montantHT + montantTVA;
setFacture(prev => ({
...prev,
montantHT,
montantTVA,
montantTTC
}));
};
const validateStep = (step: number) => {
const newErrors: Record<string, string> = {};
switch (step) {
case 1: // Informations générales
if (!facture.objet.trim()) {
newErrors.objet = 'L\'objet de la facture est obligatoire';
}
break;
case 2: // Client et références
if (!facture.client) {
newErrors.client = 'Le client est obligatoire';
}
break;
case 3: // Prestations
if (creationMode === 'manual' && (!facture.lignes || facture.lignes.length === 0)) {
newErrors.lignes = 'Au moins une prestation est obligatoire';
}
if (creationMode === 'from_devis' && !selectedDevis) {
newErrors.devis = 'Veuillez sélectionner un devis';
}
break;
case 4: // Conditions
if (!facture.conditionsPaiement?.trim()) {
newErrors.conditionsPaiement = 'Les conditions de paiement sont obligatoires';
}
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 openLigneDialog = () => {
setCurrentLigne({
designation: '',
description: '',
quantite: 1,
unite: 'h',
prixUnitaire: 0,
montantLigne: 0,
ordre: (facture.lignes?.length || 0) + 1
});
setEditingLigne(null);
setLigneDialog(true);
};
const editLigne = (index: number) => {
const ligne = facture.lignes?.[index];
if (ligne) {
setCurrentLigne({
designation: ligne.designation,
description: ligne.description || '',
quantite: ligne.quantite,
unite: ligne.unite,
prixUnitaire: ligne.prixUnitaire,
montantLigne: ligne.montantLigne || 0,
ordre: ligne.ordre
});
setEditingLigne(index);
setLigneDialog(true);
}
};
const saveLigne = () => {
const montantLigne = currentLigne.quantite * currentLigne.prixUnitaire;
const newLigne = {
...currentLigne,
montantLigne,
id: '',
dateCreation: new Date().toISOString(),
dateModification: new Date().toISOString(),
facture: {} as Facture
};
if (editingLigne !== null) {
// Modification
const updatedLignes = [...(facture.lignes || [])];
updatedLignes[editingLigne] = newLigne as LigneFacture;
setFacture(prev => ({ ...prev, lignes: updatedLignes }));
} else {
// Ajout
setFacture(prev => ({
...prev,
lignes: [...(prev.lignes || []), newLigne as LigneFacture]
}));
}
setLigneDialog(false);
};
const deleteLigne = (index: number) => {
const updatedLignes = facture.lignes?.filter((_, i) => i !== index) || [];
setFacture(prev => ({ ...prev, lignes: updatedLignes }));
};
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 {
// Simulation de création (l'API n'est pas encore implémentée)
console.log('Données de la facture:', facture);
toast.current?.show({
severity: 'info',
summary: 'Information',
detail: 'Fonctionnalité en cours d\'implémentation côté serveur',
life: 3000
});
setTimeout(() => {
router.push('/factures');
}, 1000);
} catch (error: any) {
console.error('Erreur lors de la création:', error);
toast.current?.show({
severity: 'error',
summary: 'Erreur',
detail: 'Impossible de créer la facture',
life: 5000
});
} finally {
setLoading(false);
}
};
const handleCancel = () => {
router.push('/factures');
};
const onInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, name: string) => {
const val = (e.target && e.target.value) || '';
let _facture = { ...facture };
(_facture as any)[name] = val;
setFacture(_facture);
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
const onDateChange = (e: any, name: string) => {
let _facture = { ...facture };
(_facture as any)[name] = e.value;
setFacture(_facture);
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
const onNumberChange = (e: any, name: string) => {
let _facture = { ...facture };
(_facture as any)[name] = e.value;
setFacture(_facture);
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
const onDropdownChange = (e: any, name: string) => {
let _facture = { ...facture };
(_facture as any)[name] = e.value;
setFacture(_facture);
if (errors[name]) {
const newErrors = { ...errors };
delete newErrors[name];
setErrors(newErrors);
}
};
const onLigneInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, name: string) => {
const val = (e.target && e.target.value) || '';
let _ligne = { ...currentLigne };
(_ligne as any)[name] = val;
setCurrentLigne(_ligne);
};
const onLigneNumberChange = (e: any, name: string) => {
let _ligne = { ...currentLigne };
(_ligne as any)[name] = e.value || 0;
setCurrentLigne(_ligne);
};
const onLigneDropdownChange = (e: any, name: string) => {
let _ligne = { ...currentLigne };
(_ligne as any)[name] = e.value;
setCurrentLigne(_ligne);
};
const ligneActionBodyTemplate = (rowData: LigneFacture, options: any) => {
return (
<div className="flex gap-2">
<Button
icon="pi pi-pencil"
rounded
severity="success"
size="small"
onClick={() => editLigne(options.rowIndex)}
/>
<Button
icon="pi pi-trash"
rounded
severity="danger"
size="small"
onClick={() => deleteLigne(options.rowIndex)}
/>
</div>
);
};
const ligneDialogFooter = (
<>
<Button
label="Annuler"
icon="pi pi-times"
text
onClick={() => setLigneDialog(false)}
/>
<Button
label="Enregistrer"
icon="pi pi-check"
text
onClick={saveLigne}
disabled={!currentLigne.designation || currentLigne.quantite <= 0 || currentLigne.prixUnitaire <= 0}
/>
</>
);
const renderStepContent = () => {
switch (activeIndex) {
case 0:
return (
<div className="formgrid grid">
<div className="field col-12">
<h4 className="text-primary">Mode de création de la facture</h4>
<p className="text-600">Choisissez comment vous souhaitez créer cette facture :</p>
</div>
<div className="field col-12">
<div className="flex flex-column gap-3">
<div className="flex align-items-center">
<RadioButton
inputId="manual"
name="creationMode"
value="manual"
onChange={(e) => setCreationMode(e.value)}
checked={creationMode === 'manual'}
/>
<label htmlFor="manual" className="ml-2">
<strong>Création manuelle</strong>
<div className="text-600 text-sm">Saisir les informations et prestations manuellement</div>
</label>
</div>
<div className="flex align-items-center">
<RadioButton
inputId="from_devis"
name="creationMode"
value="from_devis"
onChange={(e) => setCreationMode(e.value)}
checked={creationMode === 'from_devis'}
/>
<label htmlFor="from_devis" className="ml-2">
<strong>À partir d'un devis accepté</strong>
<div className="text-600 text-sm">Importer les informations d'un devis existant</div>
</label>
</div>
</div>
</div>
</div>
);
case 1:
return (
<div className="formgrid grid">
<div className="field col-12 md:col-6">
<label htmlFor="numero" className="font-bold">Numéro de facture</label>
<InputText
id="numero"
value={facture.numero}
disabled
placeholder="Généré automatiquement"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="typeFacture" className="font-bold">Type de facture</label>
<Dropdown
id="typeFacture"
value={facture.typeFacture}
options={typesFacture}
onChange={(e) => onDropdownChange(e, 'typeFacture')}
placeholder="Sélectionnez un type"
/>
</div>
<div className="field col-12">
<label htmlFor="objet" className="font-bold">
Objet de la facture <span className="text-red-500">*</span>
</label>
<InputText
id="objet"
value={facture.objet}
onChange={(e) => onInputChange(e, 'objet')}
className={errors.objet ? 'p-invalid' : ''}
placeholder="Objet de la facture"
/>
{errors.objet && <small className="p-error">{errors.objet}</small>}
</div>
<div className="field col-12">
<label htmlFor="description" className="font-bold">Description</label>
<InputTextarea
id="description"
value={facture.description}
onChange={(e) => onInputChange(e, 'description')}
rows={4}
placeholder="Description détaillée des travaux facturés"
/>
</div>
</div>
);
case 2:
return (
<div className="formgrid grid">
<div className="field col-12">
<label htmlFor="client" className="font-bold">
Client <span className="text-red-500">*</span>
</label>
<Dropdown
id="client"
value={facture.client}
options={clients}
onChange={(e) => onDropdownChange(e, 'client')}
placeholder="Sélectionnez un client"
className={errors.client ? 'p-invalid' : ''}
filter
/>
{errors.client && <small className="p-error">{errors.client}</small>}
</div>
<div className="field col-12">
<label htmlFor="chantier" className="font-bold">Chantier (optionnel)</label>
<Dropdown
id="chantier"
value={facture.chantier}
options={chantiers}
onChange={(e) => onDropdownChange(e, 'chantier')}
placeholder="Sélectionnez un chantier"
disabled={!facture.client}
/>
</div>
{creationMode === 'from_devis' && (
<div className="field col-12">
<label htmlFor="devis" className="font-bold">
Devis de référence <span className="text-red-500">*</span>
</label>
<Dropdown
id="devis"
value={selectedDevis?.id}
options={devisList}
onChange={(e) => {
const devis = devisList.find(d => d.value === e.value)?.devis;
setSelectedDevis(devis || null);
}}
placeholder="Sélectionnez un devis accepté"
className={errors.devis ? 'p-invalid' : ''}
disabled={!facture.client}
/>
{errors.devis && <small className="p-error">{errors.devis}</small>}
</div>
)}
<div className="field col-12 md:col-6">
<label htmlFor="dateEmission" className="font-bold">Date d'émission</label>
<Calendar
id="dateEmission"
value={facture.dateEmission}
onChange={(e) => onDateChange(e, 'dateEmission')}
dateFormat="dd/mm/yy"
showIcon
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="dateEcheance" className="font-bold">Date d'échéance</label>
<Calendar
id="dateEcheance"
value={facture.dateEcheance}
onChange={(e) => onDateChange(e, 'dateEcheance')}
dateFormat="dd/mm/yy"
showIcon
minDate={facture.dateEmission}
/>
</div>
</div>
);
case 3:
return (
<div>
{creationMode === 'manual' ? (
<>
<div className="flex justify-content-between align-items-center mb-3">
<h3>Prestations facturées</h3>
<Button
label="Ajouter une prestation"
icon="pi pi-plus"
onClick={openLigneDialog}
/>
</div>
{errors.lignes && <small className="p-error block mb-3">{errors.lignes}</small>}
<DataTable
value={facture.lignes || []}
responsiveLayout="scroll"
emptyMessage="Aucune prestation ajoutée."
>
<Column field="designation" header="Désignation" />
<Column field="quantite" header="Quantité" />
<Column field="unite" header="Unité" />
<Column
field="prixUnitaire"
header="Prix unitaire"
body={(rowData) => formatCurrency(rowData.prixUnitaire)}
/>
<Column
field="montantLigne"
header="Montant"
body={(rowData) => formatCurrency(rowData.montantLigne || 0)}
/>
<Column body={ligneActionBodyTemplate} headerStyle={{ width: '8rem' }} />
</DataTable>
</>
) : (
<div>
<h3>Prestations importées du devis</h3>
{selectedDevis ? (
<>
<p className="text-600 mb-3">
Devis: <strong>{selectedDevis.numero} - {selectedDevis.objet}</strong>
</p>
<DataTable
value={facture.lignes || []}
responsiveLayout="scroll"
emptyMessage="Aucune prestation dans le devis."
>
<Column field="designation" header="Désignation" />
<Column field="quantite" header="Quantité" />
<Column field="unite" header="Unité" />
<Column
field="prixUnitaire"
header="Prix unitaire"
body={(rowData) => formatCurrency(rowData.prixUnitaire)}
/>
<Column
field="montantLigne"
header="Montant"
body={(rowData) => formatCurrency(rowData.montantLigne || 0)}
/>
</DataTable>
</>
) : (
<p className="text-600">Veuillez d'abord sélectionner un devis à l'étape précédente.</p>
)}
</div>
)}
<div className="mt-3 text-right">
<p><strong>Montant HT: {formatCurrency(facture.montantHT || 0)}</strong></p>
<p>TVA ({facture.tauxTVA}%): {formatCurrency(facture.montantTVA || 0)}</p>
<p className="text-xl"><strong>Montant TTC: {formatCurrency(facture.montantTTC || 0)}</strong></p>
</div>
</div>
);
case 4:
return (
<div className="formgrid grid">
<div className="field col-12 md:col-6">
<label htmlFor="tauxTVA" className="font-bold">Taux TVA (%)</label>
<InputNumber
id="tauxTVA"
value={facture.tauxTVA}
onValueChange={(e) => onNumberChange(e, 'tauxTVA')}
suffix="%"
min={0}
max={100}
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="statut" className="font-bold">Statut initial</label>
<Dropdown
id="statut"
value={facture.statut}
options={statuts}
onChange={(e) => onDropdownChange(e, 'statut')}
placeholder="Sélectionnez un statut"
/>
</div>
<div className="field col-12">
<label htmlFor="conditionsPaiement" className="font-bold">
Conditions de paiement <span className="text-red-500">*</span>
</label>
<InputTextarea
id="conditionsPaiement"
value={facture.conditionsPaiement}
onChange={(e) => onInputChange(e, 'conditionsPaiement')}
rows={3}
className={errors.conditionsPaiement ? 'p-invalid' : ''}
placeholder="Conditions de paiement"
/>
{errors.conditionsPaiement && <small className="p-error">{errors.conditionsPaiement}</small>}
</div>
</div>
);
case 5:
return (
<div className="formgrid grid">
<div className="field col-12">
<h3 className="text-primary">Récapitulatif de la facture</h3>
<Divider />
</div>
<div className="field col-12 md:col-6">
<label className="font-bold">Numéro:</label>
<p>{facture.numero}</p>
</div>
<div className="field col-12 md:col-6">
<label className="font-bold">Type:</label>
<p>{typesFacture.find(t => t.value === facture.typeFacture)?.label}</p>
</div>
<div className="field col-12 md:col-6">
<label className="font-bold">Client:</label>
<p>{clients.find(c => c.value === facture.client)?.label}</p>
</div>
<div className="field col-12 md:col-6">
<label className="font-bold">Mode de création:</label>
<p>{creationMode === 'manual' ? 'Création manuelle' : 'À partir du devis'}</p>
</div>
<div className="field col-12">
<label className="font-bold">Objet:</label>
<p>{facture.objet}</p>
</div>
<div className="field col-12">
<label className="font-bold">Prestations ({facture.lignes?.length || 0}):</label>
<ul>
{facture.lignes?.map((ligne, index) => (
<li key={index}>
{ligne.designation} - {ligne.quantite} {ligne.unite} × {formatCurrency(ligne.prixUnitaire)} = {formatCurrency(ligne.montantLigne || 0)}
</li>
))}
</ul>
</div>
<div className="field col-12 text-right">
<p>Montant HT: {formatCurrency(facture.montantHT || 0)}</p>
<p>TVA ({facture.tauxTVA}%): {formatCurrency(facture.montantTVA || 0)}</p>
<p className="text-xl"><strong>Total TTC: {formatCurrency(facture.montantTTC || 0)}</strong></p>
</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>Nouvelle Facture</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 la facture"
icon="pi pi-check"
loading={loading}
disabled={loading}
/>
)}
</div>
</div>
</form>
<Dialog
visible={ligneDialog}
style={{ width: '600px' }}
header={editingLigne !== null ? 'Modifier la prestation' : 'Ajouter une prestation'}
modal
className="p-fluid"
footer={ligneDialogFooter}
onHide={() => setLigneDialog(false)}
>
<div className="formgrid grid">
<div className="field col-12">
<label htmlFor="designation" className="font-bold">Désignation</label>
<InputText
id="designation"
value={currentLigne.designation}
onChange={(e) => onLigneInputChange(e, 'designation')}
placeholder="Désignation de la prestation"
/>
</div>
<div className="field col-12">
<label htmlFor="ligneDescription" className="font-bold">Description</label>
<InputTextarea
id="ligneDescription"
value={currentLigne.description}
onChange={(e) => onLigneInputChange(e, 'description')}
rows={2}
placeholder="Description détaillée"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="quantite" className="font-bold">Quantité</label>
<InputNumber
id="quantite"
value={currentLigne.quantite}
onValueChange={(e) => onLigneNumberChange(e, 'quantite')}
min={0.01}
minFractionDigits={0}
maxFractionDigits={2}
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="unite" className="font-bold">Unité</label>
<Dropdown
id="unite"
value={currentLigne.unite}
options={unites}
onChange={(e) => onLigneDropdownChange(e, 'unite')}
placeholder="Sélectionnez une unité"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="prixUnitaire" className="font-bold">Prix unitaire ()</label>
<InputNumber
id="prixUnitaire"
value={currentLigne.prixUnitaire}
onValueChange={(e) => onLigneNumberChange(e, 'prixUnitaire')}
mode="currency"
currency="EUR"
locale="fr-FR"
min={0}
/>
</div>
<div className="field col-12">
<label className="font-bold">Montant de la ligne:</label>
<p className="text-primary text-xl">
{formatCurrency(currentLigne.quantite * currentLigne.prixUnitaire)}
</p>
</div>
</div>
</Dialog>
</Card>
</div>
</div>
);
};
export default NouvelleFacturePage;