Files
Reem-Notas/app/views/admin/romaneios/index.html.erb

295 lines
16 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/admin/romaneios/index.html.erb %>
<div class="space-y-6">
<div>
<h1 class="text-2xl font-bold text-white">Romaneio de entrega</h1>
<p class="text-gray-400 text-sm mt-0.5">
Monta o "CONTROLE DE ENTREGA" — uma folha por veículo, assinada pelo motorista na retirada
</p>
</div>
<%= render 'shared/flash' %>
<div class="p-4 bg-blue-900/20 border border-blue-500/40 rounded-xl text-blue-200/90 text-sm flex items-start gap-2">
<span class="text-base leading-none"></span>
<p>
A coluna <strong>APARELHO</strong> é marcada sozinha para quem está como
<strong>NOVO</strong> na operação do mês, e o <strong>telefone</strong> vem da própria
operação — não é preciso subir uma segunda planilha. Tudo continua editável na tela
antes de imprimir.
</p>
</div>
<%# Logo do cabeçalho — zona "Arquivos" do programa original.
Fica ANTES da importação porque é configuração que vale para todos os
romaneios, não escolha por plano: quem troca de contratante troca uma vez. %>
<div class="bg-[#111] border border-white/5 rounded-2xl p-5">
<%= form_with url: atualizar_logo_admin_romaneios_path, method: :post, multipart: true,
data: { turbo: false },
class: 'flex flex-col sm:flex-row sm:items-end gap-3' do %>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-300 mb-1.5">
Logo do romaneio
<span class="text-gray-500 font-normal">— canto esquerdo da folha</span>
</label>
<p class="text-sm text-gray-400 mb-2">
Em uso:
<strong class="text-white"><%= @logo_atual.presence || 'logo-gade.png (padrão)' %></strong>
</p>
<%# O logo está configurado mas o ARQUIVO sumiu do disco. Antes isto era
mudo: a tela dizia o nome do logo novo e o PDF saía com o antigo.
Agora aparece aqui, que é onde a pessoa está olhando quando troca. %>
<% if @logo_problema == :sumiu %>
<p class="text-sm text-red-300 bg-red-900/30 border border-red-500/40 rounded-lg px-3 py-2 mb-2">
O arquivo deste logo <strong>não está mais no servidor</strong> — os PDFs estão
saindo com o logo padrão. Envie o arquivo de novo.
</p>
<% end %>
<input type="file" name="logo" accept="image/png,image/jpeg"
class="w-full text-sm text-gray-300 file:mr-3 file:py-2.5 file:px-4 file:rounded-lg
file:border-0 file:bg-[#1a1a1a] file:text-gray-200 file:cursor-pointer">
<p class="text-xs text-gray-500 mt-2">
PNG ou JPG, até 2 MB — é o que o PDF consegue embutir. Vale para os próximos PDFs gerados.
</p>
</div>
<button type="submit"
class="px-5 py-3 bg-[#1a1a1a] hover:bg-[#242424] text-white border border-white/10
rounded-xl min-h-[48px] whitespace-nowrap">
Trocar logo
</button>
<% end %>
</div>
<%# Importação %>
<div class="bg-[#111] border border-white/5 rounded-2xl p-5 space-y-4">
<h2 class="text-white font-semibold">Importar plano</h2>
<%# UM formulário com DUAS actions: "Buscar no SimpliRoute" usa a action do
<form> e "Enviar planilha" a troca por `formaction`, para não repetir data,
operação e rótulo em dois formulários que sairiam de sincronia.
`authenticity_token: form_authenticity_token` é o que faz o segundo botão
funcionar. Com `load_defaults 7.1` o Rails liga `per_form_csrf_tokens`: o
token embutido vale só para o par (action, method) do <form>, então o POST
que o `formaction` manda para /importar_planilha era recusado SEMPRE, com a
tela "A ação foi recusada por uma verificação de segurança" e volta ao
início — enquanto o botão de buscar, na action original, passava. O helper
sem argumentos devolve o token GLOBAL, válido para as duas actions. %>
<%# ── Período dos planos ────────────────────────────────────────────────
Form SEPARADO e em GET: filtra a LISTA de planos, não importa nada.
Fica antes do formulário de importação porque é o que se mexe primeiro
quando o plano procurado não está entre os 5.
Por que período e não data: período é aproximado por natureza — "agosto"
a pessoa sabe; "20/08" ela não sabia, e era esse chute que a tela veio
eliminar. É também o recorte que a própria tela do SimpliRoute usa, então
não há tradução a fazer entre as duas. %>
<%= form_with url: admin_romaneios_path, method: :get,
class: 'flex flex-wrap items-end gap-3 mb-4 pb-4 border-b border-white/5' do %>
<div>
<label class="block text-xs font-medium text-gray-400 mb-1.5">Planos de</label>
<input type="date" name="plano_inicio" value="<%= @plano_inicio %>"
class="bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-2.5 text-white
focus:outline-none focus:border-orange-500 min-h-[48px]">
</div>
<div>
<label class="block text-xs font-medium text-gray-400 mb-1.5">até</label>
<input type="date" name="plano_fim" value="<%= @plano_fim %>"
class="bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-2.5 text-white
focus:outline-none focus:border-orange-500 min-h-[48px]">
</div>
<button type="submit"
class="px-4 py-2.5 bg-[#1a1a1a] hover:bg-[#242424] text-gray-200 border border-white/10
rounded-xl min-h-[48px] whitespace-nowrap">
<%= icone :buscar, cor: nil %> Filtrar planos
</button>
<%# Rótulo que diz o que o controle FAZ (diretriz 2): limpa o período e
devolve os 5 mais recentes — não "limpa a tela". %>
<% if @planos_filtrados %>
<%= link_to 'Voltar aos 5 mais recentes', admin_romaneios_path,
class: 'px-4 py-2.5 text-gray-400 hover:text-white border border-[#2a2a2a] rounded-xl min-h-[48px] inline-flex items-center whitespace-nowrap' %>
<% end %>
<% end %>
<%= form_with url: admin_romaneios_path, method: :post, multipart: true,
authenticity_token: form_authenticity_token,
data: { turbo: false }, class: 'space-y-4' do %>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<%# ── Plano ────────────────────────────────────────────────────────
Antes aqui se pedia a DATA do plano, e era o passo que mais errava:
o operador pensa por operação ("lançou a UBS Sudeste"), e a data
nem estava ao alcance dele — o plano tem uma JANELA e o dia das
rotas ora é o começo dela, ora o fim (EMAD SETEMBRO 2026 vai de
31/08 a 08/09 e roda em 31/08; UBS OESTE AGOSTO vai de 18 a 20/08 e
roda em 20/08). Agora ele escolhe pelo NOME e quem descobre o dia é
a API.
Cinco planos porque a ferramenta é usada logo depois de lançar uma
operação — o que ele quer está sempre entre os últimos.
A lista vazia (API fora, token vencido) NÃO tranca a tela: cai no
campo de data, que é o comportamento antigo inteiro. %>
<div>
<label class="block text-sm font-medium text-gray-300 mb-1.5">Plano</label>
<% if @planos.present? %>
<select name="plano_id"
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
focus:outline-none focus:border-orange-500 min-h-[48px]">
<% @planos.each_with_index do |pl, i| %>
<option value="<%= pl['id'] %>" <%= 'selected' if i.zero? %>>
<%= pl['name'] %> · <%= Array(pl['routes']).size %> veículos
</option>
<% end %>
</select>
<p class="text-xs text-gray-500 mt-1.5">
<% if @planos_filtrados %>
<%= pluralize(@planos.size, 'plano', plural: 'planos') %> no período.
<% else %>
Os 5 mais recentes. O dia é descoberto pelo plano.
<% end %>
</p>
<% else %>
<p class="text-xs text-orange-400/80 mb-1.5">
<% if @planos_filtrados %>
Nenhum plano nesse período — amplie a faixa ou limpe o filtro.
<% else %>
Não consegui listar os planos do SimpliRoute agora — informe a data.
<% end %>
</p>
<% end %>
<%# A data solta só aparece quando NÃO há lista de planos: ela é a rede
para a API fora do ar, e voltar a oferecê-la de graça reintroduziria
justamente o chute que esta tela existe para eliminar. Para alcançar
um plano antigo o caminho é o filtro de período, logo abaixo. %>
<% if @planos.blank? %>
<input type="date" name="planned_date" required value="<%= @data_padrao %>"
class="w-full mt-2 bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
focus:outline-none focus:border-orange-500 min-h-[48px]">
<% end %>
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-1.5">
Operação do mês
<span class="text-gray-500 font-normal">(APARELHO e telefone)</span>
</label>
<select name="operacao_tabela"
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
focus:outline-none focus:border-orange-500 min-h-[48px]">
<%# "Deixe em branco" e não "Sem operação": deixar vazio agora tenta
deduzir a operação pelo nome do PLANO, e é esse o caminho normal.
Escolher aqui é o override para quando a dedução não acerta. %>
<option value="">Deduzir pelo nome do plano</option>
<% @operacoes_agrupadas.each do |titulo, ops| %>
<optgroup label="<%= titulo %>">
<% ops.each do |op| %>
<option value="<%= op[:tabela] %>"><%= op[:label] %></option>
<% end %>
</optgroup>
<% end %>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-1.5">
PLANO <span class="text-gray-500 font-normal">(canto direito do PDF)</span>
</label>
<input type="text" name="rotulo_plano" placeholder="<%= @planos.present? ? 'Deixe em branco — vem do plano' : 'Ex.: ENTREGAS EMAD 09.2026' %>"
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
placeholder-gray-600 focus:outline-none focus:border-orange-500 min-h-[48px]">
<p class="text-xs text-gray-500 mt-1.5">
<% if @planos.present? %>
Em branco, usa o nome do plano escolhido — é por ele que a operação
do mês (quem é <strong>NOVO</strong>) é encontrada. Preencha só para
imprimir um texto diferente no canto do PDF.
<% else %>
Cole o nome do plano como está no SimpliRoute — é por ele que a
operação do mês (quem é <strong>NOVO</strong>) é encontrada.
<% end %>
</p>
</div>
</div>
<div class="flex flex-col sm:flex-row gap-3 sm:items-center">
<button type="submit"
class="px-6 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold rounded-xl
transition-colors min-h-[48px] whitespace-nowrap">
<%= icone :buscar, cor: nil %> Buscar no SimpliRoute
</button>
<%# Reserva: o plano do dia pode ainda não estar publicado. %>
<details class="flex-1">
<summary class="cursor-pointer text-sm text-gray-400 hover:text-white select-none py-3">
Ou enviar a planilha do plano (.xlsx)
</summary>
<div class="mt-2 flex flex-col sm:flex-row gap-3">
<input type="file" name="arquivo" accept=".xlsx"
class="flex-1 text-sm text-gray-300 file:mr-3 file:py-2.5 file:px-4 file:rounded-lg
file:border-0 file:bg-[#1a1a1a] file:text-gray-200 file:cursor-pointer">
<button type="submit" formaction="<%= importar_planilha_admin_romaneios_path %>"
class="px-5 py-3 bg-[#1a1a1a] hover:bg-[#242424] text-white border border-white/10
rounded-xl min-h-[48px] whitespace-nowrap">
<%= icone :baixar, cor: nil %> Enviar planilha
</button>
</div>
<p class="text-xs text-gray-500 mt-2">
Use quando o plano do dia ainda não estiver publicado no SimpliRoute.
O arquivo é lido na hora e não fica guardado no sistema.
</p>
</details>
</div>
<% end %>
</div>
<%# Romaneios já importados %>
<div class="bg-[#111] border border-white/5 rounded-2xl overflow-hidden">
<% if @romaneios.empty? %>
<div class="p-8 text-center">
<p class="text-gray-400">Nenhum romaneio ainda.</p>
<p class="text-gray-500 text-sm mt-1">Escolha a data do plano acima e clique em “Buscar no SimpliRoute”.</p>
</div>
<% else %>
<table class="w-full text-sm">
<thead class="bg-[#1a1a1a] text-gray-400 text-xs uppercase tracking-wider">
<tr>
<th class="text-left px-4 py-3">Data</th>
<th class="text-left px-4 py-3">Plano</th>
<th class="text-left px-4 py-3">Operação</th>
<th class="text-left px-4 py-3">Veículos</th>
<th class="text-left px-4 py-3">Origem</th>
<th class="px-4 py-3"></th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
<% @romaneios.each do |r| %>
<tr class="hover:bg-white/5">
<td class="px-4 py-3 text-white"><%= l r.planned_date, format: '%d/%m/%Y' %></td>
<td class="px-4 py-3 text-gray-200"><%= r.rotulo_plano.presence || '—' %></td>
<td class="px-4 py-3 text-gray-400"><%= r.operacao_label || '—' %></td>
<td class="px-4 py-3 text-gray-400"><%= r.romaneio_veiculos.size %></td>
<td class="px-4 py-3">
<span class="px-2 py-0.5 rounded-md text-xs bg-white/5 text-gray-300"><%= r.origem %></span>
</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<%= link_to rotulo(:editar, 'Abrir'), admin_romaneio_path(r),
class: 'text-orange-400 hover:text-orange-300 font-medium' %>
<%# button_to em BLOCO: sem bloco ele vira <input type="submit">, que
não aceita o SVG do ícone dentro (ver ApplicationHelper#rotulo). %>
<%= button_to admin_romaneio_path(r), method: :delete,
form: { data: { turbo_confirm: 'Excluir este romaneio e todas as edições feitas nele?' },
class: 'inline-block ml-3' },
class: 'text-gray-500 hover:text-red-400' do %>
<%= icone :excluir, cor: nil %> Excluir
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
</div>
</div>