// ECO-PANNEAU.FR - _react/admin/_admin_messages.jsx // 1. - LOGIQUE MÉTIER : GÉNÉRATION DES CONVERSATIONS ADMIN window.pano_buildAdminChatThreads = (interactions, panneaux, clients) => { const threadsMap = {}; const moderationMessages = []; interactions.forEach(m => { if (m.isAlert === 2) { moderationMessages.push(m); return; } let threadId; let title = "Conversation"; const isAdminOrSystem = m.authorType === 'Admin' || m.authorType === 'Système' || m.authorType === 'Systeme' || m.authorType === 'System'; let targetEmail = isAdminOrSystem ? m.target : m.author; let type = 'contact'; let unread = (!m.resolved && !isAdminOrSystem) ? 1 : 0; if (m.panneauId === 'CONTACT_PUBLIC') { const visitorEmail = isAdminOrSystem ? m.target : m.author; threadId = `CONTACT_PUBLIC_${visitorEmail}`; title = `Contact Site : ${visitorEmail}`; targetEmail = visitorEmail; type = 'contact'; } else if (m.panneauId.startsWith('SUPPORT_')) { threadId = m.panneauId; const cUid = m.panneauId.split('_')[1]; const c = clients.find(cl => cl.id === cUid); title = `Support${c ? ' : ' + (c.company || c.name || c.full_name || c.email) : ''}`; targetEmail = c ? c.email : ''; type = 'support'; } else { return; } if (!threadsMap[threadId]) { threadsMap[threadId] = { id: threadId, panneauId: m.panneauId, title, targetEmail, type, messages: [], unread: 0, panneauData: null }; } threadsMap[threadId].messages.push(m); threadsMap[threadId].unread += unread; }); if (moderationMessages.length > 0) { threadsMap['MODERATION_QUEUE'] = { id: 'MODERATION_QUEUE', panneauId: 'MODERATION_QUEUE', title: 'Modération en attente', targetEmail: 'Système d\'alerte', type: 'contact', messages: moderationMessages, unread: moderationMessages.length, panneauData: null }; } return Object.values(threadsMap).sort((a, b) => { if (a.id === 'MODERATION_QUEUE') return -1; if (b.id === 'MODERATION_QUEUE') return 1; const dateA = a.messages.length > 0 ? new Date(String(a.messages[a.messages.length - 1].created_at || '').replace(' ', 'T')).getTime() : 0; const dateB = b.messages.length > 0 ? new Date(String(b.messages[b.messages.length - 1].created_at || '').replace(' ', 'T')).getTime() : 0; return dateB - dateA; }); }; // 2. - COMPOSANT CARTE ISOLÉ ET RÉUTILISABLE (ZÉRO-DETTE) window.pano_AdminMessageCard = ({ thread: t, isPinned, toggleDashboardPin, openChat }) => { const { MessageSquareIcon, MessageCircleIcon, MailIcon, AlertTriangleIcon, PinIcon } = window.pano_getIcons(); const { IconBadge, NotificationBadge, DataCard } = window.pano_getComponents(); const getThreadIcon = (type, threadId) => { if (threadId === 'MODERATION_QUEUE') return AlertTriangleIcon; switch(type) { case 'support': return MessageSquareIcon; case 'contact': return MailIcon; default: return MessageCircleIcon; } }; const getThreadVariant = (type, threadId) => { if (threadId === 'MODERATION_QUEUE') return 'danger'; switch(type) { case 'support': return 'success'; case 'contact': return 'warning'; default: return 'secondary'; } }; const getThreadTitleClass = (type, threadId) => { if (threadId === 'MODERATION_QUEUE') return 'font-black text-amber-700'; if (type === 'support') return 'font-black text-emerald-700'; if (type === 'contact') return 'font-black text-amber-700'; return 'font-bold text-slate-800'; }; const lastMsg = t.messages.length > 0 ? t.messages[t.messages.length - 1] : null; const isModQueue = t.id === 'MODERATION_QUEUE'; return ( openChat(t.id, e)} className="group gap-2 relative cursor-pointer p-3 transition-colors hover:border-emerald-300 min-w-0" > {/* ÉPINGLE VOLANTE ET INCLINÉE (INDIGO POUR LES MESSAGES) */} {!isModQueue && (
{ e.stopPropagation(); toggleDashboardPin && toggleDashboardPin('threads', t.id, e); }} className={`absolute top-2 right-2 z-20 p-1 cursor-pointer transition-all duration-200 ${isPinned ? 'text-indigo-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"} >
)}
{IconBadge && ( )}

{t.title}

{t.targetEmail &&

{t.targetEmail}

}
0 ? 'text-slate-800 font-bold' : 'text-slate-600'}`}> {isModQueue ? {window.pano_formatPlural(t.unread, "Cliquez pour contrôler 1 message suspect.", `Cliquez pour contrôler ${t.unread} messages suspects.`)} : (lastMsg ? lastMsg.detail.replace(/\[ATTACHMENT:[^\]]+\]/g, '[Pièce jointe]').replace(/<[^>]*>?/gm, '') : Nouvelle conversation...) }
{isModQueue ? 'Urgent' : t.type} {lastMsg ? new Date(String(lastMsg.created_at || '').replace(' ', 'T')).toLocaleString('fr-FR') : ''}
); }; // 3. - COMPOSANT PRINCIPAL : MESSAGERIE ADMIN window.pano_AdminMessagesTab = ({ data, refreshData, showToast, adminOpts, toggleDashboardPin }) => { const { useState } = React; // SÉCURITÉ ANTI-FUITE DE MÉMOIRE : Utilisation du Hook Global Zéro-Dette const { isMounted, safeFetch } = window.pano_useSafeFetch(); // 3.1 - États et Routage Zéro-Dette const [isSaving, setIsSaving] = useState(false); const urlModal = window.pano_useUrlModal ? window.pano_useUrlModal() : {}; const routerActiveDialog = urlModal.activeDialog; const routerOpenDialog = urlModal.openDialog; const routerCloseLayer = urlModal.closeCurrentLayer; const [broadcastSubject, setBroadcastSubject] = useState("Message du support technique."); const [broadcastMessage, setBroadcastMessage] = useState(""); const [adminPassword, setAdminPassword] = useState(""); // 3.2 - Composants et Icônes const { MessageSquareIcon, AlertTriangleIcon, SendIcon, LoaderIcon, PinIcon } = window.pano_getIcons(); const { Button, Modal, FormInput, FormTextarea, CardGrid, SearchBar, EmptySearch, PaginationFooter, AdminMessageCard } = window.pano_getComponents(); // 3.3 - Logique de recherche et pagination const panneaux = data.panneaux || []; const clients = data.clients || []; const interactions = data.interactions || []; const threads = window.pano_buildAdminChatThreads(interactions, panneaux, clients); const { searchQuery, setSearchQuery, visibleCount, setVisibleCount, filteredData } = window.pano_useSearchAndPagination(threads, (t, q) => { const n = window.pano_normalizeString; const statusStr = n(t.unread > 0 ? "non lu urgent " : "lu ") + n(t.id === 'MODERATION_QUEUE' ? "moderation alerte" : ""); return n(t.title).includes(q) || n(t.targetEmail).includes(q) || n(t.type).includes(q) || statusStr.includes(q); }); // 3.4 - Actions const openChat = (id, e) => { if (e) { e.preventDefault(); e.stopPropagation(); } urlModal.openChat(id, false); if (refreshData) refreshData(); }; const handleBroadcastSubmit = async (e) => { e.preventDefault(); if (!broadcastSubject.trim() || !broadcastMessage.trim() || !adminPassword) return; const d = await safeFetch('admin/broadcast', { body: { subject: broadcastSubject, message: broadcastMessage, password: adminPassword }, setLoading: setIsSaving, successMessage: "Alerte globale diffusée avec succès !" }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d) { setBroadcastSubject("Message du support technique."); setBroadcastMessage(''); setAdminPassword(''); routerCloseLayer(); if (refreshData) refreshData(); } }; const pinnedIds = adminOpts?.threads || []; const attentionThreads = []; const pinnedThreads = []; const otherThreads = []; // CLASSIFICATION : URGENCE > FAVORIS > RESTE filteredData.forEach(t => { const isPinned = pinnedIds.includes(t.id); if (t.unread > 0 || t.id === 'MODERATION_QUEUE') { attentionThreads.push(t); } else if (isPinned) { pinnedThreads.push(t); } else { otherThreads.push(t); } }); const displayedOtherThreads = otherThreads.slice(0, visibleCount); // 4. - RENDU UI return (
{/* 4.1 - En-tête */}

Messagerie Globale

Vue globale du support et du contact public.

{threads.length > 0 && SearchBar && (
)} {threads.length === 0 && (
)}
{/* 4.2 - Liste des conversations */} {threads.length === 0 ? (

Aucune conversation détectée sur la plateforme.

) : filteredData.length === 0 ? ( EmptySearch && ) : ( <> {/* A. PRIORITÉ */} {attentionThreads.length > 0 && (

Modération et Non lus

{attentionThreads.map(t => )}
)} {/* B. FAVORIS */} {pinnedThreads.length > 0 && (

Favoris (Épinglés)

{pinnedThreads.map(t => )}
)} {/* C. LE RESTE */} {otherThreads.length > 0 && (

Historique des conversations

{displayedOtherThreads.map(t => )} {PaginationFooter && ( )}
)} )} {/* 4.3 - Modale : Diffusion d'alerte globale (Broadcast) */} {routerActiveDialog === 'broadcast' && Modal && ( ( <> )} >
{FormInput && ( setBroadcastSubject(e.target.value)} placeholder="Ex: Mise à jour importante de la plateforme..." required disabled={isSaving} className="min-w-0 w-full" /> )} {FormTextarea && ( setBroadcastMessage(e.target.value)} placeholder="Saisissez votre annonce générale..." rows={6} required disabled={isSaving} className="min-w-0 w-full" /> )}
Attention : Ce message sera envoyé par e-mail simultanément à tous les clients inscrits.
{FormInput && ( setAdminPassword(e.target.value)} required disabled={isSaving} className="min-w-0 w-full" /> )}
)}
); }; /* EOF ========== [_react/admin/_admin_messages.jsx] */