// ECO-PANNEAU.FR - _react/clients/_clients_livraisons.jsx // ============================================================================ // 1. COMPOSANT CARTE ISOLÉ ET RÉUTILISABLE CLIENT (LIVRAISONS) // ============================================================================ window.pano_ClientDeliveryCard = ({ delivery: c, isPinned, toggleDashboardPin, refreshData, isMini = false, limitShippingForce = 90, openModal, closeCurrentLayer, openDialog, activeDialog, dialogId, activeModal, targetId }) => { const { useState } = React; // SÉCURITÉ ANTI-FUITE DE MÉMOIRE : Utilisation du Hook Global Zéro-Dette const { isMounted, safeFetch } = window.pano_useSafeFetch(); const [isSaving, setIsSaving] = useState(false); const [rejectReason, setRejectReason] = useState(''); const [confirmConfig, setConfirmConfig] = useState(null); const { PackageIcon, CheckCircleIcon, ExternalLinkIcon, LoaderIcon, AlertTriangleIcon, EyeIcon, XIcon, PinIcon, Trash2Icon } = window.pano_getIcons(); const { Modal, StatusBadge, IconBadge, Button, DataCard, FormTextarea, ConfirmModal, UniversalViewer } = window.pano_getComponents(); const currentStatus = c.shipping_status || 'En attente de validation'; let variant = 'default'; if (currentStatus === 'Attente validation client') variant = 'warning'; if (currentStatus === 'Annulé') variant = 'slate'; const handleConfirmDelivery = async () => { const d = await safeFetch('panneaux/shipping', { body: { id: c.id, shipping_status: 'Réception confirmée' }, setLoading: setIsSaving, successMessage: "Réception confirmée !" }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d) { if (refreshData) refreshData(); closeCurrentLayer(); } }; const updateShippingStatus = async (newStatus) => { const d = await safeFetch('panneaux/shipping', { body: { id: c.id, shipping_status: newStatus }, setLoading: setIsSaving, successMessage: "Statut logistique mis à jour." }); if (!isMounted.current) return; // SÉCURITÉ : Coupe-circuit if (d && refreshData) refreshData(); }; // ------------------------------------------------------------------------ // Rendu : Mode Mini (Historique compact) // ------------------------------------------------------------------------ if (isMini) { return ( <>

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

Qté: {c.physicalPanels} • {currentStatus}

{c.a1PdfId && (
{activeDialog === 'confirm' && dialogId === c.id && confirmConfig && ConfirmModal && } {activeModal === 'view_a1_pdf' && targetId === c.id && UniversalViewer && ( )} ); } // ------------------------------------------------------------------------ // Rendu : Mode Standard (Cartes complètes pour l'action requise / en cours) // ------------------------------------------------------------------------ let isForceDeliveryAvailable = false; if (currentStatus === 'Expédié' && c.updated_at) { const updateTime = new Date(c.updated_at.replace(' ', 'T')).getTime(); const daysSince = (Date.now() - updateTime) / (1000 * 3600 * 24); if (daysSince > limitShippingForce) isForceDeliveryAvailable = true; } return ( <>
{ e.stopPropagation(); toggleDashboardPin && toggleDashboardPin('pinned_deliveries', c.id, e); }} className={`absolute top-2 right-2 z-20 p-1 cursor-pointer transition-all duration-200 ${isPinned ? 'text-amber-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 au tableau de bord"} >

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

{(c.shippingAddress || c.location) && (

{c.shippingAddress || c.location}

)}
{c.physicalPanels} Qté
{/* Bloc : Annulé */} {currentStatus === 'Annulé' && (

Commande Annulée

)} {/* Bloc : Attente validation client */} {currentStatus === 'Attente validation client' && (
e.stopPropagation()}>

Maquette à valider

Le support technique a généré une nouvelle maquette pour votre projet. Veuillez la contrôler et la valider formellement pour lancer l'impression.

{c.a1PdfId && ( )}
)} {/* Bloc : Expédié */} {currentStatus === 'Expédié' && (
e.stopPropagation()}>
{c.tracking_number ? (

{c.tracking_number}

) : (

En cours de livraison

)} {c.tracking_link && (
{isForceDeliveryAvailable && ( )}
)} {/* Bloc : Réception confirmée */} {currentStatus.startsWith('Réception confirmée') && (

Colis Livré

)}
{c.a1PdfId && currentStatus !== 'Attente validation client' && ( )}
{/* Modales Encapsulées Zéro-Dette */} {activeModal === 'delivery_confirm' && targetId === c.id && Modal && ( ( <> )}>

Confirmez-vous la bonne réception de la commande logistique associée à ce panneau ?

)} {activeModal === 'reject_a1' && targetId === c.id && Modal && ( ( <> )}>
{ e.preventDefault(); const d = await safeFetch('panneaux/client_validate_a1', { body: { id: c.id, action: 'reject', reason: rejectReason }, setLoading: setIsSaving, successMessage: "Refus transmis au support technique." }); if (!isMounted.current) return; // SÉCURITÉ if (d) { if (refreshData) refreshData(); closeCurrentLayer(); } }} className="space-y-4 min-w-0"> {FormTextarea && setRejectReason(e.target.value)} placeholder="Ex: Le numéro de permis est incorrect, la couleur de la bordure ne correspond pas..." autoFocus className="min-w-0 w-full" />}
)} {activeDialog === 'confirm' && dialogId === c.id && confirmConfig && ConfirmModal && } {activeModal === 'view_a1_pdf' && targetId === c.id && UniversalViewer && ( )} ); }; // ============================================================================ // 2. ONGLET PRINCIPAL LOGISTIQUE CLIENT // ============================================================================ window.pano_ClientLivraisonsTab = ({ data, myClientData, refreshData, activeModal, activeDialog, targetId, dialogId, openLocalModal, openLocalDialog, closeCurrentLayer, clientOpts, toggleDashboardPin }) => { const { useState, useEffect } = React; // Icônes et Composants const { PackageIcon, AlertTriangleIcon, PinIcon, CheckCircleIcon, TruckIcon } = window.pano_getIcons(); const { CardGrid, SearchBar, EmptySearch, PaginationFooter, ClientDeliveryCard } = window.pano_getComponents(); // Données et filtrage const { visiblePanels } = window.pano_getPanelAccessRights ? window.pano_getPanelAccessRights(data.panneaux || [], myClientData.id, myClientData.email_hash) : { visiblePanels:[] }; // Ne lister que les commandes de panneaux physiques (hors brouillons/archivés) const deliveries = visiblePanels.filter(c => c.physicalPanels > 0 && c.status !== 'Brouillon' && c.shipping_status !== 'Archivé'); const { searchQuery, setSearchQuery, visibleCount, setVisibleCount, filteredData: filteredDeliveries } = window.pano_useSearchAndPagination(deliveries, (c, q) => { const n = window.pano_normalizeString; return n(c.name).includes(q) || n(c.shippingAddress).includes(q) || n(c.location).includes(q) || n(c.tracking_number).includes(q) || n(c.shipping_status).includes(q); }); const pinnedIds = clientOpts?.pinned_deliveries || []; const attentionDeliveries = []; const pinnedDeliveries = []; const activeDeliveries = []; const otherDeliveries = []; // LIMITES DYNAMIQUES const limitShippingForce = parseInt(data.settings?.limit_shipping_force || 90, 10); const MAX_HISTORY = parseInt(data.settings?.limit_history_max || 500, 10); // Répartition des commandes en 4 catégories filteredDeliveries.forEach(c => { const currentStatus = c.shipping_status || 'En attente de validation'; let isForceDeliveryAvailable = false; if (currentStatus === 'Expédié' && c.updated_at) { const updateTime = new Date(c.updated_at.replace(' ', 'T')).getTime(); const daysSince = (Date.now() - updateTime) / (1000 * 3600 * 24); if (daysSince > limitShippingForce) isForceDeliveryAvailable = true; } const needsAttention = currentStatus === 'Attente validation client' || isForceDeliveryAvailable; const isPinned = pinnedIds.includes(c.id); const isActive = !(currentStatus.startsWith('Réception confirmée') || currentStatus === 'Annulé'); if (needsAttention) { attentionDeliveries.push(c); } else if (isPinned) { pinnedDeliveries.push(c); } else if (isActive) { activeDeliveries.push(c); } else { otherDeliveries.push(c); } }); const hasMoreHistory = otherDeliveries.length > MAX_HISTORY; const historyToDisplay = hasMoreHistory ? otherDeliveries.slice(0, MAX_HISTORY) : otherDeliveries; const displayedOtherDeliveries = historyToDisplay.slice(0, visibleCount); // Navigation URL Deep Linking useEffect(() => { const url = new URL(window.location); const confirmDelivery = url.searchParams.get('confirm_delivery'); if (confirmDelivery) { url.searchParams.delete('confirm_delivery'); window.history.replaceState(window.history.state || {}, '', url); if (openLocalModal) openLocalModal('delivery_confirm', confirmDelivery); } }, [openLocalModal]); // Rendu UI global return (

Livraisons

Suivez l'état de vos commandes physiques.

{deliveries.length > 0 && SearchBar && (
)}
{deliveries.length === 0 ? (

Aucune livraison

Vous n'avez passé aucune commande de panneaux physiques.

) : filteredDeliveries.length === 0 ? ( EmptySearch && ) : ( <> {/* 1. Priorités */} {attentionDeliveries.length > 0 && (

Action requise

{ClientDeliveryCard && attentionDeliveries.map(c => ( ))}
)} {/* 2. Favoris */} {pinnedDeliveries.length > 0 && (

Favoris (Épinglés)

{ClientDeliveryCard && pinnedDeliveries.map(c => ( ))}
)} {/* 3. Commandes en cours */} {activeDeliveries.length > 0 && (

Commandes en cours

{ClientDeliveryCard && activeDeliveries.map(c => ( ))}
)} {/* 4. Historique (Compacté) */} {otherDeliveries.length > 0 && (

Historique des commandes

{ClientDeliveryCard && displayedOtherDeliveries.map(c => ( ))}
{hasMoreHistory && visibleCount >= MAX_HISTORY && (

L'affichage est limité aux {MAX_HISTORY} résultats les plus récents pour garantir les performances.

Veuillez utiliser la barre de recherche ci-dessus pour affiner les résultats et trouver des commandes plus anciennes.

)} {PaginationFooter && !hasMoreHistory && ( )} {PaginationFooter && hasMoreHistory && visibleCount < MAX_HISTORY && ( )}
)} )}
); }; /* EOF ========== [_react/clients/_clients_livraisons.jsx] */