// ECO-PANNEAU.FR - _react/clients/_clients_factures.jsx window.pano_ClientInvoicesTab = ({ data, openLocalDialog, closeCurrentLayer, activeDialog, dialogId, clientOpts, toggleDashboardPin, setArchiveConfig, refreshData }) => { const { useState, useEffect, useMemo } = React; // SÉCURITÉ ANTI-FUITE DE MÉMOIRE : Utilisation du Hook Global Zéro-Dette const { isMounted, safeFetch } = window.pano_useSafeFetch(); // 1. - Routage Zéro-Dette et Modales const urlModal = window.pano_useUrlModal ? window.pano_useUrlModal() : {}; const routerActiveDialog = activeDialog || urlModal.activeDialog; const routerDialogId = dialogId || urlModal.dialogId; const routerOpenDialog = openLocalDialog || urlModal.openDialog; const routerCloseLayer = closeCurrentLayer || urlModal.closeCurrentLayer; // 2. - États de téléchargement local const [downloadedInvoices, setDownloadedInvoices] = useState(() => { try { return JSON.parse(localStorage.getItem('pano_downloaded_invoices') || '[]'); } catch(e) { return []; } }); const [previewInvoice, setPreviewInvoice] = useState(null); const [isSaving, setIsSaving] = useState(false); // Synchronisation multi-onglets pour la pastille "Non lu" useEffect(() => { const handleUpdate = () => { try { setDownloadedInvoices(JSON.parse(localStorage.getItem('pano_downloaded_invoices') || '[]')); } catch(e) {} }; window.addEventListener('invoice_downloaded', handleUpdate); return () => window.removeEventListener('invoice_downloaded', handleUpdate); }, []); // 3. - Icônes et Composants const { FileTextIcon, DownloadIcon, InfoIcon, PinIcon, AlertTriangleIcon, EyeIcon, ArchiveIcon } = window.pano_getIcons(); const { Button, Modal, CardGrid, DataCard, SearchBar, EmptySearch, PaginationFooter, UniversalViewer, ArchiveGeneratorModal } = window.pano_getComponents(); // 4. - Données et Filtrage (Limité à 500 pour les performances) const invoices = data.invoices || []; const { searchQuery, setSearchQuery, visibleCount, setVisibleCount, filteredData: filteredInvoices } = window.pano_useSearchAndPagination(invoices, (inv, q) => { const n = window.pano_normalizeString; const dateStr = window.pano_formatDate ? window.pano_formatDate(inv.created_at) : new Date(String(inv.created_at || '').replace(' ', 'T')).toLocaleDateString('fr-FR'); return n(inv.invoice_ref || inv.invoiceNumber).includes(q) || n(inv.type).includes(q) || n(inv.amount).includes(q) || n(dateStr).includes(q) || n(inv.panneauName).includes(q); }); const MAX_DISPLAY = 500; const isLimited = filteredInvoices.length > MAX_DISPLAY; const effectiveInvoices = isLimited ? filteredInvoices.slice(0, MAX_DISPLAY) : filteredInvoices; const pinnedIds = clientOpts?.pinned_invoices || []; const attentionInvoices = []; const pinnedInvoices = []; const otherInvoices = []; // CLASSIFICATION : URGENCE > FAVORIS > RESTE effectiveInvoices.forEach(inv => { const invRef = inv.invoice_ref || inv.invoiceNumber; const isUnread = !downloadedInvoices.includes(invRef); const isPinned = pinnedIds.includes(invRef); if (isUnread) { attentionInvoices.push(inv); } else if (isPinned) { pinnedInvoices.push(inv); } else { otherInvoices.push(inv); } }); const displayedOtherInvoices = otherInvoices.slice(0, visibleCount); // 5. - Actions métier const handleDownload = (invRef) => { if (!downloadedInvoices.includes(invRef)) { const newArr = [...downloadedInvoices, invRef]; setDownloadedInvoices(newArr); localStorage.setItem('pano_downloaded_invoices', JSON.stringify(newArr)); window.dispatchEvent(new CustomEvent('invoice_downloaded')); } // Sécurité SPA : Protection contre le kill de l'App par le navigateur window.open(`${window.pano_CONFIG.apiBaseUrl}invoices/download&ref=${invRef}`, '_blank'); }; const targetInvoice = routerActiveDialog === 'invoice_details' && routerDialogId ? invoices.find(i => (i.invoice_ref || i.invoiceNumber) === routerDialogId) : null; // 6. - Rendu des cartes (Factures) const renderInvoiceCard = (inv, keyPrefix) => { const invRef = inv.invoice_ref || inv.invoiceNumber; const isUnread = !downloadedInvoices.includes(invRef); const isPinned = pinnedIds.includes(invRef); const isAvoir = inv.type.includes('Avoir') || parseFloat(inv.amount) < 0; return ( {/* ÉPINGLE VOLANTE ET INCLINÉE */}
{ e.stopPropagation(); toggleDashboardPin && toggleDashboardPin('pinned_invoices', invRef, e); }} className={`absolute top-2 right-2 z-20 p-1 cursor-pointer transition-all duration-200 ${isPinned ? 'text-amber-500 opacity-100 hover:scale-110' : 'text-slate-300 opacity-0 group-hover:opacity-100 hover:text-slate-500 hover:scale-110'}`} style={{ transform: 'rotate(30deg)' }} title={isPinned ? "Désépingler" : "Épingler au tableau de bord"} >
{isUnread && Nouvelle}
Date {window.pano_formatDate ? window.pano_formatDate(inv.created_at) : new Date(String(inv.created_at || '').replace(' ', 'T')).toLocaleDateString('fr-FR')}
Montant {inv.amount} €
{/* BLOC CENTRAL : Information par Exception (Silence si pas de projet) */}
{inv.panneauName && ( <> Projet concerné {inv.panneauName} )} {inv.type.replace(/\s*\(PI:[^)]+\)/, '')} (Réf. {invRef})
{/* ACTIONS : Masquées tant que la facture n'est pas téléchargée */}
{!isUnread && ( <>
); }; // 7. - Rendu UI return (

Factures et avoirs

Retrouvez l'historique de vos paiements et abonnements.

{invoices.length > 0 && SearchBar && (
)} {/* CORRECTION SÉCURITÉ UI : Masquage conditionnel des boutons d'export */} {invoices.length > 0 && (
)}
{invoices.length === 0 ? (
{FileTextIcon && }

Aucune facture n'a encore été générée pour votre compte.

) : filteredInvoices.length === 0 ? ( EmptySearch && ) : ( <> {/* A. PRIORITÉ (FACTURES NON TÉLÉCHARGÉES) */} {attentionInvoices.length > 0 && (

Factures non téléchargées

{attentionInvoices.map((inv, i) => renderInvoiceCard(inv, `att_${i}`))}
)} {/* B. FAVORIS */} {pinnedInvoices.length > 0 && (

Favoris (Épinglés)

{pinnedInvoices.map((inv, i) => renderInvoiceCard(inv, `pin_${i}`))}
)} {/* C. LE RESTE */} {otherInvoices.length > 0 && (

Historique complet

{displayedOtherInvoices.slice(0, visibleCount).map((inv, i) => renderInvoiceCard(inv, `oth_${i}`))} {PaginationFooter && ( )}
)} {isLimited && (

L'affichage est limité aux {MAX_DISPLAY} résultats les plus récents pour garantir les performances.

Pour consulter l'intégralité de votre facturation, veuillez télécharger un export (ZIP ou CSV) via les boutons ci-dessus.

)} )} {/* Modales contextuelles */} {routerActiveDialog === 'archive_config' && archiveConfig && ArchiveGeneratorModal && ( )} {routerActiveDialog === 'invoice_details' && targetInvoice && Modal && ( ( )} >

Date d'émission

{window.pano_formatDate ? window.pano_formatDate(targetInvoice.created_at) : targetInvoice.created_at}

Montant TTC

{targetInvoice.amount} €

{targetInvoice.panneauName && (

Projet concerné

{targetInvoice.panneauName}

)}

Objet / Description

{targetInvoice.type.replace(/\s*\(PI:[^)]+\)/, '')}

En cas de question sur cette facturation, vous pouvez contacter le support depuis l'onglet Mon compte.

)} {previewInvoice && UniversalViewer && ( setPreviewInvoice(null)} /> )}
); }; /* EOF ========== [_react/clients/_clients_factures.jsx] */