// ECO-PANNEAU.FR - _react/clients/_clients_panneaux_card.jsx // 1. - COMPOSANT CARTE INVITATION (MODE COMPACT ET STANDARD) window.pano_ClientInviteCard = ({ invite: p, setPreviewPanneau, refreshData, hasCollabFeature, isMini, onClick, // PROPS DE ROUTAGE INJECTÉES PAR LE PARENT openModal, openLocalModal, openChat }) => { const { useState } = React; // SÉCURITÉ ANTI-FUITE DE MÉMOIRE : Utilisation de notre Hook abstrait ! const { isMounted, safeFetch } = window.pano_useSafeFetch(); const [isSaving, setIsSaving] = useState(false); const [isProcessed, setIsProcessed] = useState(false); // CORRECTION ZÉRO-DETTE : Fallbacks robustes si le parent omet de passer la fonction const handleOpenModal = (n, id, s) => { if (openModal) openModal(n, id, s); else if (openLocalModal) openLocalModal(n, id, s); }; const handleOpenChat = (id, s) => { if (openChat) openChat(id, s); else { const prev = window.history.state || { panoStack: [], level: 0, currentTab: 'dashboard' }; const newStack = (prev.panoStack || []).filter(l => l.type !== 'chat'); newStack.push({ type: 'chat', name: 'chat', targetId: id }); window.history.pushState({ ...prev, panoStack: newStack, level: newStack.length }, '', window.location.href); window.dispatchEvent(new Event('pano_stack_sync')); } }; const { UsersIcon, EyeIcon, MessageSquareIcon, CheckCircleIcon, XIcon } = window.pano_getIcons(); const { IconBadge, Button, DataCard } = window.pano_getComponents(); const inviterDisplay = p.owner_name ? `${p.owner_company || 'Société'} (${p.owner_name})` : (p.owner_company || 'Société'); const respondToInvite = async (action) => { setIsProcessed(true); const d = await safeFetch('panneaux/collaborators/respond', { body: { panneau_id: p.id, action: action }, setLoading: setIsSaving, successMessage: action === 'accept' ? "Projet rejoint avec succès." : "Invitation refusée." }); if (!isMounted.current) return; if (d && refreshData) refreshData(); }; if (isProcessed) return null; if (isMini) { return (

{p.name || 'Projet sans nom'}

Invitation de {inviterDisplay}
); } return (

{p.name}

Invitation de {inviterDisplay}

{hasCollabFeature && ( )}
); }; // 2. - COMPOSANT CARTE PANNEAU CLIENT AUTONOME window.pano_ClientPanelCard = ({ panel: c, data, myClientData, localPinned, togglePin, unreadMap, setPreviewPanneau, setManagingPanneau, setValidationErrors, isSuspended, hasNewsletterFeature, hasCollabFeature, hasMessagingFeature, refreshData, setPanelTeamModal, setArchiveConfig, // PROPS DE ROUTAGE INJECTÉES PAR LE PARENT openModal, openLocalModal, openDialog, openLocalDialog, closeCurrentLayer, openChat, activeDialog, dialogId }) => { const { useState, useEffect } = React; // SÉCURITÉ ANTI-FUITE DE MÉMOIRE : Utilisation du Hook Global Zéro-Dette const { isMounted, safeFetch } = window.pano_useSafeFetch(); const [isConfirming, setIsConfirming] = useState(false); const [deleteMode, setDeleteMode] = useState(c.offerType === 'purchase' ? 'immediate' : 'scheduled'); const [deleteWaiver, setDeleteWaiver] = useState(c.offerType === 'purchase'); const [deletePassword, setDeletePassword] = useState(''); const [isSaving, setIsSaving] = useState(false); const [confirmConfig, setConfirmConfig] = useState(null); // CORRECTION ZÉRO-DETTE : Fallbacks robustes au cas où le parent (comme le Dashboard) omette de fournir la prop const handleOpenModal = (n, id, s) => { if (openModal) openModal(n, id, s); else if (openLocalModal) openLocalModal(n, id, s); }; const handleOpenDialog = (n, id, s) => { if (openDialog) openDialog(n, id, s); else if (openLocalDialog) openLocalDialog(n, id, s); }; const handleCloseLayer = () => { if (closeCurrentLayer) closeCurrentLayer(); }; const handleOpenChat = (id, s) => { if (openChat) openChat(id, s); else { const prev = window.history.state || { panoStack: [], level: 0, currentTab: 'dashboard' }; const newStack = (prev.panoStack || []).filter(l => l.type !== 'chat'); newStack.push({ type: 'chat', name: 'chat', targetId: id }); window.history.pushState({ ...prev, panoStack: newStack, level: newStack.length }, '', window.location.href); window.dispatchEvent(new Event('pano_stack_sync')); } }; // Ajout des icônes ShoppingCartIcon / PackageIcon const { BuildingIcon, BuildingTeamIcon, QrCodeIcon, EyeIcon, MessageSquareIcon, MailIcon, LockIcon, EditIcon, Trash2Icon, ArchiveIcon, AlertTriangleIcon, CheckCircleIcon, LoaderIcon, UsersIcon, RefreshCwIcon, PinIcon, DownloadIcon, ShoppingCartIcon, PackageIcon } = window.pano_getIcons(); const { IconBadge, StatusBadge, Button, NotificationBadge, DataCard, Modal, FormInput, ConfirmModal } = window.pano_getComponents(); // RÈGLES DE GESTION DU STATUT (DOE, Suppression Programmée, etc.) const isDOE = c.status === 'DOE'; const isScheduledDelete = c.status === 'Suppression programmée'; const isUnreadDOE = isDOE && !c.doe_downloaded; // CORRECTION UX : Calcul dynamique des jours restants avant suppression légale let doeDaysLeft = 0; if (isDOE) { const retention = parseInt(data?.settings?.doe_retention_days || 30, 10); const updateTime = c.updated_at ? new Date(c.updated_at.replace(' ', 'T')).getTime() : Date.now(); const daysElapsed = Math.floor((Date.now() - updateTime) / (1000 * 3600 * 24)); doeDaysLeft = Math.max(0, retention - daysElapsed); } const isOwner = c.client_uid === myClientData.id; const isMe = (uid) => uid === myClientData.id || uid === myClientData.email_hash; const myCollab = !isOwner ? c.collaborators?.find(col => isMe(col.uid)) : null; // Verrouillage total de l'édition si le panneau est en D.O.E. const canEdit = !isDOE && (isOwner || myCollab?.rights?.can_edit); const canChat = !isDOE && (isOwner || (myCollab && ['read', 'rw'].includes(myCollab.rights?.chat_access))); const isPinned = localPinned.includes(c.id); const hasDraft = c.draft_data && Object.keys(c.draft_data).length > 0; const unreadRiverain = unreadMap[c.id]?.riverain || 0; const unreadGroup = unreadMap[c.id]?.group || 0; useEffect(() => { // Verrouillage du useEffect sur la carte concernée if (activeDialog === 'delete_advanced' && dialogId === c.id) { if (c.offerType === 'purchase') { setDeleteMode('immediate'); setDeleteWaiver(true); } else { setDeleteMode('scheduled'); setDeleteWaiver(false); } } }, [activeDialog, dialogId, c.id, c.offerType]); const handleConfirmActivity = async (e) => { e.stopPropagation(); setIsConfirming(true); const d = await safeFetch('panneaux/confirm_activity', { body: { id: c.id }, successMessage: "Activité confirmée avec succès. Le délai est réinitialisé." }); if (!isMounted.current) return; setIsConfirming(false); if (d && refreshData) refreshData(); }; const handleDeleteSubmit = async (e) => { e.preventDefault(); // Forçage immédiat -> Redirection vers l'Archive D.O.E if (deleteMode === 'immediate' && (c.status === 'Actif' || c.status === 'Suppression programmée')) { handleCloseLayer(); if (setArchiveConfig) setArchiveConfig({ type: 'doe', panelId: c.id }); setTimeout(() => { handleOpenDialog('archive_config', c.id, false); }, 150); return; } const payload = { id: c.id, force_immediate: deleteMode === 'immediate' }; if (c.status === 'Actif' || c.status === 'Suppression programmée') payload.password = deletePassword; const d = await safeFetch('panneaux/delete', { body: payload, setLoading: setIsSaving, successMessage: "Opération réussie." }); if (!isMounted.current) return; if (d) { handleCloseLayer(); if (refreshData) refreshData(); } }; const handleDoeDelete = (e) => { e.stopPropagation(); setConfirmConfig({ title: "Suppression définitive de l'archive", message: "Êtes-vous sûr de vouloir détruire définitivement cette archive D.O.E ? Toutes les données (fichiers, factures) liées à ce panneau seront effacées du serveur.", confirmText: "Oui, détruire", isDestructive: true, onConfirm: async () => { setConfirmConfig(null); const d = await safeFetch('archives/doe_delete', { body: { id: c.id }, setLoading: setIsSaving, successMessage: "Archive détruite avec succès." }); if (!isMounted.current) return; if (d && refreshData) refreshData(); }, onCancel: () => setConfirmConfig(null) }); handleOpenDialog('confirm', c.id, false); }; const handleCancelDeletion = async (e) => { e.stopPropagation(); const d = await safeFetch('panneaux/cancel_deletion', { body: { id: c.id }, setLoading: setIsSaving, successMessage: "La suppression a été annulée. La facturation et le panneau reprennent normalement." }); if (!isMounted.current) return; if (d && refreshData) refreshData(); }; return ( <> {/* Épingle */}
{ e.stopPropagation(); togglePin(c.id, e); }} className={`absolute top-2 right-2 z-20 p-1 cursor-pointer transition-all duration-200 ${isPinned ? 'text-emerald-500 opacity-100 hover:scale-110' : 'text-slate-300 opacity-0 group-hover:opacity-100 hover:text-slate-500 hover:scale-110'}`} style={{ transform: 'rotate(30deg)' }} title={isPinned ? "Désépingler" : "Épingler en haut"} >

{c.name || 'Projet sans nom'}

{c.location &&

{c.location}

}
{c.status === 'Actif' && hasDraft && ( Modifié )} {c.status !== 'Actif' && c.status !== 'DOE' && }
{/* BANDEAU D.O.E. */} {isDOE && (
{isUnreadDOE ? ( <>

Action requise

Téléchargez votre archive légale D.O.E

) : ( <>

Archive D.O.E générée

Suppression automatique dans {doeDaysLeft} {window.pano_formatPlural(doeDaysLeft, "jour", "jours")}

)}
)} {/* BANDEAU SUPPRESSION PROGRAMMÉE */} {isScheduledDelete && (

Ce panneau restera actif et sera converti en archive D.O.E {(c.cancel_at || c.delete_at) ? <>le {window.pano_formatDate(c.cancel_at || c.delete_at)} : "à la fin de la période facturée"}.

)} {/* BANDEAU INACTIVITÉ */} {c.inactivity_alert_level > 0 && c.status === 'Actif' && (

Alerte inactivité : Sans confirmation de votre part, ce panneau sera supprimé sous {c.inactivity_alert_level} {c.inactivity_alert_level > 1 ? 'jours' : 'jour'}.

)} {/* BARRE D'ACTIONS INFERIEURE */} {isDOE ? (
) : (
{c.status === 'Actif' && ( )} {hasCollabFeature && ( )} {hasNewsletterFeature && ( )} {canEdit && c.status !== 'Suppression programmée' && ( )}
)}
{/* Modales Encapsulées Zéro-Dette */} {activeDialog === 'delete_advanced' && dialogId === c.id && Modal && ( ( <> )} >

Choisissez comment supprimer {c.name}.

{c.offerType === 'rental' ? ( <>

Attention

Masquer un panneau depuis votre tableau de bord ne suspend pas sa facturation.

{deleteMode === 'immediate' && ( )} ) : ( )} {(c.status === 'Actif' || c.status === 'Suppression programmée') && (
setDeletePassword(e.target.value)} placeholder="Saisissez votre mot de passe..." className="w-full min-w-0" />

Par mesure de sécurité, la suppression d'un panneau actif nécessite votre mot de passe.

)}
)} {activeDialog === 'confirm' && dialogId === c.id && confirmConfig && ConfirmModal && } ); }; /* EOF ========== [_react/clients/_clients_panneaux_card.jsx] */