/**
* =========================================================================
* PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0
* Composants : Messagerie Interne Pro (Architecture Classique et Memo)
* Gère jusqu'à 20 pièces jointes par message.
* =========================================================================
*/
const { useState, useEffect, useRef, memo, useCallback } = React;
const { MessageSquare, ArrowUp, ArrowDown, Loader, AlertTriangle, Image, FileText, X, ArrowLeft, Download } = window;
// -------------------------------------------------------------------------
// UTILITAIRE PDF.JS DYNAMIQUE (Charge une seule fois la librairie)
// -------------------------------------------------------------------------
let pdfJsLoaded = false;
let pdfJsLoadingPromise = null;
const loadPdfJs = () => {
if (pdfJsLoaded) {
return Promise.resolve();
}
if (pdfJsLoadingPromise) {
return pdfJsLoadingPromise;
}
pdfJsLoadingPromise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js';
script.onload = () => {
window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
pdfJsLoaded = true;
resolve();
};
script.onerror = reject;
document.head.appendChild(script);
});
return pdfJsLoadingPromise;
};
// Composant miniature PDF (Avec File d'attente intelligente en arrière-plan)
const PdfThumbnail = memo(({ url }) => {
const containerRef = useRef(null);
const canvasRef = useRef(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [isVisible, setIsVisible] = useState(false);
// 1. Détection de visibilité et File d'attente d'arrière-plan
useEffect(() => {
let isMounted = true;
let observerDisposed = false;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
if (isMounted) setIsVisible(true);
observer.disconnect();
observerDisposed = true;
}
},
{ rootMargin: '300px' }
);
if (containerRef.current) {
observer.observe(containerRef.current);
}
// Gestionnaire de file d'attente pour charger les PDF hors-champ
if (!window.pdfIdleQueue) window.pdfIdleQueue = [];
const queueItem = () => {
if (isMounted && !observerDisposed) {
setIsVisible(true);
observer.disconnect();
observerDisposed = true;
}
};
window.pdfIdleQueue.push(queueItem);
// Démarre le processeur de fond s'il n'est pas actif
if (!window.pdfIdleProcessorStarted) {
window.pdfIdleProcessorStarted = true;
const processIdleQueue = () => {
if (window.pdfIdleQueue && window.pdfIdleQueue.length > 0) {
const task = window.pdfIdleQueue.shift();
task();
}
// Exécute 1 rendu caché toutes les 800ms pour ne pas surcharger le CPU
setTimeout(processIdleQueue, 800);
};
// Attendre 3 secondes (laisser la priorité absolue aux messages visibles)
setTimeout(processIdleQueue, 3000);
}
return () => {
isMounted = false;
observer.disconnect();
observerDisposed = true;
if (window.pdfIdleQueue) {
const idx = window.pdfIdleQueue.indexOf(queueItem);
if (idx > -1) window.pdfIdleQueue.splice(idx, 1);
}
};
}, []);
// 2. Rendu effectif du Canvas
useEffect(() => {
if (!isVisible) return;
let isMounted = true;
let renderTask = null;
const renderPage = async () => {
try {
await loadPdfJs();
if (!isMounted) return;
const loadingTask = window.pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1 });
const canvas = canvasRef.current;
if (!canvas || !isMounted) return;
const context = canvas.getContext('2d', { alpha: false });
const scale = 200 / viewport.width;
const scaledViewport = page.getViewport({ scale });
canvas.width = Math.floor(scaledViewport.width);
canvas.height = Math.floor(scaledViewport.height);
renderTask = page.render({ canvasContext: context, viewport: scaledViewport });
await renderTask.promise;
if (isMounted) {
setLoading(false);
}
} catch (e) {
if (isMounted) {
setError(true);
setLoading(false);
}
}
};
renderPage();
return () => {
isMounted = false;
if (renderTask) {
renderTask.cancel();
}
};
}, [url, isVisible]);
return (
{loading && !error && (
)}
{error && (
)}
);
});
// Composant rendu PDF complet (Multipage)
const PdfFullViewer = memo(({ url }) => {
const [pdf, setPdf] = useState(null);
const [error, setError] = useState(false);
useEffect(() => {
let isMounted = true;
loadPdfJs().then(() => {
if (!isMounted) return;
const loadingTask = window.pdfjsLib.getDocument(url);
loadingTask.promise.then(doc => {
if (isMounted) {
setPdf(doc);
}
}).catch(() => {
if (isMounted) {
setError(true);
}
});
}).catch(() => {
if (isMounted) {
setError(true);
}
});
return () => {
isMounted = false;
};
}, [url]);
if (error) {
return (
Impossible de charger le PDF.
);
}
if (!pdf) {
return (
Génération de l'aperçu...
);
}
return (
{Array.from(new Array(pdf.numPages), (el, index) => (
))}
);
});
const PdfPage = memo(({ pdf, pageNumber }) => {
const containerRef = useRef(null);
const canvasRef = useRef(null);
const [loading, setLoading] = useState(true);
const [isVisible, setIsVisible] = useState(false);
// File d'attente d'arrière-plan pour les pages du lecteur PDF
useEffect(() => {
let isMounted = true;
let observerDisposed = false;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
if (isMounted) setIsVisible(true);
observer.disconnect();
observerDisposed = true;
}
},
{ rootMargin: '500px' }
);
if (containerRef.current) {
observer.observe(containerRef.current);
}
if (!window.pdfPageIdleQueue) window.pdfPageIdleQueue = [];
const queueItem = () => {
if (isMounted && !observerDisposed) {
setIsVisible(true);
observer.disconnect();
observerDisposed = true;
}
};
window.pdfPageIdleQueue.push(queueItem);
if (!window.pdfPageProcessorStarted) {
window.pdfPageProcessorStarted = true;
const processIdleQueue = () => {
if (window.pdfPageIdleQueue && window.pdfPageIdleQueue.length > 0) {
const task = window.pdfPageIdleQueue.shift();
task();
}
setTimeout(processIdleQueue, 600); // Rend une page toutes les 600ms en cache
};
setTimeout(processIdleQueue, 2000);
}
return () => {
isMounted = false;
observer.disconnect();
observerDisposed = true;
if (window.pdfPageIdleQueue) {
const idx = window.pdfPageIdleQueue.indexOf(queueItem);
if (idx > -1) window.pdfPageIdleQueue.splice(idx, 1);
}
};
}, []);
useEffect(() => {
if (!isVisible) return;
let isMounted = true;
let renderTask = null;
pdf.getPage(pageNumber).then(page => {
if (!isMounted) return;
const baseViewport = page.getViewport({ scale: 1.0 });
const maxWidth = 768;
const pixelRatio = Math.min(window.devicePixelRatio || 1, 2);
const targetResolution = maxWidth * pixelRatio;
let scale = targetResolution / baseViewport.width;
if (scale > 2) scale = 2;
if (scale < 0.5) scale = 0.5;
const viewport = page.getViewport({ scale });
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext('2d', { alpha: false });
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
renderTask = page.render({ canvasContext: context, viewport });
renderTask.promise.then(() => {
if(isMounted) {
setLoading(false);
}
}).catch(() => {
// Erreur silencieuse
});
});
return () => {
isMounted = false;
if (renderTask) {
renderTask.cancel();
}
};
}, [pdf, pageNumber, isVisible]);
return (
);
});
// Modale d'avertissement de modération pour les Riverains
const ModerationModal = ({ word, onModify, onRequestControl }) => (
À cause de l'utilisation de l'expression "{word}" , vous devez modifier votre message.
Vous avez également la possibilité de faire contrôler votre message par le support technique avant envoi au client.
Modifier mon message
Envoyer pour contrôle
);
// Utilitaire de vérification de liste noire
const checkForBlacklist = (text, settings) => {
if (!settings || !settings.blacklist) {
return null;
}
const words = settings.blacklist.split(',')
.map(w => w.trim().toLowerCase())
.filter(w => w);
const lowerText = text.toLowerCase();
for (let w of words) {
if (lowerText.includes(w)) {
return w;
}
}
return null;
};
// -------------------------------------------------------------------------
// COMPOSANT 1 : BULLE DE MESSAGE (MÉMORISÉE POUR PERFORMANCES EXTRÊMES)
// -------------------------------------------------------------------------
const MessageBubble = memo(({ msg, isMe, displayAuthor }) => {
const [viewing, setViewing] = useState(null);
const attachments = [];
const regex = /\[ATTACHMENT:([a-zA-Z0-9_-]+):(image|pdf)\]/g;
let match;
while ((match = regex.exec(msg.detail || '')) !== null) {
attachments.push({ id: match[1], type: match[2] });
}
const textContent = (msg.detail || '').replace(regex, '').trim();
// CORRECTION XSS STORED : On utilise authorType au lieu de author
const isHtmlAllowed = msg.authorType === 'Admin' || msg.authorType === 'Système' || msg.authorType === 'Systeme';
return (
{msg.isAlert === 2 && (
)}
{textContent && (
isHtmlAllowed ? (
/g, '>').replace(/"/g, '"').replace(/'/g, ''')
}}
className="text-sm leading-relaxed whitespace-pre-wrap admin-html"
/>
) : (
{textContent}
)
)}
{attachments.length > 0 && (
{attachments.map((att, i) => (
{att.type === 'image' && (
setViewing(att)} className="cursor-pointer shrink-0">
)}
{att.type === 'pdf' && (
setViewing(att)} className={`cursor-pointer overflow-hidden rounded-xl border flex flex-col group text-left transition-transform hover:scale-[1.02] shrink-0 ${isMe ? 'border-white/20 bg-emerald-700/50' : 'border-slate-200 bg-white'} w-32`}>
Aperçu PDF
)}
))}
)}
{displayAuthor} • {new Date(String(msg.created_at || '').replace(' ', 'T')).toLocaleString('fr-FR')}
{msg.id.toString().startsWith('temp_') && }
{viewing && (
e.stopPropagation()}>
setViewing(null)} className="flex items-center gap-2 px-4 py-2 hover:bg-white/10 rounded-xl transition text-sm font-bold">
Retour
Télécharger
{viewing.type === 'image' && (
)}
{viewing.type === 'pdf' && (
)}
)}
);
}, (prevProps, nextProps) => {
return prevProps.msg.id === nextProps.msg.id && prevProps.msg.resolved === nextProps.msg.resolved && prevProps.msg.isAlert === nextProps.msg.isAlert;
});
// -------------------------------------------------------------------------
// COMPOSANT 2 : CONTENEUR DE TCHAT (ARCHITECTURE CLASSIQUE)
// -------------------------------------------------------------------------
const ChatBox = ({ messages, currentUserRole, panneauId, refreshData, onSend, clientName, settings = {} }) => {
const containerRef = useRef(null);
const textareaRef = useRef(null);
const [draft, setDraft] = useState('');
const [sending, setSending] = useState(false);
const [selectedFiles, setSelectedFiles] = useState([]);
const [fileError, setFileError] = useState('');
const [isAtBottom, setIsAtBottom] = useState(true);
const [isDragging, setIsDragging] = useState(false);
const [dragCounter, setDragCounter] = useState(0);
const [moderationWarning, setModerationWarning] = useState(null);
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = 'auto';
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
}
}, [draft]);
useEffect(() => {
const authorRole = currentUserRole === 'admin' ? 'Admin' : 'Client';
const unread = messages.filter(m => !m.resolved && m.author !== authorRole);
if (unread.length > 0 && currentUserRole !== 'public' && currentUserRole !== 'riverain') {
fetch(window.ECO_CONFIG.apiBaseUrl + 'interactions/read', {
method: 'POST',
body: JSON.stringify({ panneauId, author: authorRole })
}).then(() => {
if(refreshData) {
refreshData(true);
}
});
if (window.Notification && Notification.permission === 'granted' && localStorage.getItem('eco_push_enabled') === 'true') {
const latestMsgId = unread[0].id || unread[0].created_at;
const lastNotified = sessionStorage.getItem('last_notified_msg_' + panneauId);
if (lastNotified !== latestMsgId) {
new Notification("eco-panneau.fr", { body: "Nouveau message reçu.", icon: "/favicon.svg" });
sessionStorage.setItem('last_notified_msg_' + panneauId, latestMsgId);
}
}
}
}, [messages, panneauId, currentUserRole, refreshData]);
const handleScroll = useCallback(() => {
if (!containerRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
const isBottom = scrollHeight - scrollTop - clientHeight < 50;
setIsAtBottom(isBottom);
}, []);
const scrollToBottom = useCallback((smooth = true) => {
if (containerRef.current) {
containerRef.current.scrollTo({
top: containerRef.current.scrollHeight,
behavior: smooth ? 'smooth' : 'auto'
});
}
}, []);
useEffect(() => {
const timer = setTimeout(() => scrollToBottom(false), 100);
return () => clearTimeout(timer);
}, [scrollToBottom]);
useEffect(() => {
if (isAtBottom) {
scrollToBottom(true);
}
}, [messages.length, isAtBottom, scrollToBottom]);
const addFiles = (fileList) => {
setFileError('');
const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
const files = Array.from(fileList).filter(f => validTypes.includes(f.type));
if (files.length === 0) {
return;
}
if (selectedFiles.length + files.length > 20) {
setFileError("Maximum 20 fichiers autorisés par message.");
return;
}
const validFiles = [];
for (let f of files) {
if (f.size > 5 * 1024 * 1024) {
setFileError("Un ou plusieurs fichiers dépassent 5 Mo.");
return;
}
validFiles.push(f);
}
setSelectedFiles(prev => [...prev, ...validFiles]);
};
const handleFileChange = (e) => {
addFiles(e.target.files);
e.target.value = null;
};
const handlePaste = (e) => {
if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) {
addFiles(e.clipboardData.files);
}
};
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
setDragCounter(prev => prev + 1);
setIsDragging(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
setDragCounter(prev => {
const nc = prev - 1;
if (nc === 0) {
setIsDragging(false);
}
return nc;
});
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDragCounter(0);
setIsDragging(false);
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
addFiles(e.dataTransfer.files);
}
};
const removeFile = (index) => {
const newFiles = [...selectedFiles];
newFiles.splice(index, 1);
setSelectedFiles(newFiles);
};
const executeSend = async (isPendingControl = false) => {
setSending(true);
let finalDraft = draft.trim();
for (let file of selectedFiles) {
const fileType = file.type.includes('pdf') ? 'pdf' : 'image';
try {
const isPublic = currentUserRole === 'public' || currentUserRole === 'riverain';
const fileId = await window.uploadFile(file, fileType, null, false, isPublic);
finalDraft += `\n[ATTACHMENT:${fileId}:${fileType}]`;
} catch(err) {
setFileError("Erreur lors de l'upload : " + err.message);
setSending(false);
return;
}
}
if (onSend) {
await onSend(finalDraft, isPendingControl);
}
setDraft('');
setSelectedFiles([]);
setSending(false);
setModerationWarning(null);
setTimeout(() => scrollToBottom(true), 50);
};
const handleInitialSend = (e) => {
e.preventDefault();
if (!draft.trim() && selectedFiles.length === 0) {
return;
}
if (currentUserRole === 'public' || currentUserRole === 'riverain') {
const word = checkForBlacklist(draft, settings);
if (word) {
setModerationWarning(word);
return;
}
}
executeSend(false);
};
return (
{isDragging && (
Déposez vos fichiers ici
Images et PDF acceptés (max 20)
)}
{moderationWarning && (
setModerationWarning(null)}
onRequestControl={() => executeSend(true)}
/>
)}
{messages.map((msg) => {
const isMe = (currentUserRole === 'admin' && msg.author === 'Admin') ||
(currentUserRole === 'client' && msg.author === 'Client') ||
(currentUserRole === 'public' && msg.author !== 'Admin' && msg.author !== 'Client' && msg.author !== 'Système' && msg.author !== 'Systeme');
let displayAuthor = msg.author;
if (panneauId === 'CONTACT_PUBLIC') {
if (msg.author === 'Admin') {
displayAuthor = 'Service client';
} else if (msg.author === 'Système' || msg.author === 'Systeme') {
displayAuthor = 'Système';
} else {
displayAuthor = `Visiteur ${msg.author}`;
}
} else {
if (msg.author === 'Admin') {
displayAuthor = 'Support';
} else if (msg.author === 'Client') {
displayAuthor = clientName || 'Client';
} else if (msg.author === 'Système' || msg.author === 'Systeme') {
displayAuthor = 'Système';
} else {
displayAuthor = 'Riverain';
}
}
return (
);
})}
{messages.length === 0 && (
Aucun message.
)}
{!isAtBottom && messages.length > 0 && (
scrollToBottom(true)}
className="bg-slate-800/90 backdrop-blur text-white px-4 py-2 rounded-full shadow-lg text-xs font-bold pointer-events-auto hover:bg-slate-900 transition flex items-center gap-2 animate-in fade-in slide-in-from-bottom-2"
>
Nouveaux messages
)}
{fileError && (
{fileError}
)}
{selectedFiles.length > 0 && (
{selectedFiles.map((f, i) => (
{f.type.includes('pdf') ? : }
{f.name}
removeFile(i)} className="text-red-500 hover:text-red-700 bg-red-50 p-1 rounded-md transition">
))}
)}
);
};
// -------------------------------------------------------------------------
// COMPOSANT 3 : FORMULAIRE DE PREMIER CONTACT (RIVERAIN)
// -------------------------------------------------------------------------
const PublicContactForm = ({ panneauId, showToast, onSuccess, isPreview, hideAlert = false, hideAttachments = false, settings = {} }) => {
const [submitting, setSubmitting] = useState(false);
const [isAlert, setIsAlert] = useState(false);
const [mountTime] = useState(Math.floor(Date.now() / 1000));
const [selectedFiles, setSelectedFiles] = useState([]);
const [message, setMessage] = useState('');
const [isDragging, setIsDragging] = useState(false);
const [dragCounter, setDragCounter] = useState(0);
const [moderationWarning, setModerationWarning] = useState(null);
const textareaRef = useRef(null);
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = '';
const scrollH = textareaRef.current.scrollHeight;
const clientH = textareaRef.current.clientHeight;
if (scrollH > clientH) {
textareaRef.current.style.height = `${scrollH}px`;
}
}
}, [message]);
const addFiles = (fileList) => {
const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
const files = Array.from(fileList).filter(f => validTypes.includes(f.type));
if (files.length === 0) {
return;
}
if (selectedFiles.length + files.length > 20) {
if (showToast) {
showToast("Maximum 20 fichiers autorisés.", "error");
}
return;
}
const validFiles = [];
for (let f of files) {
if (f.size > 5 * 1024 * 1024) {
if (showToast) {
showToast("Un ou plusieurs fichiers dépassent 5 Mo.", "error");
}
return;
}
validFiles.push(f);
}
setSelectedFiles(prev => [...prev, ...validFiles]);
};
const handleFileChange = (e) => {
addFiles(e.target.files);
e.target.value = null;
};
const handlePaste = (e) => {
if (!hideAttachments && e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) {
addFiles(e.clipboardData.files);
}
};
const handleDragEnter = (e) => {
if (hideAttachments) return;
e.preventDefault();
e.stopPropagation();
setDragCounter(prev => prev + 1);
setIsDragging(true);
};
const handleDragLeave = (e) => {
if (hideAttachments) return;
e.preventDefault();
e.stopPropagation();
setDragCounter(prev => {
const nc = prev - 1;
if (nc === 0) {
setIsDragging(false);
}
return nc;
});
};
const handleDragOver = (e) => {
if (hideAttachments) return;
e.preventDefault();
e.stopPropagation();
};
const handleDrop = (e) => {
if (hideAttachments) return;
e.preventDefault();
e.stopPropagation();
setDragCounter(0);
setIsDragging(false);
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
addFiles(e.dataTransfer.files);
}
};
const removeFile = (index) => {
const newFiles = [...selectedFiles];
newFiles.splice(index, 1);
setSelectedFiles(newFiles);
};
const executeSubmit = async (authorEmail, isPendingControl = false) => {
setSubmitting(true);
let finalDetail = message.trim();
if (!hideAttachments) {
for (let file of selectedFiles) {
const fileType = file.type.includes('pdf') ? 'pdf' : 'image';
try {
const fileId = await window.uploadFile(file, fileType, null, false, true);
finalDetail += `\n[ATTACHMENT:${fileId}:${fileType}]`;
} catch(err) {
if (showToast) {
showToast("Erreur lors de l'upload : " + err.message, 'error');
}
setSubmitting(false);
return;
}
}
}
const data = {
panneauId,
detail: finalDetail,
author: authorEmail,
isAlert: isPendingControl ? 2 : (isAlert ? 1 : 0),
hp_text: document.querySelector('input[name="hp_text"]')?.value || '',
hp_time: mountTime
};
try {
await fetch(window.ECO_CONFIG.apiBaseUrl + 'interactions', {
method: 'POST',
body: JSON.stringify(data)
});
if (showToast) {
showToast("Message envoyé. Le responsable vous répondra par e-mail.", "success");
}
setMessage('');
setIsAlert(false);
setSelectedFiles([]);
setModerationWarning(null);
if (onSuccess) {
onSuccess();
}
} catch(err) {
if (showToast) {
showToast("Erreur lors de l'envoi", "error");
}
}
setSubmitting(false);
};
const handleInitialSubmit = (e) => {
e.preventDefault();
if (isPreview) {
if (showToast) {
showToast("Ceci est un aperçu interactif. La messagerie n'est pas fonctionnelle ici.", "info");
}
return;
}
const word = checkForBlacklist(message, settings);
if (word) {
setModerationWarning(word);
return;
}
executeSubmit(e.target.email.value, false);
};
return (
{isDragging && !hideAttachments && (
Relâchez vos fichiers ici
)}
{moderationWarning && (
setModerationWarning(null)}
onRequestControl={() => {
const email = document.querySelector('input[name="email"]')?.value || '';
executeSubmit(email, true);
}}
/>
)}
{hideAttachments && selectedFiles.length > 0 && (
{selectedFiles.map((f, i) => (
{f.type.includes('pdf') ? : }
{f.name}
removeFile(i)} disabled={submitting} className="text-red-500 hover:text-red-700 bg-red-50 p-1.5 rounded-lg transition disabled:opacity-50">
))}
)}
setMessage(e.target.value)}
onPaste={handlePaste}
onInput={(e) => {
e.target.style.height = 'auto';
e.target.style.height = (e.target.scrollHeight) + 'px';
}}
required={hideAttachments || selectedFiles.length === 0}
placeholder="Votre message... (coller ou glisser des images est accepté)"
className="w-full border-2 rounded-xl p-3 text-sm outline-none transition resize-none flex-1 border-slate-200 focus:border-slate-800 overflow-hidden min-h-[120px]"
>
{!hideAttachments && selectedFiles.length > 0 && (
{selectedFiles.map((f, i) => (
{f.type.includes('pdf') ? : }
{f.name}
removeFile(i)} disabled={submitting} className="text-red-500 hover:text-red-700 bg-red-50 p-1.5 rounded-lg transition disabled:opacity-50">
))}
)}
{!hideAlert && (
setIsAlert(e.target.checked)}
className="w-4 h-4 accent-red-500 shrink-0"
/>
Signaler une urgence (nuisance grave, danger)
)}
);
};
// Injection du module RiverainContactForm avec le message de prévention
const _PublicContactFormWrapper = window.PublicContactForm;
window.PublicContactForm = (props) => {
const [isAlertLocal, setIsAlertLocal] = useState(false);
// Interception pour capturer l'état "isAlert" depuis le DOM
useEffect(() => {
const checkAlert = () => {
const checkbox = document.querySelector('input[type="checkbox"]');
if (checkbox && checkbox.checked !== isAlertLocal) {
setIsAlertLocal(checkbox.checked);
}
};
document.addEventListener('change', checkAlert);
return () => document.removeEventListener('change', checkAlert);
}, [isAlertLocal]);
return (
<_PublicContactFormWrapper {...props} />
{isAlertLocal && !props.hideAlert && (
Attention sécurité
Ce formulaire ne contacte que le chef de chantier de manière asynchrone. En cas d'accident grave, d'incendie ou de danger immédiat pour les personnes, veuillez contacter les services de secours (112, 15, 17, 18).
)}
);
};
// EXPORT GLOBAL AJOUTÉ : PdfFullViewer
Object.assign(window, { ChatBox, PublicContactForm, PdfFullViewer });
/* EOF ===== [_socle_messagerie.jsx] =============== */