teste #3

Merged
victor merged 20 commits from teste into main 2026-07-02 15:31:51 -03:00
3 changed files with 75 additions and 7 deletions
Showing only changes of commit 4b3f80a2e0 - Show all commits

View File

@@ -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,

View File

@@ -4,7 +4,12 @@
<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">
<p class="text-gray-400 text-sm font-medium">Mapa de Entregas</p>
<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">
<button type="button" data-view="heat"
class="px-3 py-1.5 bg-orange-500 text-black font-semibold">🔥 Calor</button>

View File

@@ -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); }
</style>
<% end %>
@@ -356,6 +358,7 @@
linha('📍', p.endereco) +
linha('🏥', p.unidade) +
linha('🚚', p.motorista) +
linha('🚗', p.veiculo) +
linha('🕒', p.checkout) +
foto +
'</div>';
@@ -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,8 +450,61 @@
}
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');