/** * ========================================================================= * PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0 * Interface Administrateur - Onglets Dashboard et Système/Paramètres * ========================================================================= */ const { useState, useEffect, useRef } = React; const { Activity, Users, Building, Eye, MessageSquare, Terminal, Archive, Save, Download, RefreshCw, Trash2, Loader, ShieldAlert, CreditCard, FileText, AlertTriangle, Settings, Zap } = window; const decodeHTML = (html) => { const txt = document.createElement("textarea"); txt.innerHTML = html; return txt.value; }; // ========================================================================= // 1. ONGLET : TABLEAU DE BORD (SUPERVISION ET DIAGNOSTICS) // ========================================================================= window.AdminDashboardTab = ({ data, refreshData, showToast, setActiveTab }) => { const [isSaving, setIsSaving] = useState(false); const [diagnostics, setDiagnostics] = useState(null); const [logs, setLogs] = useState(''); const [backups, setBackups] = useState([]); const [isLoadingBackups, setIsLoadingBackups] = useState(false); const [showAllBackups, setShowAllBackups] = useState(false); const [sysSummary, setSysSummary] = useState({ loaded: false, diagStatus: 'emerald', backupStatus: 'emerald', logStatus: 'emerald', php: '', ram: '', db: '', disk: '', backupsCount: 0, logDesc: '', backupDesc: '' }); const [modalView, setModalView] = useState(null); const [pwdRequest, setPwdRequest] = useState(null); const [promptDialog, setPromptDialog] = useState(null); const fetchTelemetry = async (silent = false) => { try { const cacheBuster = '&_t=' + Date.now(); const fetchOpts = silent ? { cache: 'no-store', headers: { 'x-silent': '1' } } : { cache: 'no-store' }; const [diagRes, backRes, logRes] = await Promise.all([ fetch(window.ECO_CONFIG.apiBaseUrl + 'system/diagnostics' + cacheBuster + (silent ? '&silent=1' : ''), fetchOpts).then(r => r.json()), fetch(window.ECO_CONFIG.apiBaseUrl + 'system/backups' + cacheBuster + (silent ? '&silent=1' : ''), fetchOpts).then(r => r.json()), fetch(window.ECO_CONFIG.apiBaseUrl + 'system/logs' + cacheBuster + (silent ? '&silent=1' : ''), fetchOpts).then(r => r.json()) ]); if (diagRes.status === 'success') { const dbDetail = diagRes.data.find(d => d.id === 'db')?.detail || '0 ms'; const diskDetail = diagRes.data.find(d => d.id === 'disk')?.detail || '0 GB'; const dbVal = parseFloat(dbDetail); const diskVal = parseFloat(diskDetail); let diagStatus = 'emerald'; if (dbVal > 500 || diskVal < 1) diagStatus = 'red'; else if (dbVal > 200 || diskVal < 5) diagStatus = 'amber'; const backupsData = backRes.data || []; const backupsCount = backupsData.length; let backupStatus = 'emerald'; let backupDesc = `${backupsCount} archive(s)`; if (backupsCount === 0) { backupStatus = 'red'; backupDesc = "Alerte : Aucune sauvegarde"; } else { const latestBackup = backupsData.reduce((max, b) => b.date > max.date ? b : max, backupsData[0]); const hoursSinceLast = (Date.now() - latestBackup.date) / (1000 * 60 * 60); if (hoursSinceLast > 48) { backupStatus = 'red'; backupDesc = `Critique : Retard de ${Math.floor(hoursSinceLast/24)} jours`; } else if (hoursSinceLast > 24) { backupStatus = 'amber'; backupDesc = `Attention : Retard de ${Math.floor(hoursSinceLast)}h`; } else { backupStatus = 'emerald'; backupDesc = `À jour (${backupsCount} archives)`; } } const logsContent = logRes.data?.logs || ''; let logStatus = 'emerald'; let logDesc = 'Journal vierge (Sain)'; const lowerLogs = logsContent.toLowerCase(); if (lowerLogs.includes('fatal error') || lowerLogs.includes('exception')) { logStatus = 'red'; logDesc = 'Erreurs critiques détectées !'; } else if (logsContent.length > 5) { logStatus = 'amber'; logDesc = 'Avertissements enregistrés'; } setSysSummary({ loaded: true, diagStatus, backupStatus, logStatus, php: diagRes.data.find(d => d.id === 'php')?.detail || 'OK', ram: diagRes.data.find(d => d.id === 'ram')?.detail || 'OK', db: dbDetail, disk: diskDetail, backupsCount, logDesc, backupDesc }); } } catch(e) {} }; useEffect(() => { fetchTelemetry(); let telemetryInterval; let currentDelay = 10000; let lastActivityTime = Date.now(); let isTabVisible = !document.hidden; const performTelemetrySync = async () => { if (!isTabVisible) return; await fetchTelemetry(true); const idleTime = Date.now() - lastActivityTime; if (idleTime > 120000) currentDelay = 60000; else if (idleTime > 30000) currentDelay = 30000; else currentDelay = 10000; scheduleNextPoll(currentDelay); }; const scheduleNextPoll = (delay) => { clearTimeout(telemetryInterval); if (isTabVisible) telemetryInterval = setTimeout(performTelemetrySync, delay); }; const handleVisibilityChange = () => { isTabVisible = !document.hidden; if (isTabVisible) { lastActivityTime = Date.now(); currentDelay = 10000; performTelemetrySync(); } else { clearTimeout(telemetryInterval); } }; const handleUserInteraction = () => { if (currentDelay > 10000) { lastActivityTime = Date.now(); currentDelay = 10000; scheduleNextPoll(10000); } else { lastActivityTime = Date.now(); } }; document.addEventListener('visibilitychange', handleVisibilityChange); document.addEventListener('click', handleUserInteraction); document.addEventListener('keydown', handleUserInteraction); scheduleNextPoll(currentDelay); return () => { clearTimeout(telemetryInterval); document.removeEventListener('visibilitychange', handleVisibilityChange); document.removeEventListener('click', handleUserInteraction); document.removeEventListener('keydown', handleUserInteraction); }; }, []); useEffect(() => { let interval; if (modalView === 'diagnostics') { interval = setInterval(() => { fetch(window.ECO_CONFIG.apiBaseUrl + 'system/diagnostics&silent=1&_t=' + Date.now(), { cache: 'no-store', headers: { 'x-silent': '1' } }) .then(r => r.json()) .then(d => { if (d.status === 'success') setDiagnostics(d.data); }).catch(()=>{}); }, 1000); } return () => clearInterval(interval); }, [modalView]); const fetchDiagnostics = async () => { setModalView('diagnostics'); setDiagnostics(null); const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/diagnostics&_t=' + Date.now(), { cache: 'no-store' }); const d = await res.json(); if(d.status === 'success') setDiagnostics(d.data); }; const fetchLogs = async () => { setModalView('logs'); setLogs('Chargement...'); const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/logs&_t=' + Date.now(), { cache: 'no-store' }); const d = await res.json(); setLogs(d.data?.logs || 'Aucun log trouvé.'); }; const clearLogs = async () => { await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/logs/clear', { method: 'POST' }); fetchLogs(); fetchTelemetry(); }; const loadBackups = async () => { setModalView('backups'); setShowAllBackups(false); setIsLoadingBackups(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/backups&_t=' + Date.now(), { cache: 'no-store' }); const d = await res.json(); if(d.status === 'success') setBackups(d.data); } catch(e) { showToast("Erreur lors du chargement des sauvegardes", "error"); } finally { setIsLoadingBackups(false); } }; const createBackup = () => { setPromptDialog({ title: "Nouvelle sauvegarde", message: "Saisissez un marqueur ou label personnalisé pour identifier cette sauvegarde manuelle :", placeholder: "Ex : Avant la mise à jour...", confirmText: "Sauvegarder", onConfirm: async (label) => { setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/backups/create', { method: 'POST', body: JSON.stringify({ label }) }); if ((await res.json()).status === 'success') { showToast("Sauvegarde créée avec succès.", "success"); loadBackups(); fetchTelemetry(); } else showToast("Erreur de sauvegarde", "error"); } catch(e) { showToast("Erreur réseau", "error"); } finally { setIsSaving(false); } } }); }; const restoreBackup = (filename) => { setPwdRequest({ title: "Restauration système", desc: "ATTENTION CRITIQUE : La restauration écrasera la base de données actuelle. Saisissez le mot de passe administrateur :", onConfirm: async (pwd) => { setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/backups/restore', { method: 'POST', body: JSON.stringify({ filename, pwd }) }); if ((await res.json()).status === 'success') { alert("Restauration réussie. L'application va recharger."); window.location.reload(); } else showToast("Mot de passe incorrect ou erreur.", "error"); } finally { setIsSaving(false); } } }); }; const deleteBackup = (filename) => { setPwdRequest({ title: "Suppression d'archive", desc: "Saisissez le mot de passe administrateur pour supprimer définitivement cette sauvegarde :", onConfirm: async (pwd) => { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/backups/delete', { method: 'POST', body: JSON.stringify({ filename, pwd }) }); if ((await res.json()).status === 'success') { loadBackups(); fetchTelemetry(); } else { showToast("Mot de passe incorrect", "error"); } } }); }; const getColorClass = (status) => { if (!sysSummary.loaded) return 'text-slate-500 border-slate-200 bg-slate-50'; if (status === 'red') return 'text-red-700 border-red-200 bg-red-50 hover:border-red-400'; if (status === 'amber') return 'text-amber-700 border-amber-200 bg-amber-50 hover:border-amber-400'; return 'text-emerald-700 border-emerald-200 bg-emerald-50 hover:border-emerald-400'; }; const clients = data.clients || []; const panneaux = data.panneaux || []; const activePanneaux = panneaux.filter(p => p.status === 'Actif' && p.id !== 'demo-panneau'); const mrr = activePanneaux.filter(p => p.offerType === 'rental').reduce((sum, p) => sum + (p.currentRate || 0), 0); const interactions = data.interactions || []; const recentMessages = [...interactions].filter(m => m.target === 'Admin').reverse().slice(0, 5); return (

Supervision

Vue d'ensemble de la plateforme SaaS.

} color="text-emerald-600" bg="bg-emerald-100" /> } color="text-blue-600" bg="bg-blue-100" onClick={() => setActiveTab('clients')} /> } color="text-indigo-600" bg="bg-indigo-100" onClick={() => setActiveTab('panneaux')} /> } color="text-amber-600" bg="bg-amber-100" />

Derniers messages

{recentMessages.map((m, i) => { let displayAuthor = m.author; if (m.panneauId === 'CONTACT_PUBLIC') { if (m.author === 'Admin') displayAuthor = 'Service client'; else if (m.author === 'Système' || m.author === 'Systeme') displayAuthor = 'Système'; else displayAuthor = `Visiteur ${m.author}`; } else { if (m.author === 'Admin') displayAuthor = 'Support'; else if (m.author === 'Client') { if (m.panneauId.startsWith('SUPPORT_')) { const cUid = m.panneauId.split('_')[1]; const c = clients.find(cl => cl.id === cUid); if (c && c.name) displayAuthor = c.name; } else displayAuthor = 'Client'; } else if (m.author === 'Système' || m.author === 'Systeme') displayAuthor = 'Système'; } return (
{ let tId = m.panneauId; if (m.panneauId === 'CONTACT_PUBLIC') { tId = `CONTACT_PUBLIC_${m.author}`; } else if (m.panneauId.startsWith('SUPPORT_')) { tId = m.panneauId; } else if (m.author !== 'Client' && m.author !== 'Admin') { tId = `${m.panneauId}_${m.author}`; } else if (m.target !== 'Client' && m.target !== 'Admin' && m.target) { tId = `${m.panneauId}_${m.target}`; } const u = new URL(window.location); if (tId) u.searchParams.set('chat_id', tId); window.history.replaceState({}, '', u); setActiveTab('messages'); }}>

{displayAuthor}

{new Date(String(m.created_at || '').replace(' ', 'T')).toLocaleDateString('fr-FR')}

{m.detail.replace(/\[ATTACHMENT:[^\]]+\]/g, '[Pièce jointe]').replace(/<[^>]*>?/gm, '')}

{m.panneauId === 'CONTACT_PUBLIC' ? 'ORIGINE : Nous contacter' : 'ORIGINE : Espace client (Support)'}

); })} {recentMessages.length === 0 &&

Aucune interaction récente.

}

Serveur et maintenance

{/* MODALES SYSTÈME */} {modalView === 'diagnostics' && ( setModalView(null)}> {!diagnostics ?

Analyse complète en cours...

: (
{diagnostics.map(d => (

{d.label}

{d.status}

{d.detail}
))}
)}
)} {modalView === 'logs' && ( setModalView(null)}>
{logs}
)} {modalView === 'backups' && ( setModalView(null)} preventClose={isLoadingBackups}>
{isLoadingBackups ? (

Chargement en cours...

) : ( <> {backups.slice(0, showAllBackups ? backups.length : 5).map(b => (
{b.label === 'Automatique' ? 'CRON' : 'MANUEL'}

{decodeHTML(b.label)}

{new Date(b.date).toLocaleString('fr-FR')} • {b.size}

))} {backups.length === 0 &&

Aucune sauvegarde trouvée.

} {!showAllBackups && backups.length > 5 && ( )} )}
)} {pwdRequest && ( setPwdRequest(null)} zIndex="z-[250]">
{pwdRequest.desc}
{ e.preventDefault(); const pwd = e.target.pwd.value; if(pwd) { pwdRequest.onConfirm(pwd); setPwdRequest(null); } }}>
)} {promptDialog && setPromptDialog(null)} />}
); }; // ========================================================================= // 2. ONGLET : PARAMÈTRES DU SYSTÈME (TARIFS ET SÉCURITÉ) // ========================================================================= window.AdminSettingsTab = ({ data, refreshData, showToast, setActiveTab }) => { const [isSaving, setIsSaving] = useState(false); const [hasTva, setHasTva] = useState(true); const [showTvaWarning, setShowTvaWarning] = useState(false); const [pwdRequest, setPwdRequest] = useState(null); const [promptDialog, setPromptDialog] = useState(null); // CORRECTION : Initialisation stricte de toutes les variables pour éviter le Warning React "uncontrolled to controlled" const [settingsData, setSettingsData] = useState({ ...data.settings, ...data.prices, hostingYr: data.prices?.hostingYr ?? 1, billing_legalText: data.settings?.billing_legalText ?? '', site_banner_active: data.settings?.site_banner_active ?? '0', site_banner_msg: data.settings?.site_banner_msg ?? '', panel_banner_active: data.settings?.panel_banner_active ?? '0', panel_banner_msg: data.settings?.panel_banner_msg ?? '', allow_new_purchases: data.settings?.allow_new_purchases ?? '1', simp_opt_description: data.settings?.simp_opt_description ?? '1', simp_opt_image: data.settings?.simp_opt_image ?? '1', simp_opt_theme: data.settings?.simp_opt_theme ?? '1', simp_opt_link: data.settings?.simp_opt_link ?? '1', simp_opt_emergency: data.settings?.simp_opt_emergency ?? '1', simp_opt_schedule: data.settings?.simp_opt_schedule ?? '1', blacklist: data.settings?.blacklist ?? 'putain, connard, salope', greylist: data.settings?.greylist ?? 'merde, chier', sec_ip_limit: data.settings?.sec_ip_limit ?? 5, sec_global_limit: data.settings?.sec_global_limit ?? 1000, sec_lock_min: data.settings?.sec_lock_min ?? 10, sec_lock_max: data.settings?.sec_lock_max ?? 60, maintenance: data.settings?.maintenance ?? '0' }); const settingsLoadedRef = useRef(false); useEffect(() => { if (data.settings && !settingsLoadedRef.current) { setSettingsData({ ...data.settings, ...data.prices, hostingYr: data.prices?.hostingYr ?? 1, billing_legalText: data.settings?.billing_legalText ?? '', site_banner_active: data.settings?.site_banner_active ?? '0', site_banner_msg: data.settings?.site_banner_msg ?? '', panel_banner_active: data.settings?.panel_banner_active ?? '0', panel_banner_msg: data.settings?.panel_banner_msg ?? '', allow_new_purchases: data.settings?.allow_new_purchases ?? '1', simp_opt_description: data.settings?.simp_opt_description ?? '1', simp_opt_image: data.settings?.simp_opt_image ?? '1', simp_opt_theme: data.settings?.simp_opt_theme ?? '1', simp_opt_link: data.settings?.simp_opt_link ?? '1', simp_opt_emergency: data.settings?.simp_opt_emergency ?? '1', simp_opt_schedule: data.settings?.simp_opt_schedule ?? '1', blacklist: data.settings?.blacklist ?? 'putain, connard, salope', greylist: data.settings?.greylist ?? 'merde, chier', sec_ip_limit: data.settings?.sec_ip_limit ?? 5, sec_global_limit: data.settings?.sec_global_limit ?? 1000, sec_lock_min: data.settings?.sec_lock_min ?? 10, sec_lock_max: data.settings?.sec_lock_max ?? 60, maintenance: data.settings?.maintenance ?? '0' }); setHasTva(data.settings?.billing_has_tva !== '0'); settingsLoadedRef.current = true; } }, [data]); const handleAutoFill = () => { setPromptDialog({ title: "Saisie automatique", message: "Saisissez le SIRET (14 chiffres) ou SIREN (9 chiffres) de l'entreprise qui gère la plateforme (Vous) :", placeholder: "N° SIREN ou SIRET", confirmText: "Rechercher", onConfirm: async (num) => { const cleanNum = num.replace(/\D/g, ''); if (cleanNum.length !== 9 && cleanNum.length !== 14) return showToast("Le numéro doit contenir exactement 9 ou 14 chiffres.", "error"); setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/sirene', { method: 'POST', body: JSON.stringify({ q: cleanNum }) }); const responseData = await res.json(); if (responseData.status !== 'success') throw new Error(responseData.message); const apiData = responseData.data; if (apiData.results && apiData.results.length > 0) { const company = apiData.results[0]; const expectedSiren = cleanNum.substring(0, 9); if (company.siren !== expectedSiren) { setIsSaving(false); return showToast("Numéro invalide : Aucune entreprise correspondante.", "error"); } const siege = company.siege || {}; let foundSiret = siege.siret || company.siren + "00010"; if (cleanNum.length === 14) { if (siege.siret === cleanNum) foundSiret = cleanNum; else if (company.matching_etablissements) { const exactEtab = company.matching_etablissements.find(e => e.siret === cleanNum); if (exactEtab) foundSiret = cleanNum; } } const addressParts = [siege.numero_voie, siege.indice_repetition, siege.type_voie, siege.libelle_voie, siege.code_postal, siege.libelle_commune]; const address = addressParts.filter(Boolean).join(' ').replace(/\s+/g, ' ').trim() || "Adresse non renseignée"; const tvaKey = (12 + 3 * (parseInt(company.siren, 10) % 97)) % 97; const calculatedTva = `FR${tvaKey.toString().padStart(2, '0')}${company.siren}`; setSettingsData({ ...settingsData, billing_company: company.nom_complet, billing_address: address, billing_siret: foundSiret, billing_tva: calculatedTva }); setHasTva(true); showToast("Informations récupérées avec succès !", "success"); } else showToast("Aucune entreprise trouvée.", "error"); } catch (err) { showToast("Le service gouvernemental est momentanément indisponible.", "error"); } setIsSaving(false); } }); }; const handleToggleTva = () => { if (hasTva) { setShowTvaWarning(true); } else { setHasTva(true); } }; const confirmTvaExemption = () => { setHasTva(false); setSettingsData({ ...settingsData, billing_tva: '' }); setShowTvaWarning(false); }; const handleSaveSettings = () => { setPwdRequest({ title: "Modification globale", desc: "Saisissez le mot de passe administrateur pour confirmer la modification des paramètres de la plateforme.", onConfirm: async (pwd) => { setIsSaving(true); try { const payload = { ...settingsData, pwd, billing_has_tva: hasTva ? '1' : '0', price_rentalMo: settingsData.rentalMo, price_purchase: settingsData.purchase, price_boardFirst: settingsData.boardFirst, price_boardAdd: settingsData.boardAdd, price_noAds: settingsData.noAds, price_hostingYr: settingsData.hostingYr, billing_legalText: settingsData.billing_legalText, sec_ip_limit: settingsData.sec_ip_limit, sec_global_limit: settingsData.sec_global_limit, sec_lock_min: settingsData.sec_lock_min, sec_lock_max: settingsData.sec_lock_max }; const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'settings/update', { method: 'POST', body: JSON.stringify(payload) }); if ((await res.json()).status === 'success') { showToast("Paramètres globaux mis à jour."); refreshData(); } else showToast("Mot de passe invalide", 'error'); } finally { setIsSaving(false); } } }); }; return (

Paramètres système

Tarification globale et mentions légales.

Cyberdéfense et pare-feu (Panic Mode)

Paramétrez les seuils de déclenchement du confinement automatique en cas d'attaque réseau.

Nombre d'erreurs de mot de passe avant bannissement de l'IP (15 min).

setSettingsData({...settingsData, sec_ip_limit: e.target.value})} className="w-full border-2 border-slate-200 rounded-xl p-2 text-sm focus:border-red-500 outline-none transition" />

Nombre d'IPs malveillantes simultanées avant confinement du site.

setSettingsData({...settingsData, sec_global_limit: e.target.value})} className="w-full border-2 border-slate-200 rounded-xl p-2 text-sm focus:border-red-500 outline-none transition" />

En minutes (Valeur aléatoire entre le min et le max).

setSettingsData({...settingsData, sec_lock_min: e.target.value})} className="w-full border-2 border-slate-200 rounded-xl p-2 text-sm focus:border-red-500 outline-none transition" />

En minutes.

setSettingsData({...settingsData, sec_lock_max: e.target.value})} className="w-full border-2 border-slate-200 rounded-xl p-2 text-sm focus:border-red-500 outline-none transition" />

Paramètres des ventes

setSettingsData({...settingsData, rentalMo: v})} /> setSettingsData({...settingsData, purchase: v})} /> setSettingsData({...settingsData, hostingYr: v})} /> setSettingsData({...settingsData, boardFirst: v})} /> setSettingsData({...settingsData, boardAdd: v})} /> setSettingsData({...settingsData, noAds: v})} />

Mentions légales (CGV et RGPD)

Émetteur (facturation)

setSettingsData({...settingsData, billing_company: e.target.value})} className="w-full border border-slate-200 rounded-lg p-2 text-sm outline-none focus:border-slate-400 bg-white" />
setSettingsData({...settingsData, billing_siret: e.target.value})} className="w-full border border-slate-200 rounded-lg p-2 text-sm outline-none focus:border-slate-400 bg-white" />
setSettingsData({...settingsData, billing_address: e.target.value})} className="w-full border border-slate-200 rounded-lg p-2 text-sm outline-none focus:border-slate-400 bg-white" />
{hasTva && (
setSettingsData({...settingsData, billing_tva: e.target.value})} placeholder="FR..." className="w-full border border-slate-200 rounded-lg p-2 text-sm outline-none focus:border-slate-400 uppercase bg-white" />
)}

Bandeaux d'information

Affichez des messages importantes aux utilisateurs de la plateforme.

Visible par les clients et visiteurs (sauf espace riverain).

Visible uniquement par les riverains qui scannent un panneau.

Modération et mots-clés

Ces listes permettent de détecter les panneaux ou messages ne respectant pas les règles de bienséance. (Mots séparés par des virgules)

Options du mode simplifié

Sélectionnez les fonctionnalités accessibles aux clients utilisant l'interface "Simplifiée".

{[ { key: 'simp_opt_description', label: 'Description des travaux' }, { key: 'simp_opt_image', label: 'Image du panneau' }, { key: 'simp_opt_theme', label: 'Couleur du thème' }, { key: 'simp_opt_link', label: 'Lien promoteur' }, { key: 'simp_opt_emergency', label: 'N° d\'urgence' }, { key: 'simp_opt_schedule', label: 'Horaires des nuisances' } ].map(opt => ( ))}
{showTvaWarning && ( setShowTvaWarning(false)}>

Exonération de TVA

En désactivant ce champ, vous indiquez que votre propre société (la plateforme eco-panneau) n'est pas assujettie à la TVA (ex : auto-entrepreneur).

Conséquence immédiate :

  • Toutes vos futures factures seront émises sans TVA (Prix TTC = Prix HT).
  • La mention légale "TVA non applicable, art. 293 B du CGI" figurera sur les factures clients.
  • Les montants envoyés à Stripe ne seront plus majorés de 20%.
)} {pwdRequest && ( setPwdRequest(null)} zIndex="z-[250]">
{pwdRequest.desc}
{ e.preventDefault(); const pwd = e.target.pwd.value; if(pwd) { pwdRequest.onConfirm(pwd); setPwdRequest(null); } }}>
)} {promptDialog && setPromptDialog(null)} />}
); }; /* EOF ========== [_www/_react/_admin_systeme.jsx] */