From e4b17653ef326ec55a9d01baf3b7a91797c9a845d26ce3a9f3976537bbf06cb6 Mon Sep 17 00:00:00 2001 From: victor Date: Wed, 26 Aug 2026 15:40:50 -0300 Subject: [PATCH] =?UTF-8?q?Modifica=C3=A7=C3=A3o=20na=20area=20do=20motori?= =?UTF-8?q?sta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/carrossel_controller.js | 82 +++++++++++++++---- app/views/consolidacoes/_lista.html.erb | 8 ++ .../consolidacoes/_por_motorista.html.erb | 11 ++- app/views/motorista/dashboard/index.html.erb | 17 +--- .../shared/_carrossel_controles.html.erb | 24 ++++++ 5 files changed, 108 insertions(+), 34 deletions(-) create mode 100644 app/views/shared/_carrossel_controles.html.erb diff --git a/app/javascript/controllers/carrossel_controller.js b/app/javascript/controllers/carrossel_controller.js index 74e6dbd..2793498 100644 --- a/app/javascript/controllers/carrossel_controller.js +++ b/app/javascript/controllers/carrossel_controller.js @@ -1,42 +1,90 @@ // app/javascript/controllers/carrossel_controller.js -// Stimulus — paginação em "carrossel" para listas longas no celular (painel do -// motorista). Os itens já vêm todos no HTML; aqui só mostramos uma PÁGINA por -// vez, com setas e o indicador "1 / 3". +// Stimulus — paginação em "carrossel" para listas longas: mostra N itens por vez +// com setas grandes e o indicador "1–10 de 12". // -// Por que no cliente e não com ?pg= no servidor: a lista divide a tela com os -// cards de valor e o filtro de período — recarregar a página a cada seta faria o -// motorista perder a posição e piscar a tela inteira num 3G. +// PADRÃO EM TELA PEQUENA, LISTA INTEIRA EM TELA GRANDE. Em celular e monitor +// pequeno, uma lista de 30 cards vira rolagem infinita e o rodapé/ações da tela +// somem de vista; num monitor grande a mesma lista cabe e paginar só atrapalha +// (Ctrl+F do navegador não acha o que está escondido). O corte é por largura de +// janela — `maxLargura` — e é reavaliado no resize/rotação da tela. // -// Sem itens suficientes para duas páginas, os controles somem sozinhos (nada de -// seta morta ocupando espaço). +// Os itens já vêm todos no HTML; aqui só escondemos os de fora da página. Por +// isso não serve para listas de centenas de itens (aí é paginação no servidor). +// +// Uso: +//
<- acima disso, mostra tudo +//
<- cada item +// … +// +//
+// +// Um item com data-carrossel-inicial="true" (ex.: o motorista com a composição +// aberta) define em qual página o carrossel abre — senão o clique dele "sumia". import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["item", "controles", "indicador", "anterior", "proximo"] - static values = { tamanho: { type: Number, default: 10 } } + static values = { + tamanho: { type: Number, default: 10 }, + maxLargura: { type: Number, default: 1366 } + } connect() { - this.pagina = 0 + this.pagina = this.paginaInicial() + this._aoRedimensionar = () => this.render() + window.addEventListener("resize", this._aoRedimensionar) this.render() } - proxima() { this.ir(this.pagina + 1) } + disconnect() { + window.removeEventListener("resize", this._aoRedimensionar) + } + + proxima() { this.ir(this.pagina + 1) } anterior() { this.ir(this.pagina - 1) } ir(destino) { this.pagina = Math.min(Math.max(destino, 0), this.totalPaginas - 1) this.render() - // Volta ao topo da lista: sem isso, na 2ª página o motorista continua vendo - // o fim da anterior e parece que nada mudou. + // Volta ao topo da lista: sem isso, na 2ª página o leitor continua vendo o + // fim da anterior e parece que nada mudou. this.element.scrollIntoView({ behavior: "smooth", block: "start" }) } + // Tela grande: sem paginação (a lista inteira aparece). + get paginando() { + return window.innerWidth <= this.maxLarguraValue && + this.itemTargets.length > this.tamanhoValue + } + get totalPaginas() { return Math.max(Math.ceil(this.itemTargets.length / this.tamanhoValue), 1) } + paginaInicial() { + const i = this.itemTargets.findIndex((el) => el.dataset.carrosselInicial === "true") + return i < 0 ? 0 : Math.floor(i / this.tamanhoValue) + } + render() { + const total = this.itemTargets.length + + if (!this.paginando) { + this.itemTargets.forEach((item) => item.classList.remove("hidden")) + if (this.hasControlesTarget) this.controlesTarget.classList.add("hidden") + return + } + + // Uma tela menor pode ter reduzido o total de páginas desde a última vez. + this.pagina = Math.min(this.pagina, this.totalPaginas - 1) + const inicio = this.pagina * this.tamanhoValue const fim = inicio + this.tamanhoValue @@ -45,12 +93,11 @@ export default class extends Controller { }) if (!this.hasControlesTarget) return + this.controlesTarget.classList.remove("hidden") - this.controlesTarget.classList.toggle("hidden", this.totalPaginas < 2) if (this.hasIndicadorTarget) { - // "1–10 de 12" e não "página 1 de 2": o motorista quer saber quantos - // fechamentos existem, não quantas páginas a tela tem. - const total = this.itemTargets.length + // "1–10 de 12" e não "página 1 de 2": quem lê quer saber quantos itens + // existem, não quantas páginas a tela tem. this.indicadorTarget.textContent = `${inicio + 1}–${Math.min(fim, total)} de ${total}` } if (this.hasAnteriorTarget) this.alternar(this.anteriorTarget, this.pagina === 0) @@ -60,7 +107,6 @@ export default class extends Controller { // Botão de ponta: desabilitado de verdade (não só apagado), para o toque não // "funcionar sem fazer nada". alternar(botao, desligado) { - if (!botao) return botao.disabled = desligado botao.classList.toggle("opacity-30", desligado) botao.classList.toggle("pointer-events-none", desligado) diff --git a/app/views/consolidacoes/_lista.html.erb b/app/views/consolidacoes/_lista.html.erb index a4c4307..e359537 100644 --- a/app/views/consolidacoes/_lista.html.erb +++ b/app/views/consolidacoes/_lista.html.erb @@ -1,8 +1,12 @@ <%# app/views/consolidacoes/_lista.html.erb — visão por CONSOLIDAÇÃO %> <% if @consolidacoes.any? %> + <%# Em celular/monitor pequeno a lista vira carrossel de 10 em 10; em tela + grande aparece inteira (ver carrossel_controller.js). %> +
<% @consolidacoes.each do |c| %> <%= link_to consolidacao_path(c), + data: { carrossel_target: 'item' }, class: 'block bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 rounded-xl p-5 transition-colors' do %>
@@ -28,6 +32,10 @@ <% end %> <% end %>
+ + <%= render 'shared/carrossel_controles', + anterior: 'Consolidações anteriores', proximo: 'Próximas consolidações' %> +
<% else %>

<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>

diff --git a/app/views/consolidacoes/_por_motorista.html.erb b/app/views/consolidacoes/_por_motorista.html.erb index bba2c91..06eaf02 100644 --- a/app/views/consolidacoes/_por_motorista.html.erb +++ b/app/views/consolidacoes/_por_motorista.html.erb @@ -46,10 +46,15 @@

Ajuste as datas acima — o recorte pega a consolidação inteira dentro da faixa.

<% else %> + <%# Em celular/monitor pequeno vira carrossel de 10 em 10; em tela grande sai a + lista inteira. O motorista com a composição aberta define a página inicial + (data-carrossel-inicial) — senão o clique dele cairia numa página escondida. %> +
<% @totais.linhas.each do |linha| %> <% aberto = @detalhe_motorista == linha[:motorista] %> -
+
<%# Cabeçalho do motorista — o clique abre/fecha a composição %> <%= link_to consolidacoes_path(ctx.merge(detalhe: (aberto ? nil : linha[:motorista])).compact_blank), @@ -121,6 +126,10 @@ <% end %>
+ <%= render 'shared/carrossel_controles', + anterior: 'Motoristas anteriores', proximo: 'Próximos motoristas' %> +
+

<%= icone :dica %> <% if @status_totais == 'finalizada' %> diff --git a/app/views/motorista/dashboard/index.html.erb b/app/views/motorista/dashboard/index.html.erb index 1083ead..ac30001 100644 --- a/app/views/motorista/dashboard/index.html.erb +++ b/app/views/motorista/dashboard/index.html.erb @@ -185,21 +185,8 @@ <% end %>

- <%# Setas: alvos grandes (56px) porque a tela é usada de luva/no trânsito. - Somem sozinhas quando há uma página só (ver carrossel_controller.js). %> - + <%= render 'shared/carrossel_controles', + anterior: 'Fechamentos anteriores', proximo: 'Próximos fechamentos' %>
<% else %>
diff --git a/app/views/shared/_carrossel_controles.html.erb b/app/views/shared/_carrossel_controles.html.erb new file mode 100644 index 0000000..8ab3b90 --- /dev/null +++ b/app/views/shared/_carrossel_controles.html.erb @@ -0,0 +1,24 @@ +<%# app/views/shared/_carrossel_controles.html.erb + Setas + indicador do carrossel (ver carrossel_controller.js). Nascem + escondidos: quem decide se esta tela é pequena o bastante para paginar é o + JS — em monitor grande a lista aparece inteira e estes controles não voltam. + + Alvos de 56px: a tela é usada no celular, em movimento. + + locals: anterior:/proximo: (rótulos acessíveis, opcionais). %> +<% anterior = local_assigns.fetch(:anterior, 'Itens anteriores') %> +<% proximo = local_assigns.fetch(:proximo, 'Próximos itens') %> + +