diff --git a/app/controllers/admin/romaneios_controller.rb b/app/controllers/admin/romaneios_controller.rb index 4505b86..961ec95 100644 --- a/app/controllers/admin/romaneios_controller.rb +++ b/app/controllers/admin/romaneios_controller.rb @@ -32,6 +32,10 @@ class Admin::RomaneiosController < ApplicationController # Mesma lista da tela de criação: o vínculo manual da operação acontece aqui # também, porque é aqui que se descobre que APARELHO saiu vazio. @operacoes_agrupadas = Operacao.agrupadas_por_mes + # A prévia aberta é ESTADO NA URL (?previa=1), não estado de navegador. Ver o + # comentário em _previa.html.erb: hash (#previa) não sobrevive ao Turbo e + # checkbox não sobrevive à troca de folha. + @previa = params[:previa].present? end # Caminho A: busca o plano no SimpliRoute (API, com queda para o espelho). diff --git a/app/javascript/controllers/romaneio_controller.js b/app/javascript/controllers/romaneio_controller.js index d837b19..80d90b5 100644 --- a/app/javascript/controllers/romaneio_controller.js +++ b/app/javascript/controllers/romaneio_controller.js @@ -131,13 +131,13 @@ export default class extends Controller { this.buscaTarget.focus() } - // ── Prévia (pop-up por :target, CSS puro) ─────────────────────────────── - // Abrir, fechar e trocar de folha são do NAVEGADOR: link para `#previa`, - // link para `#sem-previa` e links `?veiculo=X#previa`. Nada disso depende - // deste controller — é o que mantém a prévia viva quando o JS não carrega. - // O que sobra aqui é recarregar a folha depois de uma edição. + // ── Prévia (pop-up; estado na URL, ?previa=1) ─────────────────────────── + // Abrir, fechar e trocar de folha são LINKS comuns — nada disso depende deste + // controller, e é o que mantém a prévia viva quando o JS não carrega. O + // overlay só existe no DOM quando a prévia está aberta, então a presença do + // alvo já é a resposta. O que sobra aqui é recarregar a folha após uma edição. previewAberta() { - return this.hasOverlayTarget && this.overlayTarget.matches(":target") + return this.hasOverlayTarget } carregarPreview() { @@ -161,11 +161,6 @@ export default class extends Controller { this.carregarPreview() } - // O operador pode abrir a prévia DEPOIS de editar: aí o iframe ainda mostra a - // folha antiga. Como quem abre é o `:target`, o gancho é o hashchange. - aoTrocarHash() { - if (this.previewAberta() && this.previewDesatualizado) this.carregarPreview() - } // ── Infra ─────────────────────────────────────────────────────────────── gravar(linhaId, campo, valor, input, botao, aoConcluir) { diff --git a/app/views/admin/romaneios/_previa.html.erb b/app/views/admin/romaneios/_previa.html.erb index 598f3e4..de3aeb4 100644 --- a/app/views/admin/romaneios/_previa.html.erb +++ b/app/views/admin/romaneios/_previa.html.erb @@ -1,40 +1,44 @@ -<%# Prévia do PDF em POP-UP, com navegação de veículo igual à do carrossel do - motorista (setas grandes + contador "3 de 72"). +<%# Prévia do PDF em POP-UP, com navegação entre as folhas no mesmo desenho do + carrossel do motorista (setas grandes + contador "3 de 72"). - ── POR QUE É CSS PURO E NÃO JAVASCRIPT ──────────────────────────────────── - Abrir/fechar sai do `:target`: o botão é um link para `#previa` e o pop-up só - aparece quando ele é o alvo da URL. As setas são links de verdade para - `?veiculo=X#previa`, então trocar de folha mantém o pop-up aberto. + ── O ESTADO "ABERTA" MORA NA URL (?previa=1) ────────────────────────────── + Foram três tentativas até aqui, e cada uma quebrou de um jeito: - Isso não é preciosismo: a prévia é a ÚNICA conferência antes de imprimir e - já morreu três vezes junto com o JavaScript (assets 404 no servidor, com a - tela abrindo normal e o botão sem reação). Do jeito atual ela funciona com o - JS quebrado, sem JS, no celular, e continua endereçável — dá para mandar o - link já com a prévia aberta. + 1. modal por JavaScript → morreu junto com os assets 404 no servidor: o + botão não fazia NADA e a tela abria normal, sem erro visível; + 2.
nativo → funcionava, mas não é pop-up e não dava para + navegar entre as folhas sem fechar; + 3. :target (#previa) → abria com o JS fora do ar e parava de abrir + quando ele voltava: o Turbo intercepta o clique, revisita a página e o + hash se perde no caminho. Pior dos mundos — funcionava no ambiente + quebrado e falhava no ambiente são. - `loading="lazy"` no iframe: fechado, o pop-up é `display:none` e o navegador - NÃO busca o PDF. É o que preserva a economia de não gerar um documento a cada - carregamento da tela. + Com a prévia na query string nada disso se aplica: é uma página como outra + qualquer, o Turbo trata como navegação normal, funciona sem JavaScript, + sobrevive ao F5 e ao "voltar", e o link pode ser enviado para outra pessoa + já com a folha aberta. + + Fechado, este partial não renderiza NADA: o iframe nem existe, então o + servidor não monta um PDF que ninguém pediu (era o custo do desenho + original, que montava o iframe a cada carregamento da tela). + <%# AVISO SEMPRE VISÍVEL, não um alerta que aparece só no erro: quando o + navegador se recusa a desenhar o PDF, ele não avisa ninguém — o quadro + fica branco e a leitura natural é "o sistema está quebrado". O navegador + tem uma opção, ligada por padrão em algumas instalações, de BAIXAR PDF em + vez de exibir; bloqueador de conteúdo (Brave Shields, uBlock) e bloqueio + de pop-up derrubam o mesmo quadro. Como não dá para detectar isso de + dentro da página (o iframe é opaco por segurança), a saída honesta é + dizer onde fica a alternativa. %> +

+ Quadro em branco? O navegador pode estar configurado para baixar PDF + em vez de exibir, ou um bloqueador está barrando — + <%= link_to 'abra em outra aba', + pdf_admin_romaneio_path(@romaneio, veiculo: @veiculo), + target: '_blank', rel: 'noopener', + class: 'underline text-gray-300 hover:text-white' %> + ou libere PDF para este site nas permissões (cadeado da barra de endereço). +

+ <%# Controles no mesmo desenho do carrossel do motorista (redondos, 56px, próximo em laranja), mas como LINKS: cada folha é uma URL. Na ponta vira apagado em vez de sumir — botão que some muda o layout no meio da conferência e o operador perde o alvo do clique. %>
<% if anterior %> - <%= link_to admin_romaneio_path(@romaneio, veiculo: anterior, anchor: 'previa'), + <%= link_to admin_romaneio_path(@romaneio, veiculo: anterior, previa: 1), 'aria-label': "Folha anterior (#{anterior})", class: 'w-14 h-14 rounded-full flex items-center justify-center bg-[#1a1a1a]/90 backdrop-blur border border-white/15 text-white @@ -83,7 +107,7 @@ <% if proximo %> - <%= link_to admin_romaneio_path(@romaneio, veiculo: proximo, anchor: 'previa'), + <%= link_to admin_romaneio_path(@romaneio, veiculo: proximo, previa: 1), 'aria-label': "Próxima folha (#{proximo})", class: 'w-14 h-14 rounded-full flex items-center justify-center bg-orange-500/90 backdrop-blur border border-orange-400 text-black diff --git a/app/views/admin/romaneios/_veiculos.html.erb b/app/views/admin/romaneios/_veiculos.html.erb index 3e245a5..277d983 100644 --- a/app/views/admin/romaneios/_veiculos.html.erb +++ b/app/views/admin/romaneios/_veiculos.html.erb @@ -20,7 +20,7 @@ <% atual = v.veiculo == @veiculo %> <%# Link de verdade (não botão com JS): o veículo vive na query string, então ele é endereçável, volta com o "voltar" do navegador e sobrevive a um F5. %> - class="shrink-0 lg:shrink flex items-center justify-between gap-3 min-h-[48px] @@ -47,13 +47,13 @@ <% prox_v = pos_v && pos_v < nomes_v.size - 1 ? nomes_v[pos_v + 1] : nil %> <% base_v = 'flex-1 min-h-[48px] flex items-center justify-center rounded-xl border' %> <% if ant_v %> - <% else %> <% end %> <% if prox_v %> - <% else %> @@ -61,9 +61,12 @@
+ <%# "Motorista deste veículo — só na tela" não cabe numa linha na coluna + estreita; o complemento vai embaixo, como legenda, em vez de quebrar no + meio do travessão. %> <% atual = @veiculos.find { |v| v.veiculo == @veiculo } %>

- Não sai impresso — a linha do rodapé é assinada à mão na retirada. + A linha do rodapé é assinada à mão na retirada.

- <%# Link, não botão: abre o pop-up da prévia pelo `:target` do CSS (ver - `_previa`), então continua funcionando com o JavaScript fora do ar — foi - o botão daqui que ficou morto três vezes seguidas. %> - + <%# Link, não botão: a prévia aberta é a URL `?previa=1` (ver `_previa`), + então isto funciona com o JavaScript fora do ar — foi o botão daqui que + ficou morto três vezes seguidas. %> + <%= link_to admin_romaneio_path(@romaneio, veiculo: @veiculo, previa: 1), + class: 'w-full min-h-[48px] flex items-center justify-center gap-2 bg-[#1a1a1a] + hover:bg-[#242424] text-white border border-white/10 rounded-xl' do %> <%= icone :visualizar, cor: nil, espaco: false %> Ver prévia do PDF - + <% end %> <%= link_to pdf_admin_romaneio_path(@romaneio, veiculo: @veiculo), target: '_blank', rel: 'noopener', class: 'w-full min-h-[48px] flex items-center justify-center gap-2 bg-[#1a1a1a] hover:bg-[#242424] text-white border border-white/10 rounded-xl' do %> <%= icone :baixar, cor: nil, espaco: false %> PDF deste veículo <% end %> + <%# Rótulo em DUAS linhas de propósito. "PDF de TODOS os veículos" numa linha + só não cabe nos 260px da coluna: quebrava sozinho e deixava o ícone + pendurado num degrau ao lado do texto. Aqui a quebra é intencional — o + ícone fica centrado na altura e a contagem vira a segunda linha, que + ainda informa quantas folhas saem. %> <%= link_to pdf_admin_romaneio_path(@romaneio), - class: 'w-full min-h-[48px] flex items-center justify-center gap-2 bg-[#f97316] - hover:bg-orange-500 text-white font-semibold rounded-xl' do %> - <%= icone :baixar, cor: nil, espaco: false %> PDF de TODOS os veículos + class: 'w-full min-h-[56px] flex items-center justify-center gap-2.5 px-3 py-2 + bg-[#f97316] hover:bg-orange-500 text-white rounded-xl text-left' do %> + <%= icone :baixar, cor: nil, espaco: false, classe: 'shrink-0' %> + + PDF de todos + <%= @veiculos.size %> veículos, uma folha cada + <% end %>
diff --git a/app/views/admin/romaneios/show.html.erb b/app/views/admin/romaneios/show.html.erb index 095a9b0..6129b65 100644 --- a/app/views/admin/romaneios/show.html.erb +++ b/app/views/admin/romaneios/show.html.erb @@ -1,10 +1,9 @@ <%# app/views/admin/romaneios/show.html.erb Editor do romaneio: veículos à esquerda, tabela editável à direita, e o PDF REAL num pop-up com navegação entre as folhas (partial `_previa`, que explica - por que abrir/fechar/trocar de folha é CSS puro e não JavaScript). %> + por que a prévia aberta é estado na URL e não estado de JavaScript). %>
@@ -81,8 +80,9 @@
- <%# Pop-up da prévia: FORA da grade porque cobre a tela inteira quando - aberto (`:target`). Fechado não ocupa espaço nem busca o PDF. %> - <%= render 'previa' %> + <%# Pop-up da prévia: FORA da grade porque cobre a tela inteira. Só existe no + HTML quando ?previa=1 — fechado, nem o iframe é criado, então o servidor + não monta um PDF que ninguém pediu (ver `_previa`). %> + <%= render 'previa' if @previa %> <% end %> diff --git a/docs/BATERIA-DE-TESTES.md b/docs/BATERIA-DE-TESTES.md index f37eeb1..f0a1b6d 100644 --- a/docs/BATERIA-DE-TESTES.md +++ b/docs/BATERIA-DE-TESTES.md @@ -44,6 +44,21 @@ agent-browser network requests | grep -E " (4[0-9]{2}|5[0-9]{2})$" # deve vir - Qualquer request com status ≥ 400 ⇒ FALHOU (anote a URL — um asset `*_controller-*.js` com 404 significa Stimulus quebrado na página inteira; foi exatamente assim que o bug do preview do romaneio passou despercebido). + +⚠️ **404 de asset engana duas vezes.** O Cloudflare guarda os arquivos por um ano, +então uma tela pode parecer boa enquanto a origem já não serve nada — sempre +confira com cache-buster: + +```bash +curl -s -o /dev/null -w "%{http_code}\n" "https://teste.reemtransportes.com.br/assets/controllers/.js?cb=$RANDOM" +``` + +E se **todos** derem 404, o problema é o build inteiro, não os arquivos: a tarefa +`tailwindcss:build` aborta o `assets:precompile` quando falta +`app/assets/tailwind/application.css`. O Propshaft, sem manifesto, continua +gerando URLs com o digest certo — por isso a página abre normal e o JS some em +silêncio. Confira no servidor com +`docker compose logs app | grep '\[boot\] assets'`. - Exceção: chamadas para domínios de terceiros (cloudflareinsights etc.) com falha não reprovam o caso; registre como observação. @@ -115,34 +130,65 @@ agent-browser wait --load networkidle Na tela do romaneio: -A prévia é um `
` nativo — abre e fecha **sem JavaScript**, de propósito -(quando os assets caem, ela precisa continuar funcionando): +A prévia é um pop-up cujo estado mora na **URL** (`?previa=1`) — abre, fecha e +troca de folha por navegação normal, sem JavaScript. **Teste sempre navegando de +verdade**, nunca com `.click()` de dentro do `eval`: o clique programático já +mascarou um bug (com o Turbo carregado, o mecanismo anterior por `#hash` não +abria, mas o `.click()` fazia parecer que abria). ```bash -# 4a. Fechada ao carregar, e o PDF ainda NÃO foi buscado (iframe é loading=lazy): -agent-browser eval "(()=>{const d=document.querySelector('details[data-romaneio-target=overlay]'); return d ? 'aberto:'+d.open : 'PREVIA AUSENTE';})()" -# esperado: "aberto:false" +# 4a. Fechada, o pop-up NÃO existe no HTML (e nenhum PDF é gerado): +agent-browser open "https://teste.reemtransportes.com.br/admin/romaneios/13" +agent-browser eval "document.querySelector('[data-romaneio-target=overlay]') ? 'PRESENTE (erro)' : 'ausente (correto)'" -# 4b. Abrir e conferir que o iframe aponta para o PDF: -agent-browser eval "document.querySelector('details[data-romaneio-target=overlay] summary').click()" -agent-browser eval "(()=>{const d=document.querySelector('details[data-romaneio-target=overlay]'); const f=d.querySelector('iframe'); return 'aberto:'+d.open+' src:'+(f?.getAttribute('src')||'sem src');})()" -# esperado: "aberto:true src:/admin/romaneios//pdf?veiculo=..." +# 4b. Abrir pelo botão, com clique REAL: +agent-browser snapshot -i | grep -i "prévia" # pegar o ref do link +agent-browser click @eN +agent-browser eval "(()=>{const o=document.querySelector('[data-romaneio-target=overlay]'); if(!o) return 'NAO ABRIU'; const c=[...o.querySelectorAll('span')].find(s=>s.textContent.includes(' de ')); return 'aberto | '+c.textContent.trim()+' | url='+location.search;})()" +# esperado: "aberto | 1 de 72 · GADE_001 | url=?veiculo=GADE_001&previa=1" -# 4c. Fechar de novo: -agent-browser eval "document.querySelector('details[data-romaneio-target=overlay] summary').click()" +# 4c. Avançar de folha SEM fechar (é o carrossel): +agent-browser eval "(()=>{const o=document.querySelector('[data-romaneio-target=overlay]'); const a=[...o.querySelectorAll('a')].filter(x=>(x.getAttribute('href')||'').includes('previa')); return a.at(-1).getAttribute('href');})()" +# copie o href e navegue nele com `agent-browser open`; o pop-up tem que continuar +# aberto e o contador virar "2 de 72". -# 4d. O PDF responde e é PDF de verdade (não a página de login): -agent-browser eval "(async()=>{const r=await fetch(document.querySelector('details[data-romaneio-target=overlay] iframe').src); const b=await r.blob(); return r.status+' '+b.type+' '+Math.round(b.size/1024)+'KB';})()" +# 4d. Fechar pelo ✕ (volta para a URL sem previa) e o pop-up sumir do HTML. + +# 4e. O PDF responde e é PDF de verdade (não a página de login): +agent-browser eval "(async()=>{const r=await fetch('/admin/romaneios/13/pdf?veiculo=GADE_001&cb='+Math.random()); const b=await r.blob(); return r.status+' '+b.type+' '+Math.round(b.size/1024)+'KB';})()" # esperado: "200 application/pdf KB" ``` -**PASSOU se**: 4a fechada, 4b abre com src do PDF, 4c fecha, 4d devolve -`application/pdf`, Teste 0 limpo. **NÃO** clicar em "Reimportar plano" (tem -confirmação). +**PASSOU se**: 4a ausente, 4b abre com o contador certo, 4c avança mantendo +aberto, 4d fecha, 4e devolve `application/pdf`. **NÃO** clicar em "Reimportar +plano" (tem confirmação). -⚠️ Se 4d devolver `text/html`, a sessão caiu no meio do teste — refaça o login e +⚠️ Se 4e devolver `text/html`, a sessão caiu no meio do teste — refaça o login e repita, **não** registre como falha do PDF (já deu falso positivo assim). +### Caso 4f — Prévia em branco é o NAVEGADOR, não o sistema + +O quadro do PDF pode aparecer branco mesmo com tudo funcionando, porque o +navegador se recusa a desenhar PDF em iframe. As três causas conhecidas: + +1. **"Baixar PDFs em vez de abri-los"** ligado (Chrome/Brave/Edge: + `chrome://settings/content/pdfDocuments`); +2. **bloqueador de conteúdo** (Brave Shields, uBlock) barrando o quadro; +3. **bloqueio de pop-up/redirecionamento** do navegador. + +O sistema **não consegue detectar isso** (o iframe é opaco por segurança), por +isso o pop-up traz um aviso fixo com a saída ("abra em outra aba"). Antes de +abrir bug de "prévia não aparece", confirme que **não** é o navegador: + +```bash +# Se ISTO devolve application/pdf, o servidor está certo e o problema é o navegador: +agent-browser eval "(async()=>{const r=await fetch('/admin/romaneios/13/pdf?veiculo=GADE_001&cb='+Math.random()); const b=await r.blob(); return r.status+' '+b.type;})()" +``` + +**PASSOU se**: o aviso está visível no rodapé do pop-up e o `fetch` acima devolve +`application/pdf`. Se o fetch devolve PDF e o quadro está branco, o veredito é +**"configuração do navegador"**, não falha do sistema — registre assim. + ### Caso 4e — Vínculo manual da operação (só leitura) O bloco "Operação do mês" tem que aparecer em todo romaneio, e o formulário