From 4c035d4a4cabc62721424dced21d1efc3bc76436f783afebfb3a96321dfc9943 Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 29 Jun 2026 21:16:22 -0300 Subject: [PATCH] =?UTF-8?q?Altera=C3=A7=C3=A3o=20do=20Mapa=20com=20os=20po?= =?UTF-8?q?ntos=20da=20onde=20foi=20entreque?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/analytics/operacao_metricas.rb | 11 ++++- app/views/operacoes_dashboard/index.html.erb | 51 ++++++++++++++++---- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/app/services/analytics/operacao_metricas.rb b/app/services/analytics/operacao_metricas.rb index 744a6ed..7a19cc1 100644 --- a/app/services/analytics/operacao_metricas.rb +++ b/app/services/analytics/operacao_metricas.rb @@ -181,7 +181,8 @@ module Analytics endereco: r['endereco_completo'].presence || r['address'].presence, unidade: r['contact_name'], motorista: r['driver'], - checkout: data_de(r['checkout'])&.strftime('%d/%m/%Y') + checkout: data_de(r['checkout'])&.strftime('%d/%m/%Y'), + foto: foto_url(r['foto_da_fachada']) } end.first(MAX_PONTOS) end @@ -218,6 +219,12 @@ module Analytics nil end + # Só aceita URL http(s) — evita injetar lixo no do popup. + def foto_url(valor) + url = valor.to_s.strip + url.match?(%r{\Ahttps?://}i) ? url : nil + end + def carregar return [] if @tabelas.empty? @@ -229,7 +236,7 @@ module Analytics <<~SQL.strip SELECT #{label} AS operacao, r.reference_id, r.driver, r.status, r.observation, r.contact_name, r.address, - r.checkout, r.planned_date, + r.checkout, r.planned_date, r.foto_da_fachada, r.latitude, r.longitude, r.checkout_latitude, r.checkout_longitude, #{selects_gade(conn, tabela)} diff --git a/app/views/operacoes_dashboard/index.html.erb b/app/views/operacoes_dashboard/index.html.erb index c6cc1d4..f9c05ed 100644 --- a/app/views/operacoes_dashboard/index.html.erb +++ b/app/views/operacoes_dashboard/index.html.erb @@ -341,8 +341,12 @@ function popupHtml(p) { const linha = (icone, val) => val ? ('
' + icone + ' ' + escapeHtml(val) + '
') : ''; + const foto = p.foto + ? 'Fachada' + : ''; return '' + - '
' + + '
' + '
NF ' + escapeHtml(p.nf) + '
' + '
' + escapeHtml(p.nome) + '
' + '
✔ Entrega realizada
' + @@ -350,11 +354,27 @@ linha('🏥', p.unidade) + linha('🚚', p.motorista) + linha('🕒', p.checkout) + + foto + '
'; } + // Ícone de localização (balão) — laranja da marca, sem depender de imagem externa. + function pinIcon() { + return L.divIcon({ + className: 'pin-entrega', + html: '' + + '' + + '', + iconSize: [28, 40], + iconAnchor: [14, 40], + popupAnchor: [0, -36] + }); + } + function initMapas() { if (typeof L === 'undefined') return; + const ICONE = pinIcon(); document.querySelectorAll('.mapa-box').forEach(function (box) { const el = box.querySelector('.mapa-calor'); if (!el || el._leaflet_id) return; @@ -362,22 +382,35 @@ const heat = JSON.parse(el.dataset.heat || '[]'); const pontos = JSON.parse(el.dataset.pontos || '[]'); const centro = JSON.parse(el.dataset.centro || '[-23.55,-46.63]'); - const mapa = L.map(el).setView(centro, 11); - L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + + // Camadas base (todas grátis, sem chave): escuro (padrão, combina com o + // tema), claro e satélite. + const escuro = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { + attribution: '© OpenStreetMap © CARTO', subdomains: 'abcd', maxZoom: 20 + }); + const claro = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap', maxZoom: 19 - }).addTo(mapa); + }); + const satelite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { + attribution: 'Tiles © Esri', maxZoom: 19 + }); + + const mapa = L.map(el, { layers: [escuro] }).setView(centro, 11); + L.control.layers( + { '🌙 Escuro': escuro, '🗺️ Claro': claro, '🛰️ Satélite': satelite }, + {}, { position: 'topright', collapsed: true } + ).addTo(mapa); // Camada de calor const heatLayer = (heat.length && L.heatLayer) ? L.heatLayer(heat, { radius: 18, blur: 22, maxZoom: 14 }) : null; - // Camada de pontos (marcadores com popup) + // Camada de pontos (marcadores em balão com popup) const marcadores = L.layerGroup(); pontos.forEach(function (p) { - L.circleMarker([p.lat, p.lng], { - radius: 6, color: '#0a0a0a', weight: 1, - fillColor: '#f97316', fillOpacity: 0.85 - }).bindPopup(popupHtml(p)).addTo(marcadores); + L.marker([p.lat, p.lng], { icon: ICONE }) + .bindPopup(popupHtml(p)) + .addTo(marcadores); }); // Começa no Calor (cai para Pontos se não houver calor)