// 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] */