// 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}
)}
{/* Bloc : Annulé */}
{currentStatus === 'Annulé' && (
)}
{/* 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 && (
{ e.stopPropagation(); openModal('view_a1_pdf', c.id, false); }} className="w-full py-2 bg-white text-xs justify-center shadow-sm" icon={EyeIcon}>Consulter le PDF
)}
{ setRejectReason(''); openModal('reject_a1', c.id); }} disabled={isSaving} className="flex-1 py-2 text-xs justify-center shadow-none bg-white border-none">Refuser
{
const d = await safeFetch('panneaux/client_validate_a1', {
body: { id: c.id, action: 'accept' },
setLoading: setIsSaving,
successMessage: "Maquette validée !"
});
if (!isMounted.current) return; // SÉCURITÉ
if (d && refreshData) refreshData();
}} disabled={isSaving} className="flex-[2] py-2 text-xs justify-center shadow-md text-center">Valider l'impression
)}
{/* Bloc : Expédié */}
{currentStatus === 'Expédié' && (
e.stopPropagation()}>
{c.tracking_number ? (
{c.tracking_number}
) : (
En cours de livraison
)}
{c.tracking_link && (
window.open(c.tracking_link.startsWith('http') ? c.tracking_link : `https://${c.tracking_link}`, '_blank')}
className="p-1.5 shadow-none shrink-0"
title="Suivre mon colis en ligne"
/>
)}
{isForceDeliveryAvailable && (
updateShippingStatus("Réception confirmée par le suivi")} disabled={isSaving} className="mt-3 w-full py-2 text-[10px] uppercase justify-center text-slate-500 border-dashed hover:bg-emerald-50 hover:text-emerald-700 hover:border-emerald-200 text-center" title="Le colis est indiqué comme expédié depuis longtemps. Forcer le statut à réceptionné.">
Forcer Livraison ({limitShippingForce}j+)
)}
openModal('delivery_confirm', c.id)} disabled={isSaving} className="w-full py-2.5 text-xs uppercase tracking-widest justify-center shadow-md">Confirmer la réception
)}
{/* Bloc : Réception confirmée */}
{currentStatus.startsWith('Réception confirmée') && (
)}
{c.a1PdfId && currentStatus !== 'Attente validation client' && (
{ e.stopPropagation(); openModal('view_a1_pdf', c.id, false); }}
className="p-2 shadow-sm bg-white"
title="Voir la maquette A1"
/>
)}
{currentStatus !== 'Annulé' && !currentStatus.startsWith('Réception confirmée') && currentStatus !== 'Expédié' && currentStatus !== 'Attente validation client' && (
En cours de traitement par notre équipe...
)}
{/* Actions de masquage */}
{(currentStatus.startsWith('Réception confirmée') || currentStatus === 'Annulé') && (
{
e.stopPropagation();
setConfirmConfig({
title: "Masquer la commande",
message: "Voulez-vous masquer définitivement cette commande de votre historique logistique ?",
confirmText: "Masquer",
isDestructive: true,
onConfirm: async () => {
closeCurrentLayer();
const newPhysicalPanels = currentStatus === 'Annulé' ? 0 : c.physicalPanels;
const newShippingStatus = currentStatus === 'Annulé' ? '' : 'Archivé';
const d = await safeFetch('panneaux', {
body: {
id: c.id, status: c.status, offerType: c.offerType, currentRate: c.currentRate, physicalPanels: newPhysicalPanels,
details: { ...c, shipping_status: newShippingStatus, physicalPanels: newPhysicalPanels }
},
setLoading: setIsSaving,
successMessage: "Commande masquée avec succès."
});
if (!isMounted.current) return; // SÉCURITÉ
if (d && refreshData) refreshData();
setConfirmConfig(null);
},
onCancel: () => { setConfirmConfig(null); closeCurrentLayer(); }
});
openDialog('confirm', c.id, false);
}} disabled={isSaving} className="w-full mt-2 py-1.5 text-[10px] justify-center text-slate-400 hover:text-slate-600 hover:bg-slate-100 shadow-none border-none text-center">
Masquer de l'historique
)}
{/* Modales Encapsulées Zéro-Dette */}
{activeModal === 'delivery_confirm' && targetId === c.id && Modal && (
(
<>
Annuler
{ handleConfirmDelivery(); }} disabled={isSaving} icon={isSaving ? LoaderIcon : CheckCircleIcon} className="flex-[2] py-3 uppercase tracking-widest justify-center shadow-md text-xs text-center">Oui, j'ai reçu mon colis
>
)}>
Confirmez-vous la bonne réception de la commande logistique associée à ce panneau ?
)}
{activeModal === 'reject_a1' && targetId === c.id && Modal && (
(
<>
Annuler
Confirmer le refus
>
)}>
)}
{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] */