// 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 (
Retrouvez l'historique de vos paiements et abonnements.
Aucune facture n'a encore été générée pour votre compte.
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.
Date d'émission
{window.pano_formatDate ? window.pano_formatDate(targetInvoice.created_at) : targetInvoice.created_at}
Montant TTC
{targetInvoice.amount} €
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.