/** * ========================================================================= * PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0 * Interface Administrateur - Onglets Clients et Facturation * ========================================================================= */ const { useState } = React; const { Users, KeyRound, ShieldAlert, Shield, Zap, MessageSquare, Trash2, Loader, CheckCircle, Mail, AlertTriangle, FileText, Download, RefreshCw } = window; // ========================================================================= // 1. ONGLET : BASE CLIENTS // ========================================================================= window.AdminClientsTab = ({ data, refreshData, showToast, setActiveTab }) => { const [isSaving, setIsSaving] = useState(false); const [accessRequestModal, setAccessRequestModal] = useState(null); const [creditModal, setCreditModal] = useState(null); const [pwdRequest, setPwdRequest] = useState(null); const [confirmDialog, setConfirmDialog] = useState(null); const clients = data.clients || []; const sendAccessRequest = async (uid, mode) => { setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/request_access', { method: 'POST', body: JSON.stringify({ uid, mode }) }); if ((await res.json()).status === 'success') { showToast("Demande d'accès envoyée avec succès.", "success"); setAccessRequestModal(null); refreshData(); } else { showToast("Erreur lors de la demande", "error"); } } catch (e) { showToast("Erreur réseau", "error"); } finally { setIsSaving(false); } }; const revokeAccess = async (uid) => { setConfirmDialog({ title: "Révocation d'accès", message: "Êtes-vous sûr de vouloir révoquer l'autorisation d'accès technique pour ce client ?", confirmText: "Oui, révoquer", isDestructive: true, onConfirm: async () => { setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/revoke_access', { method: 'POST', body: JSON.stringify({ uid }) }); if ((await res.json()).status === 'success') { showToast("Accès révoqué.", "success"); refreshData(); } else { showToast("Erreur lors de la révocation", "error"); } } finally { setIsSaving(false); } } }); }; const impersonateClient = async (uid) => { setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'auth/impersonate', { method: 'POST', body: JSON.stringify({ uid }) }); if ((await res.json()).status === 'success') { window.location.href = '?'; } else { showToast("Erreur d'accès", "error"); } } finally { setIsSaving(false); } }; const deleteClient = (uid, clientName) => { setPwdRequest({ title: `Suppression du compte : ${clientName}`, desc: (ATTENTION : Saisissez le mot de passe administrateur pour détruire définitivement le compte de {clientName} et toutes ses données associées. Cette action est irréversible.), onConfirm: async (pwd) => { setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/admin_delete', { method: 'POST', body: JSON.stringify({ uid, pwd }) }); if ((await res.json()).status === 'success') { showToast("Client supprimé avec succès.", "success"); refreshData(); } else { showToast("Erreur de suppression (Mot de passe incorrect ?)", 'error'); } } finally { setIsSaving(false); } } }); }; const handleAddCredit = async (e) => { e.preventDefault(); const amount = parseFloat(e.target.amount.value); const reason = e.target.reason.value; if (amount <= 0) return showToast("Montant invalide", "error"); setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'invoices/credit', { method: 'POST', body: JSON.stringify({ client_uid: creditModal.client_uid, amount, reason }) }); const d = await res.json(); if (d.status === 'success') { showToast("Crédit ajouté avec succès !", "success"); setCreditModal(null); refreshData(); } else { showToast(d.message, 'error'); } } catch (err) { showToast("Erreur réseau", "error"); } setIsSaving(false); }; return (

Base clients

Gestion des comptes et prise de contrôle.

{clients.map(c => { const isImpersonable = c.admin_access_until && new Date(String(c.admin_access_until || '').replace(' ', 'T')) > new Date(); const isSuspended = c.paymentStatus === 'suspended'; return (

{c.name}

{c.email}

ID : {c.id.substring(0,8)}

{c.wallet_balance !== 0 && ( 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}`}> {c.wallet_balance > 0 ? `Solde: +${c.wallet_balance.toFixed(2)}€` : `Dette: ${c.wallet_balance.toFixed(2)}€`} )}
{isSuspended ? 'Suspendu' : 'Actif'} {c.discount > 0 && Remise {c.discount}%}
{isImpersonable ? (
) : ( )}
); })} {clients.length === 0 && (

Aucun client enregistré.

)}
{/* MODALES LOCALES */} {accessRequestModal && ( setAccessRequestModal(null)} zIndex="z-[250]">
Comment souhaitez-vous envoyer la demande d'autorisation d'accès temporaire (24h) à ce client ?
)} {creditModal && ( setCreditModal(null)} preventClose={isSaving}>
Ajout d'un crédit (bon d'achat)
Ce montant sera ajouté au solde du client et déduit automatiquement de ses prochains achats ou abonnements.
)} {pwdRequest && ( setPwdRequest(null)} zIndex="z-[250]">
{pwdRequest.desc}
{ e.preventDefault(); const pwd = e.target.pwd.value; if(pwd) { pwdRequest.onConfirm(pwd); setPwdRequest(null); } }}>
)} {confirmDialog && setConfirmDialog(null)} />}
); }; // ========================================================================= // 2. ONGLET : FACTURATION GLOBALE // ========================================================================= window.AdminInvoicesTab = ({ data, refreshData, showToast, setActiveTab }) => { const [isSaving, setIsSaving] = useState(false); const [refundModal, setRefundModal] = useState(null); const clients = data.clients || []; 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 > refundModal.invoice.amount) return showToast("Montant invalide", "error"); setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'invoices/refund', { method: 'POST', body: JSON.stringify({ invoice_ref: refundModal.invoice.invoiceNumber, amount, mode, reason }) }); const d = await res.json(); if (d.status === 'success') { showToast("Remboursement ou avoir traité avec succès !", "success"); setRefundModal(null); refreshData(); } else { showToast(d.message, 'error'); } } catch (err) { showToast("Erreur réseau", "error"); } setIsSaving(false); }; return (

Facturation globale

Historique des paiements de tous les clients.

Export CSV
{data.invoices?.map((inv, i) => { const client = clients.find(c => c.id === inv.clientName); return ( ); })}
Date Client / Panneau Détail Montant Actions
{new Date(String(inv.created_at || '').replace(' ', 'T')).toLocaleDateString('fr-FR')}
{client?.name || 'Client Inconnu'}
{inv.panneauName}
{inv.type} {inv.amount} € {!inv.type.includes('Avoir') && ( )}
{(!data.invoices || data.invoices.length === 0) && (

Aucune facture émise.

)}
{/* MODALE DE REMBOURSEMENT */} {refundModal && ( setRefundModal(null)} preventClose={isSaving} zIndex="z-[250]">
Facture initiale : {refundModal.invoice.amount} € ({refundModal.clientName})
Générez une facture d'avoir (partielle ou totale) pour annuler comptablement cette transaction.
)}
); }; /* EOF ========== [_www/_react/_admin_clients_factures.jsx] */