Modificação na area do motorista
This commit is contained in:
@@ -1,42 +1,90 @@
|
|||||||
// app/javascript/controllers/carrossel_controller.js
|
// app/javascript/controllers/carrossel_controller.js
|
||||||
// Stimulus — paginação em "carrossel" para listas longas no celular (painel do
|
// Stimulus — paginação em "carrossel" para listas longas: mostra N itens por vez
|
||||||
// motorista). Os itens já vêm todos no HTML; aqui só mostramos uma PÁGINA por
|
// com setas grandes e o indicador "1–10 de 12".
|
||||||
// vez, com setas e o indicador "1 / 3".
|
|
||||||
//
|
//
|
||||||
// Por que no cliente e não com ?pg= no servidor: a lista divide a tela com os
|
// PADRÃO EM TELA PEQUENA, LISTA INTEIRA EM TELA GRANDE. Em celular e monitor
|
||||||
// cards de valor e o filtro de período — recarregar a página a cada seta faria o
|
// pequeno, uma lista de 30 cards vira rolagem infinita e o rodapé/ações da tela
|
||||||
// motorista perder a posição e piscar a tela inteira num 3G.
|
// 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
|
// Os itens já vêm todos no HTML; aqui só escondemos os de fora da página. Por
|
||||||
// seta morta ocupando espaço).
|
// isso não serve para listas de centenas de itens (aí é paginação no servidor).
|
||||||
|
//
|
||||||
|
// Uso:
|
||||||
|
// <div data-controller="carrossel"
|
||||||
|
// data-carrossel-tamanho-value="10" <- itens por página
|
||||||
|
// data-carrossel-max-largura-value="1366"> <- acima disso, mostra tudo
|
||||||
|
// <div data-carrossel-target="item">…</div> <- cada item
|
||||||
|
// …
|
||||||
|
// <div data-carrossel-target="controles" class="hidden">
|
||||||
|
// <button data-action="carrossel#anterior" data-carrossel-target="anterior">
|
||||||
|
// <span data-carrossel-target="indicador"></span>
|
||||||
|
// <button data-action="carrossel#proxima" data-carrossel-target="proximo">
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
//
|
||||||
|
// 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"
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = ["item", "controles", "indicador", "anterior", "proximo"]
|
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() {
|
connect() {
|
||||||
this.pagina = 0
|
this.pagina = this.paginaInicial()
|
||||||
|
this._aoRedimensionar = () => this.render()
|
||||||
|
window.addEventListener("resize", this._aoRedimensionar)
|
||||||
this.render()
|
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) }
|
anterior() { this.ir(this.pagina - 1) }
|
||||||
|
|
||||||
ir(destino) {
|
ir(destino) {
|
||||||
this.pagina = Math.min(Math.max(destino, 0), this.totalPaginas - 1)
|
this.pagina = Math.min(Math.max(destino, 0), this.totalPaginas - 1)
|
||||||
this.render()
|
this.render()
|
||||||
// Volta ao topo da lista: sem isso, na 2ª página o motorista continua vendo
|
// Volta ao topo da lista: sem isso, na 2ª página o leitor continua vendo o
|
||||||
// o fim da anterior e parece que nada mudou.
|
// fim da anterior e parece que nada mudou.
|
||||||
this.element.scrollIntoView({ behavior: "smooth", block: "start" })
|
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() {
|
get totalPaginas() {
|
||||||
return Math.max(Math.ceil(this.itemTargets.length / this.tamanhoValue), 1)
|
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() {
|
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 inicio = this.pagina * this.tamanhoValue
|
||||||
const fim = inicio + this.tamanhoValue
|
const fim = inicio + this.tamanhoValue
|
||||||
|
|
||||||
@@ -45,12 +93,11 @@ export default class extends Controller {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!this.hasControlesTarget) return
|
if (!this.hasControlesTarget) return
|
||||||
|
this.controlesTarget.classList.remove("hidden")
|
||||||
|
|
||||||
this.controlesTarget.classList.toggle("hidden", this.totalPaginas < 2)
|
|
||||||
if (this.hasIndicadorTarget) {
|
if (this.hasIndicadorTarget) {
|
||||||
// "1–10 de 12" e não "página 1 de 2": o motorista quer saber quantos
|
// "1–10 de 12" e não "página 1 de 2": quem lê quer saber quantos itens
|
||||||
// fechamentos existem, não quantas páginas a tela tem.
|
// existem, não quantas páginas a tela tem.
|
||||||
const total = this.itemTargets.length
|
|
||||||
this.indicadorTarget.textContent = `${inicio + 1}–${Math.min(fim, total)} de ${total}`
|
this.indicadorTarget.textContent = `${inicio + 1}–${Math.min(fim, total)} de ${total}`
|
||||||
}
|
}
|
||||||
if (this.hasAnteriorTarget) this.alternar(this.anteriorTarget, this.pagina === 0)
|
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
|
// Botão de ponta: desabilitado de verdade (não só apagado), para o toque não
|
||||||
// "funcionar sem fazer nada".
|
// "funcionar sem fazer nada".
|
||||||
alternar(botao, desligado) {
|
alternar(botao, desligado) {
|
||||||
if (!botao) return
|
|
||||||
botao.disabled = desligado
|
botao.disabled = desligado
|
||||||
botao.classList.toggle("opacity-30", desligado)
|
botao.classList.toggle("opacity-30", desligado)
|
||||||
botao.classList.toggle("pointer-events-none", desligado)
|
botao.classList.toggle("pointer-events-none", desligado)
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<%# app/views/consolidacoes/_lista.html.erb — visão por CONSOLIDAÇÃO %>
|
<%# app/views/consolidacoes/_lista.html.erb — visão por CONSOLIDAÇÃO %>
|
||||||
<% if @consolidacoes.any? %>
|
<% if @consolidacoes.any? %>
|
||||||
|
<%# Em celular/monitor pequeno a lista vira carrossel de 10 em 10; em tela
|
||||||
|
grande aparece inteira (ver carrossel_controller.js). %>
|
||||||
|
<div data-controller="carrossel" data-carrossel-tamanho-value="10">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<% @consolidacoes.each do |c| %>
|
<% @consolidacoes.each do |c| %>
|
||||||
<%= link_to consolidacao_path(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 %>
|
class: 'block bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 rounded-xl p-5 transition-colors' do %>
|
||||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
@@ -28,6 +32,10 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%= render 'shared/carrossel_controles',
|
||||||
|
anterior: 'Consolidações anteriores', proximo: 'Próximas consolidações' %>
|
||||||
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-12 text-center">
|
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-12 text-center">
|
||||||
<p class="mb-4"><%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %></p>
|
<p class="mb-4"><%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %></p>
|
||||||
|
|||||||
@@ -46,10 +46,15 @@
|
|||||||
<p class="text-gray-500 text-sm mt-1">Ajuste as datas acima — o recorte pega a consolidação inteira dentro da faixa.</p>
|
<p class="text-gray-500 text-sm mt-1">Ajuste as datas acima — o recorte pega a consolidação inteira dentro da faixa.</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% 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. %>
|
||||||
|
<div data-controller="carrossel" data-carrossel-tamanho-value="10">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<% @totais.linhas.each do |linha| %>
|
<% @totais.linhas.each do |linha| %>
|
||||||
<% aberto = @detalhe_motorista == linha[:motorista] %>
|
<% aberto = @detalhe_motorista == linha[:motorista] %>
|
||||||
<div class="bg-[#1a1a1a] border rounded-xl overflow-hidden transition-colors <%= aberto ? 'border-orange-500' : 'border-[#2a2a2a] hover:border-orange-500/60' %>">
|
<div data-carrossel-target="item" data-carrossel-inicial="<%= aberto %>"
|
||||||
|
class="bg-[#1a1a1a] border rounded-xl overflow-hidden transition-colors <%= aberto ? 'border-orange-500' : 'border-[#2a2a2a] hover:border-orange-500/60' %>">
|
||||||
|
|
||||||
<%# Cabeçalho do motorista — o clique abre/fecha a composição %>
|
<%# Cabeçalho do motorista — o clique abre/fecha a composição %>
|
||||||
<%= link_to consolidacoes_path(ctx.merge(detalhe: (aberto ? nil : linha[:motorista])).compact_blank),
|
<%= link_to consolidacoes_path(ctx.merge(detalhe: (aberto ? nil : linha[:motorista])).compact_blank),
|
||||||
@@ -121,6 +126,10 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%= render 'shared/carrossel_controles',
|
||||||
|
anterior: 'Motoristas anteriores', proximo: 'Próximos motoristas' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="text-gray-500 text-xs mt-4">
|
<p class="text-gray-500 text-xs mt-4">
|
||||||
<%= icone :dica %>
|
<%= icone :dica %>
|
||||||
<% if @status_totais == 'finalizada' %>
|
<% if @status_totais == 'finalizada' %>
|
||||||
|
|||||||
@@ -185,21 +185,8 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# Setas: alvos grandes (56px) porque a tela é usada de luva/no trânsito.
|
<%= render 'shared/carrossel_controles',
|
||||||
Somem sozinhas quando há uma página só (ver carrossel_controller.js). %>
|
anterior: 'Fechamentos anteriores', proximo: 'Próximos fechamentos' %>
|
||||||
<div data-carrossel-target="controles" class="hidden flex items-center justify-between gap-3 mt-4">
|
|
||||||
<button type="button" data-action="carrossel#anterior" data-carrossel-target="anterior"
|
|
||||||
class="bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 text-white rounded-2xl w-14 h-14 flex items-center justify-center"
|
|
||||||
aria-label="Fechamentos anteriores">
|
|
||||||
<%= icone :voltar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
|
|
||||||
</button>
|
|
||||||
<span data-carrossel-target="indicador" class="text-gray-300 text-base font-semibold"></span>
|
|
||||||
<button type="button" data-action="carrossel#proxima" data-carrossel-target="proximo"
|
|
||||||
class="bg-orange-500 hover:bg-orange-600 text-black rounded-2xl w-14 h-14 flex items-center justify-center"
|
|
||||||
aria-label="Próximos fechamentos">
|
|
||||||
<%= icone :avancar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-2xl p-10 text-center">
|
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-2xl p-10 text-center">
|
||||||
|
|||||||
24
app/views/shared/_carrossel_controles.html.erb
Normal file
24
app/views/shared/_carrossel_controles.html.erb
Normal file
@@ -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') %>
|
||||||
|
|
||||||
|
<div data-carrossel-target="controles" class="hidden flex items-center justify-between gap-3 mt-4">
|
||||||
|
<button type="button" data-action="carrossel#anterior" data-carrossel-target="anterior"
|
||||||
|
class="bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 text-white rounded-2xl w-14 h-14 flex items-center justify-center"
|
||||||
|
aria-label="<%= anterior %>">
|
||||||
|
<%= icone :voltar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
|
||||||
|
</button>
|
||||||
|
<span data-carrossel-target="indicador" class="text-gray-300 text-base font-semibold"></span>
|
||||||
|
<button type="button" data-action="carrossel#proxima" data-carrossel-target="proximo"
|
||||||
|
class="bg-orange-500 hover:bg-orange-600 text-black rounded-2xl w-14 h-14 flex items-center justify-center"
|
||||||
|
aria-label="<%= proximo %>">
|
||||||
|
<%= icone :avancar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user