Atualição de interface area consolidação e Relatório em PDF

This commit is contained in:
2026-06-16 16:38:42 -03:00
parent 7c57b8e09d
commit 80c068c798
5 changed files with 37 additions and 7 deletions

View File

@@ -140,7 +140,7 @@ class ConsolidacoesController < ApplicationController
# GET /consolidacoes/:id/gerar_pdf_holerite?motorista=X # GET /consolidacoes/:id/gerar_pdf_holerite?motorista=X
def gerar_pdf_holerite def gerar_pdf_holerite
authorize @consolidacao, :show? autorizar_holerite!(params[:motorista])
motorista = params[:motorista] motorista = params[:motorista]
user_mot = User.motorista.find_by('LOWER(nome) = ?', motorista.to_s.downcase) user_mot = User.motorista.find_by('LOWER(nome) = ?', motorista.to_s.downcase)
user_mot&.regenerar_login_token! if user_mot&.login_token.blank? user_mot&.regenerar_login_token! if user_mot&.login_token.blank?
@@ -167,6 +167,17 @@ class ConsolidacoesController < ApplicationController
private private
# Staff (show?) baixa qualquer holerite. O motorista baixa só o PRÓPRIO — o PDF
# já é filtrado pelo nome dele, então não há vazamento de dados de terceiros.
def autorizar_holerite!(motorista_nome)
if current_user.motorista?
raise Pundit::NotAuthorizedError unless motorista_nome.to_s.casecmp?(current_user.nome.to_s)
skip_authorization
else
authorize @consolidacao, :show?
end
end
def set_consolidacao def set_consolidacao
@consolidacao = Consolidacao.find(params[:id]) @consolidacao = Consolidacao.find(params[:id])
end end

View File

@@ -6,7 +6,7 @@ import { Controller } from "@hotwired/stimulus"
export default class extends Controller { export default class extends Controller {
static targets = [ static targets = [
"linha", "checkbox", "toggles", "barra", "percentual", "linha", "checkbox", "selecionarTodos", "toggles", "barra", "percentual",
"contadorClassificadas", "contadorTotal", "contadorClassificadas", "contadorTotal",
"qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "valorTotal" "qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "valorTotal"
] ]
@@ -64,6 +64,12 @@ export default class extends Controller {
this.atualizarResumo(resp) this.atualizarResumo(resp)
} }
// ── Selecionar / desmarcar todos os checkboxes ─────────────
selecionarTodos(event) {
const marcar = event.currentTarget.checked
this.checkboxTargets.forEach(c => { c.checked = marcar })
}
// ── Marcar selecionados como X ───────────────────────────── // ── Marcar selecionados como X ─────────────────────────────
async marcarSelecionados(event) { async marcarSelecionados(event) {
const tipo = event.currentTarget.dataset.tipo const tipo = event.currentTarget.dataset.tipo
@@ -81,6 +87,7 @@ export default class extends Controller {
}) })
ids.forEach(id => this.setToggle(id, tipo, true)) ids.forEach(id => this.setToggle(id, tipo, true))
this.checkboxTargets.forEach(c => c.checked = false) this.checkboxTargets.forEach(c => c.checked = false)
if (this.hasSelecionarTodosTarget) this.selecionarTodosTarget.checked = false
this.atualizarResumo(resp) this.atualizarResumo(resp)
} }

View File

@@ -29,13 +29,14 @@ module Pdf
secao "ENTREGAS (#{@entregas.size})" secao "ENTREGAS (#{@entregas.size})"
if @entregas.any? if @entregas.any?
linhas = [['#', 'Tracking', 'NF / Local', 'Tipo', 'Valor']] linhas = [['#', 'Tracking', 'NF', 'Endereço', 'Tipo', 'Valor']]
@entregas.each_with_index do |e, i| @entregas.each_with_index do |e, i|
original = e.entrega_original original = e.entrega_original
linhas << [ linhas << [
(i + 1).to_s, (i + 1).to_s,
e.tracking_id.to_s.first(14), e.tracking_id.to_s.first(14),
original ? "NF #{original.numero_nf} · #{original.local.to_s.first(30)}" : '—', original ? "NF #{original.numero_nf}" : '—',
original ? original.address.to_s.first(45) : '—',
e.tipo_cor[:label], e.tipo_cor[:label],
(e.desconto? ? '-' : '') + moeda(e.valor_aplicado) (e.desconto? ? '-' : '') + moeda(e.valor_aplicado)
] ]
@@ -46,7 +47,7 @@ module Pdf
t.row(0).background_color = PRETO t.row(0).background_color = PRETO
t.row(0).text_color = 'FFFFFF' t.row(0).text_color = 'FFFFFF'
t.row(0).font_style = :bold t.row(0).font_style = :bold
t.columns(4).align = :right t.columns(5).align = :right
t.row(1..-1).borders = [:bottom] t.row(1..-1).borders = [:bottom]
t.row(1..-1).border_color = 'DDDDDD' t.row(1..-1).border_color = 'DDDDDD'
end end

View File

@@ -6,7 +6,7 @@
<div class="mb-6"> <div class="mb-6">
<%= link_to '← Voltar (Passo 2)', <%= link_to '← Voltar (Passo 2)',
validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: @motorista), validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: @motorista),
class: 'text-gray-500 hover:text-orange-500 text-sm' %> class: 'inline-flex items-center gap-1 bg-[#1a1a1a] hover:bg-[#2a2a2a] text-white border border-[#2a2a2a] hover:border-orange-500 font-semibold px-4 py-2.5 rounded-lg text-sm min-h-[44px]' %>
<h1 class="text-2xl font-bold text-white mt-2">📋 Revisão — <%= @motorista %></h1> <h1 class="text-2xl font-bold text-white mt-2">📋 Revisão — <%= @motorista %></h1>
<p class="text-gray-400 text-sm"><%= @consolidacao.nome %></p> <p class="text-gray-400 text-sm"><%= @consolidacao.nome %></p>
</div> </div>
@@ -52,6 +52,7 @@
<thead class="bg-[#0a0a0a] text-gray-400"> <thead class="bg-[#0a0a0a] text-gray-400">
<tr> <tr>
<th class="text-left px-4 py-3">Tracking / NF</th> <th class="text-left px-4 py-3">Tracking / NF</th>
<th class="text-left px-4 py-3">Endereço</th>
<th class="text-left px-4 py-3">Tipo</th> <th class="text-left px-4 py-3">Tipo</th>
<th class="text-right px-4 py-3">Valor</th> <th class="text-right px-4 py-3">Valor</th>
</tr> </tr>
@@ -60,6 +61,7 @@
<% @classificacoes.each do |c| %> <% @classificacoes.each do |c| %>
<tr> <tr>
<td class="px-4 py-3 text-white font-mono text-xs"><%= c.tracking_id %></td> <td class="px-4 py-3 text-white font-mono text-xs"><%= c.tracking_id %></td>
<td class="px-4 py-3 text-gray-300 text-xs"><%= c.entrega_original&.address.presence || '—' %></td>
<td class="px-4 py-3"> <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"> <span class="<%= c.tipo_cor[:bg] %> <%= c.tipo_cor[:text] %> px-2 py-1 rounded text-xs font-bold">
<%= c.tipo_cor[:label] %> <%= c.tipo_cor[:label] %>

View File

@@ -9,7 +9,8 @@
<%# Header %> <%# Header %>
<div class="mb-6"> <div class="mb-6">
<%= link_to '← Voltar (Passo 1)', wizard_consolidacao_path(@consolidacao), class: 'text-gray-500 hover:text-orange-500 text-sm' %> <%= link_to '← Voltar (Passo 1)', wizard_consolidacao_path(@consolidacao),
class: 'inline-flex items-center gap-1 bg-[#1a1a1a] hover:bg-[#2a2a2a] text-white border border-[#2a2a2a] hover:border-orange-500 font-semibold px-4 py-2.5 rounded-lg text-sm min-h-[44px]' %>
<div class="flex items-center justify-between mt-2"> <div class="flex items-center justify-between mt-2">
<div> <div>
<h1 class="text-2xl font-bold text-white">🚚 <%= @motorista %></h1> <h1 class="text-2xl font-bold text-white">🚚 <%= @motorista %></h1>
@@ -44,6 +45,13 @@
✓ Marcar todos como Normal ✓ Marcar todos como Normal
</button> </button>
<label class="flex items-center gap-2 text-gray-300 text-sm cursor-pointer select-none border-l border-[#2a2a2a] pl-3">
<input type="checkbox" data-validacao-target="selecionarTodos"
data-action="change->validacao#selecionarTodos"
class="rounded text-orange-500 bg-[#0a0a0a] border-[#2a2a2a] w-5 h-5">
Selecionar todos
</label>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-gray-500 text-sm">Selecionados como:</span> <span class="text-gray-500 text-sm">Selecionados como:</span>
<button data-action="click->validacao#marcarSelecionados" data-tipo="entrega_normal" <button data-action="click->validacao#marcarSelecionados" data-tipo="entrega_normal"
@@ -84,6 +92,7 @@
<p class="text-white font-semibold truncate"> <p class="text-white font-semibold truncate">
NF <%= e.numero_nf %> · <%= e.local %> NF <%= e.numero_nf %> · <%= e.local %>
</p> </p>
<p class="text-gray-400 text-xs truncate">📍 <%= e.address.presence || 'sem endereço' %></p>
<p class="text-gray-500 text-xs"> <p class="text-gray-500 text-xs">
📅 <%= e.planned_date %> · 🚚 <%= e.vehicle.presence || 'sem veículo' %> 📅 <%= e.planned_date %> · 🚚 <%= e.vehicle.presence || 'sem veículo' %>
<% if e.atraso_minutos > 0 %> · ⏱️ atraso <%= e.atraso_minutos %>min<% end %> <% if e.atraso_minutos > 0 %> · ⏱️ atraso <%= e.atraso_minutos %>min<% end %>