diff --git a/app/javascript/controllers/romaneio_controller.js b/app/javascript/controllers/romaneio_controller.js index e52f359..c673a0b 100644 --- a/app/javascript/controllers/romaneio_controller.js +++ b/app/javascript/controllers/romaneio_controller.js @@ -10,8 +10,10 @@ import { Controller } from "@hotwired/stimulus" // plano inteiro. A regra de reconciliação (Romaneios::Importador) só funciona com // escrita por campo. export default class extends Controller { + // `overlay` é o
da prévia (nome mantido para não renomear em vão); + // `caixaPreview` saiu junto com o modal. static targets = ["celula", "preview", "itemVeiculo", "motorista", "rotulo", "status", "linha", "busca", - "contador", "overlay", "caixaPreview", "tituloPreview"] + "contador", "overlay", "tituloPreview"] static values = { url: String, pdfUrl: String, veiculo: String } // Recarregar o PDF a cada tecla travaria a tela; 600ms depois do último save é o @@ -129,38 +131,10 @@ export default class extends Controller { this.buscaTarget.focus() } - // ── Prévia (sobreposição) ─────────────────────────────────────────────── - abrirPreview() { - if (!this.hasOverlayTarget) return - - // Classe + atributo: só o atributo `hidden` não basta, porque a utility - // `.flex` do Tailwind vence o `[hidden] { display:none }` do preflight — - // foi assim que o modal ficou preso na tela do /admin/romaneios/13. - this.overlayTarget.hidden = false - this.overlayTarget.classList.remove("hidden") - this.overlayTarget.classList.add("flex") - // Trava o scroll do fundo: rolar a lista atrás de um modal aberto faz o - // operador perder o lugar quando fecha. - document.body.style.overflow = "hidden" - if (this.previewDesatualizado) this.carregarPreview() - } - - fecharPreview() { - if (!this.hasOverlayTarget || this.overlayTarget.hidden) return - - this.overlayTarget.hidden = true - this.overlayTarget.classList.remove("flex") - this.overlayTarget.classList.add("hidden") - document.body.style.overflow = "" - } - - // Clique no fundo escuro fecha; clique DENTRO da caixa não — senão mexer no - // PDF fecharia a prévia sem querer. - fecharPorFora(event) { - if (this.hasCaixaPreviewTarget && this.caixaPreviewTarget.contains(event.target)) return - this.fecharPreview() - } - + // ── Prévia (bloco
nativo) ───────────────────────────────────── + // Abrir e fechar é do NAVEGADOR — o
não depende deste controller, + // e é isso que mantém a prévia viva quando o JavaScript não carrega. O que + // sobra aqui é só recarregar a folha depois de uma edição. carregarPreview() { if (!this.hasPreviewTarget) return @@ -171,16 +145,25 @@ export default class extends Controller { this.previewDesatualizado = false } - // Chamada depois de cada gravação. Com a prévia FECHADA não gera PDF nenhum — - // só marca que o próximo "Ver prévia" precisa buscar de novo. + // Chamada depois de cada gravação. Com o bloco FECHADO não gera PDF nenhum — + // o `loading="lazy"` do iframe já garante isso, e recarregar o src de um + // iframe escondido forçaria o servidor a montar um PDF que ninguém vai ver. atualizarPreview() { - if (!this.hasOverlayTarget || this.overlayTarget.hidden) { + if (!this.hasOverlayTarget || !this.overlayTarget.open) { this.previewDesatualizado = true return } this.carregarPreview() } + // O operador pode abrir a prévia depois de ter editado: neste caso o iframe + // ainda mostra a folha antiga, então recarregamos no momento da abertura. + aoAbrirPreview() { + if (this.hasOverlayTarget && this.overlayTarget.open && this.previewDesatualizado) { + this.carregarPreview() + } + } + // ── Infra ─────────────────────────────────────────────────────────────── gravar(linhaId, campo, valor, input, botao, aoConcluir) { this.status("Salvando…") diff --git a/app/views/admin/romaneios/_previa.html.erb b/app/views/admin/romaneios/_previa.html.erb new file mode 100644 index 0000000..e680e40 --- /dev/null +++ b/app/views/admin/romaneios/_previa.html.erb @@ -0,0 +1,44 @@ +<%# ── Prévia do PDF ─────────────────────────────────────────────────────── +
NATIVO, e não um modal controlado por Stimulus. Isto é o + conserto de um problema que voltou três vezes: a prévia era o ÚNICO jeito + de conferir a folha antes de imprimir e morria junto com o JavaScript. + Quando os assets do servidor ficaram 404 (precompile que não rodou no + deploy), o botão "Ver prévia do PDF" não fazia nada e o operador ficava + sem saída — a tela abria normal, sem erro visível. Com
o + abrir/fechar é do navegador: funciona com o JS quebrado, sem JS, e no + celular. + + `loading="lazy"`: o iframe só busca o PDF quando o bloco é aberto. É o que + preserva a economia que o modal trazia (gerar PDF a cada carregamento da + tela era o problema original) sem precisar de JS para preencher o `src`. + + + +
diff --git a/app/views/admin/romaneios/_veiculos.html.erb b/app/views/admin/romaneios/_veiculos.html.erb index 50bee13..c454191 100644 --- a/app/views/admin/romaneios/_veiculos.html.erb +++ b/app/views/admin/romaneios/_veiculos.html.erb @@ -63,12 +63,10 @@
- <%# Prévia sob demanda: o PDF só é gerado quando alguém pede para ver. %> - + <%# A prévia NÃO tem botão aqui: ela é o bloco recolhível no topo da coluna + ao lado (`_previa`), aberto pelo próprio navegador. Um segundo botão que + dependesse de JavaScript para abrir aquele bloco voltaria a ficar morto + quando os assets falhassem — que é justamente o que se está evitando. %> <%= 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] diff --git a/app/views/admin/romaneios/index.html.erb b/app/views/admin/romaneios/index.html.erb index d9042dc..b2d0b60 100644 --- a/app/views/admin/romaneios/index.html.erb +++ b/app/views/admin/romaneios/index.html.erb @@ -55,7 +55,19 @@

Importar plano

+ <%# UM formulário com DUAS actions: "Buscar no SimpliRoute" usa a action do +
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 , 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. %> <%= form_with url: admin_romaneios_path, method: :post, multipart: true, + authenticity_token: form_authenticity_token, data: { turbo: false }, class: 'space-y-4' do %>
diff --git a/app/views/admin/romaneios/show.html.erb b/app/views/admin/romaneios/show.html.erb index d2b9bae..e33410c 100644 --- a/app/views/admin/romaneios/show.html.erb +++ b/app/views/admin/romaneios/show.html.erb @@ -1,12 +1,7 @@ <%# app/views/admin/romaneios/show.html.erb Editor do romaneio: veículos à esquerda, tabela editável à direita, e o PDF - REAL numa sobreposição aberta sob demanda. - - A prévia é um -
-
<% end %>
diff --git a/docs/BATERIA-DE-TESTES.md b/docs/BATERIA-DE-TESTES.md index 5a94281..f37eeb1 100644 --- a/docs/BATERIA-DE-TESTES.md +++ b/docs/BATERIA-DE-TESTES.md @@ -115,29 +115,33 @@ 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): + ```bash -# 4a. O modal de prévia NÃO pode estar visível no carregamento: -agent-browser eval "const o=document.querySelector('[data-romaneio-target=overlay]'); o?getComputedStyle(o).display:'sem overlay'" -# esperado: "none" +# 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" -# 4b. Abrir a prévia: -agent-browser snapshot -i | grep -i "prévia" # achar o botão "Ver prévia do PDF" -agent-browser click @eY -agent-browser eval "const o=document.querySelector('[data-romaneio-target=overlay]'); getComputedStyle(o).display + ' src:' + (document.querySelector('iframe[data-romaneio-target=preview]')?.src||'')" -# esperado: "flex src:https://.../pdf?veiculo=..." +# 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=..." -# 4c. Fechar com Esc: -agent-browser press Escape -agent-browser eval "getComputedStyle(document.querySelector('[data-romaneio-target=overlay]')).display" -# esperado: "none" +# 4c. Fechar de novo: +agent-browser eval "document.querySelector('details[data-romaneio-target=overlay] summary').click()" -# 4d. PDF responde: -agent-browser eval "fetch(document.querySelector('a[href*=pdf]').href).then(r=>r.status)" -# esperado: 200 +# 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';})()" +# esperado: "200 application/pdf KB" ``` -**PASSOU se**: 4a="none", 4b="flex" com src preenchido, 4c="none", 4d=200, -Teste 0 limpo. **NÃO** clicar em "Reimportar plano" (tem confirmação). +**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). + +⚠️ Se 4d 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 4e — Vínculo manual da operação (só leitura) @@ -210,6 +214,47 @@ agent-browser open https://teste.reemtransportes.com.br/auth/login **PASSOU se**: cai no dashboard do motorista, valores legíveis sem zoom, Teste 0 limpo. Ao final: `agent-browser set viewport 1280 800`. +## Caso 9 — Tempo de resposta (performance) + +Mede o tempo real de cada tela e do PDF. Rode logado, com a rede parada (sem +outra aba baixando coisa), e compare com a linha de base. + +```bash +# Tempo das telas (TTFB | resposta completa | DOM pronto) +for u in /dashboard /dashboard/operacoes "/consolidacoes?visao=lista" /admin/romaneios /admin/romaneios/13; do + agent-browser open "https://teste.reemtransportes.com.br$u" >/dev/null + agent-browser eval "(()=>{const n=performance.getEntriesByType('navigation')[0]; return Math.round(n.responseStart-n.requestStart)+'|'+Math.round(n.responseEnd-n.requestStart)+'|'+Math.round(n.domContentLoadedEventEnd-n.startTime);})()" +done +``` + +```bash +# Tempo do PDF (o mais pesado do sistema). Com cache-buster, senão mede o cache. +agent-browser eval "(async()=>{const t0=performance.now(); const r=await fetch('/admin/romaneios/13/pdf?veiculo=GADE_002&cb='+Math.random()); const b=await r.blob(); return Math.round(performance.now()-t0)+'ms, '+Math.round(b.size/1024)+' KB';})()" +``` + +**Linha de base medida em 28/08/2026** (romaneio de 2.047 paradas / 72 veículos, +ambiente de teste, acesso pelo domínio com Cloudflare na frente): + +| Alvo | TTFB | Resposta | DOM pronto | +|---|---|---|---| +| `/dashboard` | 339 ms | 340 ms | 493 ms | +| `/dashboard/operacoes` | 168 ms | 251 ms | 402 ms | +| `/consolidacoes?visao=lista` | 349 ms | 351 ms | 430 ms | +| `/admin/romaneios` | 49 ms | 50 ms | 127 ms | +| `/admin/romaneios/13` | 71 ms | 78 ms | 155 ms | +| **PDF de 1 veículo** | — | **228 ms** (84 KB) | — | +| **PDF de TODOS (72)** | — | **8.947 ms** (1,7 MB) | — | + +**Orçamento — FALHOU se passar de**: 1 s de TTFB em qualquer tela; 1 s no PDF de +um veículo (é ele que a prévia carrega, tem que abrir na hora); 30 s no PDF +completo (acima disso o proxy corta a conexão e o operador recebe erro). + +⚠️ **Ponto de atenção conhecido**: o PDF de todos os veículos leva ~9 s e segura +um processo do servidor esse tempo todo. Ainda dentro do aceitável para uma ação +manual e pontual, mas é o primeiro candidato a virar download em segundo plano se +o volume crescer ou se dois operadores baixarem ao mesmo tempo. **Não** compare +esse número com o do PDF de um veículo: são unidades diferentes (1 folha × 72). + ## Relatório final (obrigatório) Terminar a execução com uma tabela e a lista de falhas: @@ -225,6 +270,7 @@ Terminar a execução com uma tabela e a lista de falhas: | 6 Admin | … | … | | 7 Notificações | … | … | | 8 PIN motorista | … | … | +| 9 Performance | … | tabela de tempos | Depois da tabela, listar **somente as falhas**, cada uma com: URL, o que era esperado, o que aconteceu (mensagem de erro/console) e o caminho do screenshot.