// ECO-PANNEAU.FR - __react/socle/_socle_messagerie.jsx const { useState, useEffect, useCallback, useRef } = React; // 1. - Layout de messagerie (Vue liste) window.pano_ChatLayout = ({ children }) => { return (
{children}
); }; // 2. - Modale de messagerie globale (Flottante / Zéro-Trust) window.pano_GlobalChatModal = ({ data, myClientData, refreshData, currentUserRole, openLocalDialog, openLocalModal, setPanelTeamModal, setManagingPanneau, isLockedForClient }) => { const [forceRender, setForceRender] = useState(0); const [isSaving, setIsSaving] = useState(false); const [confirmDialog, setConfirmDialog] = useState(null); // ZÉRO-DETTE : Utilisation de notre Hook abstrait et d'une référence pour les timers const { isMounted, safeFetch } = window.pano_useSafeFetch(); const timerRef = useRef(null); // Lecture via le Routeur Zéro-Dette const { closeCurrentLayer, activeChat, changeTab, openModal } = window.pano_useUrlModal(); useEffect(() => { const handlePop = () => setForceRender(f => f + 1); window.addEventListener('popstate', handlePop); // ZÉRO-DETTE : Nettoyage des événements ET des timers asynchrones lors du démontage return () => { window.removeEventListener('popstate', handlePop); if (timerRef.current) clearTimeout(timerRef.current); }; }, []); // CORRECTION ARCHITECTURE (Erreur #310) : Les Hooks doivent toujours être déclarés avant tout "return" prématuré. const handleChatAction = useCallback((actionType, payload) => { if (actionType === 'correct_panel') { closeCurrentLayer(); // On ferme proprement le Chat // On laisse le temps à la modale de se fermer visuellement (Timer sécurisé) timerRef.current = setTimeout(() => { changeTab('panneaux'); // On bascule sur l'onglet Panneaux const p = (data.panneaux || []).find(x => x.name === payload); if (p) { // On ouvre l'éditeur ciblé timerRef.current = setTimeout(() => openModal('editor', p.id, false), 300); } }, 100); } }, [data.panneaux, closeCurrentLayer, changeTab, openModal]); // SÉCURITÉ : La modale s'affiche UNIQUEMENT si l'état actif du routeur correspond const requestedChatId = activeChat; if (!requestedChatId) return null; const { Modal, ChatBox, ConfirmModal, Button } = window.pano_getComponents(); const { UsersIcon, Trash2Icon, Share2Icon, PinIcon, KeyRoundIcon, ShieldAlertIcon, ShieldIcon } = window.pano_getIcons(); // 2.1 - Construction dynamique de l'arbre des conversations let threads = []; if (currentUserRole === 'admin') { threads = window.pano_buildAdminChatThreads ? window.pano_buildAdminChatThreads(data.interactions || [], data.panneaux || [], data.clients || []) : []; const existingThread = threads.find(t => t.id === requestedChatId); if (!existingThread) { if (requestedChatId.startsWith('SUPPORT_')) { const cUid = requestedChatId.replace('SUPPORT_', ''); const client = (data.clients || []).find(c => c.id === cUid); threads.unshift({ id: requestedChatId, panneauId: requestedChatId, title: `Support : ${client ? (client.company || client.name || client.full_name || client.email) : cUid}`, targetEmail: 'Client', messages: [], unread: 0, type: 'support', panneauData: null }); } } } else { const { pendingInvites, visiblePanels } = window.pano_getPanelAccessRights ? window.pano_getPanelAccessRights(data.panneaux || [], myClientData?.id, myClientData?.email_hash) : { pendingInvites:[], visiblePanels:[] }; threads = window.pano_buildClientChatThreads ? window.pano_buildClientChatThreads(data.interactions || [], myClientData, visiblePanels, pendingInvites) : []; if (isLockedForClient) { threads = threads.filter(t => t.type === 'support'); } // Création virtuelle des conversations vides (Groupes et Support) const existingThread = threads.find(t => t.id === requestedChatId); if (!existingThread && !isLockedForClient) { if (requestedChatId.startsWith('GROUP_')) { const pId = requestedChatId.replace('GROUP_', ''); const pData = visiblePanels.find(p => p.id === pId) || pendingInvites.find(p => p.id === pId); if (pData) { threads.unshift({ id: requestedChatId, panneauId: pData.id, title: pData.name ? `Équipe Projet : ${pData.name}` : `Équipe Projet`, targetEmail: 'Groupe', messages: [], unread: 0, type: 'group', panneauData: pData }); } } else if (requestedChatId.startsWith('SUPPORT_')) { threads.unshift({ id: requestedChatId, panneauId: requestedChatId, title: `Support Technique`, targetEmail: 'eco-panneau.fr', messages: [], unread: 0, type: 'support', panneauData: null }); } } } const selectedThread = threads.find(t => t.id === requestedChatId); if (!selectedThread) return null; // OPTIMISTIC UI : Les messages affichés proviennent directement de la source fiable const displayedMessages = selectedThread.messages; // 2.2 - Gestion des actions (Envoi, Suppression, Navigation) const closeChat = (e) => { if (e && e.preventDefault) e.preventDefault(); closeCurrentLayer(); if (refreshData) refreshData(); }; const handleSend = async (text) => { const isGroup = selectedThread.type === 'group'; const targetMail = isGroup ? 'Groupe' : selectedThread.targetEmail; const authorType = currentUserRole === 'admin' ? 'Admin' : 'Client'; const payload = { panneauId: currentUserRole === 'client' && (selectedThread.type === 'support' || selectedThread.type === 'group') ? selectedThread.id : selectedThread.panneauId, detail: text, author: authorType, targetEmail: targetMail, type: isGroup ? 'group' : 'message' }; const tempId = 'temp_' + Date.now(); const fakeMsg = { id: tempId, panneauId: payload.panneauId, detail: text, author: authorType, authorType: authorType, targetEmail: targetMail, type: payload.type, created_at: new Date().toISOString().replace('T', ' ').substring(0, 19), resolved: 0, isAlert: 0 }; window.dispatchEvent(new CustomEvent('optimistic_message', { detail: fakeMsg })); const d = await safeFetch('interactions', { body: payload }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit // CORRECTION MAJEURE: Renommage du message temporaire avec son véritable ID en base de données if (d && d.status === 'success' && d.data && d.data.id) { window.dispatchEvent(new CustomEvent('update_optimistic_message', { detail: { tempId: tempId, realId: d.data.id, created_at: d.data.created_at } })); } else { window.dispatchEvent(new CustomEvent('clear_optimistic_message', { detail: { id: tempId } })); } if (d && refreshData) refreshData(true); }; const handleDeleteThread = () => { setConfirmDialog({ title: "Effacer la conversation", message: currentUserRole === 'admin' ? "Attention : Cette action est irréversible. Tout l'historique de cette conversation sera définitivement supprimé." : "Êtes-vous sûr de vouloir supprimer définitivement tout l'historique de cette conversation avec le support technique ?", confirmText: "Oui, effacer", isDestructive: true, onConfirm: async () => { setConfirmDialog(null); const payload = { thread_id: selectedThread.id, panneau_id: selectedThread.panneauId, target_email: selectedThread.targetEmail, type: selectedThread.type }; const d = await safeFetch('interactions/delete_thread', { body: payload, setLoading: setIsSaving, successMessage: "Conversation effacée avec succès." }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d) { if (refreshData) refreshData(); closeChat(); } }, onCancel: () => setConfirmDialog(null) }); }; // 2.3 - Gestion de l'épinglage persistant let pinnedThreads = []; if (currentUserRole === 'client') { try { if (myClientData?.uiMode) { const parsed = JSON.parse(myClientData.uiMode); if (parsed && typeof parsed === 'object') { pinnedThreads = parsed.pinned_threads || []; } } } catch(e) {} } const threadIsPinned = pinnedThreads.includes(selectedThread.id); const togglePinThread = async () => { if (currentUserRole !== 'client') return; let newPinned = [...pinnedThreads]; if (threadIsPinned) { newPinned = newPinned.filter(id => id !== selectedThread.id); } else { newPinned.push(selectedThread.id); } let currentOpts = {}; try { if (myClientData?.uiMode) currentOpts = JSON.parse(myClientData.uiMode); } catch(e) {} if (typeof currentOpts !== 'object' || currentOpts === null) { currentOpts = {}; } currentOpts.pinned_threads = newPinned; const d = await safeFetch('clients/profile/update', { body: { ...myClientData, uiMode: JSON.stringify(currentOpts) } }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d && refreshData) refreshData(); }; // 2.4 - Configuration des en-têtes et des droits let rightHeaderExtras = null; let isReadOnly = false; let clientDisplayName = "Support"; if (currentUserRole === 'client') { clientDisplayName = myClientData?.full_name ? `${myClientData.full_name} (${myClientData.name})` : myClientData?.name; const isMe = (uid) => uid === myClientData?.id || uid === myClientData?.email_hash; const pData = selectedThread.type === 'group' ? selectedThread.panneauData : (data.panneaux || []).find(p => p.id === selectedThread.panneauId); const isOwner = pData ? pData.client_uid === myClientData.id : true; const myCollab = !isOwner && pData ? pData.collaborators?.find(c => isMe(c.uid)) : null; if (selectedThread.type === 'group') { isReadOnly = myCollab ? myCollab.status !== 'accepted' : false; } else if (selectedThread.type === 'riverain') { isReadOnly = myCollab ? myCollab.rights?.chat_access !== 'rw' : false; } else { isReadOnly = false; } const canEdit = isOwner || myCollab?.rights?.can_edit; const pinButton = ( ); if (selectedThread.type === 'group' && pData) { const ownerName = isOwner ? (myClientData.full_name || myClientData.name || 'Propriétaire') : (myCollab?.inviter_name || 'Propriétaire'); const ownerCompany = isOwner ? (myClientData.name || 'Société') : (myCollab?.inviter_company || 'Société'); rightHeaderExtras = (
{pinButton} {ownerName} ({ownerCompany}) - Propriétaire {pData.collaborators?.filter(c => c.status === 'accepted').map((c, i) => { const n = c.name || c.company || 'Inconnu'; const comp = c.company || 'Société'; return ( {n} ({comp}) ); })} {canEdit && ( )}
); } else if (selectedThread.type === 'support') { rightHeaderExtras = (
{pinButton}
); } else { rightHeaderExtras = (
{pinButton}
); } } else if (currentUserRole === 'admin') { if (selectedThread.id === 'MODERATION_QUEUE') { isReadOnly = true; } else { let targetClient = null; if (selectedThread.type === 'support') { const cUid = selectedThread.id.replace('SUPPORT_', ''); targetClient = (data.clients || []).find(c => c.id === cUid); } rightHeaderExtras = (
{targetClient && ( (targetClient.admin_access_until && new Date(String(targetClient.admin_access_until || '').replace(' ', 'T')) > new Date()) ? ( <> ) )}
); } } // 2.5 - Rendu UI return ( <>
{rightHeaderExtras && (
{rightHeaderExtras}
)}
{ChatBox && ( )}
{confirmDialog && ConfirmModal && ( setConfirmDialog(null)} /> )} ); }; /* EOF ========== [__react/socle/_socle_messagerie.jsx] */