Files
Reem-Notas/app/views/consolidacoes/new.html.erb
victor fedf5bfcc9 Corrige dashboard por período, CRUD de usuário, PDFs e paginação na revisão
- Dashboard: no_periodo passa a cobrir o dia inteiro (planned_date é timestamp),
  corrigindo período que zerava ao terminar no dia dos dados
- Dashboard: card de consolidações filtra por sobreposição de datas (não created_at)
- Usuários: adiciona helper role_options_for_select (corrige edição/criação)
- Consolidação: set_consolidacao nas actions de PDF/preview (corrige
  Pundit::NotDefinedError em Relatório Individual, Gerar Holerite e Preview)
- Revisão: paginação com Pagy (20/página + seletor 20/50/100)
- Nova consolidação: veículos filtrados pelo(s) motorista(s) no período
2026-06-16 14:45:06 -03:00

166 lines
7.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<%# 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>
<%# Veículos — multi-select opcional (filtrado pelo motorista + período) %>
<div>
<label class="block text-white font-semibold mb-2">
Veículos <span class="text-gray-500 font-normal text-sm">(opcional — só os veículos usados pelo(s) motorista(s) no período)</span>
</label>
<div id="veiculos-lista"
data-url="<%= veiculos_disponiveis_consolidacoes_path %>"
class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3 max-h-48 overflow-y-auto space-y-1">
<p class="text-gray-500 text-sm px-2 py-2">Selecione um motorista para listar os veículos.</p>
</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>
<script>
(function () {
const lista = document.getElementById('veiculos-lista');
if (!lista) return;
const url = lista.dataset.url;
const dataIni = document.getElementById('consolidacao_data_inicio');
const dataFim = document.getElementById('consolidacao_data_fim');
// Mantém os veículos que já estavam marcados ao recarregar a lista.
function veiculosMarcados() {
return Array.from(lista.querySelectorAll('input[name="consolidacao[vehicle_ids][]"]:checked'))
.map(c => c.value);
}
function motoristasSelecionados() {
return Array.from(document.querySelectorAll('.chk-motorista:checked')).map(c => c.value);
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, c =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}
function renderVeiculos(veiculos, marcados) {
if (!veiculos.length) {
lista.innerHTML = '<p class="text-gray-500 text-sm px-2 py-2">Nenhum veículo encontrado para este(s) motorista(s) no período.</p>';
return;
}
lista.innerHTML = veiculos.map(function (v) {
const checked = marcados.includes(v) ? 'checked' : '';
const val = escapeHtml(v);
return '<label class="flex items-center gap-2 text-white hover:bg-[#1a1a1a] rounded px-2 py-2 cursor-pointer text-sm min-h-[44px]">' +
'<input type="checkbox" name="consolidacao[vehicle_ids][]" value="' + val + '" ' + checked +
' class="rounded text-orange-500 bg-[#1a1a1a] border-[#2a2a2a]">' + val + '</label>';
}).join('');
}
let controller = null;
async function atualizarVeiculos() {
const motoristas = motoristasSelecionados();
const marcados = veiculosMarcados();
if (!motoristas.length) {
lista.innerHTML = '<p class="text-gray-500 text-sm px-2 py-2">Selecione um motorista para listar os veículos.</p>';
return;
}
const params = new URLSearchParams();
motoristas.forEach(m => params.append('motoristas[]', m));
if (dataIni && dataIni.value) params.append('inicio', dataIni.value);
if (dataFim && dataFim.value) params.append('fim', dataFim.value);
if (controller) controller.abort();
controller = new AbortController();
try {
const resp = await fetch(url + '?' + params.toString(), {
headers: { 'Accept': 'application/json' },
signal: controller.signal
});
const data = await resp.json();
renderVeiculos(data.veiculos || [], marcados);
} catch (e) {
if (e.name !== 'AbortError') {
lista.innerHTML = '<p class="text-red-400 text-sm px-2 py-2">Erro ao carregar veículos.</p>';
}
}
}
document.addEventListener('change', function (e) {
if (e.target.classList.contains('chk-motorista')) atualizarVeiculos();
});
if (dataIni) dataIni.addEventListener('change', atualizarVeiculos);
if (dataFim) dataFim.addEventListener('change', atualizarVeiculos);
// Estado inicial (caso o form volte com motoristas já marcados após erro de validação)
if (motoristasSelecionados().length) atualizarVeiculos();
})();
</script>