// ECO-PANNEAU.FR - _react/guest/_guest_view.jsx // 1. - COMPOSANT PRINCIPAL : ACCÈS INVITÉ (NO-FRICTION) window.pano_GuestView = ({ token, showToast }) => { const { useState, useEffect } = React; // ZÉRO-DETTE : Utilisation de notre Hook abstrait ! const { isMounted, safeFetch } = window.pano_useSafeFetch(); // États globaux const [viewState, setViewState] = useState('loading'); // loading, pending, accepted, rejected, error const [guestData, setGuestData] = useState(null); const [panneau, setPanneau] = useState(null); const [errorMsg, setErrorMsg] = useState(null); // États UI (Modales et actions) const [isSaving, setIsSaving] = useState(false); const [showIdentityModal, setShowIdentityModal] = useState(false); const [showSignupWall, setShowSignupWall] = useState(false); const [identityForm, setIdentityForm] = useState({ name: '', company: '' }); // États Éditeur const [editingEntity, setEditingEntity] = useState(null); const [confirmDialog, setConfirmDialog] = useState(null); // Composants et Icônes const { LoaderIcon, AlertTriangleIcon, CheckCircleIcon, ShieldIcon, LogOutIcon, EyeIcon, MessageSquareIcon, UserIcon, BuildingIcon, FileTextIcon, LockIcon } = window.pano_getIcons(); const { ConfirmModal, Button, Modal, PanneauEditorForm, EntityEditorModal, TextLogo } = window.pano_getComponents(); // INITIALISATION : Récupération du jeton et du statut useEffect(() => { fetch(window.pano_CONFIG.apiBaseUrl + 'panneaux/collaborators/guest_access&token=' + encodeURIComponent(token) + '&_t=' + Date.now()) .then(r => r.json()) .then(d => { if (!isMounted.current) return; if (d.status === 'success') { setGuestData(d.data); setPanneau(d.data.panneau); setViewState(d.data.status); // 'pending', 'accepted', 'rejected' } else { setErrorMsg(d.message); setViewState('error'); } }) .catch((e) => { if (!isMounted.current) return; if (window.pano_logFallback) window.pano_logFallback("Erreur fetch guest: " + e.message); setErrorMsg("Erreur de connexion au serveur."); setViewState('error'); }); }, [token, isMounted]); // ACTIONS MÉTIER const handleAccept = async (e) => { e.preventDefault(); if (!identityForm.name.trim() || !identityForm.company.trim()) { showToast("Veuillez remplir tous les champs.", "error"); return; } const d = await safeFetch('panneaux/collaborators/guest_respond', { body: { token, action: 'accept', name: identityForm.name.trim(), company: identityForm.company.trim() }, setLoading: setIsSaving }); if (!isMounted.current) return; if (d && d.status === 'success') { setShowIdentityModal(false); setViewState('accepted'); showToast("Invitation acceptée. Vous pouvez maintenant éditer le panneau.", "success"); } }; const handleReject = () => { setConfirmDialog({ title: "Refuser l'invitation", message: "Êtes-vous sûr de vouloir refuser cette invitation ? Vous perdrez l'accès à ce panneau.", confirmText: "Oui, refuser", isDestructive: true, onConfirm: async () => { setConfirmDialog(null); const d = await safeFetch('panneaux/collaborators/guest_respond', { body: { token, action: 'reject' }, setLoading: setIsSaving }); if (!isMounted.current) return; if (d && d.status === 'success') setViewState('rejected'); } }); }; const handleSaveEditor = async () => { const pCible = panneau || {}; // Anti-conflit d'édition en mode délégué const detailsToSave = { ...pCible, ...(pCible.draft_data || {}), draft_data: {} }; const payload = { token: token, id: String(pCible.id), status: String(pCible.status), offerType: String(pCible.offerType), details: detailsToSave }; const d = await safeFetch('panneaux/collaborators/guest_update', { body: payload, setLoading: setIsSaving, successMessage: "Modifications enregistrées avec succès." }); // Si succès, l'UI reste sur place }; // --- VUES DE LA MACHINE À ÉTATS --- if (viewState === 'loading') { return (
{LoaderIcon && }

Vérification de l'accès sécurisé...

); } if (viewState === 'error') { return (
{AlertTriangleIcon && }

Accès refusé

{errorMsg}

); } if (viewState === 'rejected') { return (

Invitation refusée

Vous avez refusé l'accès à ce panneau. Le propriétaire du projet a été notifié.

); } // LE SAS DE CONSULTATION (En attente) if (viewState === 'pending') { return (

Invitation à collaborer

Vous avez été invité sur un projet géré par eco-panneau.fr

{guestData.inviter_name} de la société {guestData.inviter_company} vous invite à participer au projet suivant :

{panneau?.name}

{panneau?.location || "Emplacement non renseigné"}

{(panneau?.a1PdfId || panneau?.imageId) && (
{panneau?.imageId && ( Aperçu Virtuel )} {panneau?.a1PdfId && ( Arrêté / BAT )}
)}
{/* MODALE D'IDENTITÉ (SAS) */} {showIdentityModal && Modal && ( !isSaving && setShowIdentityModal(false)} preventClose={isSaving}>
Afin de garantir la traçabilité des modifications sur ce panneau de chantier légal, veuillez renseigner votre identité. Aucune création de compte n'est requise.
setIdentityForm({...identityForm, name: e.target.value})} placeholder="Ex: Jean Dupont" required disabled={isSaving} className="w-full border-2 border-slate-200 rounded-xl p-3 text-sm outline-none focus:border-blue-500 font-bold" />
setIdentityForm({...identityForm, company: e.target.value})} placeholder="Ex: Bureau de contrôle ABC" required disabled={isSaving} className="w-full border-2 border-slate-200 rounded-xl p-3 text-sm outline-none focus:border-blue-500 font-bold" />
)}
); } // ÉTAT ACCEPTÉ : L'ÉDITEUR DÉVERROUILLÉ return (

Mode Invité (Sans compte)

Projet : {panneau?.name} - {panneau?.location}

{/* LE MUR D'INSCRIPTION POUR LE CHAT */}
{!guestData.can_edit && (
Vos droits d'accès sont en lecture seule. Vous ne pouvez pas modifier ce panneau.
)} {guestData.can_edit && guestData.locked_fields && guestData.locked_fields.length > 0 && (
Certains champs sensibles ont été verrouillés par {guestData.inviter_name}.
)} {PanneauEditorForm && ( window.location.href = '?'} onPublish={handleSaveEditor} onEditEntity={(loc, data) => setEditingEntity({location: loc, data})} deleteEntity={(loc) => { setConfirmDialog({ title: "Supprimer l'élément", message: "Êtes-vous sûr de vouloir supprimer cet élément de l'équipe ?", confirmText: "Supprimer", type: "error", isDestructive: true, onConfirm: () => { let newInter = [...(panneau.intervenants || [])]; let newLots = JSON.parse(JSON.stringify(panneau.lots || [])); if (loc.type === 'intervenant') { newInter.splice(loc.index, 1); } else if (loc.type === 'lot') { newLots.splice(loc.index, 1); } else { newLots[loc.lotIndex].entreprises.splice(loc.index, 1); } setPanneau({ ...panneau, intervenants: newInter, lots: newLots }); setConfirmDialog(null); } }); }} isSaving={isSaving} currentUserRole={guestData.can_edit ? "guest" : "reader"} // Adaptatif selon les droits lockedFields={guestData.locked_fields || []} showToast={showToast} myClientData={{ id: 'guest_mode', name: guestData.inviter_company }} // Fake ID strict pour éviter les conflits de permission data={{ settings: {}, team: [] }} // Injection vitale pour éviter les crashs de l'éditeur /> )}
{editingEntity && EntityEditorModal && guestData.can_edit && ( { let newInter = [...(panneau.intervenants || [])]; let newLots = JSON.parse(JSON.stringify(panneau.lots || [])); const loc = editingEntity.location; if (loc.type === 'intervenant') { if (loc.index !== undefined) newInter[loc.index] = editingEntity.data; else newInter.push({...editingEntity.data, id: window.pano_generateID()}); } else if (loc.type === 'lot') { if (loc.index !== undefined) newLots[loc.index] = {...newLots[loc.index], ...editingEntity.data}; else newLots.push({...editingEntity.data, id: window.pano_generateID(), entreprises: []}); } else if (loc.type === 'entreprise') { if (loc.index !== undefined) newLots[loc.lotIndex].entreprises[loc.index] = editingEntity.data; else newLots[loc.lotIndex].entreprises.push({...editingEntity.data, id: window.pano_generateID()}); } setPanneau({ ...panneau, intervenants: newInter, lots: newLots }); setEditingEntity(null); }} clientUid="guest_mode" // Bypass de sécurité UI panneauId={panneau?.id} onClose={() => setEditingEntity(null)} showToast={showToast} /> )} {/* LE MUR D'INSCRIPTION (Phase 3) */} {showSignupWall && Modal && ( setShowSignupWall(false)}>

Pour des raisons de traçabilité légale, l'utilisation de la messagerie pour discuter avec l'équipe du chantier ou répondre aux riverains nécessite de s'authentifier.

Passez à la vitesse supérieure !

  • Conservez l'historique de tous vos projets.
  • Discutez en temps réel avec tous les intervenants.
  • Créez vos propres panneaux.
)} {confirmDialog && ConfirmModal && ( setConfirmDialog(null)} /> )}
); }; /* EOF ========== [_react/guest/_guest_view.jsx] */