'use client'; export const dynamic = 'force-dynamic'; 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 { InputNumber } from 'primereact/inputnumber'; import { Dropdown } from 'primereact/dropdown'; import { Chip } from 'primereact/chip'; import { Divider } from 'primereact/divider'; import { factureService, clientService } from '../../../../services/api'; import { formatDate, formatCurrency } from '../../../../utils/formatters'; import type { Facture } from '../../../../types/btp'; import { StatutFacture, TypeFacture } from '../../../../types/btp'; const FacturesAvoirsPage = () => { const [avoirs, setAvoirs] = useState([]); const [clients, setClients] = useState([]); const [loading, setLoading] = useState(true); const [globalFilter, setGlobalFilter] = useState(''); const [selectedAvoirs, setSelectedAvoirs] = useState([]); const [avoirDialog, setAvoirDialog] = useState(false); const [selectedAvoir, setSelectedAvoir] = useState(null); const [isCreating, setIsCreating] = useState(false); const [avoir, setAvoir] = useState>({ id: '', numero: '', dateEmission: new Date().toISOString(), dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), datePaiement: null, objet: '', description: '', montantHT: 0, montantTTC: 0, tauxTVA: 20, statut: StatutFacture.ENVOYEE, typeFacture: TypeFacture.AVOIR, actif: true, client: null, chantier: null, devis: null, lignes: [] }); const [submitted, setSubmitted] = useState(false); const toast = useRef(null); const dt = useRef>(null); const avoirReasons = [ { label: 'Erreur de facturation', value: 'ERREUR_FACTURATION' }, { label: 'Retour de marchandise', value: 'RETOUR_MARCHANDISE' }, { label: 'Annulation partielle', value: 'ANNULATION_PARTIELLE' }, { label: 'Remise commerciale', value: 'REMISE_COMMERCIALE' }, { label: 'Défaut de conformité', value: 'DEFAUT_CONFORMITE' }, { label: 'Geste commercial', value: 'GESTE_COMMERCIAL' } ]; useEffect(() => { loadAvoirs(); loadClients(); }, []); const loadAvoirs = async () => { try { setLoading(true); const data = await factureService.getAll(); // Filtrer les avoirs const avoirsList = data.filter(facture => facture.typeFacture === TypeFacture.AVOIR); setAvoirs(avoirsList); } catch (error) { console.error('Erreur lors du chargement des avoirs:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les avoirs', life: 3000 }); } finally { setLoading(false); } }; 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 getAvoirReason = (description: string) => { // Simuler la détection de la raison à partir de la description if (description.toLowerCase().includes('erreur')) return 'ERREUR_FACTURATION'; if (description.toLowerCase().includes('retour')) return 'RETOUR_MARCHANDISE'; if (description.toLowerCase().includes('annulation')) return 'ANNULATION_PARTIELLE'; if (description.toLowerCase().includes('remise')) return 'REMISE_COMMERCIALE'; if (description.toLowerCase().includes('défaut')) return 'DEFAUT_CONFORMITE'; return 'GESTE_COMMERCIAL'; }; const getReasonLabel = (reason: string) => { const reasonObj = avoirReasons.find(r => r.value === reason); return reasonObj ? reasonObj.label : 'Autre'; }; const getAvoirSeverity = (reason: string) => { switch (reason) { case 'ERREUR_FACTURATION': case 'DEFAUT_CONFORMITE': return 'danger'; case 'RETOUR_MARCHANDISE': case 'ANNULATION_PARTIELLE': return 'warning'; case 'REMISE_COMMERCIALE': case 'GESTE_COMMERCIAL': return 'info'; default: return 'secondary'; } }; const openNew = () => { setAvoir({ id: '', numero: '', dateEmission: new Date().toISOString(), dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), datePaiement: null, objet: '', description: '', montantHT: 0, montantTTC: 0, tauxTVA: 20, statut: StatutFacture.ENVOYEE, typeFacture: TypeFacture.AVOIR, actif: true, client: null, chantier: null, devis: null, lignes: [] }); setSubmitted(false); setIsCreating(true); setAvoirDialog(true); }; const viewDetails = (avoirItem: Facture) => { setSelectedAvoir(avoirItem); setIsCreating(false); setAvoirDialog(true); }; const hideDialog = () => { setSubmitted(false); setAvoirDialog(false); setSelectedAvoir(null); setIsCreating(false); }; const saveAvoir = async () => { setSubmitted(true); if (avoir.objet.trim() && avoir.client && avoir.montantHT > 0) { try { const avoirToSave = { ...avoir, client: avoir.client ? { id: avoir.client } : null, montantTTC: avoir.montantHT * (1 + avoir.tauxTVA / 100), numero: `AV-${new Date().getFullYear()}-${String(avoirs.length + 1).padStart(4, '0')}` }; // TODO: Implémenter la création d'avoir quand l'API sera disponible console.log('Création d\'avoir:', avoirToSave); // Simulation setAvoirs(prev => [...prev, { ...avoirToSave, id: Date.now().toString() } as unknown as Facture]); setAvoirDialog(false); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Avoir créé avec succès (simulation)', life: 3000 }); } catch (error) { console.error('Erreur lors de la sauvegarde:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de sauvegarder l\'avoir', life: 3000 }); } } }; const exportCSV = () => { dt.current?.exportCSV(); }; const generateAvoirReport = () => { const totalAvoirs = avoirs.reduce((sum, a) => sum + (a.montantTTC || 0), 0); const reasonStats = {}; avoirs.forEach(a => { const reason = getAvoirReason(a.description || ''); const label = getReasonLabel(reason); if (!reasonStats[label]) { reasonStats[label] = { count: 0, value: 0 }; } reasonStats[label].count++; reasonStats[label].value += a.montantTTC || 0; }); const report = ` === RAPPORT AVOIRS === Date du rapport: ${new Date().toLocaleDateString('fr-FR')} STATISTIQUES GÉNÉRALES: - Nombre total d'avoirs: ${avoirs.length} - Montant total des avoirs: ${formatCurrency(totalAvoirs)} - Montant moyen par avoir: ${formatCurrency(totalAvoirs / (avoirs.length || 1))} RÉPARTITION PAR MOTIF: ${Object.entries(reasonStats) .sort((a: [string, any], b: [string, any]) => b[1].value - a[1].value) .map(([reason, data]: [string, any]) => `- ${reason}: ${data.count} avoirs, ${formatCurrency(data.value)} (${Math.round((data.value / totalAvoirs) * 100)}%)`) .join('\n')} ANALYSE PAR MOIS: ${getMonthlyAvoirBreakdown()} CLIENTS AVEC LE PLUS D'AVOIRS: ${getClientAvoirAnalysis()} IMPACT SUR LE CHIFFRE D'AFFAIRES: - Pourcentage du CA en avoirs: ${getCAImpact()}% - Tendance mensuelle: ${getTrend()} RECOMMANDATIONS: - Analyser les causes récurrentes d'avoirs - Mettre en place des mesures préventives - Former les équipes pour réduire les erreurs - Améliorer les processus de contrôle qualité `; const blob = new Blob([report], { type: 'text/plain;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `rapport_avoirs_${new Date().toISOString().split('T')[0]}.txt`; link.click(); toast.current?.show({ severity: 'success', summary: 'Rapport généré', detail: 'Le rapport d\'avoirs a été téléchargé', life: 3000 }); }; const getMonthlyAvoirBreakdown = () => { const months = {}; avoirs.forEach(a => { const month = new Date(a.dateEmission).toLocaleDateString('fr-FR', { year: 'numeric', month: 'long' }); if (!months[month]) { months[month] = { count: 0, value: 0 }; } months[month].count++; months[month].value += a.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} avoirs, ${formatCurrency(data.value)}`) .join('\n'); }; const getClientAvoirAnalysis = () => { const clientStats = {}; avoirs.forEach(a => { if (a.client) { const clientKey = `${a.client.prenom} ${a.client.nom}`; if (!clientStats[clientKey]) { clientStats[clientKey] = { count: 0, value: 0 }; } clientStats[clientKey].count++; clientStats[clientKey].value += a.montantTTC || 0; } }); return Object.entries(clientStats) .sort((a: [string, any], b: [string, any]) => b[1].count - a[1].count) .slice(0, 5) .map(([client, data]: [string, any]) => `- ${client}: ${data.count} avoirs, ${formatCurrency(data.value)}`) .join('\n'); }; const getCAImpact = () => { // Simulation - calcul de l'impact sur le CA const totalCA = 500000; // CA simulé const totalAvoirs = avoirs.reduce((sum, a) => sum + (a.montantTTC || 0), 0); return ((totalAvoirs / totalCA) * 100).toFixed(2); }; const getTrend = () => { // Simulation de tendance const thisMonth = avoirs.filter(a => { const avoirDate = new Date(a.dateEmission); const now = new Date(); return avoirDate.getMonth() === now.getMonth() && avoirDate.getFullYear() === now.getFullYear(); }).length; const lastMonth = avoirs.filter(a => { const avoirDate = new Date(a.dateEmission); const lastMonthDate = new Date(); lastMonthDate.setMonth(lastMonthDate.getMonth() - 1); return avoirDate.getMonth() === lastMonthDate.getMonth() && avoirDate.getFullYear() === lastMonthDate.getFullYear(); }).length; if (thisMonth > lastMonth) return 'En hausse'; if (thisMonth < lastMonth) return 'En baisse'; return 'Stable'; }; const onInputChange = (e: React.ChangeEvent, name: string) => { const val = (e.target && e.target.value) || ''; let _avoir = { ...avoir }; (_avoir as any)[name] = val; setAvoir(_avoir); }; const onNumberChange = (e: any, name: string) => { let _avoir = { ...avoir }; (_avoir as any)[name] = e.value; setAvoir(_avoir); // Recalcul automatique du TTC if (name === 'montantHT' || name === 'tauxTVA') { const montantHT = name === 'montantHT' ? e.value : _avoir.montantHT; const tauxTVA = name === 'tauxTVA' ? e.value : _avoir.tauxTVA; _avoir.montantTTC = montantHT * (1 + tauxTVA / 100); setAvoir(_avoir); } }; const onDropdownChange = (e: any, name: string) => { let _avoir = { ...avoir }; (_avoir as any)[name] = e.value; setAvoir(_avoir); }; const leftToolbarTemplate = () => { return (
Avoirs ({avoirs.length})
sum + (a.montantTTC || 0), 0))}`} className="bg-purple-100 text-purple-800" />
); }; const rightToolbarTemplate = () => { return (