Atualização dos icones para um modelo chapado

This commit is contained in:
2026-07-20 15:08:09 -03:00
parent 7d3753f9c5
commit 42d06c50a7
34 changed files with 550 additions and 337 deletions

View File

@@ -18,7 +18,7 @@
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
<div>
<h2 class="text-lg font-semibold text-white">📋 Planilha da operação</h2>
<h2 class="text-lg font-semibold text-white flex items-center gap-2"><%= icone :planilha %> Planilha da operação</h2>
<p class="text-gray-400 text-sm">Espelho por NF — pesquise por nota, motorista, unidade, ocorrência, veículo...</p>
</div>
@@ -27,7 +27,7 @@
<input type="hidden" name="<%= k %>" value="<%= v %>">
<% end %>
<div class="relative">
<span class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">🔎</span>
<span class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2"><%= icone :buscar, cor: 'text-gray-500' %></span>
<input type="search" name="q" value="<%= @busca %>" placeholder="Pesquisar..."
class="pl-9 pr-3 py-2 bg-[#0a0a0a] border border-white/10 rounded-xl text-white text-sm
w-52 sm:w-72 focus:outline-none focus:border-orange-500 placeholder-gray-500">
@@ -37,7 +37,7 @@
Buscar
</button>
<% if @busca.present? %>
<%= link_to '✕ Limpar', operacoes_planilha_path(request.query_parameters.except('q', 'pg')),
<%= link_to rotulo(:fechar, 'Limpar', cor: nil, tamanho: 'w-3 h-3'), operacoes_planilha_path(request.query_parameters.except('q', 'pg')),
data: { turbo: false }, class: 'text-xs text-orange-400 hover:text-orange-300 whitespace-nowrap' %>
<% end %>
</form>

View File

@@ -12,12 +12,12 @@
</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>
class="px-3 py-1.5 bg-orange-500 text-black font-semibold"><%= icone :calor, cor: nil, tamanho: 'w-3.5 h-3.5' %> Calor</button>
<button type="button" data-view="pontos"
class="px-3 py-1.5 text-gray-300 hover:text-white">📍 Pontos (<%= pontos.size %>)</button>
class="px-3 py-1.5 text-gray-300 hover:text-white"><%= icone :local, cor: nil, tamanho: 'w-3.5 h-3.5' %> Pontos (<%= pontos.size %>)</button>
</div>
<button type="button" class="map-fullscreen px-3 py-1.5 rounded-lg border border-white/10 text-gray-300 hover:text-white text-xs" title="Ver em tela cheia">
Tela cheia
<%= icone :tela_cheia, cor: nil, tamanho: 'w-3.5 h-3.5' %> Tela cheia
</button>
</div>
</div>
@@ -30,7 +30,7 @@
data-pontos="<%= pontos.to_json %>"
data-centro="<%= metricas.centro_mapa.to_json %>"></div>
<p class="text-xs text-gray-600 mt-2">
🔥 Calor mostra a densidade · 📍 Pontos mostra cada entrega (clique no marcador para detalhes; coordenada de check-out).
<%= icone :calor %> Calor mostra a densidade · <%= icone :local %> Pontos mostra cada entrega (clique no marcador para detalhes; coordenada de check-out).
</p>
<% end %>
</div>

View File

@@ -25,7 +25,7 @@
<p class="text-gray-400 text-sm font-medium mb-1">Total de Entregas</p>
<p class="text-6xl font-black text-white leading-none inline-block origin-left transition-transform duration-200 group-hover:scale-110"><%= metricas.total %></p>
<p class="text-xs text-gray-500 mt-2 h-0 overflow-hidden opacity-0 transition-all duration-200 group-hover:h-4 group-hover:opacity-100">
✅ <%= metricas.sucesso %> · <span class="text-blue-500">●</span> <%= metricas.recusas %> · ⏳ <%= metricas.pendentes %>
<%= icone :sucesso, cor: 'text-green-500', tamanho: 'w-3.5 h-3.5' %> <%= metricas.sucesso %> · <%= icone :ponto, cor: 'text-blue-500', tamanho: 'w-3.5 h-3.5' %> <%= metricas.recusas %> · <%= icone :parcial, cor: 'text-amber-400', tamanho: 'w-3.5 h-3.5' %> <%= metricas.pendentes %>
</p>
</div>
<div class="group bg-[#1a1a1a] rounded-2xl border border-white/5 p-7 transition-all duration-200 hover:border-green-500/40 hover:bg-[#202020] hover:-translate-y-0.5 hover:shadow-lg hover:shadow-black/40">

View File

@@ -30,7 +30,7 @@
data: { turbo: false },
title: (ativo ? 'Clique para remover o filtro' : "Filtrar por #{cel}"),
class: "inline-flex items-center gap-1 hover:text-orange-400 #{'text-orange-400 font-semibold' if ativo}" do %>
<%= cel %><% if ativo %> <span aria-hidden="true">✕</span><% end %>
<%= cel %><% if ativo %> <%= icone :fechar, cor: nil, tamanho: 'w-3 h-3' %><% end %>
<% end %>
<% else %>
<%= cel %>

View File

@@ -52,9 +52,9 @@
<h1 class="text-2xl font-bold text-white">Dashboard de Operações</h1>
<p class="text-gray-400 text-sm mt-0.5">
<% if @modo == 'global' %>
🌐 Global · <%= @periodo_inicio.strftime('%d/%m/%Y') %> <%= @periodo_fim.strftime('%d/%m/%Y') %>
<%= icone :global %> Global · <%= @periodo_inicio.strftime('%d/%m/%Y') %> <%= @periodo_fim.strftime('%d/%m/%Y') %>
<% elsif @modo == 'comparar' %>
⚖️ Comparação entre operações (cada uma no seu próprio período)
<%= icone :comparar %> Comparação entre operações (cada uma no seu próprio período)
<% elsif @metricas&.data_inicio %>
<%= @metricas.operacoes_label %> · <%= @metricas.data_inicio.strftime('%d/%m/%Y') %> <%= @metricas.data_fim.strftime('%d/%m/%Y') %>
<% elsif @operacao %>
@@ -69,7 +69,7 @@
<% if @modo == 'global' %>
<div class="flex flex-wrap items-center gap-2">
<div class="relative">
<span class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-orange-500">📅</span>
<span class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2"><%= icone :calendario %></span>
<input type="text" id="periodo-range" readonly placeholder="Selecione o período"
value="<%= @periodo_inicio.strftime('%d/%m/%Y') %> até <%= @periodo_fim.strftime('%d/%m/%Y') %>"
class="cursor-pointer pl-4 pr-10 py-2.5 bg-[#1a1a1a] border border-white/10 rounded-xl
@@ -89,12 +89,12 @@
</div>
<%# ── Abas de modo + atalho para a planilha ─────────────── %>
<% modos = { 'operacao' => '🏥 Operação', 'global' => '🌐 Global', 'comparar' => '⚖️ Comparar' } %>
<% modos = { 'operacao' => [:operacao, 'Operação'], 'global' => [:global, 'Global'], 'comparar' => [:comparar, 'Comparar'] } %>
<div class="flex flex-wrap items-center justify-between gap-2 border-b border-white/5 pb-3">
<div class="flex flex-wrap items-center gap-2">
<% modos.each do |m, label| %>
<% modos.each do |m, (ic, label)| %>
<% ativo = @modo == m %>
<%= link_to label,
<%= link_to rotulo(ic, label, cor: (ativo ? nil : 'text-brand-laranja')),
operacoes_dashboard_path(ctx_ops.merge(modo: m, inicio: ini_iso, fim: fim_iso)),
data: { turbo: false },
class: "px-4 py-2 rounded-xl text-sm font-semibold whitespace-nowrap #{ativo ? 'bg-orange-500 text-black' : 'bg-[#1a1a1a] text-gray-300 border border-white/10 hover:border-orange-500'}" %>
@@ -106,7 +106,7 @@
.slice('f_driver', 'f_sts', 'f_status', 'f_obs', 'f_resultado', 'f_data')
.merge(modo: (@modo == 'global' ? 'global' : 'operacao'), inicio: ini_iso, fim: fim_iso) %>
<% planilha_ctx[:operacao] = @operacao if @operacao %>
<%= link_to '📋 Planilha da operação', operacoes_planilha_path(planilha_ctx),
<%= link_to rotulo(:planilha, 'Planilha da operação', cor: nil), operacoes_planilha_path(planilha_ctx),
data: { turbo: false },
class: 'px-4 py-2 rounded-xl text-sm font-semibold whitespace-nowrap bg-[#1a1a1a]
text-orange-400 border border-orange-500/40 hover:bg-orange-500 hover:text-black' %>
@@ -125,7 +125,7 @@
<% @chips.each do |chip| %>
<span class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-orange-500/15 border border-orange-500/30 text-orange-300 text-xs">
<%= chip[:rotulo] %>: <strong class="text-white"><%= chip[:display] || chip[:valor] %></strong>
<%= link_to '✕', operacoes_dashboard_path(request.query_parameters.except(chip[:param])),
<%= link_to icone(:fechar, cor: nil, tamanho: 'w-3 h-3'), operacoes_dashboard_path(request.query_parameters.except(chip[:param])),
data: { turbo: false }, class: 'hover:text-white font-bold', title: 'Remover filtro' %>
</span>
<% end %>
@@ -160,7 +160,7 @@
<% elsif @modo == 'global' %>
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-4 text-sm text-gray-300">
🌐 Visão <strong class="text-white">Global</strong> — todas as
<%= icone :global %> Visão <strong class="text-white">Global</strong> — todas as
<%= @tabelas_validas.size %> operações agregadas no período.
</div>
<% if @metricas %>
@@ -208,7 +208,7 @@
<%# Detalhe completo de cada operação (recolhível) %>
<details class="bg-[#1a1a1a] rounded-2xl border border-white/5">
<summary class="cursor-pointer px-6 py-4 text-sm text-gray-300 hover:text-white select-none">
🔎 Ver detalhe completo de cada operação
<%= icone :buscar %> Ver detalhe completo de cada operação
</summary>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6 p-4 pt-0">
<div>
@@ -442,6 +442,12 @@
});
}
// Ícone do sprite (public/icons.svg) — mesmos SVG usados no resto do sistema.
function ico(id, cor) {
return '<svg width="12" height="12" fill="' + (cor || '#f97316') + '" style="vertical-align:-1px">' +
'<use href="/icons.svg#' + id + '"></use></svg>';
}
function popupHtml(p) {
const linha = (icone, val) => val ? ('<div>' + icone + ' ' + escapeHtml(val) + '</div>') : '';
const foto = p.foto
@@ -449,22 +455,22 @@
'<img src="' + escapeHtml(p.foto) + '" alt="Fachada" loading="lazy" ' +
'onerror="this.style.display=\'none\';this.parentNode.nextElementSibling.style.display=\'block\'" ' +
'style="margin-top:8px;width:100%;max-height:160px;object-fit:cover;border-radius:6px;display:block"></a>' +
'<div style="display:none;margin-top:6px;color:#9ca3af;font-size:11px">📷 Foto indisponível (link expirado/sem acesso)</div>'
: '<div style="margin-top:6px;color:#6b7280;font-size:11px">📷 Sem foto da fachada para esta entrega</div>';
'<div style="display:none;margin-top:6px;color:#9ca3af;font-size:11px">' + ico('camera-fill', '#9ca3af') + ' Foto indisponível (link expirado/sem acesso)</div>'
: '<div style="margin-top:6px;color:#6b7280;font-size:11px">' + ico('camera-fill', '#6b7280') + ' Sem foto da fachada para esta entrega</div>';
const statusLinha = p.sucesso
? '<div style="color:#16a34a;font-size:11px;margin-bottom:6px"> Entrega realizada</div>'
: '<div style="color:#3b82f6;font-size:11px;margin-bottom:6px"> Insucesso' +
? '<div style="color:#16a34a;font-size:11px;margin-bottom:6px">' + ico('check-lg', '#16a34a') + ' Entrega realizada</div>'
: '<div style="color:#3b82f6;font-size:11px;margin-bottom:6px">' + ico('x-lg', '#3b82f6') + ' Insucesso' +
(p.motivo ? ' — ' + escapeHtml(p.motivo) : '') + '</div>';
return '' +
'<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>' +
statusLinha +
linha('📍', p.endereco) +
linha('🏥', p.unidade) +
linha('🚚', p.motorista) +
linha('🚗', p.veiculo) +
linha('🕒', p.checkout) +
linha(ico('geo-alt-fill'), p.endereco) +
linha(ico('hospital-fill'), p.unidade) +
linha(ico('truck'), p.motorista) +
linha(ico('car-front-fill'), p.veiculo) +
linha(ico('clock-fill'), p.checkout) +
foto +
'</div>';
}
@@ -513,7 +519,9 @@
const mapa = L.map(el, { layers: [escuro] }).setView(centro, 11);
L.control.layers(
{ '🌙 Escuro': escuro, '🗺️ Claro': claro, '🛰️ Satélite': satelite },
{ [ico('moon-fill') + ' Escuro']: escuro,
[ico('map-fill') + ' Claro']: claro,
[ico('broadcast-pin') + ' Satélite']: satelite },
{}, { position: 'topright', collapsed: true }
).addTo(mapa);

View File

@@ -15,10 +15,10 @@
<%# ── Cabeçalho ─────────────────────────────────────────── %>
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-white">📋 Planilha da Operação</h1>
<h1 class="text-2xl font-bold text-white flex items-center gap-2"><%= icone :planilha %> Planilha da Operação</h1>
<p class="text-gray-400 text-sm mt-0.5">
<% if @modo == 'global' %>
🌐 Global · <%= @periodo_inicio.strftime('%d/%m/%Y') %> <%= @periodo_fim.strftime('%d/%m/%Y') %>
<%= icone :global %> Global · <%= @periodo_inicio.strftime('%d/%m/%Y') %> <%= @periodo_fim.strftime('%d/%m/%Y') %>
<% elsif @metricas&.data_inicio %>
<%= @metricas.operacoes_label %> · <%= @metricas.data_inicio.strftime('%d/%m/%Y') %> <%= @metricas.data_fim.strftime('%d/%m/%Y') %>
<% elsif @operacao %>
@@ -27,7 +27,7 @@
</p>
</div>
<%= link_to 'Dashboard de Operações',
<%= link_to rotulo(:voltar, 'Dashboard de Operações', cor: nil),
operacoes_dashboard_path({ modo: @modo, operacao: @operacao, inicio: ini_iso, fim: fim_iso }.compact),
data: { turbo: false },
class: 'px-4 py-2.5 rounded-xl text-sm whitespace-nowrap bg-[#1a1a1a] text-gray-300 border border-white/10 hover:border-orange-500' %>
@@ -35,10 +35,10 @@
<%# ── Seleção de escopo: operação única ou global ───────── %>
<div class="flex flex-wrap items-center gap-2">
<% pills = { 'operacao' => '🏥 Operação', 'global' => '🌐 Global' } %>
<% pills.each do |m, label| %>
<% pills = { 'operacao' => [:operacao, 'Operação'], 'global' => [:global, 'Global'] } %>
<% pills.each do |m, (ic, label)| %>
<% ativo = @modo == m %>
<%= link_to label,
<%= link_to rotulo(ic, label, cor: (ativo ? nil : 'text-brand-laranja')),
operacoes_planilha_path({ modo: m, operacao: @operacao, inicio: ini_iso, fim: fim_iso, q: @busca.presence }.compact),
data: { turbo: false },
class: "px-4 py-2 rounded-xl text-sm font-semibold whitespace-nowrap #{ativo ? 'bg-orange-500 text-black' : 'bg-[#1a1a1a] text-gray-300 border border-white/10 hover:border-orange-500'}" %>
@@ -64,7 +64,7 @@
<%# Planilha Entregas preenchida (modelo entregue ao cliente) %>
<% if @operacao %>
<%= link_to '⬇️ Baixar Excel preenchido', operacoes_planilha_baixar_path(operacao: @operacao),
<%= link_to rotulo(:baixar, 'Baixar Excel preenchido', cor: nil), operacoes_planilha_baixar_path(operacao: @operacao),
data: { turbo: false },
title: 'Planilha Entregas da operação com status/ocorrências preenchidos pelo rastreio',
class: 'px-4 py-2.5 bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-xl text-sm whitespace-nowrap' %>
@@ -79,7 +79,7 @@
<% @chips.each do |chip| %>
<span class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-orange-500/15 border border-orange-500/30 text-orange-300 text-xs">
<%= chip[:rotulo] %>: <strong class="text-white"><%= chip[:display] || chip[:valor] %></strong>
<%= link_to '✕', operacoes_planilha_path(request.query_parameters.except(chip[:param], 'pg')),
<%= link_to icone(:fechar, cor: nil, tamanho: 'w-3 h-3'), operacoes_planilha_path(request.query_parameters.except(chip[:param], 'pg')),
data: { turbo: false }, class: 'hover:text-white font-bold', title: 'Remover filtro' %>
</span>
<% end %>