// mb-discover.jsx — Descobrir (map + filters + radius + list) // ── Centros de cada região (lat, lng) ──────────────────────── const REGION_CENTERS = { "Todas": [-10.9472, -37.0731], "Aracaju": [-10.9472, -37.0731], "Barra dos Coqueiros": [-10.9098, -37.0395], "Zona de Expansão": [-11.0500, -37.1200], }; // ── Mapa Leaflet real ──────────────────────────────────────── function MapCanvas({ partners, radiusKm, regionFilter, userPos, selectedId, onSelect }) { const mapDiv = React.useRef(null); const mapInst = React.useRef(null); const markers = React.useRef({}); const circle = React.useRef(null); const userMk = React.useRef(null); // Inicializa o mapa uma única vez React.useEffect(() => { if (!window.L || !mapDiv.current || mapInst.current) return; const center = REGION_CENTERS[regionFilter || "Todas"] || REGION_CENTERS["Aracaju"]; const map = L.map(mapDiv.current, { center, zoom: 14, zoomControl: false, attributionControl: false, }); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19 }).addTo(map); mapInst.current = map; return () => { map.remove(); mapInst.current = null; }; }, []); // Navega quando muda região (explícita) React.useEffect(() => { if (!mapInst.current) return; const c = REGION_CENTERS[regionFilter || "Todas"] || REGION_CENTERS["Aracaju"]; mapInst.current.flyTo(c, 14, { duration: 1 }); }, [regionFilter]); // Voa para posição do usuário quando GPS resolve e nenhuma região está fixada React.useEffect(() => { if (!mapInst.current || !userPos || (regionFilter && regionFilter !== "Todas")) return; mapInst.current.flyTo([userPos.lat, userPos.lng], 13, { duration: 1.2 }); }, [userPos?.lat, userPos?.lng]); // Atualiza marcadores quando parceiros mudam React.useEffect(() => { if (!mapInst.current || !window.L) return; const map = mapInst.current; Object.values(markers.current).forEach(m => map.removeLayer(m)); markers.current = {}; partners.forEach(p => { const lat = p._raw?.latitude || p.latitude; const lng = p._raw?.longitude || p.longitude; if (!lat || !lng) return; const sel = p.id === selectedId; const color = p.color || "#1E63C8"; const icon = L.divIcon({ className: "", html: `
${sel ? `
${p.name}
` : ""}
`, iconSize: [38, 46], iconAnchor: [19, 46], }); const mk = L.marker([lat, lng], { icon }).addTo(map).on("click", () => onSelect(p.id)); markers.current[p.id] = mk; }); }, [partners, selectedId]); // Círculo de raio React.useEffect(() => { if (!mapInst.current || !window.L) return; if (circle.current) mapInst.current.removeLayer(circle.current); const c = REGION_CENTERS[regionFilter || "Todas"] || REGION_CENTERS["Aracaju"]; const lat = userPos?.lat || c[0]; const lng = userPos?.lng || c[1]; circle.current = L.circle([lat, lng], { radius: radiusKm * 1000, color: "#1E63C8", fillColor: "#1E63C8", fillOpacity: 0.07, weight: 1.5, dashArray: "6 4", interactive: false, // não intercepta eventos de ponteiro }).addTo(mapInst.current); }, [radiusKm, regionFilter, userPos]); // Marcador do usuário React.useEffect(() => { if (!mapInst.current || !window.L || !userPos) return; if (userMk.current) mapInst.current.removeLayer(userMk.current); const icon = L.divIcon({ className: "", html: `
`, iconSize: [18, 18], iconAnchor: [9, 9], }); userMk.current = L.marker([userPos.lat, userPos.lng], { icon }).addTo(mapInst.current); }, [userPos]); return
; } // ── Partner card (compact carousel + full list) ────────────── function PartnerCard({ p, onOpen, variant = "list" }) { const compact = variant === "carousel"; return ( ); } function DescobrirScreen({ filt, setFilt, segments, onOpenStore, openFilters, onNovoLar, onServices, onGastro, onCachePartners }) { const segs = segments || SEGMENTS; const [view, setView] = React.useState("mapa"); const [moreSegsOpen, setMoreSegsOpen] = React.useState(false); const [sel, setSel] = React.useState(null); const [userPos, setUserPos] = React.useState(null); const [query, setQuery] = React.useState(""); const carRef = React.useRef(null); const searchRef = React.useRef(null); // Geolocalização do usuário React.useEffect(() => { if (!navigator.geolocation) return; navigator.geolocation.getCurrentPosition( pos => setUserPos({ lat: pos.coords.latitude, lng: pos.coords.longitude }), () => {} // silencia erros de permissão ); }, []); // Dados reais da API const [apiPartners, setApiPartners] = React.useState(null); const [loading, setLoading] = React.useState(false); React.useEffect(() => { setLoading(true); const params = {}; if (filt.seg) params.segmentId = filt.seg; if (filt.cat) params.categoryId = filt.cat; if (filt.mode && filt.mode !== "todos") params.mode = filt.mode; const regionFixed = filt.region && filt.region !== "Todas"; if (regionFixed) { // Região escolhida manualmente → centra a busca no centro da região, não no GPS const [lat, lng] = REGION_CENTERS[filt.region] || REGION_CENTERS["Aracaju"]; params.lat = lat; params.lng = lng; params.radius = Math.max(filt.radius || 10, 30); // raio mín. 30 km para cobrir a região } else if (userPos?.lat != null) { // "Todas" + GPS → busca pela posição real do usuário params.lat = userPos.lat; params.lng = userPos.lng; params.radius = filt.radius || 10; } else { // Sem GPS e sem região → fallback para Aracaju const [lat, lng] = REGION_CENTERS["Aracaju"]; params.lat = lat; params.lng = lng; params.radius = 30; } API.discover(params) .then(d => { let mapped = (d.companies || []).map(apiCompanyToPartner); // Recalcula distâncias client-side se tiver posição do usuário if (userPos?.lat) mapped = recalcDistances(mapped, userPos.lat, userPos.lng); setApiPartners(mapped); if (onCachePartners) onCachePartners(mapped); }) .catch(() => setApiPartners(null)) .finally(() => setLoading(false)); }, [filt.region, filt.seg, filt.cat, filt.mode, filt.radius, userPos?.lat, userPos?.lng]); // Quando userPos chega depois dos parceiros já carregados, recalcula client-side React.useEffect(() => { if (!userPos?.lat || !apiPartners) return; setApiPartners(prev => recalcDistances(prev, userPos.lat, userPos.lng)); }, [userPos?.lat, userPos?.lng]); // Fallback para mock enquanto carrega const rawList = apiPartners !== null ? apiPartners : PARTNERS; const list = React.useMemo(() => { const q = query.trim().toLowerCase(); const base = rawList.filter(p => { if (filt.region !== "Todas" && p.region !== filt.region) return false; if (p.hub) return false; if (q && !p.name.toLowerCase().includes(q) && !(p.cat || "").toLowerCase().includes(q) && !(p.area || "").toLowerCase().includes(q)) return false; return true; }); return base.sort((a, b) => planRank(b) - planRank(a) || b.rating - a.rating); }, [rawList, filt.region, query]); // Detecta usuário fora da área de cobertura — baseado só na distância GPS, // não em list.length (que pode estar populado com mock quando API falha) const outOfCoverage = React.useMemo(() => { if (!userPos?.lat || filt.region !== "Todas") return false; const minDist = Math.min(...Object.values(REGION_CENTERS).map(([lat, lng]) => haversineKm(userPos.lat, userPos.lng, lat, lng) )); return minDist > 50; }, [userPos?.lat, userPos?.lng, filt.region]); // Quando fora da cobertura, não exibe parceiros (independente de mock/API) const displayList = outOfCoverage ? [] : list; const select = (id) => { setSel(id); const car = carRef.current; const el = car && car.querySelector(`[data-pid="${id}"]`); if (car && el) { const cr = car.getBoundingClientRect(); const er = el.getBoundingClientRect(); const delta = (er.left + er.width / 2) - (cr.left + cr.width / 2); car.scrollBy({ left: delta, behavior: "smooth" }); } }; const activeChips = (filt.seg ? 1 : 0) + (filt.cat ? 1 : 0) + (filt.mode !== "todos" ? 1 : 0); return (
{view === "mapa" ? :
} {/* Top floating controls */}
setQuery(e.target.value)} placeholder="Buscar parceiros e produtos" style={{ flex: 1, border: "none", outline: "none", background: "transparent", font: "500 14.5px var(--ui)", color: "var(--text)", }} /> {query && ( )}
{/* region + segment chips — 9 visíveis + botão "Ver mais" */}
{filt.region} {segs.slice(0, 9).map(s => ( setFilt(f => ({ ...f, seg: f.seg === s.id ? null : s.id, cat: null }))} style={{ boxShadow: "0 3px 10px rgba(16,18,45,.08)" }}>{s.question || s.q} ))} {segs.length > 9 && (() => { const extraActive = filt.seg && segs.slice(9).some(s => s.id === filt.seg); const activeSeg = extraActive ? segs.find(s => s.id === filt.seg) : null; return ( setMoreSegsOpen(true)} icon={activeSeg ? (activeSeg.icon || segIcon(activeSeg.id)) : "grid"} style={{ boxShadow: "0 3px 10px rgba(16,18,45,.08)", flexShrink: 0 }}> {activeSeg ? (activeSeg.question || activeSeg.q || "") : "Ver mais"} ); })()}
{/* Modal "mais segmentos" */} {moreSegsOpen && (
setMoreSegsOpen(false)} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,.45)", backdropFilter: "blur(3px)" }} />
Mais segmentos
{segs.slice(9).map(s => ( { setFilt(f => ({ ...f, seg: f.seg === s.id ? null : s.id, cat: null })); setMoreSegsOpen(false); }}> {s.question || s.q} ))}
{filt.seg && segs.slice(9).some(s => s.id === filt.seg) && ( )}
)}
{/* Floating button — only in list mode (back to map) */} {view === "lista" && (
)} {/* Bottom content */} {view === "mapa" ? (
{/* control row above the carousel */}
select(list[0]?.id)} size={40} color="var(--accent)" />
{displayList.length} parceiros por perto
{displayList.map(p => (
setSel(p.id)}>
))} {displayList.length === 0 && ( outOfCoverage ? (
Você está fora da nossa área
Ainda não temos parceiros por aí. Nossos parceiros estão em Aracaju, Barra dos Coqueiros e Zona de Expansão.
) : (
Nenhum parceiro nesse raio. Aumente o raio ou troque os filtros.
) )}
) : (
{displayList.length} parceiros encontrados
{displayList.map(p => )} {displayList.length === 0 && ( outOfCoverage ? (
Você está fora da nossa área de cobertura
Ainda não temos parceiros por aí. Atualmente cobrimos Aracaju, Barra dos Coqueiros e Zona de Expansão.
) : (
Nenhum parceiro encontrado com esses filtros.
) )}
)}
); } Object.assign(window, { MapCanvas, PartnerCard, DescobrirScreen });