From 4b3f80a2e0293c3bde90d0a3cf5afe828ad3bdd964df7759232a70218d62cc93 Mon Sep 17 00:00:00 2001 From: victor Date: Tue, 30 Jun 2026 10:23:04 -0300 Subject: [PATCH] =?UTF-8?q?Adi=C3=A7=C3=A3o=20do=20campo=20de=20busca=20no?= =?UTF-8?q?=20mapa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/analytics/operacao_metricas.rb | 3 +- app/views/operacoes_dashboard/_mapa.html.erb | 7 +- app/views/operacoes_dashboard/index.html.erb | 72 ++++++++++++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/app/services/analytics/operacao_metricas.rb b/app/services/analytics/operacao_metricas.rb index 7a19cc1..aad45f3 100644 --- a/app/services/analytics/operacao_metricas.rb +++ b/app/services/analytics/operacao_metricas.rb @@ -181,6 +181,7 @@ module Analytics endereco: r['endereco_completo'].presence || r['address'].presence, unidade: r['contact_name'], motorista: r['driver'], + veiculo: r['vehicle'], checkout: data_de(r['checkout'])&.strftime('%d/%m/%Y'), foto: foto_url(r['foto_da_fachada']) } @@ -235,7 +236,7 @@ module Analytics label = conn.quote(Operacao.label(tabela)) <<~SQL.strip SELECT #{label} AS operacao, - r.reference_id, r.driver, r.status, r.observation, r.contact_name, r.address, + r.reference_id, r.driver, r.vehicle, r.status, r.observation, r.contact_name, r.address, r.checkout, r.planned_date, r.foto_da_fachada, r.latitude, r.longitude, r.checkout_latitude, r.checkout_longitude, diff --git a/app/views/operacoes_dashboard/_mapa.html.erb b/app/views/operacoes_dashboard/_mapa.html.erb index 027bdea..301f472 100644 --- a/app/views/operacoes_dashboard/_mapa.html.erb +++ b/app/views/operacoes_dashboard/_mapa.html.erb @@ -4,7 +4,12 @@

Mapa de Entregas

-
+
+
+ + +
diff --git a/app/views/operacoes_dashboard/index.html.erb b/app/views/operacoes_dashboard/index.html.erb index a0fa2bb..3612f7c 100644 --- a/app/views/operacoes_dashboard/index.html.erb +++ b/app/views/operacoes_dashboard/index.html.erb @@ -24,6 +24,8 @@ .flatpickr-months .flatpickr-prev-month svg, .flatpickr-months .flatpickr-next-month svg { fill: #f97316; } /* O mapa do Leaflet usa fundo claro — moldura escura ao redor */ .leaflet-container { background: #1a1a1a; } + /* Realça ruas e nomes no tile escuro (CARTO dark_all tem baixo contraste) */ + .tiles-escuro-contraste { filter: brightness(1.45) contrast(1.2) saturate(0.9); } <% end %> @@ -356,6 +358,7 @@ linha('📍', p.endereco) + linha('🏥', p.unidade) + linha('🚚', p.motorista) + + linha('🚗', p.veiculo) + linha('🕒', p.checkout) + foto + '
'; @@ -389,7 +392,8 @@ // 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 + attribution: '© OpenStreetMap © CARTO', subdomains: 'abcd', maxZoom: 20, + className: 'tiles-escuro-contraste' }); const claro = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap', maxZoom: 19 @@ -412,21 +416,26 @@ // O popup é uma FUNÇÃO, então o HTML da foto só é montado/baixado quando // o balão é clicado — nada de imagem é requisitado ao carregar o mapa. let marcadores = null; + let entradas = []; // [{ marker, p }] — para a busca filtrar sem recriar + const resultadosBusca = L.layerGroup(); function construirMarcadores() { if (marcadores) return marcadores; marcadores = L.layerGroup(); pontos.forEach(function (p) { - L.marker([p.lat, p.lng], { icon: ICONE }) - .bindPopup(function () { return popupHtml(p); }, { maxWidth: 260 }) - .addTo(marcadores); + const marker = L.marker([p.lat, p.lng], { icon: ICONE }) + .bindPopup(function () { return popupHtml(p); }, { maxWidth: 260 }); + marker.addTo(marcadores); + entradas.push({ marker: marker, p: p }); }); return marcadores; } // Começa no Calor (cai para Pontos se não houver calor) + let vistaAtual = heatLayer ? 'heat' : 'pontos'; (heatLayer || construirMarcadores()).addTo(mapa); function mostrar(view) { + vistaAtual = view; if (heatLayer) mapa.removeLayer(heatLayer); if (marcadores) mapa.removeLayer(marcadores); if (view === 'pontos') { construirMarcadores().addTo(mapa); } @@ -441,9 +450,62 @@ } box.querySelectorAll('.map-toggle button').forEach(function (b) { - b.addEventListener('click', function () { mostrar(b.dataset.view); }); + b.addEventListener('click', function () { + const inputBusca = box.querySelector('.mapa-busca'); + if (inputBusca && inputBusca.value) { inputBusca.value = ''; } + limparBusca(); + mostrar(b.dataset.view); + }); }); + // ── Busca por NF / paciente / veículo ── + const inputBusca = box.querySelector('.mapa-busca'); + const infoBusca = box.querySelector('.mapa-busca-info'); + + function limparBusca() { + mapa.removeLayer(resultadosBusca); + resultadosBusca.clearLayers(); + if (infoBusca) infoBusca.textContent = ''; + } + + function aplicarBusca(termo) { + const q = String(termo || '').trim().toLowerCase(); + if (!q) { + limparBusca(); + mostrar(vistaAtual); // restaura a visão padrão (Calor ou Pontos) + return; + } + construirMarcadores(); + if (heatLayer) mapa.removeLayer(heatLayer); + if (marcadores) mapa.removeLayer(marcadores); + resultadosBusca.clearLayers(); + + const casa = function (v) { return String(v == null ? '' : v).toLowerCase().indexOf(q) !== -1; }; + const achados = entradas.filter(function (e) { + return casa(e.p.nf) || casa(e.p.nome) || casa(e.p.veiculo); + }); + achados.forEach(function (e) { e.marker.addTo(resultadosBusca); }); + resultadosBusca.addTo(mapa); + + if (achados.length) { + const grupo = L.featureGroup(achados.map(function (e) { return e.marker; })); + mapa.fitBounds(grupo.getBounds(), { padding: [40, 40], maxZoom: 16 }); + if (achados.length === 1) { achados[0].marker.openPopup(); } + if (infoBusca) infoBusca.textContent = achados.length + (achados.length === 1 ? ' resultado' : ' resultados'); + } else { + if (infoBusca) infoBusca.textContent = 'Nenhum resultado'; + } + } + + if (inputBusca) { + let debounce = null; + inputBusca.addEventListener('input', function () { + clearTimeout(debounce); + const valor = inputBusca.value; + debounce = setTimeout(function () { aplicarBusca(valor); }, 200); + }); + } + // Tela cheia (Fullscreen API sobre o container do mapa) const btnFs = box.querySelector('.map-fullscreen'); if (btnFs) {