231 lines
10 KiB
Plaintext
231 lines
10 KiB
Plaintext
<%# 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 %>
|
||
|
||
<%# Operação — multi-select opcional (pré-preenche e filtra por NF) %>
|
||
<div>
|
||
<label class="block text-white font-semibold mb-2">
|
||
🗂️ Operação <span class="text-gray-500 font-normal text-sm">(opcional — preenche motoristas, veículos e período, e filtra as entregas por nota fiscal)</span>
|
||
</label>
|
||
<div id="operacoes-lista"
|
||
class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3 max-h-48 overflow-y-auto space-y-1">
|
||
<% if @operacoes.any? %>
|
||
<% @operacoes.each do |op| %>
|
||
<label class="flex items-center gap-2 text-white hover:bg-[#1a1a1a] rounded px-2 py-2 cursor-pointer text-sm min-h-[44px]">
|
||
<%= check_box_tag 'consolidacao[operacoes][]', op[:tabela], false,
|
||
class: 'chk-operacao rounded text-orange-500 bg-[#1a1a1a] border-[#2a2a2a]', id: nil %>
|
||
<%= op[:label] %>
|
||
</label>
|
||
<% end %>
|
||
<% else %>
|
||
<p class="text-gray-500 text-sm px-2 py-2">Nenhuma tabela de operação encontrada no banco.</p>
|
||
<% end %>
|
||
</div>
|
||
<p id="operacao-status" class="text-gray-500 text-xs mt-1"></p>
|
||
</div>
|
||
|
||
<%# 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 =>
|
||
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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);
|
||
|
||
// ── Operações: ao marcar, pré-preenche período + motoristas + veículos ──
|
||
const opStatus = document.getElementById('operacao-status');
|
||
const dadosOpUrl = '<%= dados_da_operacao_consolidacoes_path %>';
|
||
|
||
function operacoesSelecionadas() {
|
||
return Array.from(document.querySelectorAll('.chk-operacao:checked')).map(c => c.value);
|
||
}
|
||
|
||
async function aplicarOperacoes() {
|
||
const ops = operacoesSelecionadas();
|
||
if (!ops.length) { if (opStatus) opStatus.textContent = ''; return; }
|
||
|
||
if (opStatus) opStatus.textContent = 'Carregando dados da operação…';
|
||
const params = new URLSearchParams();
|
||
ops.forEach(o => params.append('operacoes[]', o));
|
||
|
||
try {
|
||
const resp = await fetch(dadosOpUrl + '?' + params.toString(), { headers: { 'Accept': 'application/json' } });
|
||
const data = await resp.json();
|
||
|
||
if (data.inicio && dataIni) dataIni.value = data.inicio;
|
||
if (data.fim && dataFim) dataFim.value = data.fim;
|
||
|
||
const motset = new Set(data.motoristas || []);
|
||
document.querySelectorAll('.chk-motorista').forEach(c => { if (motset.has(c.value)) c.checked = true; });
|
||
|
||
await atualizarVeiculos();
|
||
const vehset = new Set(data.veiculos || []);
|
||
lista.querySelectorAll('input[name="consolidacao[vehicle_ids][]"]').forEach(c => { if (vehset.has(c.value)) c.checked = true; });
|
||
|
||
if (opStatus) {
|
||
opStatus.textContent = (data.motoristas || []).length + ' motorista(s) e ' +
|
||
(data.veiculos || []).length + ' veículo(s) nesta(s) operação(ões).';
|
||
}
|
||
} catch (e) {
|
||
if (opStatus) opStatus.textContent = 'Erro ao carregar dados da operação.';
|
||
}
|
||
}
|
||
|
||
document.addEventListener('change', function (e) {
|
||
if (e.target.classList.contains('chk-operacao')) aplicarOperacoes();
|
||
});
|
||
|
||
// Estado inicial (caso o form volte com motoristas já marcados após erro de validação)
|
||
if (motoristasSelecionados().length) atualizarVeiculos();
|
||
})();
|
||
</script>
|