/**
* =========================================================================
* PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0
* Interface Client - Onglets : Mes Panneaux et Livraisons
* =========================================================================
*/
// =========================================================================
// 1. ONGLET : MES PANNEAUX
// =========================================================================
window.ClientPanelsTab = ({
data,
myClientData,
refreshData,
showToast,
setActiveTab,
setManagingPanneau,
setValidationErrors,
setModalStep,
setIsModalOpen,
setPreviewPanneau,
setPanelTeamModal,
setArchiveConfig,
handleDeletePanelClick,
setConfirmDialog
}) => {
const { useState } = React;
const [isSaving, setIsSaving] = useState(false);
// RÉCUPÉRATION SÉCURISÉE DES ICÔNES ET COMPOSANTS
const BuildingIcon = window.BuildingIcon || (() => null);
const PlusIcon = window.PlusIcon || (() => null);
const AlertTriangleIcon = window.AlertTriangleIcon || (() => null);
const UsersIcon = window.UsersIcon || (() => null);
const CheckCircleIcon = window.CheckCircleIcon || (() => null);
const Trash2Icon = window.Trash2Icon || (() => null);
const MessageSquareIcon = window.MessageSquareIcon || (() => null);
const EyeIcon = window.EyeIcon || (() => null);
const QrCodeIcon = window.QrCodeIcon || (() => null);
const Share2Icon = window.Share2Icon || (() => null);
const LockIcon = window.LockIcon || (() => null);
const EditIcon = window.EditIcon || (() => null);
const ArchiveIcon = window.ArchiveIcon || (() => null);
const PowerIcon = window.PowerIcon || (() => null);
const CopyIcon = window.CopyIcon || (() => null);
const XIcon = window.XIcon || (() => null);
const StatusBadge = window.StatusBadge || (({status}) => {status});
// Fallback robuste avec restauration de la soumission native au clavier
const Button = window.Button || (({children, onClick, className, disabled, title, icon: Icon, type}) => {
const handleAction = (e) => {
if (disabled) return;
if (onClick) onClick(e);
if (type === 'submit') {
const form = e.currentTarget.closest('form');
if (form && typeof form.requestSubmit === 'function') form.requestSubmit();
}
};
return (
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleAction(e); } }}
className={`flex items-center justify-center gap-2 cursor-pointer transition ${className || ''} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
title={title}
>
{Icon && }
{children}
{type === 'submit' && }
);
});
// FACTORISATION : Logique métier d'accès aux panneaux
const { pendingInvites, visiblePanels } = window.getPanelAccessRights
? window.getPanelAccessRights(data.panneaux || [], myClientData.id)
: { pendingInvites:[], visiblePanels:[] };
const isSuspended = myClientData.paymentStatus === 'suspended';
const purchasesAllowed = data.settings?.allow_new_purchases !== '0';
const respondToInvite = async (panneauId, action) => {
const d = await window.apiFetch('panneaux/collaborators/respond', {
body: { panneau_id: panneauId, action },
setLoading: setIsSaving,
successMessage: "Invitation " + (action === 'accept' ? 'acceptée' : 'refusée') + " avec succès."
});
if (d) refreshData();
};
return (
Mes panneaux
Gestion de vos affichages et de vos collaborations.
{!isSuspended && (
)}
{(!purchasesAllowed) && (
La validation de nouvelles commandes est temporairement suspendue. Vous pouvez toujours préparer vos brouillons.
)}
{pendingInvites.length > 0 && (
Invitations en attente
{/* 3 colonnes décalées à 2xl */}
{pendingInvites.map((p, i) => {
const collab = p.collaborators?.find(c => c.uid === myClientData.id);
return (
{p.name}
Invitation à collaborer envoyée par {collab?.inviter_name || 'le propriétaire'} ({collab?.inviter_company || 'Société'})
);
})}
)}
{/* 3 colonnes décalées à 2xl */}
{visiblePanels.map((c, i) => {
const isOwner = c.client_uid === myClientData.id;
const myCollabData = !isOwner ? c.collaborators?.find(col => col.uid === myClientData.id) : null;
const canEdit = isOwner || myCollabData?.rights?.can_edit;
// Calcul robuste du délai d'expiration des brouillons
let draftCountdown = 100;
if (c.status === 'Brouillon') {
const timeStr = c.updated_at || c.created_at;
if (timeStr) {
const parsedTime = new Date(String(timeStr).replace(' ', 'T')).getTime();
if (!isNaN(parsedTime)) {
const diffDays = Math.floor((Date.now() - parsedTime) / (1000 * 3600 * 24));
draftCountdown = Math.max(0, 100 - diffDays);
}
}
}
return (
{!isOwner && (
Collaboration
)}
{c.name || 'Brouillon sans nom'}
{c.location || 'Localisation non définie'}
{c.status === 'Actif' && (
Abonnement
{c.offerType === 'rental' ? 'Location' : 'Achat'}
)}
{c.status === 'Brouillon' && (
Suppression automatique dans {draftCountdown} jour(s)
)}
{c.status === 'Suppression programmée' && (
Suppression complète à la fin de la période
)}
{c.status === 'Actif' && (
<>
{
const u = new URL(window.location);
u.searchParams.set('chat_id', 'GROUP_' + c.id);
window.history.replaceState({}, '', u);
setActiveTab('messages');
}} title="Discussion de l'équipe du projet" className="flex-1 sm:flex-none justify-center bg-blue-50 text-blue-700 hover:bg-blue-100 px-4 py-2 rounded-xl text-xs font-bold transition flex items-center gap-2 shadow-sm cursor-pointer">
Équipe
{isOwner && (
)}
{canEdit && (
<>
{ setManagingPanneau(c); setModalStep('legal_vault'); setIsModalOpen(true); }} title="Documents d'huissier" className="flex-1 sm:flex-none justify-center px-4 py-2 rounded-xl text-xs font-bold bg-slate-900 text-white hover:bg-slate-800 flex items-center gap-2 transition shadow-sm cursor-pointer">
Coffre-fort
{!isSuspended && (
)}
>
)}
{isOwner && (
<>
>
)}
>
)}
{c.status === 'Brouillon' && !isSuspended && (
<>
{(isOwner || canEdit) && (
)}
{isOwner && (
);
})}
{visiblePanels.length === 0 && (
Aucun panneau
Créez votre premier panneau pour démarrer ou attendez une invitation à collaborer.
)}
);
};
// =========================================================================
// 2. ONGLET : LIVRAISONS
// =========================================================================
window.ClientLivraisonsTab = ({ data, myClientData, refreshData, showToast }) => {
const { useState, useEffect } = React;
const [deliveryConfirmModal, setDeliveryConfirmModal] = useState(null);
const [isSaving, setIsSaving] = useState(false);
// RÉCUPÉRATION SÉCURISÉE DES ICÔNES ET COMPOSANTS
const PackageIcon = window.PackageIcon || (() => null);
const MapPinIcon = window.MapPinIcon || (() => null);
const CheckCircleIcon = window.CheckCircleIcon || (() => null);
const ExternalLinkIcon = window.ExternalLinkIcon || (() => null);
const LoaderIcon = window.LoaderIcon || (() => null);
const Modal = window.Modal || (() => null);
const StatusBadge = window.StatusBadge || (({status}) => {status});
// Fallback robuste avec restauration de la soumission native au clavier
const Button = window.Button || (({children, onClick, className, disabled, title, icon: Icon, type}) => {
const handleAction = (e) => {
if (disabled) return;
if (onClick) onClick(e);
if (type === 'submit') {
const form = e.currentTarget.closest('form');
if (form && typeof form.requestSubmit === 'function') form.requestSubmit();
}
};
return (
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleAction(e); } }}
className={`flex items-center justify-center gap-2 cursor-pointer transition ${className || ''} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
title={title}
>
{Icon && }
{children}
{type === 'submit' && }
);
});
// FACTORISATION : Logique métier d'accès aux panneaux
const { visiblePanels } = window.getPanelAccessRights
? window.getPanelAccessRights(data.panneaux || [], myClientData.id)
: { visiblePanels:[] };
const deliveries = visiblePanels.filter(c => c.physicalPanels > 0);
const activeDeliveriesList = deliveries.filter(c => c.shipping_status !== 'Livré');
const historyDeliveriesList = deliveries.filter(c => c.shipping_status === 'Livré');
useEffect(() => {
const url = new URL(window.location);
const confirmDelivery = url.searchParams.get('confirm_delivery');
if (confirmDelivery) {
setDeliveryConfirmModal(confirmDelivery);
url.searchParams.delete('confirm_delivery');
window.history.replaceState({}, '', url);
}
}, []);
const handleConfirmDelivery = async () => {
if (!deliveryConfirmModal) return;
const d = await window.apiFetch('panneaux/shipping', {
body: { id: deliveryConfirmModal, shipping_status: 'Livré' },
setLoading: setIsSaving,
successMessage: "Réception confirmée, merci !"
});
if (d) {
refreshData();
setDeliveryConfirmModal(null);
}
};
const DeliveryCard = ({ c, isHistory }) => {
const isShipped = c.shipping_status === 'Expédié';
const isWaiting = c.shipping_status === 'Validation en cours' || c.shipping_status === "Attente d'impression";
const displayStatus = c.shipping_status === "Attente d'impression" ? "Validée (en attente d'impression)" : c.shipping_status;
return (
{c.name}
{c.shippingAddress || c.location}
Quantité
{c.physicalPanels}
{c.tracking_number && (
)}
{isWaiting && (
Votre commande est en cours de traitement par nos équipes. Vous recevrez un lien de suivi dès l'expédition.
)}
{isShipped && !isHistory && (
setDeliveryConfirmModal(c.id)} disabled={isSaving} className="w-full py-3 text-sm">
Confirmer la réception
)}
);
};
return (
Livraisons
Suivi de vos commandes de panneaux physiques sur eco-panneau.fr.
{deliveries.length === 0 ? (
Aucune livraison
Aucune commande de panneaux physiques associée à vos chantiers.
) : (
<>
{activeDeliveriesList.length > 0 && (
En cours ({activeDeliveriesList.length})
{/* 3 colonnes décalées à 2xl */}
{activeDeliveriesList.map(c => )}
)}
{historyDeliveriesList.length > 0 && (
Historique ({historyDeliveriesList.length})
{/* 3 colonnes décalées à 2xl */}
{historyDeliveriesList.map(c => )}
)}
>
)}
{/* MODALE DE CONFIRMATION DE LIVRAISON */}
{deliveryConfirmModal && Modal && (
setDeliveryConfirmModal(null)} preventClose={isSaving}>
{PackageIcon && } Colis livré ?
En confirmant, vous indiquez avoir bien reçu l'intégralité de votre commande de panneaux physiques pour ce chantier.
setDeliveryConfirmModal(null)} disabled={isSaving} className="w-full sm:flex-1 py-3 justify-center">
Annuler
Oui, j'ai reçu mon colis
)}
);
};
/* EOF ========== [_www/_react/_clients_panneaux.jsx] */