// 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 && (
(
<>
>
)}
>
)}
);
};
/* EOF ========== [_react/admin/_admin_messages.jsx] */