- Correction des erreurs TypeScript dans userService.ts et workflowTester.ts - Ajout des propriétés manquantes aux objets User mockés - Conversion des dates de string vers objets Date - Correction des appels asynchrones et des types incompatibles - Ajout de dynamic rendering pour résoudre les erreurs useSearchParams - Enveloppement de useSearchParams dans Suspense boundary - Configuration de force-dynamic au niveau du layout principal Build réussi: 126 pages générées avec succès 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
671 lines
27 KiB
TypeScript
671 lines
27 KiB
TypeScript
'use client';
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import { Card } from 'primereact/card';
|
|
import { Button } from 'primereact/button';
|
|
import { DataTable } from 'primereact/datatable';
|
|
import { Column } from 'primereact/column';
|
|
import { InputText } from 'primereact/inputtext';
|
|
import { InputTextarea } from 'primereact/inputtextarea';
|
|
import { Dropdown } from 'primereact/dropdown';
|
|
import { Tag } from 'primereact/tag';
|
|
import { Toast } from 'primereact/toast';
|
|
import { Toolbar } from 'primereact/toolbar';
|
|
import { Dialog } from 'primereact/dialog';
|
|
import { ConfirmDialog } from 'primereact/confirmdialog';
|
|
import { Menu } from 'primereact/menu';
|
|
import { Badge } from 'primereact/badge';
|
|
import { Checkbox } from 'primereact/checkbox';
|
|
import { factureService } from '../../../../services/api';
|
|
import { formatDate } from '../../../../utils/formatters';
|
|
|
|
interface FactureTemplate {
|
|
id: string;
|
|
nom: string;
|
|
description: string;
|
|
type: string;
|
|
categorie: string;
|
|
lignes: Array<{
|
|
designation: string;
|
|
quantite: number;
|
|
unite: string;
|
|
prixUnitaire: number;
|
|
}>;
|
|
tauxTVA: number;
|
|
conditionsPaiement: string;
|
|
actif: boolean;
|
|
dateCreation: Date;
|
|
utilisations: number;
|
|
}
|
|
|
|
const FactureTemplatesPage = () => {
|
|
const toast = useRef<Toast>(null);
|
|
const menuRef = useRef<Menu>(null);
|
|
|
|
const [templates, setTemplates] = useState<FactureTemplate[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [showDialog, setShowDialog] = useState(false);
|
|
const [editingTemplate, setEditingTemplate] = useState<FactureTemplate | null>(null);
|
|
const [selectedTemplate, setSelectedTemplate] = useState<FactureTemplate | null>(null);
|
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
|
|
const [formData, setFormData] = useState<Partial<FactureTemplate>>({
|
|
nom: '',
|
|
description: '',
|
|
type: 'FACTURE',
|
|
categorie: '',
|
|
lignes: [],
|
|
tauxTVA: 20,
|
|
conditionsPaiement: 'Paiement à 30 jours',
|
|
actif: true
|
|
});
|
|
|
|
const typeOptions = [
|
|
{ label: 'Facture', value: 'FACTURE' },
|
|
{ label: 'Acompte', value: 'ACOMPTE' },
|
|
{ label: 'Facture de situation', value: 'SITUATION' },
|
|
{ label: 'Facture de solde', value: 'SOLDE' }
|
|
];
|
|
|
|
const categorieOptions = [
|
|
{ label: 'Gros œuvre', value: 'GROS_OEUVRE' },
|
|
{ label: 'Second œuvre', value: 'SECOND_OEUVRE' },
|
|
{ label: 'Finitions', value: 'FINITIONS' },
|
|
{ label: 'Plomberie', value: 'PLOMBERIE' },
|
|
{ label: 'Électricité', value: 'ELECTRICITE' },
|
|
{ label: 'Chauffage', value: 'CHAUFFAGE' },
|
|
{ label: 'Rénovation', value: 'RENOVATION' },
|
|
{ label: 'Maintenance', value: 'MAINTENANCE' }
|
|
];
|
|
|
|
useEffect(() => {
|
|
loadTemplates();
|
|
}, []);
|
|
|
|
const loadTemplates = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// TODO: Remplacer par un vrai appel API
|
|
// const response = await factureService.getTemplates();
|
|
|
|
// Données simulées pour la démonstration
|
|
const mockTemplates: FactureTemplate[] = [
|
|
{
|
|
id: '1',
|
|
nom: 'Facture Rénovation Standard',
|
|
description: 'Template pour factures de rénovation complète',
|
|
type: 'FACTURE',
|
|
categorie: 'RENOVATION',
|
|
lignes: [
|
|
{ designation: 'Main d\'œuvre', quantite: 1, unite: 'forfait', prixUnitaire: 2500 },
|
|
{ designation: 'Matériaux', quantite: 1, unite: 'forfait', prixUnitaire: 1800 },
|
|
{ designation: 'Évacuation déchets', quantite: 1, unite: 'forfait', prixUnitaire: 300 }
|
|
],
|
|
tauxTVA: 20,
|
|
conditionsPaiement: 'Paiement à 30 jours',
|
|
actif: true,
|
|
dateCreation: new Date('2024-01-15'),
|
|
utilisations: 45
|
|
},
|
|
{
|
|
id: '2',
|
|
nom: 'Acompte Gros Œuvre',
|
|
description: 'Template pour acomptes sur travaux de gros œuvre',
|
|
type: 'ACOMPTE',
|
|
categorie: 'GROS_OEUVRE',
|
|
lignes: [
|
|
{ designation: 'Acompte 30% - Fondations', quantite: 1, unite: 'forfait', prixUnitaire: 0 }
|
|
],
|
|
tauxTVA: 20,
|
|
conditionsPaiement: 'Paiement à réception',
|
|
actif: true,
|
|
dateCreation: new Date('2024-02-10'),
|
|
utilisations: 28
|
|
},
|
|
{
|
|
id: '3',
|
|
nom: 'Facture Maintenance Préventive',
|
|
description: 'Template pour factures de maintenance préventive',
|
|
type: 'FACTURE',
|
|
categorie: 'MAINTENANCE',
|
|
lignes: [
|
|
{ designation: 'Contrôle technique', quantite: 1, unite: 'forfait', prixUnitaire: 150 },
|
|
{ designation: 'Remplacement pièces', quantite: 1, unite: 'forfait', prixUnitaire: 200 },
|
|
{ designation: 'Rapport de maintenance', quantite: 1, unite: 'forfait', prixUnitaire: 50 }
|
|
],
|
|
tauxTVA: 20,
|
|
conditionsPaiement: 'Paiement à 15 jours',
|
|
actif: true,
|
|
dateCreation: new Date('2024-03-05'),
|
|
utilisations: 67
|
|
}
|
|
];
|
|
|
|
setTemplates(mockTemplates);
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement des templates:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Impossible de charger les templates'
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleNew = () => {
|
|
setEditingTemplate(null);
|
|
setFormData({
|
|
nom: '',
|
|
description: '',
|
|
type: 'FACTURE',
|
|
categorie: '',
|
|
lignes: [],
|
|
tauxTVA: 20,
|
|
conditionsPaiement: 'Paiement à 30 jours',
|
|
actif: true
|
|
});
|
|
setShowDialog(true);
|
|
};
|
|
|
|
const handleEdit = (template: FactureTemplate) => {
|
|
setEditingTemplate(template);
|
|
setFormData({ ...template });
|
|
setShowDialog(true);
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
if (editingTemplate) {
|
|
// TODO: Appel API pour mise à jour
|
|
// await factureService.updateTemplate(editingTemplate.id, formData);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Template modifié avec succès'
|
|
});
|
|
} else {
|
|
// TODO: Appel API pour création
|
|
// await factureService.createTemplate(formData);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Template créé avec succès'
|
|
});
|
|
}
|
|
|
|
setShowDialog(false);
|
|
loadTemplates();
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors de la sauvegarde:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Erreur lors de la sauvegarde'
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (template: FactureTemplate) => {
|
|
try {
|
|
// TODO: Appel API pour suppression
|
|
// await factureService.deleteTemplate(template.id);
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Template supprimé avec succès'
|
|
});
|
|
|
|
loadTemplates();
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors de la suppression:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Erreur lors de la suppression'
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleUseTemplate = async (template: FactureTemplate) => {
|
|
try {
|
|
// TODO: Créer une nouvelle facture basée sur le template
|
|
toast.current?.show({
|
|
severity: 'info',
|
|
summary: 'Info',
|
|
detail: 'Redirection vers la création de facture...'
|
|
});
|
|
|
|
// Simuler la redirection
|
|
setTimeout(() => {
|
|
window.location.href = `/factures/nouveau?template=${template.id}`;
|
|
}, 1000);
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors de l\'utilisation du template:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Erreur lors de l\'utilisation du template'
|
|
});
|
|
}
|
|
};
|
|
|
|
const getMenuItems = (template: FactureTemplate) => [
|
|
{
|
|
label: 'Utiliser',
|
|
icon: 'pi pi-plus',
|
|
command: () => handleUseTemplate(template)
|
|
},
|
|
{
|
|
label: 'Modifier',
|
|
icon: 'pi pi-pencil',
|
|
command: () => handleEdit(template)
|
|
},
|
|
{
|
|
label: 'Dupliquer',
|
|
icon: 'pi pi-copy',
|
|
command: () => {
|
|
setEditingTemplate(null);
|
|
setFormData({
|
|
...template,
|
|
nom: `${template.nom} (Copie)`,
|
|
id: undefined
|
|
});
|
|
setShowDialog(true);
|
|
}
|
|
},
|
|
{
|
|
separator: true
|
|
},
|
|
{
|
|
label: 'Supprimer',
|
|
icon: 'pi pi-trash',
|
|
className: 'text-red-500',
|
|
command: () => {
|
|
setSelectedTemplate(template);
|
|
// TODO: Afficher dialog de confirmation
|
|
}
|
|
}
|
|
];
|
|
|
|
const actionBodyTemplate = (rowData: FactureTemplate) => (
|
|
<div className="flex gap-2">
|
|
<Button
|
|
icon="pi pi-plus"
|
|
className="p-button-text p-button-sm p-button-success"
|
|
tooltip="Utiliser ce template"
|
|
onClick={() => handleUseTemplate(rowData)}
|
|
/>
|
|
<Button
|
|
icon="pi pi-ellipsis-v"
|
|
className="p-button-text p-button-sm"
|
|
onClick={(e) => {
|
|
setSelectedTemplate(rowData);
|
|
menuRef.current?.toggle(e);
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
const typeBodyTemplate = (rowData: FactureTemplate) => (
|
|
<Tag
|
|
value={rowData.type}
|
|
severity={rowData.type === 'FACTURE' ? 'success' : 'info'}
|
|
/>
|
|
);
|
|
|
|
const categorieBodyTemplate = (rowData: FactureTemplate) => {
|
|
const categorie = categorieOptions.find(opt => opt.value === rowData.categorie);
|
|
return (
|
|
<Tag
|
|
value={categorie?.label || rowData.categorie}
|
|
severity="info"
|
|
/>
|
|
);
|
|
};
|
|
|
|
const statutBodyTemplate = (rowData: FactureTemplate) => (
|
|
<Tag
|
|
value={rowData.actif ? 'Actif' : 'Inactif'}
|
|
severity={rowData.actif ? 'success' : 'danger'}
|
|
/>
|
|
);
|
|
|
|
const utilisationsBodyTemplate = (rowData: FactureTemplate) => (
|
|
<Badge value={rowData.utilisations} severity="info" />
|
|
);
|
|
|
|
const toolbarStartTemplate = () => (
|
|
<div className="flex align-items-center gap-2">
|
|
<h2 className="text-xl font-bold m-0">Templates de Factures</h2>
|
|
</div>
|
|
);
|
|
|
|
const toolbarEndTemplate = () => (
|
|
<div className="flex align-items-center gap-2">
|
|
<span className="p-input-icon-left">
|
|
<i className="pi pi-search" />
|
|
<InputText
|
|
value={globalFilter}
|
|
onChange={(e) => setGlobalFilter(e.target.value)}
|
|
placeholder="Rechercher..."
|
|
/>
|
|
</span>
|
|
<Button
|
|
label="Nouveau Template"
|
|
icon="pi pi-plus"
|
|
onClick={handleNew}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="grid">
|
|
<Toast ref={toast} />
|
|
<ConfirmDialog />
|
|
<Menu ref={menuRef} model={selectedTemplate ? getMenuItems(selectedTemplate) : []} popup />
|
|
|
|
<div className="col-12">
|
|
<Toolbar start={toolbarStartTemplate} end={toolbarEndTemplate} />
|
|
</div>
|
|
|
|
{/* Statistiques rapides */}
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="h-full">
|
|
<div className="flex justify-content-between mb-3">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Total Templates</span>
|
|
<div className="text-900 font-medium text-xl">{templates.length}</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-blue-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-file text-blue-500 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="h-full">
|
|
<div className="flex justify-content-between mb-3">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Templates Actifs</span>
|
|
<div className="text-900 font-medium text-xl">
|
|
{templates.filter(t => t.actif).length}
|
|
</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-green-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-check-circle text-green-500 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="h-full">
|
|
<div className="flex justify-content-between mb-3">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Plus Utilisé</span>
|
|
<div className="text-900 font-medium text-xl">
|
|
{Math.max(...templates.map(t => t.utilisations), 0)} fois
|
|
</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-orange-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-star text-orange-500 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="h-full">
|
|
<div className="flex justify-content-between mb-3">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Types</span>
|
|
<div className="text-900 font-medium text-xl">
|
|
{new Set(templates.map(t => t.type)).size}
|
|
</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-purple-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-tags text-purple-500 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Table des templates */}
|
|
<div className="col-12">
|
|
<Card>
|
|
<DataTable
|
|
value={templates}
|
|
loading={loading}
|
|
globalFilter={globalFilter}
|
|
responsiveLayout="scroll"
|
|
paginator
|
|
rows={10}
|
|
rowsPerPageOptions={[5, 10, 25]}
|
|
emptyMessage="Aucun template trouvé"
|
|
header={
|
|
<div className="flex justify-content-between align-items-center">
|
|
<span className="text-xl font-bold">Liste des Templates</span>
|
|
<span className="text-sm text-600">{templates.length} template(s)</span>
|
|
</div>
|
|
}
|
|
>
|
|
<Column field="nom" header="Nom" sortable />
|
|
<Column field="description" header="Description" />
|
|
<Column
|
|
field="type"
|
|
header="Type"
|
|
body={typeBodyTemplate}
|
|
sortable
|
|
/>
|
|
<Column
|
|
field="categorie"
|
|
header="Catégorie"
|
|
body={categorieBodyTemplate}
|
|
sortable
|
|
/>
|
|
<Column
|
|
field="lignes"
|
|
header="Nb Lignes"
|
|
body={(rowData) => rowData.lignes?.length || 0}
|
|
style={{ width: '100px' }}
|
|
/>
|
|
<Column
|
|
field="utilisations"
|
|
header="Utilisations"
|
|
body={utilisationsBodyTemplate}
|
|
sortable
|
|
style={{ width: '120px' }}
|
|
/>
|
|
<Column
|
|
field="actif"
|
|
header="Statut"
|
|
body={statutBodyTemplate}
|
|
style={{ width: '100px' }}
|
|
/>
|
|
<Column
|
|
field="dateCreation"
|
|
header="Créé le"
|
|
body={(rowData) => formatDate(rowData.dateCreation)}
|
|
sortable
|
|
style={{ width: '120px' }}
|
|
/>
|
|
<Column
|
|
header="Actions"
|
|
body={actionBodyTemplate}
|
|
style={{ width: '120px' }}
|
|
/>
|
|
</DataTable>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Dialog de création/modification */}
|
|
<Dialog
|
|
header={editingTemplate ? "Modifier le template" : "Nouveau template"}
|
|
visible={showDialog}
|
|
onHide={() => setShowDialog(false)}
|
|
style={{ width: '800px' }}
|
|
footer={
|
|
<div className="flex justify-content-end gap-2">
|
|
<Button
|
|
label="Annuler"
|
|
icon="pi pi-times"
|
|
className="p-button-outlined"
|
|
onClick={() => setShowDialog(false)}
|
|
/>
|
|
<Button
|
|
label="Enregistrer"
|
|
icon="pi pi-save"
|
|
onClick={handleSave}
|
|
/>
|
|
</div>
|
|
}
|
|
>
|
|
<div className="grid">
|
|
<div className="col-12 md:col-6">
|
|
<div className="field">
|
|
<label htmlFor="nom" className="font-semibold">Nom *</label>
|
|
<InputText
|
|
id="nom"
|
|
value={formData.nom}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, nom: e.target.value }))}
|
|
className="w-full"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6">
|
|
<div className="field">
|
|
<label htmlFor="type" className="font-semibold">Type *</label>
|
|
<Dropdown
|
|
id="type"
|
|
value={formData.type}
|
|
options={typeOptions}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, type: e.value }))}
|
|
className="w-full"
|
|
placeholder="Sélectionner un type"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<div className="field">
|
|
<label htmlFor="categorie" className="font-semibold">Catégorie *</label>
|
|
<Dropdown
|
|
id="categorie"
|
|
value={formData.categorie}
|
|
options={categorieOptions}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, categorie: e.value }))}
|
|
className="w-full"
|
|
placeholder="Sélectionner une catégorie"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<div className="field">
|
|
<label htmlFor="description" className="font-semibold">Description</label>
|
|
<InputTextarea
|
|
id="description"
|
|
value={formData.description}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
|
|
className="w-full"
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6">
|
|
<div className="field">
|
|
<label htmlFor="tauxTVA" className="font-semibold">Taux TVA (%)</label>
|
|
<InputText
|
|
id="tauxTVA"
|
|
value={formData.tauxTVA?.toString()}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, tauxTVA: parseFloat(e.target.value) || 0 }))}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6">
|
|
<div className="field">
|
|
<label htmlFor="conditionsPaiement" className="font-semibold">Conditions de paiement</label>
|
|
<InputText
|
|
id="conditionsPaiement"
|
|
value={formData.conditionsPaiement}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, conditionsPaiement: e.target.value }))}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<div className="field">
|
|
<div className="flex align-items-center">
|
|
<Checkbox
|
|
inputId="actif"
|
|
checked={formData.actif}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, actif: e.checked || false }))}
|
|
/>
|
|
<label htmlFor="actif" className="ml-2">Template actif</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<h6>Lignes du template</h6>
|
|
<p className="text-sm text-600 mb-3">
|
|
Les lignes seront automatiquement ajoutées lors de l'utilisation du template.
|
|
</p>
|
|
|
|
{formData.lignes && formData.lignes.length > 0 ? (
|
|
<DataTable value={formData.lignes} responsiveLayout="scroll">
|
|
<Column field="designation" header="Désignation" />
|
|
<Column field="quantite" header="Quantité" style={{ width: '100px' }} />
|
|
<Column field="unite" header="Unité" style={{ width: '80px' }} />
|
|
<Column
|
|
field="prixUnitaire"
|
|
header="Prix unitaire"
|
|
style={{ width: '120px' }}
|
|
body={(rowData) => `${rowData.prixUnitaire}€`}
|
|
/>
|
|
</DataTable>
|
|
) : (
|
|
<div className="text-center p-4 border-2 border-dashed border-300 border-round">
|
|
<i className="pi pi-inbox text-4xl text-400 mb-3"></i>
|
|
<p className="text-600">Aucune ligne définie</p>
|
|
<Button
|
|
label="Ajouter des lignes"
|
|
icon="pi pi-plus"
|
|
className="p-button-outlined p-button-sm"
|
|
onClick={() => {
|
|
// TODO: Ouvrir dialog pour ajouter des lignes
|
|
toast.current?.show({
|
|
severity: 'info',
|
|
summary: 'Info',
|
|
detail: 'Fonctionnalité en cours de développement'
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default FactureTemplatesPage;
|