Fases 4, 5 e 6 — Job 1h + Auditoria + Consolidação + Wizard de validação

This commit is contained in:
2026-06-11 12:02:50 -03:00
parent b77fc0122c
commit 90ed705ed8
29 changed files with 1617 additions and 616 deletions

View File

@@ -0,0 +1,74 @@
<%# app/views/consolidacao_entregas/revisar.html.erb — PASSO 3: Revisar %>
<% content_for :title, "Revisar — #{@motorista}" %>
<div class="max-w-3xl mx-auto">
<div class="mb-6">
<%= link_to '← Voltar (Passo 2)',
validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: @motorista),
class: 'text-gray-500 hover:text-orange-500 text-sm' %>
<h1 class="text-2xl font-bold text-white mt-2">📋 Revisão — <%= @motorista %></h1>
<p class="text-gray-400 text-sm"><%= @consolidacao.nome %></p>
</div>
<%# Resumo por tipo %>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6">
<% [
['entrega_normal', 'Normal', 'bg-orange-500 text-black'],
['retirada', 'Retirada', 'bg-orange-800 text-white'],
['bonus', 'Bônus', 'bg-white text-black'],
['desconto', 'Desconto', 'bg-black text-white border border-gray-700']
].each do |tipo, label, cores| %>
<div class="<%= cores %> rounded-xl p-4 text-center">
<p class="font-black text-3xl"><%= @resumo[tipo] || 0 %></p>
<p class="text-sm font-semibold"><%= label %></p>
</div>
<% end %>
</div>
<%# Total %>
<div class="bg-[#1a1a1a] border border-orange-500 rounded-xl p-6 mb-6 text-center">
<p class="text-gray-400 text-sm mb-1">Total a pagar — <%= @motorista %></p>
<p class="text-orange-500 font-black text-4xl"><%= moeda(@total) %></p>
<p class="text-gray-500 text-xs mt-1"><%= @classificacoes.size %> entregas classificadas</p>
</div>
<%# Detalhe das classificações %>
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl overflow-hidden mb-6">
<table class="w-full text-sm">
<thead class="bg-[#0a0a0a] text-gray-400">
<tr>
<th class="text-left px-4 py-3">Tracking / NF</th>
<th class="text-left px-4 py-3">Tipo</th>
<th class="text-right px-4 py-3">Valor</th>
</tr>
</thead>
<tbody class="divide-y divide-[#2a2a2a]">
<% @classificacoes.each do |c| %>
<tr>
<td class="px-4 py-3 text-white font-mono text-xs"><%= c.tracking_id %></td>
<td class="px-4 py-3">
<span class="<%= c.tipo_cor[:bg] %> <%= c.tipo_cor[:text] %> px-2 py-1 rounded text-xs font-bold">
<%= c.tipo_cor[:label] %>
</span>
</td>
<td class="px-4 py-3 text-right font-bold <%= c.desconto? ? 'text-red-400' : 'text-white' %>">
<%= c.desconto? ? '-' : '' %><%= moeda(c.valor_aplicado) %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<%# Ações finais %>
<div class="flex flex-col md:flex-row gap-3">
<% if @proximo %>
<%= link_to "Próximo motorista: #{@proximo} →",
validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: @proximo),
class: 'flex-1 bg-orange-500 hover:bg-orange-600 text-black font-bold py-3.5 rounded-lg min-h-[48px] flex items-center justify-center text-center' %>
<% end %>
<%= link_to '💾 Salvar rascunho e voltar', wizard_consolidacao_path(@consolidacao),
class: 'flex-1 bg-[#1a1a1a] hover:bg-[#2a2a2a] text-white border border-[#2a2a2a] font-bold py-3.5 rounded-lg min-h-[48px] flex items-center justify-center' %>
</div>
</div>

View File

@@ -0,0 +1,166 @@
<%# app/views/consolidacao_entregas/validar.html.erb — PASSO 2: Validar entregas %>
<% content_for :title, "Validar — #{@motorista}" %>
<div class="max-w-6xl mx-auto" data-controller="validacao"
data-validacao-url-value="<%= classificar_consolidacao_consolidacao_entregas_path(@consolidacao) %>"
data-validacao-massa-url-value="<%= classificar_em_massa_consolidacao_consolidacao_entregas_path(@consolidacao) %>"
data-validacao-motorista-value="<%= @motorista %>"
data-validacao-total-value="<%= @entregas.size + @classificacoes.size - (@entregas.count { |e| @classificacoes.key?(e.tracking_id) }) %>">
<%# Header %>
<div class="mb-6">
<%= link_to '← Voltar (Passo 1)', wizard_consolidacao_path(@consolidacao), class: 'text-gray-500 hover:text-orange-500 text-sm' %>
<div class="flex items-center justify-between mt-2">
<div>
<h1 class="text-2xl font-bold text-white">🚚 <%= @motorista %></h1>
<p class="text-gray-400 text-sm"><%= @consolidacao.nome %> · <%= l @consolidacao.data_inicio, format: :short %> → <%= l @consolidacao.data_fim, format: :short %></p>
</div>
<%= link_to 'Revisar (Passo 3) →',
revisar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: @motorista),
class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-5 py-3 rounded-lg min-h-[48px] flex items-center' %>
</div>
</div>
<%# Barra de progresso %>
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 mb-4">
<div class="flex justify-between text-sm mb-2">
<span class="text-white font-semibold">
<span data-validacao-target="contadorClassificadas"><%= @classificacoes.size %></span>
de <span data-validacao-target="contadorTotal"><%= @entregas.size %></span> entregas classificadas
</span>
<span class="text-orange-500 font-bold" data-validacao-target="percentual"></span>
</div>
<div class="w-full bg-[#0a0a0a] rounded-full h-3">
<div data-validacao-target="barra" class="h-3 rounded-full bg-orange-500 transition-all duration-300" style="width: 0%"></div>
</div>
</div>
<%# Ações em massa + filtro %>
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 mb-4 flex flex-wrap items-center gap-3">
<span class="text-gray-400 text-sm font-semibold">Ações em massa:</span>
<button data-action="click->validacao#marcarTodos" data-tipo="entrega_normal"
class="bg-orange-500 hover:bg-orange-600 text-black font-bold px-4 py-2.5 rounded-lg text-sm min-h-[44px]">
✓ Marcar todos como Normal
</button>
<div class="flex items-center gap-2">
<span class="text-gray-500 text-sm">Selecionados como:</span>
<button data-action="click->validacao#marcarSelecionados" data-tipo="entrega_normal"
class="bg-orange-500 text-black font-bold px-3 py-2 rounded-lg text-xs min-h-[44px]">Normal</button>
<button data-action="click->validacao#marcarSelecionados" data-tipo="retirada"
class="bg-orange-800 text-white font-bold px-3 py-2 rounded-lg text-xs min-h-[44px]">Retirada</button>
<button data-action="click->validacao#marcarSelecionados" data-tipo="bonus"
class="bg-white text-black font-bold px-3 py-2 rounded-lg text-xs border border-orange-500 min-h-[44px]">Bônus</button>
<button data-action="click->validacao#marcarSelecionados" data-tipo="desconto"
class="bg-black text-white font-bold px-3 py-2 rounded-lg text-xs border border-gray-700 min-h-[44px]">Desconto</button>
</div>
<div class="ml-auto">
<%= link_to params[:apenas_pendentes] == '1' ? 'Mostrar todas' : 'Apenas não classificadas',
validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: @motorista,
apenas_pendentes: params[:apenas_pendentes] == '1' ? nil : '1'),
class: 'text-orange-400 hover:text-orange-300 text-sm border border-orange-500/40 px-3 py-2 rounded-lg' %>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4">
<%# ── Lista de entregas (3 colunas) ─────────────────── %>
<div class="lg:col-span-3 space-y-2">
<% if @entregas.any? %>
<% @entregas.each do |e| %>
<% classificacao = @classificacoes[e.tracking_id] %>
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 flex flex-col md:flex-row md:items-center gap-3"
data-validacao-target="linha" data-tracking="<%= e.tracking_id %>"
data-classificada="<%= classificacao ? '1' : '0' %>">
<%# Checkbox seleção em massa %>
<input type="checkbox" data-validacao-target="checkbox" value="<%= e.tracking_id %>"
class="rounded text-orange-500 bg-[#0a0a0a] border-[#2a2a2a] w-5 h-5">
<%# Dados da entrega %>
<div class="flex-1 min-w-0">
<p class="text-white font-semibold truncate">
NF <%= e.numero_nf %> · <%= e.local %>
</p>
<p class="text-gray-500 text-xs">
📅 <%= e.planned_date %> · 📍 <%= e.route_id.to_s.first(8) %>…
<% if e.atraso_minutos > 0 %> · ⏱️ atraso <%= e.atraso_minutos %>min<% end %>
</p>
</div>
<%# Toggles coloridos — laranja=normal, laranja escuro=retirada, branco=bônus, preto=desconto %>
<div class="flex gap-1.5" data-validacao-target="toggles" data-tracking="<%= e.tracking_id %>">
<% [
['entrega_normal', 'Normal', 'bg-orange-500 text-black'],
['retirada', 'Retirada', 'bg-orange-800 text-white'],
['bonus', 'Bônus', 'bg-white text-black border border-orange-500'],
['desconto', 'Desc.', 'bg-black text-white border border-gray-700']
].each do |tipo, label, cores| %>
<button data-action="click->validacao#classificar"
data-tracking="<%= e.tracking_id %>" data-tipo="<%= tipo %>"
class="toggle-btn <%= cores %> font-bold px-3 py-2.5 rounded-lg text-xs min-h-[44px] transition-all
<%= classificacao&.tipo == tipo ? 'ring-2 ring-green-400 scale-105' : 'opacity-50 hover:opacity-100' %>">
<%= label %>
</button>
<% end %>
</div>
</div>
<% end %>
<% else %>
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-10 text-center">
<p class="text-4xl mb-3">🎉</p>
<p class="text-gray-400">Nenhuma entrega pendente neste filtro.</p>
</div>
<% end %>
</div>
<%# ── Resumo lateral (1 coluna, sticky) ─────────────── %>
<div class="lg:col-span-1">
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-5 sticky top-4 space-y-4">
<h3 class="text-white font-bold">📊 Resumo</h3>
<div class="space-y-2 text-sm">
<div class="flex justify-between items-center">
<span class="flex items-center gap-2"><span class="w-3 h-3 bg-orange-500 rounded-full"></span> Normal</span>
<span class="text-white font-bold" data-validacao-target="qtdNormal">0</span>
</div>
<div class="flex justify-between items-center">
<span class="flex items-center gap-2"><span class="w-3 h-3 bg-orange-800 rounded-full"></span> Retirada</span>
<span class="text-white font-bold" data-validacao-target="qtdRetirada">0</span>
</div>
<div class="flex justify-between items-center">
<span class="flex items-center gap-2"><span class="w-3 h-3 bg-white rounded-full border border-orange-500"></span> Bônus</span>
<span class="text-white font-bold" data-validacao-target="qtdBonus">0</span>
</div>
<div class="flex justify-between items-center">
<span class="flex items-center gap-2"><span class="w-3 h-3 bg-black border border-gray-600 rounded-full"></span> Desconto</span>
<span class="text-white font-bold" data-validacao-target="qtdDesconto">0</span>
</div>
</div>
<div class="border-t border-[#2a2a2a] pt-4">
<p class="text-gray-500 text-xs mb-1">Total do motorista</p>
<p class="text-orange-500 font-black text-2xl" data-validacao-target="valorTotal">R$ 0,00</p>
</div>
<div class="border-t border-[#2a2a2a] pt-4 text-xs text-gray-500 space-y-1">
<p>💰 Normal: <%= moeda(@precos[:entrega_normal]) %></p>
<p>📦 Retirada: <%= moeda(@precos[:retirada]) %></p>
<p>⭐ Bônus: <%= moeda(@precos[:bonus]) %></p>
<p>⚠️ Desconto: -<%= moeda(@precos[:desconto]) %></p>
</div>
</div>
</div>
</div>
</div>
<%# Estado inicial para o Stimulus %>
<script type="application/json" id="validacao-estado-inicial">
<%= raw({
classificadas: @classificacoes.size,
por_tipo: @classificacoes.values.group_by(&:tipo).transform_values(&:size),
valor_total: @classificacoes.values.sum { |c| c.tipo == 'desconto' ? -c.valor_aplicado : c.valor_aplicado }.to_f
}.to_json) %>
</script>

View File

@@ -0,0 +1,65 @@
<%# app/views/consolidacoes/index.html.erb %>
<% content_for :title, 'Consolidações' %>
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-white">📦 Consolidações</h1>
<%= link_to '+ Nova Consolidação', new_consolidacao_path,
class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-5 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
</div>
<%# ── Filtros ──────────────────────────────────────────── %>
<%= form_with url: consolidacoes_path, method: :get,
class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 mb-6 grid grid-cols-1 md:grid-cols-5 gap-3' do |f| %>
<%= f.text_field :nome, value: params[:nome], placeholder: 'Buscar por nome…',
class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5 focus:border-orange-500 focus:outline-none' %>
<%= f.select :status,
options_for_select([['Todos os status', ''], ['📝 Rascunho', 'rascunho'], ['✅ Finalizada', 'finalizada']], params[:status]),
{}, class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
<%= f.date_field :inicio, value: params[:inicio],
class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
<%= f.date_field :fim, value: params[:fim],
class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
<div class="flex gap-2">
<%= f.submit 'Filtrar', class: 'flex-1 bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-lg cursor-pointer' %>
<%= link_to 'Limpar', consolidacoes_path, class: 'px-4 py-2.5 text-gray-400 hover:text-white border border-[#2a2a2a] rounded-lg' %>
</div>
<% end %>
<%# ── Lista ────────────────────────────────────────────── %>
<% if @consolidacoes.any? %>
<div class="space-y-3">
<% @consolidacoes.each do |c| %>
<%= link_to consolidacao_path(c),
class: 'block bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 rounded-xl p-5 transition-colors' do %>
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
<div>
<div class="flex items-center gap-3 mb-1">
<h3 class="text-white font-bold text-lg"><%= c.nome %></h3>
<%= badge_status(c.status) %>
</div>
<p class="text-gray-400 text-sm">
📅 <%= l c.data_inicio, format: :short %> → <%= l c.data_fim, format: :short %>
· 👤 <%= c.consolidacao_motoristas.count %> motorista(s)
· Criada por <%= c.criador.nome_display %>
</p>
</div>
<div class="text-right">
<p class="text-orange-500 font-black text-2xl"><%= moeda(c.valor_total) %></p>
<p class="text-gray-500 text-xs"><%= c.consolidacao_entregas.count %> entregas classificadas</p>
</div>
</div>
<% end %>
<% end %>
</div>
<% else %>
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-12 text-center">
<p class="text-5xl mb-4">📭</p>
<p class="text-gray-400 mb-4">Nenhuma consolidação encontrada.</p>
<%= link_to 'Criar primeira consolidação', new_consolidacao_path,
class: 'inline-block bg-orange-500 hover:bg-orange-600 text-black font-bold px-5 py-3 rounded-lg' %>
</div>
<% end %>

View File

@@ -0,0 +1,89 @@
<%# app/views/consolidacoes/new.html.erb %>
<% content_for :title, 'Nova Consolidação' %>
<div class="max-w-3xl mx-auto">
<h1 class="text-2xl font-bold text-white mb-6"> Nova Consolidação</h1>
<%= form_with model: @consolidacao, class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-6 space-y-6' do |f| %>
<% if @consolidacao.errors.any? %>
<div class="bg-red-900/40 border border-red-600 rounded-lg p-4">
<p class="text-red-400 font-bold mb-1">⚠️ Corrija os erros:</p>
<ul class="text-red-300 text-sm list-disc list-inside">
<% @consolidacao.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%# Nome %>
<div>
<%= f.label :nome, 'Nome da consolidação', class: 'block text-white font-semibold mb-2' %>
<%= f.text_field :nome, placeholder: 'Ex: Pagamento Janeiro/2026 — Quinzena 1',
class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-4 py-3 focus:border-orange-500 focus:outline-none text-base' %>
</div>
<%# Período %>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<%= f.label :data_inicio, 'Data início', class: 'block text-white font-semibold mb-2' %>
<%= f.date_field :data_inicio,
class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-4 py-3 focus:border-orange-500 focus:outline-none' %>
</div>
<div>
<%= f.label :data_fim, 'Data fim', class: 'block text-white font-semibold mb-2' %>
<%= f.date_field :data_fim,
class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-4 py-3 focus:border-orange-500 focus:outline-none' %>
</div>
</div>
<%# Motoristas — multi-select %>
<div>
<label class="block text-white font-semibold mb-2">
Motoristas <span class="text-orange-500">*</span>
<span class="text-gray-500 font-normal text-sm">(<%= @motoristas.size %> encontrados no banco)</span>
</label>
<div class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3 max-h-64 overflow-y-auto space-y-1">
<% if @motoristas.any? %>
<label class="flex items-center gap-2 text-orange-400 text-sm font-semibold pb-2 border-b border-[#2a2a2a] cursor-pointer">
<input type="checkbox" onclick="document.querySelectorAll('.chk-motorista').forEach(c => c.checked = this.checked)"
class="rounded text-orange-500 bg-[#1a1a1a] border-[#2a2a2a]">
Selecionar todos
</label>
<% @motoristas.each do |m| %>
<label class="flex items-center gap-2 text-white hover:bg-[#1a1a1a] rounded px-2 py-2 cursor-pointer min-h-[44px]">
<%= check_box_tag 'motoristas[]', m, false, class: 'chk-motorista rounded text-orange-500 bg-[#1a1a1a] border-[#2a2a2a]', id: nil %>
<%= m %>
</label>
<% end %>
<% else %>
<p class="text-gray-500 text-sm p-2">Nenhum motorista encontrado na tabela de entregas.</p>
<% end %>
</div>
</div>
<%# Rotas — multi-select opcional %>
<div>
<label class="block text-white font-semibold mb-2">
Rotas <span class="text-gray-500 font-normal text-sm">(opcional — filtra entregas por operação)</span>
</label>
<div class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3 max-h-48 overflow-y-auto space-y-1">
<% @rotas.each do |r| %>
<label class="flex items-center gap-2 text-white hover:bg-[#1a1a1a] rounded px-2 py-2 cursor-pointer text-sm font-mono min-h-[44px]">
<%= check_box_tag 'consolidacao[route_ids][]', r, false, class: 'rounded text-orange-500 bg-[#1a1a1a] border-[#2a2a2a]', id: nil %>
<%= r %>
</label>
<% end %>
</div>
</div>
<%# Ações %>
<div class="flex gap-3 pt-2">
<%= f.submit 'Criar e iniciar validação →',
class: 'flex-1 bg-orange-500 hover:bg-orange-600 text-black font-bold py-3.5 rounded-lg min-h-[48px] cursor-pointer transition-colors text-base' %>
<%= link_to 'Cancelar', consolidacoes_path,
class: 'px-6 py-3.5 text-gray-400 hover:text-white border border-[#2a2a2a] rounded-lg min-h-[48px] flex items-center' %>
</div>
<% end %>
</div>

View File

@@ -0,0 +1,91 @@
<%# app/views/consolidacoes/wizard.html.erb — PASSO 1: Selecionar motorista %>
<% content_for :title, "Wizard — #{@consolidacao.nome}" %>
<div class="max-w-3xl mx-auto">
<%# Header do wizard %>
<div class="mb-6">
<%= link_to '← Voltar para consolidações', consolidacoes_path, class: 'text-gray-500 hover:text-orange-500 text-sm' %>
<h1 class="text-2xl font-bold text-white mt-2"><%= @consolidacao.nome %></h1>
<p class="text-gray-400 text-sm">
📅 <%= l @consolidacao.data_inicio, format: :short %> → <%= l @consolidacao.data_fim, format: :short %>
· <%= badge_status(@consolidacao.status) %>
</p>
</div>
<%# Indicador de passos %>
<div class="flex items-center gap-2 mb-8">
<div class="flex items-center gap-2 text-orange-500 font-bold">
<span class="w-8 h-8 bg-orange-500 text-black rounded-full flex items-center justify-center">1</span>
Selecionar motorista
</div>
<div class="flex-1 h-px bg-[#2a2a2a]"></div>
<div class="flex items-center gap-2 text-gray-600">
<span class="w-8 h-8 bg-[#1a1a1a] border border-[#2a2a2a] rounded-full flex items-center justify-center">2</span>
Validar entregas
</div>
<div class="flex-1 h-px bg-[#2a2a2a]"></div>
<div class="flex items-center gap-2 text-gray-600">
<span class="w-8 h-8 bg-[#1a1a1a] border border-[#2a2a2a] rounded-full flex items-center justify-center">3</span>
Revisar
</div>
</div>
<%# Lista de motoristas %>
<div class="space-y-3">
<% @motoristas.each do |m| %>
<%= link_to validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: m[:registro].motorista_nome),
class: "block bg-[#1a1a1a] border rounded-xl p-5 transition-colors
#{m[:completo] ? 'border-green-600 hover:border-green-500' : 'border-[#2a2a2a] hover:border-orange-500'}" do %>
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-full flex items-center justify-center text-xl font-black
<%= m[:completo] ? 'bg-green-600 text-white' : 'bg-orange-500 text-black' %>">
<%= m[:completo] ? '✓' : m[:registro].motorista_nome.first.upcase %>
</div>
<div>
<p class="text-white font-bold text-lg"><%= m[:registro].motorista_nome %></p>
<p class="text-gray-400 text-sm">
<%= m[:total] %> entregas pagas no período
· <span class="<%= m[:completo] ? 'text-green-400' : 'text-orange-400' %>">
<%= m[:classificadas] %>/<%= m[:total] %> classificadas
</span>
</p>
</div>
</div>
<div class="text-right">
<p class="text-orange-500 font-bold"><%= moeda(m[:registro].valor_total) %></p>
<span class="text-gray-600 text-2xl">→</span>
</div>
</div>
<div class="mt-3">
<% pct = m[:total].zero? ? 0 : (m[:classificadas] * 100 / m[:total]) %>
<div class="w-full bg-[#0a0a0a] rounded-full h-2">
<div class="h-2 rounded-full <%= m[:completo] ? 'bg-green-500' : 'bg-orange-500' %> transition-all"
style="width: <%= pct %>%"></div>
</div>
</div>
<% end %>
<% end %>
</div>
<%# Finalizar — só habilita se tudo classificado %>
<% tudo_completo = @motoristas.all? { |m| m[:completo] } && @motoristas.any? %>
<div class="mt-8 bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-5 flex items-center justify-between">
<div>
<p class="text-white font-bold">Total da consolidação</p>
<p class="text-orange-500 font-black text-3xl"><%= moeda(@consolidacao.valor_total) %></p>
</div>
<% if @consolidacao.rascunho? %>
<%= button_to 'Finalizar consolidação ✓', finalizar_consolidacao_path(@consolidacao),
method: :post,
disabled: !tudo_completo,
data: { turbo_confirm: 'Finalizar? Após finalizada, a consolidação não poderá ser editada por operadores.' },
class: "font-bold px-6 py-3.5 rounded-lg min-h-[48px] transition-colors
#{tudo_completo ? 'bg-green-600 hover:bg-green-500 text-white cursor-pointer' : 'bg-[#2a2a2a] text-gray-600 cursor-not-allowed'}" %>
<% end %>
</div>
<% unless tudo_completo %>
<p class="text-gray-500 text-sm mt-2 text-center">⚠️ Classifique todas as entregas de todos os motoristas para poder finalizar.</p>
<% end %>
</div>

View File

@@ -1,169 +1,95 @@
<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html lang="pt-BR" class="<%= @tema == 'light' ? '' : 'dark' %>">
<html lang="pt-BR" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-param" content="<%= request_forgery_protection_token %>">
<meta name="csrf-token" content="<%= form_authenticity_token %>">
<title><%= content_for?(:title) ? yield(:title) + ' · ' : '' %>Gade Logística</title>
<title><%= content_for?(:title) ? "#{yield(:title)} | Gade Logística" : "Gade Logística" %></title>
<%= stylesheet_link_tag 'application', data: { turbo_track: 'reload' } %>
<%= javascript_importmap_tags %>
<%# Tailwind via CDN em desenvolvimento — compilar em produção %>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
brand: {
preto: '#0a0a0a',
'preto-card': '#1a1a1a',
laranja: '#f97316',
'laranja-escuro': '#ea580c',
branco: '#ffffff',
}
}
}
}
}
</script>
<%# Google Fonts — Inter %>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
:root {
--bg-main: #0a0a0a;
--bg-card: #1a1a1a;
--accent: #f97316;
--text-main: #ffffff;
--text-muted:#9ca3af;
--border: rgba(255,255,255,0.05);
body { font-family: 'Inter', sans-serif; }
/* Scrollbar tema escuro */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #0a0a0a; }
::-webkit-scrollbar-thumb { background: #f97316; border-radius: 3px; }
/* Loading spinner laranja */
.spinner {
border: 3px solid #1a1a1a;
border-top-color: #f97316;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
html.light {
--bg-main: #f8fafc;
--bg-card: #ffffff;
--text-main: #0f172a;
--text-muted:#64748b;
--border: rgba(0,0,0,0.08);
}
body { background-color: var(--bg-main); color: var(--text-main); }
.scrollbar-hidden::-webkit-scrollbar { display: none; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
<%= yield :head %>
</head>
<body class="font-sans antialiased min-h-screen" style="background-color: var(--bg-main)">
<body class="bg-[#0a0a0a] text-white min-h-screen">
<% if user_signed_in? && !current_user.motorista? %>
<%# Layout principal com sidebar %>
<div class="flex min-h-screen">
<%# Sidebar Desktop %>
<aside id="sidebar"
class="fixed inset-y-0 left-0 z-50 w-64 flex-col hidden lg:flex
border-r border-white/5"
style="background-color: var(--bg-card)">
<%= render 'layouts/sidebar' %>
</aside>
<%# Overlay mobile %>
<div id="sidebar-overlay"
class="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm hidden lg:hidden"
onclick="toggleSidebar()"></div>
<%# Sidebar Mobile (drawer) %>
<aside id="sidebar-mobile"
class="fixed inset-y-0 left-0 z-50 w-64 flex flex-col lg:hidden
transform -translate-x-full transition-transform duration-300 ease-in-out
border-r border-white/5"
style="background-color: var(--bg-card)">
<%= render 'layouts/sidebar' %>
</aside>
<%# Main content %>
<div class="flex-1 lg:ml-64 flex flex-col min-h-screen">
<%# Topbar %>
<header class="sticky top-0 z-30 px-4 lg:px-6 py-4 flex items-center justify-between
border-b border-white/5 backdrop-blur-md"
style="background-color: rgba(10,10,10,0.8)">
<%# Hamburger mobile %>
<button onclick="toggleSidebar()"
class="lg:hidden p-2 text-gray-400 hover:text-white rounded-lg hover:bg-white/10 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<%# Breadcrumb / título da página %>
<div class="flex-1 lg:ml-0 ml-3">
<span class="text-white font-semibold text-sm lg:text-base">
<%= content_for?(:page_title) ? yield(:page_title) : 'Dashboard' %>
</span>
</div>
<%# Ações do header %>
<div class="flex items-center gap-3">
<%# Toggle tema %>
<%= button_to toggle_tema_configuracoes_path, method: :post,
class: 'p-2 text-gray-400 hover:text-white hover:bg-white/10
rounded-lg transition-colors cursor-pointer',
title: @tema == 'dark' ? 'Mudar para tema claro' : 'Mudar para tema escuro' do %>
<% if @tema == 'dark' %>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
</svg>
<% else %>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
</svg>
<% end %>
<% end %>
<%# Avatar / menu usuário %>
<div class="relative" x-data="{ open: false }">
<button class="flex items-center gap-2 p-1.5 rounded-xl hover:bg-white/10 transition-colors">
<div class="w-8 h-8 rounded-full bg-[#f97316]/20 border border-[#f97316]/30
flex items-center justify-center text-[#f97316] font-bold text-sm">
<%= current_user.name.to_s[0].upcase %>
</div>
<span class="hidden sm:block text-white text-sm font-medium pr-1">
<%= current_user.nome_display %>
</span>
</button>
</div>
<%# Logout %>
<%= button_to destroy_user_session_path, method: :delete,
class: 'px-3 py-2 text-gray-500 hover:text-red-400 text-sm
hover:bg-red-900/20 rounded-lg transition-colors cursor-pointer' do %>
Sair
<% end %>
</div>
</header>
<%# Conteúdo principal %>
<main class="flex-1 p-4 lg:p-6">
<%= yield %>
</main>
</div>
</div>
<% elsif user_signed_in? && current_user.motorista? %>
<%# Layout simplificado para motorista %>
<div class="min-h-screen" style="background-color: var(--bg-main)">
<main class="max-w-2xl mx-auto px-4 py-6">
<%= yield %>
</main>
</div>
<% else %>
<%# Layout sem sidebar (login, etc) %>
<%= yield %>
<%# ── Navbar ─────────────────────────────────────────── %>
<% if user_signed_in? %>
<%= render 'layouts/navbar' %>
<% end %>
<%# ── Flash Messages ─────────────────────────────────── %>
<% if notice.present? %>
<div id="flash-notice"
class="fixed top-4 right-4 z-50 bg-orange-500 text-black font-semibold px-5 py-3 rounded-lg shadow-lg flex items-center gap-2 animate-fade-in"
data-controller="flash">
<span>✅</span> <%= notice %>
</div>
<% end %>
<% if alert.present? %>
<div id="flash-alert"
class="fixed top-4 right-4 z-50 bg-red-600 text-white font-semibold px-5 py-3 rounded-lg shadow-lg flex items-center gap-2"
data-controller="flash">
<span>⚠️</span> <%= alert %>
</div>
<% end %>
<%# ── Conteúdo principal ──────────────────────────────── %>
<main class="<%= user_signed_in? ? 'ml-0 md:ml-64 p-4 md:p-8' : '' %> min-h-screen">
<%= yield %>
</main>
<%# Auto-hide flash após 4s (JS simples) %>
<script>
function toggleSidebar() {
const mobile = document.getElementById('sidebar-mobile');
const overlay = document.getElementById('sidebar-overlay');
const isOpen = !mobile.classList.contains('-translate-x-full');
if (isOpen) {
mobile.classList.add('-translate-x-full');
overlay.classList.add('hidden');
} else {
mobile.classList.remove('-translate-x-full');
overlay.classList.remove('hidden');
}
}
// Fecha sidebar ao pressionar Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const mobile = document.getElementById('sidebar-mobile');
if (mobile && !mobile.classList.contains('-translate-x-full')) toggleSidebar();
}
});
setTimeout(() => {
['flash-notice', 'flash-alert'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.transition = 'opacity 0.5s', el.style.opacity = '0',
setTimeout(() => el.remove(), 500);
});
}, 4000);
</script>
</body>
</html>