613 lines
27 KiB
TypeScript
Executable File
613 lines
27 KiB
TypeScript
Executable File
'use client';
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import { DataTable } from 'primereact/datatable';
|
|
import { Column } from 'primereact/column';
|
|
import { Button } from 'primereact/button';
|
|
import { InputText } from 'primereact/inputtext';
|
|
import { Card } from 'primereact/card';
|
|
import { Toast } from 'primereact/toast';
|
|
import { Toolbar } from 'primereact/toolbar';
|
|
import { Tag } from 'primereact/tag';
|
|
import { Dialog } from 'primereact/dialog';
|
|
import { Calendar } from 'primereact/calendar';
|
|
import { InputTextarea } from 'primereact/inputtextarea';
|
|
import { Dropdown } from 'primereact/dropdown';
|
|
import { Chip } from 'primereact/chip';
|
|
import { Divider } from 'primereact/divider';
|
|
import { factureService } from '../../../../services/api';
|
|
import { formatDate, formatCurrency } from '../../../../utils/formatters';
|
|
import type { Facture } from '../../../../types/btp';
|
|
|
|
const FacturesPayeesPage = () => {
|
|
const [factures, setFactures] = useState<Facture[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
const [selectedFactures, setSelectedFactures] = useState<Facture[]>([]);
|
|
const [detailDialog, setDetailDialog] = useState(false);
|
|
const [selectedFacture, setSelectedFacture] = useState<Facture | null>(null);
|
|
const [filterPeriod, setFilterPeriod] = useState('ALL');
|
|
const [dateRange, setDateRange] = useState<Date[]>([]);
|
|
const toast = useRef<Toast>(null);
|
|
const dt = useRef<DataTable<Facture[]>>(null);
|
|
|
|
const periodOptions = [
|
|
{ label: 'Toutes les factures', value: 'ALL' },
|
|
{ label: 'Ce mois-ci', value: 'THIS_MONTH' },
|
|
{ label: 'Mois dernier', value: 'LAST_MONTH' },
|
|
{ label: 'Ce trimestre', value: 'THIS_QUARTER' },
|
|
{ label: 'Cette année', value: 'THIS_YEAR' },
|
|
{ label: 'Période personnalisée', value: 'CUSTOM' }
|
|
];
|
|
|
|
useEffect(() => {
|
|
loadFactures();
|
|
}, [filterPeriod, dateRange]);
|
|
|
|
const loadFactures = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const data = await factureService.getAll();
|
|
// Filtrer les factures payées
|
|
let facturesPayees = data.filter(facture =>
|
|
facture.statut === 'PAYEE' && facture.datePaiement
|
|
);
|
|
|
|
// Appliquer le filtre de période
|
|
facturesPayees = applyPeriodFilter(facturesPayees);
|
|
|
|
setFactures(facturesPayees);
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement des factures:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Impossible de charger les factures payées',
|
|
life: 3000
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const applyPeriodFilter = (facturesList: Facture[]) => {
|
|
const now = new Date();
|
|
|
|
switch (filterPeriod) {
|
|
case 'THIS_MONTH':
|
|
return facturesList.filter(f => {
|
|
const paymentDate = new Date(f.datePaiement!);
|
|
return paymentDate.getMonth() === now.getMonth() &&
|
|
paymentDate.getFullYear() === now.getFullYear();
|
|
});
|
|
|
|
case 'LAST_MONTH':
|
|
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1);
|
|
return facturesList.filter(f => {
|
|
const paymentDate = new Date(f.datePaiement!);
|
|
return paymentDate.getMonth() === lastMonth.getMonth() &&
|
|
paymentDate.getFullYear() === lastMonth.getFullYear();
|
|
});
|
|
|
|
case 'THIS_QUARTER':
|
|
const quarterStart = new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1);
|
|
return facturesList.filter(f => {
|
|
const paymentDate = new Date(f.datePaiement!);
|
|
return paymentDate >= quarterStart && paymentDate <= now;
|
|
});
|
|
|
|
case 'THIS_YEAR':
|
|
return facturesList.filter(f => {
|
|
const paymentDate = new Date(f.datePaiement!);
|
|
return paymentDate.getFullYear() === now.getFullYear();
|
|
});
|
|
|
|
case 'CUSTOM':
|
|
if (dateRange.length === 2) {
|
|
return facturesList.filter(f => {
|
|
const paymentDate = new Date(f.datePaiement!);
|
|
return paymentDate >= dateRange[0] && paymentDate <= dateRange[1];
|
|
});
|
|
}
|
|
return facturesList;
|
|
|
|
default:
|
|
return facturesList;
|
|
}
|
|
};
|
|
|
|
const getDaysToPayment = (dateEmission: string | Date, datePaiement: string | Date) => {
|
|
const emissionDate = new Date(dateEmission);
|
|
const paymentDate = new Date(datePaiement);
|
|
const diffTime = paymentDate.getTime() - emissionDate.getTime();
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
return diffDays;
|
|
};
|
|
|
|
const getPaymentPerformance = (dateEcheance: string | Date, datePaiement: string | Date) => {
|
|
const dueDate = new Date(dateEcheance);
|
|
const paymentDate = new Date(datePaiement);
|
|
const diffTime = paymentDate.getTime() - dueDate.getTime();
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays < 0) return { status: 'EN AVANCE', days: Math.abs(diffDays), severity: 'success' as const };
|
|
if (diffDays === 0) return { status: 'À L\'ÉCHÉANCE', days: 0, severity: 'info' as const };
|
|
return { status: 'EN RETARD', days: diffDays, severity: 'warning' as const };
|
|
};
|
|
|
|
const viewDetails = (facture: Facture) => {
|
|
setSelectedFacture(facture);
|
|
setDetailDialog(true);
|
|
};
|
|
|
|
const exportCSV = () => {
|
|
dt.current?.exportCSV();
|
|
};
|
|
|
|
const generatePaymentReport = () => {
|
|
const totalReceived = factures.reduce((sum, f) => sum + (f.montantTTC || 0), 0);
|
|
const avgPaymentTime = factures.length > 0 ?
|
|
factures.reduce((sum, f) => sum + getDaysToPayment(f.dateEmission, f.datePaiement!), 0) / factures.length : 0;
|
|
|
|
const onTimePayments = factures.filter(f => {
|
|
const perf = getPaymentPerformance(f.dateEcheance, f.datePaiement!);
|
|
return perf.status !== 'EN RETARD';
|
|
});
|
|
|
|
const earlyPayments = factures.filter(f => {
|
|
const perf = getPaymentPerformance(f.dateEcheance, f.datePaiement!);
|
|
return perf.status === 'EN AVANCE';
|
|
});
|
|
|
|
const report = `
|
|
=== RAPPORT ENCAISSEMENTS ===
|
|
Période: ${getPeriodLabel()}
|
|
Date du rapport: ${new Date().toLocaleDateString('fr-FR')}
|
|
|
|
STATISTIQUES GÉNÉRALES:
|
|
- Nombre de factures payées: ${factures.length}
|
|
- Montant total encaissé: ${formatCurrency(totalReceived)}
|
|
- Montant moyen par facture: ${formatCurrency(totalReceived / (factures.length || 1))}
|
|
- Délai moyen de paiement: ${Math.round(avgPaymentTime)} jours
|
|
|
|
PERFORMANCE DE PAIEMENT:
|
|
- Paiements à l'heure: ${onTimePayments.length} (${Math.round((onTimePayments.length / factures.length) * 100)}%)
|
|
- Paiements en avance: ${earlyPayments.length} (${Math.round((earlyPayments.length / factures.length) * 100)}%)
|
|
- Taux de ponctualité: ${Math.round((onTimePayments.length / factures.length) * 100)}%
|
|
|
|
RÉPARTITION PAR MOIS:
|
|
${getMonthlyBreakdown()}
|
|
|
|
ANALYSE PAR CLIENT:
|
|
${getClientPaymentAnalysis()}
|
|
|
|
TOP 5 PLUS GROSSES FACTURES:
|
|
${factures
|
|
.sort((a, b) => (b.montantTTC || 0) - (a.montantTTC || 0))
|
|
.slice(0, 5)
|
|
.map(f => `- ${f.numero}: ${formatCurrency(f.montantTTC || 0)} - ${f.client ? `${f.client.prenom} ${f.client.nom}` : 'N/A'} - ${formatDate(f.datePaiement!)}`)
|
|
.join('\n')}
|
|
|
|
CLIENTS LES PLUS PONCTUELS:
|
|
${getBestPayingClients()}
|
|
|
|
RECOMMANDATIONS:
|
|
- Maintenir les bonnes relations avec les clients ponctuels
|
|
- Analyser les facteurs de succès pour améliorer les délais globaux
|
|
- Proposer des remises pour paiement anticipé
|
|
- Utiliser cette base pour évaluer la solvabilité des clients
|
|
`;
|
|
|
|
const blob = new Blob([report], { type: 'text/plain;charset=utf-8;' });
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = `rapport_encaissements_${new Date().toISOString().split('T')[0]}.txt`;
|
|
link.click();
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Rapport généré',
|
|
detail: 'Le rapport d\'encaissements a été téléchargé',
|
|
life: 3000
|
|
});
|
|
};
|
|
|
|
const getPeriodLabel = () => {
|
|
switch (filterPeriod) {
|
|
case 'THIS_MONTH': return 'Ce mois-ci';
|
|
case 'LAST_MONTH': return 'Mois dernier';
|
|
case 'THIS_QUARTER': return 'Ce trimestre';
|
|
case 'THIS_YEAR': return 'Cette année';
|
|
case 'CUSTOM':
|
|
return dateRange.length === 2 ?
|
|
`Du ${formatDate(dateRange[0])} au ${formatDate(dateRange[1])}` :
|
|
'Période personnalisée';
|
|
default: return 'Toutes les factures';
|
|
}
|
|
};
|
|
|
|
const getMonthlyBreakdown = () => {
|
|
const months = {};
|
|
factures.forEach(f => {
|
|
const month = new Date(f.datePaiement!).toLocaleDateString('fr-FR', { year: 'numeric', month: 'long' });
|
|
if (!months[month]) {
|
|
months[month] = { count: 0, value: 0 };
|
|
}
|
|
months[month].count++;
|
|
months[month].value += f.montantTTC || 0;
|
|
});
|
|
|
|
return Object.entries(months)
|
|
.sort((a, b) => new Date(a[0]).getTime() - new Date(b[0]).getTime())
|
|
.map(([month, data]: [string, any]) => `- ${month}: ${data.count} factures, ${formatCurrency(data.value)}`)
|
|
.join('\n');
|
|
};
|
|
|
|
const getClientPaymentAnalysis = () => {
|
|
const clientStats = {};
|
|
factures.forEach(f => {
|
|
if (f.client) {
|
|
const clientKey = `${f.client.prenom} ${f.client.nom}`;
|
|
if (!clientStats[clientKey]) {
|
|
clientStats[clientKey] = { count: 0, value: 0, totalDays: 0 };
|
|
}
|
|
clientStats[clientKey].count++;
|
|
clientStats[clientKey].value += f.montantTTC || 0;
|
|
clientStats[clientKey].totalDays += getDaysToPayment(f.dateEmission, f.datePaiement!);
|
|
}
|
|
});
|
|
|
|
return Object.entries(clientStats)
|
|
.sort((a: [string, any], b: [string, any]) => b[1].value - a[1].value)
|
|
.slice(0, 5)
|
|
.map(([client, data]: [string, any]) => `- ${client}: ${data.count} factures, ${formatCurrency(data.value)}, délai moyen: ${Math.round(data.totalDays / data.count)} jours`)
|
|
.join('\n');
|
|
};
|
|
|
|
const getBestPayingClients = () => {
|
|
const clientStats = {};
|
|
factures.forEach(f => {
|
|
if (f.client) {
|
|
const clientKey = `${f.client.prenom} ${f.client.nom}`;
|
|
if (!clientStats[clientKey]) {
|
|
clientStats[clientKey] = { onTime: 0, total: 0 };
|
|
}
|
|
clientStats[clientKey].total++;
|
|
const perf = getPaymentPerformance(f.dateEcheance, f.datePaiement!);
|
|
if (perf.status !== 'EN RETARD') {
|
|
clientStats[clientKey].onTime++;
|
|
}
|
|
}
|
|
});
|
|
|
|
return Object.entries(clientStats)
|
|
.filter(([_, data]: [string, any]) => data.total >= 2) // Au moins 2 factures
|
|
.sort((a: [string, any], b: [string, any]) => (b[1].onTime / b[1].total) - (a[1].onTime / a[1].total))
|
|
.slice(0, 5)
|
|
.map(([client, data]: [string, any]) => `- ${client}: ${Math.round((data.onTime / data.total) * 100)}% ponctuel (${data.onTime}/${data.total})`)
|
|
.join('\n');
|
|
};
|
|
|
|
const leftToolbarTemplate = () => {
|
|
return (
|
|
<div className="my-2 flex gap-2 align-items-center">
|
|
<h5 className="m-0 flex align-items-center text-green-600">
|
|
<i className="pi pi-check-circle mr-2"></i>
|
|
Factures payées ({factures.length})
|
|
</h5>
|
|
<Chip
|
|
label={`Total encaissé: ${formatCurrency(factures.reduce((sum, f) => sum + (f.montantTTC || 0), 0))}`}
|
|
className="bg-green-100 text-green-800"
|
|
/>
|
|
<Dropdown
|
|
value={filterPeriod}
|
|
options={periodOptions}
|
|
onChange={(e) => setFilterPeriod(e.value)}
|
|
className="w-12rem"
|
|
/>
|
|
{filterPeriod === 'CUSTOM' && (
|
|
<Calendar
|
|
value={dateRange}
|
|
onChange={(e) => setDateRange(e.value as Date[])}
|
|
selectionMode="range"
|
|
placeholder="Sélectionner la période"
|
|
className="w-15rem"
|
|
/>
|
|
)}
|
|
<Button
|
|
label="Rapport d'encaissements"
|
|
icon="pi pi-chart-bar"
|
|
severity="success"
|
|
size="small"
|
|
onClick={generatePaymentReport}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const rightToolbarTemplate = () => {
|
|
return (
|
|
<Button
|
|
label="Exporter"
|
|
icon="pi pi-upload"
|
|
severity="help"
|
|
onClick={exportCSV}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const actionBodyTemplate = (rowData: Facture) => {
|
|
return (
|
|
<div className="flex gap-1">
|
|
<Button
|
|
icon="pi pi-eye"
|
|
rounded
|
|
severity="info"
|
|
size="small"
|
|
tooltip="Voir détails du paiement"
|
|
onClick={() => viewDetails(rowData)}
|
|
/>
|
|
<Button
|
|
icon="pi pi-print"
|
|
rounded
|
|
severity="help"
|
|
size="small"
|
|
tooltip="Imprimer reçu"
|
|
onClick={() => {
|
|
toast.current?.show({
|
|
severity: 'info',
|
|
summary: 'Impression',
|
|
detail: `Impression du reçu pour ${rowData.numero}`,
|
|
life: 3000
|
|
});
|
|
}}
|
|
/>
|
|
<Button
|
|
icon="pi pi-file-pdf"
|
|
rounded
|
|
severity={"secondary" as any}
|
|
size="small"
|
|
tooltip="Générer attestation de paiement"
|
|
onClick={() => {
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Attestation',
|
|
detail: `Attestation générée pour ${rowData.numero}`,
|
|
life: 3000
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const statusBodyTemplate = (rowData: Facture) => {
|
|
const performance = getPaymentPerformance(rowData.dateEcheance, rowData.datePaiement!);
|
|
|
|
return (
|
|
<div className="flex align-items-center gap-2">
|
|
<Tag value="Payée" severity="success" />
|
|
<Tag
|
|
value={performance.status}
|
|
severity={performance.severity}
|
|
className="text-xs"
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const paymentBodyTemplate = (rowData: Facture) => {
|
|
const performance = getPaymentPerformance(rowData.dateEcheance, rowData.datePaiement!);
|
|
const paymentDays = getDaysToPayment(rowData.dateEmission, rowData.datePaiement!);
|
|
|
|
return (
|
|
<div>
|
|
<div className="font-bold text-green-600">{formatDate(rowData.datePaiement!)}</div>
|
|
<small className="text-600">
|
|
Payée en {paymentDays} jour{paymentDays > 1 ? 's' : ''}
|
|
</small>
|
|
{performance.status === 'EN AVANCE' && (
|
|
<div className="text-green-600 text-xs">
|
|
<i className="pi pi-check mr-1"></i>
|
|
{performance.days} jour{performance.days > 1 ? 's' : ''} en avance
|
|
</div>
|
|
)}
|
|
{performance.status === 'EN RETARD' && (
|
|
<div className="text-orange-600 text-xs">
|
|
<i className="pi pi-clock mr-1"></i>
|
|
{performance.days} jour{performance.days > 1 ? 's' : ''} de retard
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const clientBodyTemplate = (rowData: Facture) => {
|
|
if (!rowData.client) return '';
|
|
return `${rowData.client.prenom} ${rowData.client.nom}`;
|
|
};
|
|
|
|
const performanceBodyTemplate = (rowData: Facture) => {
|
|
const performance = getPaymentPerformance(rowData.dateEcheance, rowData.datePaiement!);
|
|
const paymentDays = getDaysToPayment(rowData.dateEmission, rowData.datePaiement!);
|
|
|
|
// Score de performance (0-100)
|
|
let score = 100;
|
|
if (performance.status === 'EN RETARD') {
|
|
score = Math.max(0, 100 - (performance.days * 5)); // -5 points par jour de retard
|
|
} else if (performance.status === 'EN AVANCE') {
|
|
score = 100 + Math.min(20, performance.days * 2); // +2 points par jour d'avance (max +20)
|
|
}
|
|
|
|
let scoreColor = '#22c55e'; // green
|
|
if (score < 70) scoreColor = '#ef4444'; // red
|
|
else if (score < 85) scoreColor = '#f59e0b'; // orange
|
|
|
|
return (
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold mb-1" style={{ color: scoreColor }}>
|
|
{Math.round(score)}
|
|
</div>
|
|
<small className="text-600">Score de ponctualité</small>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const header = (
|
|
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
|
|
<h5 className="m-0">Factures payées - Suivi des encaissements</h5>
|
|
<span className="block mt-2 md:mt-0 p-input-icon-left">
|
|
<i className="pi pi-search" />
|
|
<InputText
|
|
type="search"
|
|
placeholder="Rechercher..."
|
|
onInput={(e) => setGlobalFilter(e.currentTarget.value)}
|
|
/>
|
|
</span>
|
|
</div>
|
|
);
|
|
|
|
const detailDialogFooter = (
|
|
<Button
|
|
label="Fermer"
|
|
icon="pi pi-times"
|
|
text
|
|
onClick={() => setDetailDialog(false)}
|
|
/>
|
|
);
|
|
|
|
return (
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<Card>
|
|
<Toast ref={toast} />
|
|
<Toolbar className="mb-4" left={leftToolbarTemplate} right={rightToolbarTemplate} />
|
|
|
|
<DataTable
|
|
ref={dt}
|
|
value={factures}
|
|
selection={selectedFactures}
|
|
onSelectionChange={(e) => setSelectedFactures(e.value)}
|
|
dataKey="id"
|
|
paginator
|
|
rows={10}
|
|
rowsPerPageOptions={[5, 10, 25]}
|
|
className="datatable-responsive"
|
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
|
currentPageReportTemplate="Affichage de {first} à {last} sur {totalRecords} factures"
|
|
globalFilter={globalFilter}
|
|
emptyMessage="Aucune facture payée trouvée."
|
|
header={header}
|
|
responsiveLayout="scroll"
|
|
loading={loading}
|
|
sortField="datePaiement"
|
|
sortOrder={-1}
|
|
>
|
|
<Column selectionMode="multiple" headerStyle={{ width: '4rem' }} />
|
|
<Column field="numero" header="Numéro" sortable headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="objet" header="Objet" sortable headerStyle={{ minWidth: '15rem' }} />
|
|
<Column field="client" header="Client" body={clientBodyTemplate} sortable headerStyle={{ minWidth: '12rem' }} />
|
|
<Column field="dateEmission" header="Date émission" body={(rowData) => formatDate(rowData.dateEmission)} sortable headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="dateEcheance" header="Échéance" body={(rowData) => formatDate(rowData.dateEcheance)} sortable headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="datePaiement" header="Paiement" body={paymentBodyTemplate} sortable headerStyle={{ minWidth: '12rem' }} />
|
|
<Column field="montantTTC" header="Montant TTC" body={(rowData) => formatCurrency(rowData.montantTTC)} sortable headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="performance" header="Performance" body={performanceBodyTemplate} headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="statut" header="Statut" body={statusBodyTemplate} headerStyle={{ minWidth: '12rem' }} />
|
|
<Column body={actionBodyTemplate} headerStyle={{ minWidth: '12rem' }} />
|
|
</DataTable>
|
|
|
|
<Dialog
|
|
visible={detailDialog}
|
|
style={{ width: '600px' }}
|
|
header="Détails du paiement"
|
|
modal
|
|
className="p-fluid"
|
|
footer={detailDialogFooter}
|
|
onHide={() => setDetailDialog(false)}
|
|
>
|
|
{selectedFacture && (
|
|
<div className="formgrid grid">
|
|
<div className="field col-12">
|
|
<h6>Informations de la facture</h6>
|
|
<p><strong>Numéro:</strong> {selectedFacture.numero}</p>
|
|
<p><strong>Objet:</strong> {selectedFacture.objet}</p>
|
|
<p><strong>Client:</strong> {selectedFacture.client ? `${selectedFacture.client.prenom} ${selectedFacture.client.nom}` : 'N/A'}</p>
|
|
<p><strong>Date d'émission:</strong> {formatDate(selectedFacture.dateEmission)}</p>
|
|
<p><strong>Date d'échéance:</strong> {formatDate(selectedFacture.dateEcheance)}</p>
|
|
</div>
|
|
|
|
<Divider />
|
|
|
|
<div className="field col-12">
|
|
<h6>Détails du paiement</h6>
|
|
<p><strong>Date de paiement:</strong> {formatDate(selectedFacture.datePaiement!)}</p>
|
|
<p><strong>Montant payé:</strong> {formatCurrency(selectedFacture.montantTTC || 0)}</p>
|
|
<p><strong>Délai de paiement:</strong> {getDaysToPayment(selectedFacture.dateEmission, selectedFacture.datePaiement!)} jours</p>
|
|
|
|
{(() => {
|
|
const perf = getPaymentPerformance(selectedFacture.dateEcheance, selectedFacture.datePaiement!);
|
|
return (
|
|
<p>
|
|
<strong>Performance:</strong>
|
|
<Tag
|
|
value={perf.status}
|
|
severity={perf.severity}
|
|
className="ml-2"
|
|
/>
|
|
{perf.days > 0 && (
|
|
<span className="ml-2">
|
|
({perf.days} jour{perf.days > 1 ? 's' : ''})
|
|
</span>
|
|
)}
|
|
</p>
|
|
);
|
|
})()}
|
|
</div>
|
|
|
|
<Divider />
|
|
|
|
<div className="field col-12">
|
|
<h6>Actions disponibles</h6>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
label="Imprimer reçu"
|
|
icon="pi pi-print"
|
|
size="small"
|
|
onClick={() => {
|
|
toast.current?.show({
|
|
severity: 'info',
|
|
summary: 'Impression',
|
|
detail: `Impression du reçu pour ${selectedFacture.numero}`,
|
|
life: 3000
|
|
});
|
|
}}
|
|
/>
|
|
<Button
|
|
label="Attestation"
|
|
icon="pi pi-file-pdf"
|
|
severity={"secondary" as any}
|
|
size="small"
|
|
onClick={() => {
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Attestation',
|
|
detail: `Attestation générée pour ${selectedFacture.numero}`,
|
|
life: 3000
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Dialog>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default FacturesPayeesPage;
|
|
|
|
|