Initial commit
This commit is contained in:
750
app/(main)/devis/nouveau/page.tsx
Normal file
750
app/(main)/devis/nouveau/page.tsx
Normal file
@@ -0,0 +1,750 @@
|
||||
'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<Toast>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [clients, setClients] = useState<any[]>([]);
|
||||
const [lignes, setLignes] = useState<DevisLigne[]>([]);
|
||||
|
||||
const [devis, setDevis] = useState<Devis>({
|
||||
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<DevisLigne>({
|
||||
id: '',
|
||||
designation: '',
|
||||
quantite: 1,
|
||||
unite: 'u',
|
||||
prixUnitaire: 0,
|
||||
montantHT: 0
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
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<string, string> = {};
|
||||
|
||||
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<HTMLInputElement | HTMLTextAreaElement>, 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<HTMLInputElement>, 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 (
|
||||
<div className="formgrid grid">
|
||||
<div className="field col-12">
|
||||
<label htmlFor="objet" className="font-bold">
|
||||
Objet du devis <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<InputText
|
||||
id="objet"
|
||||
value={devis.objet}
|
||||
onChange={(e) => onInputChange(e, 'objet')}
|
||||
className={errors.objet ? 'p-invalid' : ''}
|
||||
placeholder="Objet du devis"
|
||||
/>
|
||||
{errors.objet && <small className="p-error">{errors.objet}</small>}
|
||||
</div>
|
||||
|
||||
<div className="field col-12">
|
||||
<label htmlFor="client" className="font-bold">
|
||||
Client <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Dropdown
|
||||
id="client"
|
||||
value={devis.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="description" className="font-bold">Description</label>
|
||||
<InputTextarea
|
||||
id="description"
|
||||
value={devis.description}
|
||||
onChange={(e) => onInputChange(e, 'description')}
|
||||
rows={4}
|
||||
placeholder="Description détaillée des travaux"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label htmlFor="dateEmission" className="font-bold">
|
||||
Date d'émission <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Calendar
|
||||
id="dateEmission"
|
||||
value={devis.dateEmission}
|
||||
onChange={(e) => onDateChange(e, 'dateEmission')}
|
||||
dateFormat="dd/mm/yy"
|
||||
showIcon
|
||||
className={errors.dateEmission ? 'p-invalid' : ''}
|
||||
/>
|
||||
{errors.dateEmission && <small className="p-error">{errors.dateEmission}</small>}
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label htmlFor="dateValidite" className="font-bold">
|
||||
Date de validité <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Calendar
|
||||
id="dateValidite"
|
||||
value={devis.dateValidite}
|
||||
onChange={(e) => onDateChange(e, 'dateValidite')}
|
||||
dateFormat="dd/mm/yy"
|
||||
showIcon
|
||||
className={errors.dateValidite ? 'p-invalid' : ''}
|
||||
minDate={devis.dateEmission}
|
||||
/>
|
||||
{errors.dateValidite && <small className="p-error">{errors.dateValidite}</small>}
|
||||
</div>
|
||||
|
||||
<div className="field col-12">
|
||||
<label htmlFor="statut" className="font-bold">Statut</label>
|
||||
<Dropdown
|
||||
id="statut"
|
||||
value={devis.statut}
|
||||
options={statuts}
|
||||
onChange={(e) => onDropdownChange(e, 'statut')}
|
||||
placeholder="Sélectionnez un statut"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 1:
|
||||
return (
|
||||
<div>
|
||||
<h4 className="text-primary mb-4">Lignes de devis</h4>
|
||||
|
||||
{/* Formulaire d'ajout de ligne */}
|
||||
<Card className="mb-4">
|
||||
<h5>Ajouter une ligne</h5>
|
||||
<div className="formgrid grid">
|
||||
<div className="field col-12 md:col-4">
|
||||
<label htmlFor="designation" className="font-bold">Désignation</label>
|
||||
<InputText
|
||||
id="designation"
|
||||
value={nouvelleLigne.designation}
|
||||
onChange={(e) => onLigneInputChange(e, 'designation')}
|
||||
placeholder="Description du poste"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-2">
|
||||
<label htmlFor="quantite" className="font-bold">Quantité</label>
|
||||
<InputNumber
|
||||
id="quantite"
|
||||
value={nouvelleLigne.quantite}
|
||||
onValueChange={(e) => onLigneNumberChange(e, 'quantite')}
|
||||
min={0}
|
||||
maxFractionDigits={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-2">
|
||||
<label htmlFor="unite" className="font-bold">Unité</label>
|
||||
<Dropdown
|
||||
id="unite"
|
||||
value={nouvelleLigne.unite}
|
||||
options={unites}
|
||||
onChange={(e) => onLigneDropdownChange(e, 'unite')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-2">
|
||||
<label htmlFor="prixUnitaire" className="font-bold">Prix unitaire</label>
|
||||
<InputNumber
|
||||
id="prixUnitaire"
|
||||
value={nouvelleLigne.prixUnitaire}
|
||||
onValueChange={(e) => onLigneNumberChange(e, 'prixUnitaire')}
|
||||
mode="currency"
|
||||
currency="EUR"
|
||||
locale="fr-FR"
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-2">
|
||||
<label htmlFor="montantHT" className="font-bold">Montant HT</label>
|
||||
<InputNumber
|
||||
id="montantHT"
|
||||
value={nouvelleLigne.montantHT}
|
||||
mode="currency"
|
||||
currency="EUR"
|
||||
locale="fr-FR"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-content-end">
|
||||
<Button
|
||||
label="Ajouter"
|
||||
icon="pi pi-plus"
|
||||
onClick={ajouterLigne}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Tableau des lignes */}
|
||||
<DataTable value={lignes} emptyMessage="Aucune ligne ajoutée">
|
||||
<Column field="designation" header="Désignation" />
|
||||
<Column
|
||||
field="quantite"
|
||||
header="Quantité"
|
||||
body={(rowData) => `${rowData.quantite} ${rowData.unite}`}
|
||||
/>
|
||||
<Column
|
||||
field="prixUnitaire"
|
||||
header="Prix unitaire"
|
||||
body={(rowData) => rowData.prixUnitaire.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}
|
||||
/>
|
||||
<Column
|
||||
field="montantHT"
|
||||
header="Montant HT"
|
||||
body={(rowData) => rowData.montantHT.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}
|
||||
/>
|
||||
<Column
|
||||
body={(rowData) => (
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
rounded
|
||||
severity="danger"
|
||||
size="small"
|
||||
onClick={() => supprimerLigne(rowData.id)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</DataTable>
|
||||
|
||||
{errors.lignes && <small className="p-error">{errors.lignes}</small>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<div className="formgrid grid">
|
||||
<div className="field col-12">
|
||||
<h4 className="text-primary mb-4">Récapitulatif des montants</h4>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label htmlFor="montantHT" className="font-bold">Montant HT</label>
|
||||
<InputNumber
|
||||
id="montantHT"
|
||||
value={devis.montantHT}
|
||||
mode="currency"
|
||||
currency="EUR"
|
||||
locale="fr-FR"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label htmlFor="tauxTVA" className="font-bold">
|
||||
Taux TVA (%) <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<InputNumber
|
||||
id="tauxTVA"
|
||||
value={devis.tauxTVA}
|
||||
onValueChange={(e) => onNumberChange(e, 'tauxTVA')}
|
||||
suffix="%"
|
||||
min={0}
|
||||
max={100}
|
||||
className={errors.tauxTVA ? 'p-invalid' : ''}
|
||||
/>
|
||||
{errors.tauxTVA && <small className="p-error">{errors.tauxTVA}</small>}
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label htmlFor="montantTVA" className="font-bold">Montant TVA</label>
|
||||
<InputNumber
|
||||
id="montantTVA"
|
||||
value={devis.montantTTC - devis.montantHT}
|
||||
mode="currency"
|
||||
currency="EUR"
|
||||
locale="fr-FR"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label htmlFor="montantTTC" className="font-bold">Montant TTC</label>
|
||||
<InputNumber
|
||||
id="montantTTC"
|
||||
value={devis.montantTTC}
|
||||
mode="currency"
|
||||
currency="EUR"
|
||||
locale="fr-FR"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 3:
|
||||
return (
|
||||
<div className="formgrid grid">
|
||||
<div className="field col-12">
|
||||
<h3 className="text-primary">Récapitulatif du devis</h3>
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label className="font-bold">Objet :</label>
|
||||
<p>{devis.objet}</p>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label className="font-bold">Client :</label>
|
||||
<p>{clients.find(c => c.value === devis.client)?.label}</p>
|
||||
</div>
|
||||
|
||||
<div className="field col-12">
|
||||
<label className="font-bold">Description :</label>
|
||||
<p>{devis.description || 'Aucune description'}</p>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label className="font-bold">Date d'émission :</label>
|
||||
<p>{devis.dateEmission?.toLocaleDateString('fr-FR')}</p>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label className="font-bold">Date de validité :</label>
|
||||
<p>{devis.dateValidite?.toLocaleDateString('fr-FR')}</p>
|
||||
</div>
|
||||
|
||||
<div className="field col-12">
|
||||
<label className="font-bold">Lignes du devis :</label>
|
||||
<DataTable value={lignes} size="small">
|
||||
<Column field="designation" header="Désignation" />
|
||||
<Column
|
||||
field="quantite"
|
||||
header="Quantité"
|
||||
body={(rowData) => `${rowData.quantite} ${rowData.unite}`}
|
||||
/>
|
||||
<Column
|
||||
field="prixUnitaire"
|
||||
header="Prix unitaire"
|
||||
body={(rowData) => rowData.prixUnitaire.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}
|
||||
/>
|
||||
<Column
|
||||
field="montantHT"
|
||||
header="Montant HT"
|
||||
body={(rowData) => rowData.montantHT.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}
|
||||
/>
|
||||
</DataTable>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-4">
|
||||
<label className="font-bold">Montant HT :</label>
|
||||
<p>{devis.montantHT?.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}</p>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-4">
|
||||
<label className="font-bold">TVA ({devis.tauxTVA}%) :</label>
|
||||
<p>{(devis.montantTTC - devis.montantHT)?.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}</p>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-4">
|
||||
<label className="font-bold">Montant TTC :</label>
|
||||
<p className="text-primary font-bold text-xl">{devis.montantTTC?.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })}</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>Nouveau Devis</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 devis"
|
||||
icon="pi pi-check"
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NouveauDevisPage;
|
||||
Reference in New Issue
Block a user