/** * Oveyo PWA — Page Carte (Leaflet réelle + filtres produits + liste boutiques) * V3.2 : boutiques cliquables (liste + markers) → ouvre ShopDetailSheet */ const { useState, useEffect, useRef } = React; const Lu2 = window.LucideReact || {}; const { Card: CardC, IconBubble: IB, Spinner: Sp, EmptyState: ES } = window.OveyoUI; const { useShops: uShops, useCatalog: uCatalog } = window.OveyoState; const MapPage = ({ state, setActiveTab, onOpenSheet }) => { const { Store, Check, ShieldCheck, Clock, MapPin, Filter, ChevronRight } = Lu2; const { products } = uCatalog(); const [selectedProduct, setSelectedProduct] = useState('sucre'); const [neighborhoodFilter, setNeighborhoodFilter] = useState(state.user.neighborhood_slug || ''); const { shops, loading } = uShops(selectedProduct, neighborhoodFilter); const mapRef = useRef(null); const mapInstance = useRef(null); const markersGroup = useRef(null); // ref vers la dernière callback onOpenSheet — pour qu'elle soit accessible // depuis l'event handler des markers Leaflet sans recréer toute la carte const onOpenSheetRef = useRef(onOpenSheet); useEffect(() => { onOpenSheetRef.current = onOpenSheet; }, [onOpenSheet]); // Init Leaflet map (une seule fois) useEffect(() => { if (!mapRef.current || mapInstance.current) return; if (typeof L === 'undefined') return; const map = L.map(mapRef.current, { center: [14.7359, -17.4631], zoom: 13, zoomControl: false, attributionControl: false, }); L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap contributors, © CARTO', }).addTo(map); L.control.zoom({ position: 'topright' }).addTo(map); L.control.attribution({ position: 'bottomleft', prefix: false }).addTo(map); mapInstance.current = map; markersGroup.current = L.layerGroup().addTo(map); // Géolocaliser l'utilisateur if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (pos) => { map.setView([pos.coords.latitude, pos.coords.longitude], 14); L.circleMarker([pos.coords.latitude, pos.coords.longitude], { radius: 8, fillColor: '#F4632B', color: 'white', weight: 3, fillOpacity: 1, }).addTo(map).bindPopup('Tu es ici'); }, () => {}, { timeout: 5000 } ); } return () => { map.remove(); mapInstance.current = null; }; }, []); // Mettre à jour les marqueurs quand les boutiques changent useEffect(() => { if (!mapInstance.current || !markersGroup.current) return; markersGroup.current.clearLayers(); shops.forEach((s) => { if (!s.lat || !s.lng) return; const color = s.is_abuse ? '#F4632B' : s.stock === 'rupture' ? '#6B7891' : '#102A43'; const label = s.price ? `${s.price} F` : '—'; const icon = L.divIcon({ className: 'price-marker', html: `
${label}
`, iconSize: [40, 30], iconAnchor: [20, 30], }); const marker = L.marker([s.lat, s.lng], { icon }).addTo(markersGroup.current); // C2 : click sur marker → ouvre ShopDetailSheet (au lieu d'un popup) marker.on('click', () => { if (typeof onOpenSheetRef.current === 'function' && s.id) { onOpenSheetRef.current('shop-detail', { shop_id: s.id }); } }); }); // Adapter le zoom aux marqueurs si on en a if (shops.length > 0) { const validShops = shops.filter((s) => s.lat && s.lng); if (validShops.length > 0) { const bounds = L.latLngBounds(validShops.map((s) => [s.lat, s.lng])); mapInstance.current.fitBounds(bounds, { padding: [40, 40], maxZoom: 15 }); } } }, [shops]); return (
{/* Carte */}
{/* Filtres */}
{products.map((p) => ( ))}
{/* Liste boutiques */} {loading ? (
) : shops.length === 0 ? ( ) : (
{shops.map((s, i) => (
{/* Ligne principale — devient un BOUTON CLIQUABLE */} {!s.verified && ( )} {!!s.is_abuse && (
Boutique invitée à justifier — réponse en attente
)}
))}
)}
); }; window.OveyoPages = window.OveyoPages || {}; window.OveyoPages.MapPage = MapPage;