Adição do campo de busca no mapa
This commit is contained in:
@@ -181,6 +181,7 @@ module Analytics
|
|||||||
endereco: r['endereco_completo'].presence || r['address'].presence,
|
endereco: r['endereco_completo'].presence || r['address'].presence,
|
||||||
unidade: r['contact_name'],
|
unidade: r['contact_name'],
|
||||||
motorista: r['driver'],
|
motorista: r['driver'],
|
||||||
|
veiculo: r['vehicle'],
|
||||||
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'])
|
foto: foto_url(r['foto_da_fachada'])
|
||||||
}
|
}
|
||||||
@@ -235,7 +236,7 @@ module Analytics
|
|||||||
label = conn.quote(Operacao.label(tabela))
|
label = conn.quote(Operacao.label(tabela))
|
||||||
<<~SQL.strip
|
<<~SQL.strip
|
||||||
SELECT #{label} AS operacao,
|
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.checkout, r.planned_date, r.foto_da_fachada,
|
||||||
r.latitude, r.longitude,
|
r.latitude, r.longitude,
|
||||||
r.checkout_latitude, r.checkout_longitude,
|
r.checkout_latitude, r.checkout_longitude,
|
||||||
|
|||||||
@@ -4,7 +4,12 @@
|
|||||||
<div class="mapa-box bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
<div class="mapa-box bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
|
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
|
||||||
<p class="text-gray-400 text-sm font-medium">Mapa de Entregas</p>
|
<p class="text-gray-400 text-sm font-medium">Mapa de Entregas</p>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<input type="search" class="mapa-busca w-56 max-w-full bg-[#1a1a1a] border border-white/10 rounded-lg px-3 py-1.5 text-xs text-gray-200 placeholder-gray-500 focus:outline-none focus:border-orange-500"
|
||||||
|
placeholder="Buscar NF, paciente ou veículo…" autocomplete="off">
|
||||||
|
<span class="mapa-busca-info text-xs text-gray-500"></span>
|
||||||
|
</div>
|
||||||
<div class="map-toggle inline-flex rounded-lg overflow-hidden border border-white/10 text-xs">
|
<div class="map-toggle inline-flex rounded-lg overflow-hidden border border-white/10 text-xs">
|
||||||
<button type="button" data-view="heat"
|
<button type="button" data-view="heat"
|
||||||
class="px-3 py-1.5 bg-orange-500 text-black font-semibold">🔥 Calor</button>
|
class="px-3 py-1.5 bg-orange-500 text-black font-semibold">🔥 Calor</button>
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
.flatpickr-months .flatpickr-prev-month svg, .flatpickr-months .flatpickr-next-month svg { fill: #f97316; }
|
.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 */
|
/* O mapa do Leaflet usa fundo claro — moldura escura ao redor */
|
||||||
.leaflet-container { background: #1a1a1a; }
|
.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); }
|
||||||
</style>
|
</style>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
@@ -356,6 +358,7 @@
|
|||||||
linha('📍', p.endereco) +
|
linha('📍', p.endereco) +
|
||||||
linha('🏥', p.unidade) +
|
linha('🏥', p.unidade) +
|
||||||
linha('🚚', p.motorista) +
|
linha('🚚', p.motorista) +
|
||||||
|
linha('🚗', p.veiculo) +
|
||||||
linha('🕒', p.checkout) +
|
linha('🕒', p.checkout) +
|
||||||
foto +
|
foto +
|
||||||
'</div>';
|
'</div>';
|
||||||
@@ -389,7 +392,8 @@
|
|||||||
// Camadas base (todas grátis, sem chave): escuro (padrão, combina com o
|
// Camadas base (todas grátis, sem chave): escuro (padrão, combina com o
|
||||||
// tema), claro e satélite.
|
// tema), claro e satélite.
|
||||||
const escuro = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
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', {
|
const claro = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
attribution: '© OpenStreetMap', maxZoom: 19
|
attribution: '© OpenStreetMap', maxZoom: 19
|
||||||
@@ -412,21 +416,26 @@
|
|||||||
// O popup é uma FUNÇÃO, então o HTML da foto só é montado/baixado quando
|
// 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.
|
// o balão é clicado — nada de imagem é requisitado ao carregar o mapa.
|
||||||
let marcadores = null;
|
let marcadores = null;
|
||||||
|
let entradas = []; // [{ marker, p }] — para a busca filtrar sem recriar
|
||||||
|
const resultadosBusca = L.layerGroup();
|
||||||
function construirMarcadores() {
|
function construirMarcadores() {
|
||||||
if (marcadores) return marcadores;
|
if (marcadores) return marcadores;
|
||||||
marcadores = L.layerGroup();
|
marcadores = L.layerGroup();
|
||||||
pontos.forEach(function (p) {
|
pontos.forEach(function (p) {
|
||||||
L.marker([p.lat, p.lng], { icon: ICONE })
|
const marker = L.marker([p.lat, p.lng], { icon: ICONE })
|
||||||
.bindPopup(function () { return popupHtml(p); }, { maxWidth: 260 })
|
.bindPopup(function () { return popupHtml(p); }, { maxWidth: 260 });
|
||||||
.addTo(marcadores);
|
marker.addTo(marcadores);
|
||||||
|
entradas.push({ marker: marker, p: p });
|
||||||
});
|
});
|
||||||
return marcadores;
|
return marcadores;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Começa no Calor (cai para Pontos se não houver calor)
|
// Começa no Calor (cai para Pontos se não houver calor)
|
||||||
|
let vistaAtual = heatLayer ? 'heat' : 'pontos';
|
||||||
(heatLayer || construirMarcadores()).addTo(mapa);
|
(heatLayer || construirMarcadores()).addTo(mapa);
|
||||||
|
|
||||||
function mostrar(view) {
|
function mostrar(view) {
|
||||||
|
vistaAtual = view;
|
||||||
if (heatLayer) mapa.removeLayer(heatLayer);
|
if (heatLayer) mapa.removeLayer(heatLayer);
|
||||||
if (marcadores) mapa.removeLayer(marcadores);
|
if (marcadores) mapa.removeLayer(marcadores);
|
||||||
if (view === 'pontos') { construirMarcadores().addTo(mapa); }
|
if (view === 'pontos') { construirMarcadores().addTo(mapa); }
|
||||||
@@ -441,9 +450,62 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
box.querySelectorAll('.map-toggle button').forEach(function (b) {
|
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)
|
// Tela cheia (Fullscreen API sobre o container do mapa)
|
||||||
const btnFs = box.querySelector('.map-fullscreen');
|
const btnFs = box.querySelector('.map-fullscreen');
|
||||||
if (btnFs) {
|
if (btnFs) {
|
||||||
|
|||||||
Reference in New Issue
Block a user