/** * ========================================================================= * PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0 * Interface Administrateur - Onglet Facturation Globale * ========================================================================= */ window.AdminInvoicesTab = ({ data, refreshData, showToast, setActiveTab, openLocalModal, openLocalDialog, closeCurrentLayer, activeModal, activeDialog, targetId }) => { const { useState } = React; // FACTORISATION : Utilisation du Hook global de gestion des modales par URL const { activeModal: urlModal, activeDialog: urlDialog, targetId: urlTargetId, openModal, openDialog, closeCurrentLayer: urlCloseLayer } = window.useUrlModal(); // Priorité aux props passées par le parent, sinon fallback sur le hook const routerActiveModal = activeModal || urlModal; const routerActiveDialog = activeDialog || urlDialog; const routerCloseLayer = closeCurrentLayer || urlCloseLayer; const routerOpenModal = openLocalModal || openModal; const routerOpenDialog = openLocalDialog || openDialog; const [isSaving, setIsSaving] = useState(false); // Cibles des modales const [refundTarget, setRefundTarget] = useState(null); const [archives, setArchives] = useState([]); const [loadingArchives, setLoadingArchives] = useState(false); // FACTORISATION : Récupération sécurisée et propre des icônes const { DownloadIcon, RefreshCwIcon, FileTextIcon, LoaderIcon, CheckCircleIcon, ArchiveIcon } = window.getIcons(); const Modal = window.Modal || (() => null); const Button = window.Button || (({children}) => ); const clients = data.clients || []; const isImpersonating = data.settings?.impersonating === true; const handleRefund = async (e) => { e.preventDefault(); const amount = parseFloat(e.target.amount.value); const reason = e.target.reason.value; const mode = e.target.mode.value; if (amount <= 0 || amount > refundTarget.maxRefundable) return showToast("Montant invalide", "error"); const d = await window.apiFetch('invoices/refund', { body: { invoice_ref: refundTarget.invoice.invoiceNumber, amount, mode, reason }, setLoading: setIsSaving, successMessage: "Remboursement ou avoir traité avec succès !" }); if (d) { routerCloseLayer(); refreshData(); } }; const openLegalArchives = async () => { routerOpenModal('legal_archive'); const d = await window.apiFetch('invoices/legal_archive', { method: 'GET', setLoading: setLoadingArchives }); if (d) { setArchives(d.data || []); } else { routerCloseLayer(); } }; return (

Facturation globale

Historique des paiements de tous clients.

{!isImpersonating && ( )}
{/* EN-TÊTES EN FLEXBOX */}
Date
Client / Panneau
Détail
Montant
Actions
{/* CONTENU EN FLEXBOX RESPONSIVE */}
{data.invoices?.map((inv, i) => { const client = clients.find(c => c.id === inv.clientName); const isAvoir = inv.type.includes('Avoir') || parseFloat(inv.amount) < 0; let maxRefundable = 0; if (!isAvoir) { const relatedAvoirs = data.invoices.filter(r => (r.type.includes('Avoir') || parseFloat(r.amount) < 0) && r.type.includes(inv.invoiceNumber) ); const totalRefunded = relatedAvoirs.reduce((sum, r) => sum + Math.abs(parseFloat(r.amount)), 0); maxRefundable = parseFloat((parseFloat(inv.amount) - totalRefunded).toFixed(2)); } return (
Date {window.formatDate ? window.formatDate(inv.created_at) : new Date(String(inv.created_at || '').replace(' ', 'T')).toLocaleDateString('fr-FR')}
Client / Panneau
{client?.name || 'Client inconnu'}
{inv.panneauName}
Détail {inv.type}
Montant {inv.amount} €
); })}
{(!data.invoices || data.invoices.length === 0) && (
{FileTextIcon && }

Aucune facture émise.

)}
{/* MODALES LOCALES DÉLÉGUÉES (LAYER 1 et 2) */} {routerActiveDialog === 'refund' && refundTarget && Modal && (
* Champs obligatoires
Règle stricte : Aucun remboursement n'est jamais accepté sauf en cas d'erreur de facturation.
Facture initiale : {refundTarget.invoice.amount} € ({refundTarget.clientName})
{parseFloat(refundTarget.invoice.amount) > refundTarget.maxRefundable && ( Attention : Des avoirs partiels ont déjà été émis pour cette facture.
Reste remboursable : {refundTarget.maxRefundable} €
)} Générez une facture d'avoir (partielle ou totale) pour annuler comptablement cette transaction.
)} {routerActiveModal === 'legal_archive' && Modal && (
Cet espace inaltérable contient les copies des factures de tous les clients ayant été définitivement supprimés de la plateforme, pour garantir la conformité comptable.
{loadingArchives ? (
{LoaderIcon && }

Déchiffrement de l'index...

) : (
Réf / Date
Client supprimé
Montant
PDF
{archives.map((arc, i) => (
Réf / Date
{arc.ref}
{window.formatDate ? window.formatDate(arc.date) : arc.date}
Client supprimé {arc.clientName}
Montant {arc.amount} €
))} {archives.length === 0 && (
Aucune archive légale trouvée.
)}
)}
)}
); }; /* EOF ========== [_react/_admin_factures.jsx] */