'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([]); const [loading, setLoading] = useState(true); const [globalFilter, setGlobalFilter] = useState(''); const [selectedFactures, setSelectedFactures] = useState([]); const [detailDialog, setDetailDialog] = useState(false); const [selectedFacture, setSelectedFacture] = useState(null); const [filterPeriod, setFilterPeriod] = useState('ALL'); const [dateRange, setDateRange] = useState([]); const toast = useRef(null); const dt = useRef>(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 (
Factures payées ({factures.length})
sum + (f.montantTTC || 0), 0))}`} className="bg-green-100 text-green-800" /> setFilterPeriod(e.value)} className="w-12rem" /> {filterPeriod === 'CUSTOM' && ( setDateRange(e.value as Date[])} selectionMode="range" placeholder="Sélectionner la période" className="w-15rem" /> )}
); }; const rightToolbarTemplate = () => { return (