// ECO-PANNEAU.FR - _react/clients/_clients_dashboard.jsx window.pano_ClientDashboardTab = ({ data, myClientData, setActiveTab, setManagingPanneau, setValidationErrors, setPreviewPanneau, openLocalModal, openLocalDialog, closeCurrentLayer, activeModal, activeDialog, targetId, dialogId, clientOpts, toggleDashboardPin, handleCreateNewPanel, refreshData, setPanelTeamModal, setArchiveConfig }) => { const { useState, useEffect } = React; // ZÉRO-DETTE : Utilisation de notre Hook abstrait ! const { isMounted, safeFetch } = window.pano_useSafeFetch(); const [isSavingOrder, setIsSavingOrder] = useState(false); // CORRECTION : Nettoyage des icônes inutilisées (Dette technique) const { UsersIcon, BuildingIcon, EyeIcon, MessageSquareIcon, MessageCircleIcon, ArchiveIcon, AlertTriangleIcon, PackageIcon, PinIcon, ChevronUpIcon, ChevronDownIcon, SlidersHorizontalIcon, PlusIcon, CheckCircleIcon, XIcon, EditIcon, QrCodeIcon, MailIcon } = window.pano_getIcons(); // CORRECTION ZÉRO-DETTE : Retrait de UniversalViewer du composant parent const { StatCard, Button, IconBadge, NotificationBadge, Modal, CardGrid, DataCard, TextLogo, StatusBadge, ClientPanelCard, ClientInviteCard, ClientDeliveryCard } = window.pano_getComponents(); // 3. - Gestion de l'ordre d'affichage (Reorder) const defaultOrder = ['actions', 'pinned', 'stats', 'chats']; const currentOrder = clientOpts?.dashboard_order || defaultOrder; const currentActionsMode = clientOpts?.actions_mode || 'full'; const [tempOrder, setTempOrder] = useState(currentOrder); const [tempActionsMode, setTempActionsMode] = useState(currentActionsMode); useEffect(() => { setTempOrder(currentOrder); setTempActionsMode(currentActionsMode); }, [JSON.stringify(currentOrder), currentActionsMode]); const blockNames = { pinned: "Favoris (Épinglés)", actions: "À traiter en priorité", stats: "Vue d'ensemble", chats: "Conversations récentes" }; const moveBlock = (index, dir) => { const newOrder = [...tempOrder]; if (dir === 'up' && index > 0) { [newOrder[index-1], newOrder[index]] = [newOrder[index], newOrder[index-1]]; } else if (dir === 'down' && index < newOrder.length - 1) { [newOrder[index+1], newOrder[index]] = [newOrder[index], newOrder[index+1]]; } setTempOrder(newOrder); }; const saveOrder = async (e) => { setIsSavingOrder(true); const newOpts = { ...clientOpts, dashboard_order: tempOrder, actions_mode: tempActionsMode }; const d = await safeFetch('clients/profile/update', { body: { ...myClientData, uiMode: JSON.stringify(newOpts) } }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit setIsSavingOrder(false); if (d && refreshData) refreshData(); if (closeCurrentLayer) closeCurrentLayer(e); }; // 4. - Traitement des données (Entonnoir) const allPanels = data.panneaux || []; const interactions = data.interactions || []; const { pendingInvites, visiblePanels } = window.pano_getPanelAccessRights ? window.pano_getPanelAccessRights(allPanels, myClientData.id, myClientData.email_hash) : { pendingInvites:[], visiblePanels:[] }; const activePendingInvites = pendingInvites; const activePanelsCount = visiblePanels.filter(p => p.status === 'Actif').length; const draftPanelsCount = visiblePanels.filter(p => p.status === 'Brouillon').length; const totalViews = data.stats?.totalViews || 0; const threads = window.pano_buildClientChatThreads ? window.pano_buildClientChatThreads(data.interactions || [], myClientData, visiblePanels, pendingInvites) : []; const dashboardThreads = threads.slice(0, 5); const unreadMap = {}; threads.forEach(t => { if (t.unread > 0 && t.panneauId) { if (!unreadMap[t.panneauId]) unreadMap[t.panneauId] = { total: 0, riverain: 0, group: 0 }; unreadMap[t.panneauId].total += t.unread; if (t.type === 'riverain') unreadMap[t.panneauId].riverain += t.unread; if (t.type === 'group') unreadMap[t.panneauId].group += t.unread; } }); const pinnedPanelsIds = clientOpts?.pinned_panels || []; const pinnedPanels = visiblePanels.filter(p => pinnedPanelsIds.includes(p.id)); const pinnedDeliveriesIds = clientOpts?.pinned_deliveries || []; const pinnedDeliveries = visiblePanels.filter(p => pinnedDeliveriesIds.includes(p.id) && p.physicalPanels > 0 && p.status !== 'Brouillon'); const limitShippingForce = parseInt(data.settings?.limit_shipping_force || 90, 10); // CORRECTION SÉCURITÉ/UX : Intégration D.O.E non lus ET retrait des alertes logistiques (pour éviter le doublon avec ClientDeliveryCard) const attentionPanels = visiblePanels.filter(p => { const unreadCount = unreadMap[p.id]?.total || 0; const isUnreadDOE = p.status === 'DOE' && !p.doe_downloaded; return p.status === 'Suspendu' || p.status === 'Attente validation' || p.inactivity_alert_level > 0 || unreadCount > 0 || isUnreadDOE; }); const pendingLogistics = visiblePanels.filter(p => { if (p.physicalPanels <= 0) return false; const currentStatus = p.shipping_status || 'En attente de validation'; if (currentStatus === 'Attente validation client') return true; if (currentStatus === 'Expédié' && p.updated_at) { const updateTime = new Date(p.updated_at.replace(' ', 'T')).getTime(); const daysSince = (Date.now() - updateTime) / (1000 * 3600 * 24); return daysSince > limitShippingForce; } return false; }); const isFeatureEnabled = (adminOpt, clientVal) => { if (adminOpt === 'disabled') return false; if (adminOpt === 'active') return true; if (adminOpt === 'optional_on') return clientVal !== false; if (adminOpt === 'optional_off' || adminOpt === 'optional') return clientVal === true; return true; }; const hasNewsletterFeature = isFeatureEnabled(data.settings?.opt_newsletter, clientOpts?.newsletter); const hasCollabFeature = isFeatureEnabled(data.settings?.opt_collab, clientOpts?.collab); const hasMessagingFeature = isFeatureEnabled(data.settings?.opt_messaging, clientOpts?.messaging); const isSuspended = myClientData.paymentStatus === 'suspended'; // 5. - Blocs de rendu dynamiques const renderPinnedBlock = () => { if (pinnedPanels.length === 0 && pinnedDeliveries.length === 0) return null; return (

Vos favoris

{pinnedPanels.map(p => ( toggleDashboardPin('pinned_panels', id, e)} unreadMap={unreadMap} setPreviewPanneau={setPreviewPanneau} openLocalModal={openLocalModal} openLocalDialog={openLocalDialog} setManagingPanneau={setManagingPanneau} setValidationErrors={setValidationErrors} isSuspended={isSuspended} hasNewsletterFeature={hasNewsletterFeature} hasCollabFeature={hasCollabFeature} hasMessagingFeature={hasMessagingFeature} refreshData={refreshData} closeCurrentLayer={closeCurrentLayer} setPanelTeamModal={setPanelTeamModal} setArchiveConfig={setArchiveConfig} activeModal={activeModal} activeDialog={activeDialog} targetId={targetId} dialogId={dialogId} /> ))} {/* CORRECTION ZÉRO-DETTE : Distribution des props du routeur */} {ClientDeliveryCard && pinnedDeliveries.map(c => ( toggleDashboardPin('pinned_deliveries', id, e)} refreshData={refreshData} limitShippingForce={limitShippingForce} openModal={openLocalModal} openDialog={openLocalDialog} closeCurrentLayer={closeCurrentLayer} activeModal={activeModal} activeDialog={activeDialog} targetId={targetId} dialogId={dialogId} /> ))}
); }; const renderActionsBlock = () => { if (attentionPanels.length === 0 && !isSuspended && activePendingInvites.length === 0 && pendingLogistics.length === 0) return null; const attentionPanelsExtra = attentionPanels.length > 4 ? attentionPanels.length - 4 : 0; const pendingLogisticsExtra = pendingLogistics.length > 4 ? pendingLogistics.length - 4 : 0; return (

À traiter en priorité

{isSuspended && (

Compte suspendu (Impayé)

Veuillez régulariser votre situation pour réactiver vos panneaux.

)} {(activePendingInvites.length > 0 || attentionPanels.length > 0 || pendingLogistics.length > 0) && ( {currentActionsMode === 'full' ? ( <> {activePendingInvites.map(p => ( ))} {attentionPanels.slice(0, 4).map(p => ( toggleDashboardPin('pinned_panels', id, e)} unreadMap={unreadMap} setPreviewPanneau={setPreviewPanneau} openLocalModal={openLocalModal} openLocalDialog={openLocalDialog} setManagingPanneau={setManagingPanneau} setValidationErrors={setValidationErrors} isSuspended={isSuspended} hasNewsletterFeature={hasNewsletterFeature} hasCollabFeature={hasCollabFeature} hasMessagingFeature={hasMessagingFeature} refreshData={refreshData} closeCurrentLayer={closeCurrentLayer} setPanelTeamModal={setPanelTeamModal} setArchiveConfig={setArchiveConfig} activeModal={activeModal} activeDialog={activeDialog} targetId={targetId} dialogId={dialogId} /> ))} {attentionPanelsExtra > 0 && ( setActiveTab('panels')} className="cursor-pointer group flex items-center justify-center hover:scale-[1.02] transition-transform min-w-0">

{window.pano_formatPlural(attentionPanelsExtra, `+ ${attentionPanelsExtra} autre panneau`, `+ ${attentionPanelsExtra} autres panneaux`)}

)} {ClientDeliveryCard && pendingLogistics.slice(0, 4).map(p => ( toggleDashboardPin('pinned_deliveries', id, e)} refreshData={refreshData} limitShippingForce={limitShippingForce} openModal={openLocalModal} openDialog={openLocalDialog} closeCurrentLayer={closeCurrentLayer} activeModal={activeModal} activeDialog={activeDialog} targetId={targetId} dialogId={dialogId} /> ))} {pendingLogisticsExtra > 0 && ( setActiveTab('livraisons')} className="cursor-pointer group flex items-center justify-center hover:scale-[1.02] transition-transform min-w-0">

{window.pano_formatPlural(pendingLogisticsExtra, `+ ${pendingLogisticsExtra} autre commande`, `+ ${pendingLogisticsExtra} autres commandes`)}

)} ) : ( <> {activePendingInvites.map(p => ( setActiveTab('panels')} refreshData={refreshData} hasCollabFeature={hasCollabFeature} openModal={openLocalModal} openDialog={openLocalDialog} closeCurrentLayer={closeCurrentLayer} activeModal={activeModal} activeDialog={activeDialog} targetId={targetId} dialogId={dialogId} /> ))} {attentionPanels.slice(0, 4).map(p => { let badgeVariant = 'warning'; if (p.status === 'Suspendu' || p.status === 'Attente validation') badgeVariant = 'danger'; const isUnreadDOE = p.status === 'DOE' && !p.doe_downloaded; if (isUnreadDOE) badgeVariant = 'warning'; return ( setActiveTab('panels')} className="cursor-pointer group hover:scale-[1.02] transition-transform min-w-0" >

{p.name}

{isUnreadDOE ? 'Télécharger D.O.E' : 'Action requise'} {!isUnreadDOE && }
); })} {attentionPanelsExtra > 0 && ( setActiveTab('panels')} className="cursor-pointer group flex items-center justify-center hover:scale-[1.02] transition-transform min-w-0">

{window.pano_formatPlural(attentionPanelsExtra, `+ ${attentionPanelsExtra} autre panneau`, `+ ${attentionPanelsExtra} autres panneaux`)}

)} {pendingLogistics.slice(0, 4).map(p => { const currentStatus = p.shipping_status || 'En attente de validation'; const isRefused = currentStatus === 'Maquette refusée'; const isClientValidation = currentStatus === 'Attente validation client'; const variant = isRefused ? 'danger' : (isClientValidation ? 'warning' : 'warning'); return ( setActiveTab('livraisons')} className="cursor-pointer group hover:scale-[1.02] transition-transform min-w-0">

{p.name}

Logistique: {currentStatus === 'Expédié' ? `Non réceptionné (> ${limitShippingForce}j)` : currentStatus}

); })} {pendingLogisticsExtra > 0 && ( setActiveTab('livraisons')} className="cursor-pointer group flex items-center justify-center hover:scale-[1.02] transition-transform min-w-0">

{window.pano_formatPlural(pendingLogisticsExtra, `+ ${pendingLogisticsExtra} autre commande`, `+ ${pendingLogisticsExtra} autres commandes`)}

)} )}
)}
); }; const renderStatsBlock = () => (

Vue d'ensemble

} variant="success" onClick={() => setActiveTab('panels')} /> } variant="secondary" onClick={() => setActiveTab('panels')} /> } variant="info" />
); const renderChatsBlock = () => { return (

Conversations récentes

setActiveTab('messages')} className="text-xs font-bold text-emerald-600 hover:underline cursor-pointer">Voir tout
{dashboardThreads.map(t => { const lastMsg = t.messages.length > 0 ? t.messages[t.messages.length - 1] : null; const IconToUse = t.type === 'support' ? MessageSquareIcon : MessageCircleIcon; return ( { e.stopPropagation(); if(openLocalModal) openLocalModal('chat', t.id, false); }} className="cursor-pointer hover:border-emerald-300 transition group p-4" >
{IconBadge && }

{t.title}

{t.targetEmail}

0 ? 'text-slate-800 font-bold' : 'text-slate-500'}`}> {lastMsg ? lastMsg.detail.replace(/\[ATTACHMENT:[^\]]+\]/g, '[Pièce jointe]').replace(/<[^>]*>?/gm, '') : Nouvelle conversation...}
); })} {dashboardThreads.length === 0 && (

Aucune interaction récente.

)}
); }; const blocksMap = { pinned: renderPinnedBlock(), actions: renderActionsBlock(), stats: renderStatsBlock(), chats: renderChatsBlock() }; const demoPanneau = allPanels.find(p => p.id === 'demo-panneau') || { id: 'demo-panneau', status: 'Actif', offerType: 'demo', name: 'Panneau de démonstration', location: 'Paris', themeColor: '#059669', hasNoAds: false }; const demoHasDraft = demoPanneau.draft_data && Object.keys(demoPanneau.draft_data).length > 0; return ( <>

Tableau de bord

Bienvenue sur votre espace personnel .

{visiblePanels.some(p => p.id === 'demo-panneau') && (

Panneau de démonstration {demoHasDraft && Modifié}

Modifiez ce panneau pour mettre à jour la page d'accueil.

)} {currentOrder.map(blockKey => { const blockContent = blocksMap[blockKey]; if (!blockContent) return null; if (blockKey === 'chats') { return
{blockContent}
; } return ( {blockContent} ); })}
{/* MODALE DE RÉORGANISATION GLOBALE */} {activeDialog === 'reorder_dashboard' && Modal && ( ( <> )} >

Utilisez les flèches pour réorganiser les grandes sections de votre tableau de bord selon vos priorités.

{tempOrder.map((blockKey, idx) => (
{blockNames[blockKey]}
))}

Format de la section "À traiter en priorité"

)} ); }; /* EOF ========== [_react/clients/_clients_dashboard.jsx] */