'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 { Dialog } from 'primereact/dialog'; import { Toast } from 'primereact/toast'; import { Toolbar } from 'primereact/toolbar'; import { Tag } from 'primereact/tag'; import { Dropdown } from 'primereact/dropdown'; import { Calendar } from 'primereact/calendar'; import { InputTextarea } from 'primereact/inputtextarea'; import { InputNumber } from 'primereact/inputnumber'; import { factureService, clientService } from '../../../services/api'; import { formatDate, formatCurrency } from '../../../utils/formatters'; import { ErrorHandler } from '../../../services/errorHandler'; import type { Facture, Client } from '../../../types/btp'; import { StatutFacture, TypeFacture } from '../../../types/btp'; import { ActionButtonGroup, ViewButton, EditButton, DeleteButton, ActionButton } from '../../../components/ui/ActionButton'; const FacturesPage = () => { const [factures, setFactures] = useState([]); const [clients, setClients] = useState([]); const [loading, setLoading] = useState(true); const [globalFilter, setGlobalFilter] = useState(''); const [selectedFactures, setSelectedFactures] = useState([]); const [factureDialog, setFactureDialog] = useState(false); const [deleteFactureDialog, setDeleteFactureDialog] = useState(false); const [deleteFacturessDialog, setDeleteFacturessDialog] = useState(false); const [facture, setFacture] = useState({ id: '', numero: '', dateEmission: new Date().toISOString(), dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), datePaiement: undefined, objet: '', description: '', montantHT: 0, montantTTC: 0, tauxTVA: 20, statut: StatutFacture.BROUILLON, typeFacture: TypeFacture.FACTURE, dateCreation: new Date().toISOString(), dateModification: new Date().toISOString(), actif: true, client: {} as Client, chantier: undefined, devis: undefined }); const [submitted, setSubmitted] = useState(false); const toast = useRef(null); const dt = useRef>(null); const statuts = [ { label: 'Brouillon', value: 'BROUILLON' }, { label: 'Émise', value: 'EMISE' }, { label: 'Envoyée', value: 'ENVOYEE' }, { label: 'Payée', value: 'PAYEE' }, { label: 'En retard', value: 'EN_RETARD' }, { label: 'Annulée', value: 'ANNULEE' } ]; const types = [ { label: 'Facture', value: 'FACTURE' }, { label: 'Facture d\'acompte', value: 'ACOMPTE' }, { label: 'Facture de solde', value: 'SOLDE' }, { label: 'Avoir', value: 'AVOIR' } ]; useEffect(() => { loadFactures(); loadClients(); }, []); const loadFactures = async () => { try { setLoading(true); const data = await factureService.getAll(); setFactures(data); } catch (error) { console.error('Erreur lors du chargement des factures:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les factures', 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 openNew = () => { setFacture({ id: '', numero: '', dateEmission: new Date().toISOString(), dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), datePaiement: undefined, objet: '', description: '', montantHT: 0, montantTTC: 0, tauxTVA: 20, statut: StatutFacture.BROUILLON, typeFacture: TypeFacture.FACTURE, dateCreation: new Date().toISOString(), dateModification: new Date().toISOString(), actif: true, client: {} as Client, chantier: undefined, devis: undefined }); setSubmitted(false); setFactureDialog(true); }; const hideDialog = () => { setSubmitted(false); setFactureDialog(false); }; const hideDeleteFactureDialog = () => { setDeleteFactureDialog(false); }; const hideDeleteFacturessDialog = () => { setDeleteFacturessDialog(false); }; const saveFacture = async () => { setSubmitted(true); // Validation complète côté client const validationErrors = []; if (!facture.objet.trim()) { validationErrors.push("L'objet de la facture est obligatoire"); } if (!facture.client) { validationErrors.push("Le client est obligatoire"); } if (!facture.numero.trim()) { validationErrors.push("Le numéro de facture est obligatoire"); } if (facture.montantHT <= 0) { validationErrors.push("Le montant HT doit être supérieur à 0"); } if (facture.tauxTVA < 0 || facture.tauxTVA > 100) { validationErrors.push("Le taux de TVA doit être entre 0 et 100%"); } if (!facture.dateEcheance || facture.dateEcheance <= facture.dateEmission) { validationErrors.push("La date d'échéance doit être postérieure à la date d'émission"); } if (validationErrors.length > 0) { toast.current?.show({ severity: 'error', summary: 'Erreurs de validation', detail: validationErrors.join(', '), life: 5000 }); return; } if (facture.objet.trim() && facture.client) { try { let updatedFactures = [...factures]; const factureToSave = { ...facture, montantTTC: facture.montantHT * (1 + facture.tauxTVA / 100) }; if (facture.id) { // Mise à jour de la facture existante const updatedFacture = await factureService.update(facture.id, factureToSave); setFactures(factures.map(f => f.id === facture.id ? updatedFacture : f)); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Facture mise à jour avec succès', life: 3000 }); } else { // Création d'une nouvelle facture const newFacture = await factureService.create(factureToSave); setFactures([...factures, newFacture]); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Facture créée avec succès', life: 3000 }); } setFactureDialog(false); setFacture({ id: '', numero: '', dateEmission: new Date().toISOString(), dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), datePaiement: undefined, objet: '', description: '', montantHT: 0, montantTTC: 0, tauxTVA: 20, statut: StatutFacture.BROUILLON, typeFacture: TypeFacture.FACTURE, dateCreation: new Date().toISOString(), dateModification: new Date().toISOString(), actif: true, client: {} as Client, chantier: undefined, devis: undefined }); } catch (error) { console.error('Erreur lors de la sauvegarde:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de sauvegarder la facture', life: 3000 }); } } }; const editFacture = (facture: Facture) => { setFacture({ ...facture, client: facture.client?.id || null }); setFactureDialog(true); }; const confirmDeleteFacture = (facture: Facture) => { setFacture(facture); setDeleteFactureDialog(true); }; const deleteFacture = async () => { try { if (facture.id) { await factureService.delete(facture.id); setFactures(factures.filter(f => f.id !== facture.id)); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Facture supprimée avec succès', life: 3000 }); } setDeleteFactureDialog(false); } catch (error) { console.error('Erreur lors de la suppression:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de supprimer la facture', life: 3000 }); } }; const exportCSV = () => { dt.current?.exportCSV(); }; const onInputChange = (e: React.ChangeEvent, name: string) => { const val = (e.target && e.target.value) || ''; let _facture = { ...facture }; (_facture as any)[name] = val; setFacture(_facture); }; const onDateChange = (e: any, name: string) => { let _facture = { ...facture }; (_facture as any)[name] = e.value; setFacture(_facture); }; const onNumberChange = (e: any, name: string) => { let _facture = { ...facture }; (_facture as any)[name] = e.value; setFacture(_facture); // Recalcul automatique du TTC if (name === 'montantHT' || name === 'tauxTVA') { const montantHT = name === 'montantHT' ? e.value : _facture.montantHT; const tauxTVA = name === 'tauxTVA' ? e.value : _facture.tauxTVA; _facture.montantTTC = montantHT * (1 + tauxTVA / 100); setFacture(_facture); } }; const onDropdownChange = (e: any, name: string) => { let _facture = { ...facture }; (_facture as any)[name] = e.value; setFacture(_facture); }; const leftToolbarTemplate = () => { return (
); }; const rightToolbarTemplate = () => { return (
); }; const actionBodyTemplate = (rowData: Facture) => { return ( { toast.current?.show({ severity: 'info', summary: 'Aperçu', detail: `Aperçu de la facture ${rowData.numero}`, life: 3000 }); }} /> { toast.current?.show({ severity: 'info', summary: 'Envoi', detail: `Envoi de la facture ${rowData.numero}`, life: 3000 }); }} /> editFacture(rowData)} /> confirmDeleteFacture(rowData)} /> ); }; const statusBodyTemplate = (rowData: Facture) => { const getSeverity = (status: string) => { switch (status) { case 'BROUILLON': return 'secondary'; case 'EMISE': return 'info'; case 'ENVOYEE': return 'info'; case 'PAYEE': return 'success'; case 'EN_RETARD': return 'danger'; case 'ANNULEE': return 'danger'; default: return 'secondary'; } }; const getLabel = (status: string) => { switch (status) { case 'BROUILLON': return 'Brouillon'; case 'EMISE': return 'Émise'; case 'ENVOYEE': return 'Envoyée'; case 'PAYEE': return 'Payée'; case 'EN_RETARD': return 'En retard'; case 'ANNULEE': return 'Annulée'; default: return status; } }; return ( ); }; const typeBodyTemplate = (rowData: Facture) => { const getTypeLabel = (type: string) => { switch (type) { case 'FACTURE': return 'Facture'; case 'ACOMPTE': return 'Acompte'; case 'SOLDE': return 'Solde'; case 'AVOIR': return 'Avoir'; default: return type; } }; return ( ); }; const clientBodyTemplate = (rowData: Facture) => { if (!rowData.client) return ''; return `${rowData.client.prenom} ${rowData.client.nom}`; }; const dateBodyTemplate = (rowData: Facture, field: string) => { const date = (rowData as any)[field]; return date ? formatDate(date) : ''; }; const echeanceBodyTemplate = (rowData: Facture) => { const today = new Date(); const echeanceDate = new Date(rowData.dateEcheance); const isOverdue = echeanceDate < today && rowData.statut !== 'PAYEE'; return (
{isOverdue && } {formatDate(rowData.dateEcheance)}
); }; const header = (
Gestion des Factures
setGlobalFilter(e.currentTarget.value)} />
); const factureDialogFooter = ( <>