teste #3
@@ -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 <img src> 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)}
|
||||
|
||||
@@ -341,8 +341,12 @@
|
||||
|
||||
function popupHtml(p) {
|
||||
const linha = (icone, val) => val ? ('<div>' + icone + ' ' + escapeHtml(val) + '</div>') : '';
|
||||
const foto = p.foto
|
||||
? '<img src="' + escapeHtml(p.foto) + '" alt="Fachada" loading="lazy" ' +
|
||||
'style="margin-top:8px;width:100%;max-height:150px;object-fit:cover;border-radius:6px">'
|
||||
: '';
|
||||
return '' +
|
||||
'<div style="min-width:200px">' +
|
||||
'<div style="min-width:210px;max-width:240px">' +
|
||||
'<div style="font-weight:700;color:#f97316">NF ' + escapeHtml(p.nf) + '</div>' +
|
||||
'<div style="font-weight:600;margin-bottom:4px">' + escapeHtml(p.nome) + '</div>' +
|
||||
'<div style="color:#16a34a;font-size:11px;margin-bottom:6px">✔ Entrega realizada</div>' +
|
||||
@@ -350,11 +354,27 @@
|
||||
linha('🏥', p.unidade) +
|
||||
linha('🚚', p.motorista) +
|
||||
linha('🕒', p.checkout) +
|
||||
foto +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Ícone de localização (balão) — laranja da marca, sem depender de imagem externa.
|
||||
function pinIcon() {
|
||||
return L.divIcon({
|
||||
className: 'pin-entrega',
|
||||
html: '<svg width="28" height="40" viewBox="0 0 28 40" xmlns="http://www.w3.org/2000/svg">' +
|
||||
'<path d="M14 0C6.8 0 1 5.8 1 13c0 9.7 13 27 13 27s13-17.3 13-27C27 5.8 21.2 0 14 0z" ' +
|
||||
'fill="#f97316" stroke="#0a0a0a" stroke-width="1.5"/>' +
|
||||
'<circle cx="14" cy="13" r="5" fill="#0a0a0a"/></svg>',
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user