Corrige 3 erros de runtime + filtro de faixa de datas no dashboard

Erros corrigidos (reportados em prints):
- Logout dava "No route matches [GET] /auth/logout": link usava method:
  :delete (rails-ujs), que o Turbo ignora -> agora data-turbo-method
- /admin/usuarios estourava NoMethodError 'admin_ou_gerente?'/'admin?' na
  UserPolicy -> adicionados helpers que delegam para o User
- Nova consolidação estourava NoMethodError 'new?' na ConsolidacaoPolicy ->
  ApplicationPolicy passa a definir new? (= create?) e edit? (= update?)

Dashboard:
- Substitui a navegação por mês por um filtro de calendário com faixa de
  datas (Flatpickr, range mode) que auto-submete inicio/fim
- Controller passa a consultar por período (Entrega.no_periodo) em vez de
  do_mes; gráfico diário limitado a ~1 trimestre

Specs: UserPolicy + new? na ConsolidacaoPolicy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 22:27:57 -03:00
parent 732c6daae8
commit bb1dead9fd
7 changed files with 151 additions and 62 deletions

View File

@@ -3,39 +3,27 @@
<div class="space-y-6">
<%# Header + filtro de mês %>
<%# Header + filtro por faixa de datas %>
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-white">Dashboard</h1>
<p class="text-gray-400 text-sm mt-0.5">
<%= @data_referencia.strftime('%B de %Y').capitalize %> · Atualizado às <%= Time.now.strftime('%H:%M') %>
<%= @periodo_inicio.strftime('%d/%m/%Y') %> <%= @periodo_fim.strftime('%d/%m/%Y') %>
· Atualizado às <%= Time.now.strftime('%H:%M') %>
</p>
</div>
<%# Navegação de mês %>
<div class="flex items-center gap-2">
<%= link_to dashboard_path(data: (@data_referencia - 1.month).to_s),
class: 'p-2.5 bg-[#1a1a1a] hover:bg-white/10 border border-white/10 rounded-xl
text-gray-400 hover:text-white transition-colors' do %>
<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="M15 19l-7-7 7-7"/>
</svg>
<% end %>
<div class="px-4 py-2.5 bg-[#1a1a1a] border border-white/10 rounded-xl text-white font-medium min-w-[140px] text-center">
<%= @data_referencia.strftime('%b %Y').capitalize %>
<%# Filtro de calendário — seleciona uma faixa de datas %>
<form id="dashboard-filtro-form" method="get" action="<%= dashboard_path %>" class="flex 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>
<input type="text" id="periodo-range" readonly placeholder="Selecione o período"
class="cursor-pointer pl-4 pr-10 py-2.5 bg-[#1a1a1a] border border-white/10 rounded-xl
text-white text-sm w-[240px] focus:outline-none focus:border-orange-500 placeholder-gray-500">
</div>
<% unless @data_referencia >= Date.today.beginning_of_month %>
<%= link_to dashboard_path(data: (@data_referencia + 1.month).to_s),
class: 'p-2.5 bg-[#1a1a1a] hover:bg-white/10 border border-white/10 rounded-xl
text-gray-400 hover:text-white transition-colors' do %>
<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="M9 5l7 7-7 7"/>
</svg>
<% end %>
<% end %>
</div>
<input type="hidden" name="inicio" id="inicio-hidden" value="<%= @periodo_inicio.strftime('%Y-%m-%d') %>">
<input type="hidden" name="fim" id="fim-hidden" value="<%= @periodo_fim.strftime('%Y-%m-%d') %>">
</form>
</div>
<%# CARDS GRANDES — KPIs %>
@@ -81,7 +69,7 @@
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
<p class="text-gray-400 text-sm font-medium mb-2">🚗 Motoristas</p>
<p class="text-4xl font-black text-white mb-1"><%= @motoristas.count %></p>
<p class="text-gray-500 text-sm">com entregas no mês</p>
<p class="text-gray-500 text-sm">com entregas no período</p>
</div>
</div>
@@ -93,7 +81,7 @@
<div class="flex items-center justify-between mb-6">
<div>
<h2 class="text-lg font-semibold text-white">Evolução Diária</h2>
<p class="text-gray-400 text-sm">Valor estimado por dia no mês</p>
<p class="text-gray-400 text-sm">Valor estimado por dia no período</p>
</div>
<div class="flex items-center gap-2 text-xs text-gray-500">
<span class="inline-block w-3 h-0.5 bg-[#f97316] rounded"></span> Valor (R$)
@@ -137,7 +125,7 @@
<% else %>
<div class="text-center py-8 text-gray-500">
<div class="text-3xl mb-2">🚚</div>
<p class="text-sm">Nenhuma entrega paga neste mês</p>
<p class="text-sm">Nenhuma entrega paga neste período</p>
</div>
<% end %>
</div>
@@ -162,6 +150,41 @@
</div>
<%# Flatpickr — date range picker (CDN, mesmo padrão do Chart.js) %>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/l10n/pt.js"></script>
<script>
(function () {
const input = document.getElementById('periodo-range');
if (!input || typeof flatpickr === 'undefined') return;
const inicio = document.getElementById('inicio-hidden').value;
const fim = document.getElementById('fim-hidden').value;
// formata Date local → YYYY-MM-DD (evita deslocamento de fuso do toISOString)
const toISO = (d) =>
d.getFullYear() + '-' +
String(d.getMonth() + 1).padStart(2, '0') + '-' +
String(d.getDate()).padStart(2, '0');
flatpickr(input, {
mode: 'range',
locale: 'pt',
dateFormat: 'd/m/Y',
defaultDate: [inicio, fim],
maxDate: 'today',
onClose: function (selectedDates) {
if (selectedDates.length === 2) {
document.getElementById('inicio-hidden').value = toISO(selectedDates[0]);
document.getElementById('fim-hidden').value = toISO(selectedDates[1]);
document.getElementById('dashboard-filtro-form').requestSubmit();
}
}
});
})();
</script>
<%# Chart.js via CDN %>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>

View File

@@ -60,7 +60,7 @@
<%# Footer da sidebar — sair %>
<div class="px-6 py-4 border-t border-[#2a2a2a]">
<%= link_to destroy_user_session_path, method: :delete,
<%= link_to destroy_user_session_path, data: { turbo_method: :delete, turbo_confirm: 'Deseja sair?' },
class: "flex items-center gap-2 text-gray-400 hover:text-red-400 text-sm transition-colors" do %>
<span>🚪</span> Sair
<% end %>