// ECO-PANNEAU.FR - _react/admin/_admin.jsx
window.pano_AdminView = ({ data, refreshData, initialTab }) => {
const { useState, useEffect, useMemo } = React;
// ZÉRO-DETTE : Utilisation du Hook global !
const { isMounted, safeFetch } = window.pano_useSafeFetch();
const urlModal = window.pano_useUrlModal ? window.pano_useUrlModal() : {};
const activeTab = urlModal.currentTab || initialTab || 'dashboard';
const setActiveTab = urlModal.changeTab;
const {
activeModal, activeDialog, activeChat, targetId, dialogId,
openModal, openDialog, closeCurrentLayer, replaceCurrentLayer, openChat
} = urlModal;
const adminData = data.me || data.myAdminData || data.myClientData || data.adminData || {};
const [adminOpts, setAdminOpts] = useState(() => {
try {
const rawMode = adminData.ui_mode || adminData.uiMode;
return rawMode ? JSON.parse(rawMode) : {};
} catch (e) {
if (window.pano_logFallback) window.pano_logFallback("Erreur de parsing des options Admin (ui_mode).");
return {};
}
});
useEffect(() => {
try {
const rawMode = adminData.ui_mode || adminData.uiMode;
if (rawMode) {
setAdminOpts(JSON.parse(rawMode));
}
} catch(e) {}
}, [adminData.ui_mode, adminData.uiMode]);
const updateAdminOpts = async (newOpts) => {
setAdminOpts(newOpts);
const payload = {
...adminData,
name: adminData.name || 'Système',
full_name: adminData.full_name || adminData.fullName || 'Système',
uiMode: JSON.stringify(newOpts)
};
await safeFetch('clients/profile/update', { body: payload, silent: true });
if (!isMounted.current) return;
if (refreshData) refreshData();
};
const toggleDashboardPin = (listKey, id, e) => {
if (e) { e.preventDefault(); e.stopPropagation(); }
const currentArr = adminOpts[listKey] || [];
const newArr = currentArr.includes(id) ? currentArr.filter(x => x !== id) : [...currentArr, id];
const newOpts = { ...adminOpts, [listKey]: newArr };
updateAdminOpts(newOpts);
};
const [managingPanneau, setManagingPanneau] = useState(null);
const [editingEntity, setEditingEntity] = useState(null);
const [draggedItem, setDraggedItem] = useState(null);
const [isSaving, setIsSaving] = useState(false);
const [validationErrors, setValidationErrors] = useState([]);
const [previewPanneau, setPreviewPanneau] = useState(null);
const [confirmConfig, setConfirmConfig] = useState(null);
// ÉTAT CENTRALISÉ DE LA TÉLÉMÉTRIE
const [sysSummary, setSysSummary] = useState({
loaded: false, diagStatus: 'emerald', backupStatus: 'emerald', logStatus: 'emerald',
queueStatus: 'success', php: '', ram: '', db: '', disk: '', backupsCount: 0, logDesc: '', backupDesc: ''
});
const icons = useMemo(() => window.pano_getIcons(), []);
const {
ActivityIcon, BuildingIcon, PackageIcon, UsersIcon, CreditCardIcon,
MessageSquareIcon, SettingsIcon, ShieldAlertIcon, ServerIcon, RefreshCwIcon, SlidersHorizontalIcon
} = icons;
const comps = useMemo(() => window.pano_getComponents(), []);
const {
AdminDashboardTab, AdminPanelsTab, AdminLogisticsTab, AdminClientsTab,
AdminInvoicesTab, AdminMessagesTab, AdminSettingsTab, AdminReglesTab, AdminCyberdefenseTab,
DashboardLayout, PanneauEditorForm, EntityEditorModal, PreviewModal,
ConfirmModal, GlobalChatModal, Button, Modal,
AdminModalsSystem, AdminModalsBackups
} = comps;
const panneaux = data.panneaux || [];
const interactions = data.interactions || [];
const unseenPanels = panneaux.filter(p =>
p.id !== 'demo-panneau' && p.status !== 'Brouillon' &&
((!p.admin_seen && p.status === 'Actif') || p.status === 'Attente validation')
).length;
const pendingShipping = panneaux.filter(p =>
p.physicalPanels > 0 && p.status !== 'Brouillon' && p.id !== 'demo-panneau' &&
['En attente de validation', 'Maquette refusée', 'En attente de commande au fournisseur', "En attente d'impression"].includes(p.shipping_status)
).length;
const unreadMessages = interactions.filter(m =>
m.isAlert === 2 || (!m.resolved && m.authorType !== 'Admin' && m.authorType !== 'Systeme' && m.authorType !== 'System')
).length;
const navItems = [
{ id: 'dashboard', label: 'Supervision', icon: },
{ id: 'panneaux', label: 'Parc de panneaux', icon: , badge: unseenPanels },
{ id: 'logistique', label: 'Logistique (A1)', icon: , badge: pendingShipping },
{ id: 'clients', label: 'Base clients', icon: },
{ id: 'factures', label: 'Facturation globale', icon: },
{ id: 'messages', label: 'Messagerie globale', icon: , badge: unreadMessages },
{ id: 'systeme', label: 'Paramètres système', icon: },
{ id: 'regles', label: 'Règles et Options', icon: },
{ id: 'cyberdefense', label: 'Cyberdéfense', icon: }
];
const handleLogout = async () => {
await safeFetch('auth/logout', { method: 'GET', silent: true });
window.location.href = '?';
};
// MOTEUR CENTRAL DE TÉLÉMÉTRIE
const fetchTelemetry = async (silent = false) => {
try {
const [diagRes, backRes, logRes] = await Promise.all([
safeFetch('system/diagnostics', { method: 'GET', silent: true }),
safeFetch('system/backups', { method: 'GET', silent: true }),
safeFetch('system/logs', { method: 'GET', silent: true })
]);
if (!isMounted.current) return;
if (diagRes && 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 queueStatus = diagRes.data.find(d => d.id === 'queue')?.status || 'success';
const dbVal = parseFloat(dbDetail);
const diskVal = parseFloat(diskDetail);
let diagStatus = 'emerald';
if (dbVal > 500 || diskVal < 1 || queueStatus === 'error') diagStatus = 'red';
else if (dbVal > 200 || diskVal < 5 || queueStatus === 'warning') diagStatus = 'amber';
else if (queueStatus === 'info') diagStatus = 'blue';
const backupsData = (backRes && backRes.data) ? backRes.data : [];
const backupsCount = backupsData.length;
let backupStatus = 'emerald';
let backupDesc = window.pano_formatPlural(backupsCount, "archive", "archives");
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) {
const delayDays = Math.floor(hoursSinceLast/24);
backupStatus = 'red';
backupDesc = `Critique : Retard de ${window.pano_formatPlural(delayDays, "jour", "jours")}`;
}
else if (hoursSinceLast > 24) {
backupStatus = 'amber';
backupDesc = `Attention : Retard de ${Math.floor(hoursSinceLast)}h`;
}
else {
backupStatus = 'emerald';
backupDesc = `À jour (${window.pano_formatPlural(backupsCount, "archive", "archives")})`;
}
}
const logsContent = (logRes && logRes.data?.logs) ? 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, queueStatus,
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) {}
};
// BOUCLE DE POLLING CENTRALISÉ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);
if (!isMounted.current) return;
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);
};
}, []);
const adminSettings = data.settings || {};
// CORRECTION: Validation souple des valeurs numériques ou string "0"/"1"
const isBillingComplete = !!(
adminSettings.billing_company?.trim() &&
adminSettings.billing_address?.trim() &&
adminSettings.billing_siret?.trim() &&
(adminSettings.billing_has_tva == 0 || adminSettings.billing_tva?.trim())
);
const isMaintenance = adminSettings.maintenance == 1;
const purchasesAllowed = adminSettings.allow_new_purchases != 0;
let sysStatusColor = 'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.8)]';
if (isMaintenance || !isBillingComplete || sysSummary.diagStatus === 'red' || sysSummary.backupStatus === 'red' || sysSummary.logStatus === 'red') {
sysStatusColor = 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.8)]';
} else if (!purchasesAllowed || sysSummary.diagStatus === 'amber' || sysSummary.backupStatus === 'amber' || sysSummary.logStatus === 'amber') {
sysStatusColor = 'bg-amber-500 shadow-[0_0_8px_rgba(245,158,11,0.8)]';
} else if (sysSummary.diagStatus === 'blue') {
sysStatusColor = 'bg-blue-500 shadow-[0_0_8px_rgba(59,130,246,0.8)]';
}
const sidebarFooter = (
openModal('diagnostics', null, false)} className="flex justify-between items-center bg-slate-800 hover:bg-slate-700 text-white rounded-xl p-3 cursor-pointer transition w-full group min-w-0" title="Diagnostics système et Maintenance">
État système
);
const handleCreateNewPanel = async () => {
setIsSaving(true);
let idReservé = null;
let maxTries = 5;
let serverData = {};
while (!idReservé && maxTries > 0) {
if (!isMounted.current) break;
const tempId = 'pan_' + window.pano_generateID().replace(/-/g, '').substring(0, 12);
try {
const d = await safeFetch('panneaux/reserve_id', { body: { id: tempId }, silent: true });
if (d && d.status === 'success') {
idReservé = tempId;
serverData = d.data || {};
}
} catch(e) {
if (window.pano_logFallback) window.pano_logFallback(`Erreur lors de la réservation ID: ${e.message}`);
}
maxTries--;
}
if (!isMounted.current) return;
setIsSaving(false);
if (idReservé) {
setManagingPanneau({
...serverData,
id: idReservé, name: '', location: '', permitNumber: '',
status: 'Réservé', offerType: 'rental', currentRate: data.prices?.rentalMo ?? 180,
physicalPanels: 0, hasNoAds: false, privateDocs: [], shippingAddress: '',
client_uid: '' // Côté admin, le client_uid doit être attribué via l'interface
});
setValidationErrors([]);
openModal('editor', idReservé, false);
} else {
if (window.pano_showToast) window.pano_showToast("Erreur lors de la réservation de l'identifiant. Veuillez réessayer.", "error");
}
};
const demoPanneau = panneaux.find(p => p.id === 'demo-panneau') || { id: 'demo-panneau', status: 'Actif', offerType: 'demo', name: 'Panneau de démonstration', location: 'Paris', themeColor: '#059669', hasNoAds: false };
const panneauCible = managingPanneau || (targetId ? (panneaux.find(p => p.id === targetId) || (targetId === 'demo-panneau' ? demoPanneau : null)) : null);
const currentTargetForPreview = activeModal === 'preview' || activeModal === 'preview_admin' ? targetId : (activeDialog === 'preview_draft' ? dialogId : targetId);
const previewCible = previewPanneau || (currentTargetForPreview ? (panneaux.find(p => p.id === currentTargetForPreview) || (currentTargetForPreview === 'demo-panneau' ? demoPanneau : null)) : null);
const saveEditedEntity = () => {
const p = panneauCible || {};
const currentData = p.draft_data && Object.keys(p.draft_data).length > 0 ? { ...p, ...p.draft_data } : p;
let newInter = [...(currentData.intervenants || [])];
let newLots = JSON.parse(JSON.stringify(currentData.lots || []));
const loc = editingEntity.location;
if (loc.type === 'intervenant') {
if (loc.index !== undefined) newInter[loc.index] = editingEntity.data;
else newInter.push({...editingEntity.data, id: crypto.randomUUID()});
} else if (loc.type === 'lot') {
if (loc.index !== undefined) newLots[loc.index] = {...newLots[loc.index], ...editingEntity.data};
else newLots.push({...editingEntity.data, id: crypto.randomUUID(), entreprises: []});
} else if (loc.type === 'entreprise') {
if (loc.index !== undefined) newLots[loc.lotIndex].entreprises[loc.index] = editingEntity.data;
else newLots[loc.lotIndex].entreprises.push({...editingEntity.data, id: crypto.randomUUID()});
}
const isDraftPanel = p.status === 'Brouillon' || p.status === 'Réservé';
if (!isDraftPanel) {
setManagingPanneau({ ...p, draft_data: { ...(p.draft_data || {}), intervenants: newInter, lots: newLots } });
} else {
setManagingPanneau({ ...p, intervenants: newInter, lots: newLots });
}
closeCurrentLayer();
};
const deleteEntity = (loc) => {
setConfirmConfig({
title: "Supprimer l'élément",
message: "Êtes-vous sûr de vouloir supprimer cet élément de l'équipe du chantier ?",
confirmText: "Supprimer",
type: 'error',
isDestructive: true,
onConfirm: () => {
const p = panneauCible || {};
const currentData = p.draft_data && Object.keys(p.draft_data).length > 0 ? { ...p, ...p.draft_data } : p;
let newInter = [...(currentData.intervenants || [])];
let newLots = JSON.parse(JSON.stringify(currentData.lots || []));
if (loc.type === 'intervenant') newInter.splice(loc.index, 1);
else if (loc.type === 'lot') newLots.splice(loc.index, 1);
else newLots[loc.lotIndex].entreprises.splice(loc.index, 1);
const isDraftPanel = p.status === 'Brouillon' || p.status === 'Réservé';
if (!isDraftPanel) {
setManagingPanneau({ ...p, draft_data: { ...(p.draft_data || {}), intervenants: newInter, lots: newLots } });
} else {
setManagingPanneau({ ...p, intervenants: newInter, lots: newLots });
}
closeCurrentLayer();
}
});
openDialog('confirm', null, true);
};
const handleDragStart = (e, location) => { setDraggedItem(location); e.dataTransfer.effectAllowed = 'move'; };
const handleDragOver = (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; };
const handleDrop = (e, dropLocation) => {
e.preventDefault();
if (!draggedItem || draggedItem.type !== dropLocation.type) return;
if (draggedItem.type === 'entreprise' && draggedItem.lotIndex !== dropLocation.lotIndex) return;
const p = panneauCible || {};
const currentData = p.draft_data && Object.keys(p.draft_data).length > 0 ? { ...p, ...p.draft_data } : p;
let newInter = [...(currentData.intervenants || [])];
let newLots = JSON.parse(JSON.stringify(currentData.lots || []));
if (draggedItem.type === 'intervenant') {
const item = newInter.splice(draggedItem.index, 1)[0];
newInter.splice(dropLocation.index, 0, item);
} else if (draggedItem.type === 'lot') {
const item = newLots.splice(draggedItem.index, 1)[0];
newLots.splice(dropLocation.index, 0, item);
} else if (draggedItem.type === 'entreprise') {
const arr = newLots[dropLocation.lotIndex].entreprises;
const item = arr.splice(draggedItem.index, 1)[0];
arr.splice(dropLocation.index, 0, item);
}
const isDraftPanel = p.status === 'Brouillon' || p.status === 'Réservé';
if (!isDraftPanel) {
setManagingPanneau({ ...p, draft_data: { ...(p.draft_data || {}), intervenants: newInter, lots: newLots } });
} else {
setManagingPanneau({ ...p, intervenants: newInter, lots: newLots });
}
setDraggedItem(null);
};
const validatePanel = (uiPrefs = {}) => {
const p = { ...(panneauCible || {}), ...(panneauCible?.draft_data || {}) };
if (p.id === 'demo-panneau' || p.offerType === 'demo') return [];
const errors = [];
if (!p.name?.trim()) errors.push("Le nom du chantier est manquant.");
if (!p.location?.trim()) errors.push("L'adresse du panneau est manquante.");
if (!uiPrefs.simp_opt_hide_legal && !p.pdfId) errors.push("L'arrêté légal (PDF) n'a pas été uploadé.");
return errors;
};
const handleSavePanneau = async (forceDraft = false, forceReactivate = false, uiPrefs = {}) => {
const pCible = panneauCible || {};
if (!pCible.id) return;
if (!forceDraft && (pCible.status === 'Actif' || forceReactivate)) {
const errs = validatePanel(uiPrefs);
if (errs.length > 0) {
setValidationErrors(errs);
setTimeout(() => {
const scrollEls = document.querySelectorAll('.custom-scrollbar');
scrollEls.forEach(el => el.scrollTo({ top: 0, behavior: 'smooth' }));
}, 50);
return;
}
}
let finalStatus = pCible.status;
if (finalStatus === 'Réservé') finalStatus = 'Brouillon';
let detailsToSave;
const original = data.panneaux.find(x => x.id === pCible.id) || (pCible.id === 'demo-panneau' ? demoPanneau : pCible);
if (forceReactivate || !forceDraft) {
finalStatus = 'Actif';
detailsToSave = { ...pCible, ...(pCible.draft_data || {}), draft_data: {} };
detailsToSave.name = detailsToSave.name?.trim() || 'Projet sans nom';
detailsToSave.location = detailsToSave.location?.trim() || '';
} else {
if (finalStatus === 'Brouillon') {
detailsToSave = { ...pCible, draft_data: {} };
detailsToSave.name = detailsToSave.name?.trim() || 'Brouillon sans nom';
detailsToSave.location = detailsToSave.location?.trim() || '';
} else {
const newDraftData = pCible.draft_data ? { ...pCible.draft_data } : {};
if (Object.keys(newDraftData).length > 0) {
newDraftData.name = newDraftData.name?.trim() || 'Projet sans nom';
newDraftData.location = newDraftData.location?.trim() || '';
}
detailsToSave = { ...original, draft_data: newDraftData };
}
}
setIsSaving(true);
// --- JUST-IN-TIME FETCH (JIT) POUR ÉRADIQUER LE CONFLIT D'ÉDITION ---
let freshOriginal = null;
if (pCible.id !== 'demo-panneau') {
try {
const dSync = await safeFetch('sync', { method: 'GET', silent: true });
if (dSync && dSync.data && dSync.data.panneaux) {
freshOriginal = dSync.data.panneaux.find(x => x.id === pCible.id);
}
} catch(e) {}
}
const originalToUse = freshOriginal || original;
// --- ANTI-CONFLIT D'ÉDITION : Fusion des métadonnées serveurs ultra-fraîches ---
if (originalToUse) {
Object.assign(detailsToSave, {
updated_at: originalToUse.updated_at,
shipping_status: originalToUse.shipping_status,
tracking_number: originalToUse.tracking_number,
tracking_link: originalToUse.tracking_link,
admin_seen: originalToUse.admin_seen
});
}
const payload = {
id: pCible.id, status: finalStatus, offerType: pCible.offerType, currentRate: pCible.currentRate, physicalPanels: pCible.physicalPanels,
details: detailsToSave
};
const d = await safeFetch('panneaux', {
body: payload,
setLoading: setIsSaving,
successMessage: forceDraft ? "Brouillon sauvegardé !" : "Panneau publié avec succès !"
});
if (!isMounted.current) return;
if (d) {
setManagingPanneau(null);
closeCurrentLayer();
refreshData();
}
};
const handleDiscardDraft = async () => {
const p = panneauCible || {};
if (!p.id) return;
const original = data.panneaux.find(x => x.id === p.id) || (p.id === 'demo-panneau' ? demoPanneau : undefined);
if (!original || !original.draft_data || Object.keys(original.draft_data).length === 0) {
setManagingPanneau(null);
closeCurrentLayer();
return;
}
const payload = {
id: original.id, status: original.status, offerType: original.offerType, currentRate: original.currentRate, physicalPanels: original.physicalPanels,
details: { ...original, draft_data: {} }
};
const d = await safeFetch('panneaux', {
body: payload,
setLoading: setIsSaving,
successMessage: "Modifications annulées. Version en ligne restaurée."
});
if (!isMounted.current) return;
if (d) {
setManagingPanneau(null);
closeCurrentLayer();
if (refreshData) refreshData();
}
};
const renderContent = () => {
const baseProps = {
data, refreshData, activeModal, activeDialog, targetId, dialogId, adminOpts, toggleDashboardPin, updateAdminOpts,
openLocalModal: openModal, openLocalDialog: openDialog, closeCurrentLayer, handleCreateNewPanel
};
switch(activeTab) {
case 'dashboard': return AdminDashboardTab && ;
case 'panneaux': return AdminPanelsTab && ;
case 'logistique': return AdminLogisticsTab && ;
case 'clients': return AdminClientsTab && ;
case 'factures': return AdminInvoicesTab && ;
case 'messages': return AdminMessagesTab && ;
case 'systeme': return AdminSettingsTab && ;
case 'regles': return AdminReglesTab && ;
case 'cyberdefense': return AdminCyberdefenseTab && ;
default: return AdminDashboardTab && ;
}
};
if (!DashboardLayout) return null;
return (
{renderContent()}
{GlobalChatModal && (
)}
{AdminModalsSystem && (
)}
{AdminModalsBackups && (
)}
{activeModal === 'editor' && panneauCible && PanneauEditorForm && (
p.id === panneauCible.id) || (panneauCible.id === 'demo-panneau' ? demoPanneau : undefined)}
onCancel={() => { setManagingPanneau(null); closeCurrentLayer(); }}
onSaveDraft={() => handleSavePanneau(true)}
onPublish={(uiPrefs) => {
const errs = validatePanel(uiPrefs);
if (errs.length > 0) {
setValidationErrors(errs);
setTimeout(() => {
const scrollEls = document.querySelectorAll('.custom-scrollbar');
scrollEls.forEach(el => el.scrollTo({ top: 0, behavior: 'smooth' }));
}, 50);
} else {
setValidationErrors([]);
handleSavePanneau(false, false, uiPrefs);
}
}}
onDiscardDraft={handleDiscardDraft}
onPreview={(draftPanel) => { setPreviewPanneau(draftPanel); openDialog('preview_draft', null, true); }}
onEditEntity={(loc, eData) => { setEditingEntity({location: loc, data: eData}); openDialog('entity_editor', null, true); }}
draggedItem={draggedItem}
handleDragStart={handleDragStart}
handleDragOver={handleDragOver}
handleDrop={handleDrop}
deleteEntity={deleteEntity}
isSaving={isSaving}
validationErrors={validationErrors}
currentUserRole="admin"
/>
)}
{activeDialog === 'entity_editor' && editingEntity && EntityEditorModal && (
)}
{(activeModal === 'preview' || activeModal === 'preview_admin' || activeDialog === 'preview_draft') && previewCible && PreviewModal && (
{ setPreviewPanneau(null); closeCurrentLayer(); }}
refreshData={refreshData}
isAdmin={true}
onValidate={async () => {
const d = await safeFetch('panneaux/mark_seen', { body: { id: previewCible.id }, successMessage: "Panneau validé." });
if (!isMounted.current) return;
if(d) { closeCurrentLayer(); refreshData(); }
}}
onContactClient={() => {
if (openChat) openChat('SUPPORT_' + (previewCible.client_uid || previewCible.clientName), false);
}}
onSuspend={() => {
openDialog('suspend', previewCible.id, true);
}}
onDelete={() => {
openDialog('delete_panel', previewCible.id, true);
}}
/>
)}
{activeDialog === 'confirm' && confirmConfig && ConfirmModal && }
);
};
/* EOF ========== [_react/admin/_admin.jsx] */