// ECO-PANNEAU.FR - _react/clients/_clients_modals_delegate.jsx const { useState, useEffect } = React; // 1. - MODALE : GESTION DE LA SAISIE DÉLÉGUÉE window.pano_ClientDelegateShareModal = ({ managingPanneau, setManagingPanneau, onClose, refreshData, showToast }) => { // ZÉRO-DETTE : Utilisation de notre Hook abstrait ! const { isMounted, safeFetch } = window.pano_useSafeFetch(); // 1.1 - Routage et modales const { activeDialog, openDialog, closeCurrentLayer } = window.pano_useUrlModal(); const routerCloseLayer = onClose || closeCurrentLayer; // 1.2 - Composants et Icônes const { PlusIcon, LockIcon, CopyIcon, Trash2Icon, LinkIcon, MailIcon, EditIcon, SaveIcon } = window.pano_getIcons(); const { Modal, AlertBox, EmptyState, Toggle, ConfirmModal, Button } = window.pano_getComponents(); // 1.3 - États locaux const [newLink, setNewLink] = useState({ email: '', expDays: 7, lockedFields: [] }); const [editingLink, setEditingLink] = useState(null); const [isSaving, setIsSaving] = useState(false); const [confirmConfig, setConfirmConfig] = useState(null); const isDirty = newLink.email.trim() !== '' || newLink.lockedFields.length > 0 || editingLink !== null; const AVAILABLE_FIELDS = [ { id: 'name', label: 'Nom du chantier' }, { id: 'location', label: 'Lieu' }, { id: 'maitreOuvrage', label: "Maître d'ouvrage" }, { id: 'permitNumber', label: 'N° de permis' }, { id: 'description', label: 'Description' }, { id: 'promoterLink', label: 'Lien promoteur' }, { id: 'emergencyPhone', label: 'N° d\'urgence' }, { id: 'noiseSchedule', label: 'Horaires' }, { id: 'intervenants', label: 'Intervenants' }, { id: 'lots', label: 'Lots' }, { id: 'pdfId', label: 'Arrêté (PDF)' } ]; // 2. - Rendu UI return ( <> ( )} >
* Champs obligatoires
Créez des liens sécurisés pour confier la saisie d'informations ou le dépôt de documents (PDF) à d'autres membres de l'équipe du chantier sans leur donner accès à votre compte ou à votre facturation. Le lien sera directement envoyé par e-mail au destinataire !
{ e.preventDefault(); if (!newLink.email.trim() || !newLink.email.includes('@')) return showToast("Veuillez saisir une adresse e-mail valide.", "error"); // CORRECTION SÉCURITÉ : Utilisation du générateur d'ID avec fallback const linkId = window.pano_generateID(); const expTime = Math.floor(Date.now() / 1000) + (newLink.expDays * 86400); const linkObj = { id: linkId, name: newLink.email.trim(), exp: expTime, active: true, lockedFields: newLink.lockedFields }; const updatedPanneau = { ...managingPanneau, delegate_links: [...(managingPanneau.delegate_links || []), linkObj] }; setIsSaving(true); const dSave = await safeFetch('panneaux', { body: { id: updatedPanneau.id, status: updatedPanneau.status, offerType: updatedPanneau.offerType, physicalPanels: updatedPanneau.physicalPanels, details: updatedPanneau } }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (dSave) { const dSend = await safeFetch('panneaux/delegate_link/send', { body: { id: updatedPanneau.id, link_id: linkId, email: newLink.email.trim(), exp_days: newLink.expDays } }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (dSend) { showToast("Le lien de saisie a été envoyé avec succès.", "success"); setManagingPanneau(updatedPanneau); setNewLink({ email: '', expDays: 7, lockedFields: [] }); if (refreshData) refreshData(); } else { showToast("Le lien a été créé mais une erreur est survenue lors de l'envoi de l'e-mail.", "error"); } } else { showToast("Erreur lors de la création du lien.", "error"); } if (isMounted.current) setIsSaving(false); }} className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm space-y-4">

Envoyer un accès délégué

setNewLink({...newLink, email: e.target.value})} placeholder="Ex: architecte@exemple.com" className="w-full border border-slate-200 rounded-xl p-3 text-sm outline-none focus:border-blue-500 font-bold" disabled={isSaving} />
setNewLink({...newLink, expDays: parseInt(e.target.value)||7})} className="w-full border border-slate-200 rounded-xl p-3 text-sm outline-none focus:border-blue-500 font-bold" disabled={isSaving} />
{AVAILABLE_FIELDS.map(f => (
!isSaving && setNewLink(p => ({...p, lockedFields: p.lockedFields.includes(f.id) ? p.lockedFields.filter(x=>x!==f.id) : [...p.lockedFields, f.id]}))} className={`px-3 py-1.5 rounded-lg text-xs font-bold transition border ${isSaving ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'} ${newLink.lockedFields.includes(f.id) ? 'bg-red-50 text-red-600 border-red-200' : 'bg-slate-50 text-slate-500 border-slate-200 hover:bg-slate-100'}`}> {newLink.lockedFields.includes(f.id) && }{f.label}
))}

Liens générés pour ce panneau

{(!managingPanneau.delegate_links || managingPanneau.delegate_links.length === 0) && ( )} {managingPanneau.delegate_links?.map((link, idx) => { const isExpired = link.exp < Date.now() / 1000; const isEditing = editingLink && editingLink.id === link.id; return (

{link.name}

Exp. : {new Date(link.exp * 1000).toLocaleDateString('fr-FR')} {isExpired && (Expiré)}

{ const links = [...managingPanneau.delegate_links]; links[idx] = { ...links[idx], active: newVal }; const updatedPanneau = { ...managingPanneau, delegate_links: links }; const d = await safeFetch('panneaux', { body: { id: updatedPanneau.id, status: updatedPanneau.status, offerType: updatedPanneau.offerType, physicalPanels: updatedPanneau.physicalPanels, details: updatedPanneau }, setLoading: setIsSaving }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d) { setManagingPanneau(updatedPanneau); if (refreshData) refreshData(); } }} />
{isEditing ? (
{AVAILABLE_FIELDS.map(f => (
!isSaving && setEditingLink(p => ({...p, lockedFields: p.lockedFields.includes(f.id) ? p.lockedFields.filter(x=>x!==f.id) : [...p.lockedFields, f.id]}))} className={`px-3 py-1.5 rounded-lg text-xs font-bold transition border ${isSaving ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'} ${editingLink.lockedFields.includes(f.id) ? 'bg-red-50 text-red-600 border-red-200' : 'bg-slate-50 text-slate-500 border-slate-200 hover:bg-slate-100'}`}> {editingLink.lockedFields.includes(f.id) && }{f.label}
))}
) : ( link.lockedFields && link.lockedFields.length > 0 && (

Verrouillés : {link.lockedFields.join(', ')}

) )}
); })}
{confirmDialog && ConfirmModal && ( setConfirmDialog(null)} /> )} ); }; /* EOF ========== [_react/clients/_clients_modals_delegate.jsx] */