Atualização Automatica dos numeros e correção de erro e falha de segurança
This commit is contained in:
@@ -72,6 +72,44 @@ module ApplicationHelper
|
||||
tag.span label, class: "inline-flex items-center px-2.5 py-1 rounded-full text-xs font-bold #{cor}"
|
||||
end
|
||||
|
||||
# Horário da última sincronização dos dados de operação.
|
||||
#
|
||||
# O backend atualiza os dados a cada 30 min, das 08h às 18h (horário de
|
||||
# Brasília). Como as tabelas externas de rastreio não têm um carimbo de
|
||||
# sincronização confiável, derivamos o último "slot" concluído a partir dessa
|
||||
# grade fixa. Usa Time.current (fuso do app), NUNCA Time.now — que pega o fuso
|
||||
# do container (UTC) e por isso mostrava o horário adiantado/errado.
|
||||
DADOS_SYNC_INICIO = 8 # abre às 08h
|
||||
DADOS_SYNC_FIM = 18 # fecha às 18h
|
||||
DADOS_SYNC_PASSO = 30 # minutos entre atualizações
|
||||
|
||||
def ultima_atualizacao_dados(agora = Time.current)
|
||||
agora = agora.in_time_zone
|
||||
abre = agora.change(hour: DADOS_SYNC_INICIO, min: 0)
|
||||
fecha = agora.change(hour: DADOS_SYNC_FIM, min: 0)
|
||||
|
||||
if agora < abre
|
||||
# Antes de abrir: a última atualização foi o fechamento do dia anterior.
|
||||
(abre - 1.day).change(hour: DADOS_SYNC_FIM, min: 0)
|
||||
elsif agora >= fecha
|
||||
fecha
|
||||
else
|
||||
# Arredonda para baixo ao múltiplo de 30 min dentro da janela.
|
||||
minuto = (agora.min / DADOS_SYNC_PASSO) * DADOS_SYNC_PASSO
|
||||
agora.change(min: minuto, sec: 0)
|
||||
end
|
||||
end
|
||||
|
||||
# Rótulo amigável: "às 14:30" (hoje) ou "em 05/07 às 18:00" (outro dia).
|
||||
def ultima_atualizacao_label(agora = Time.current)
|
||||
t = ultima_atualizacao_dados(agora)
|
||||
if t.to_date == agora.in_time_zone.to_date
|
||||
"às #{t.strftime('%H:%M')}"
|
||||
else
|
||||
"em #{t.strftime('%d/%m')} às #{t.strftime('%H:%M')}"
|
||||
end
|
||||
end
|
||||
|
||||
# Barra de progresso laranja
|
||||
def progress_bar(percentual, label: nil)
|
||||
pct = percentual.to_i.clamp(0, 100)
|
||||
|
||||
45
app/javascript/controllers/auto_refresh_controller.js
Normal file
45
app/javascript/controllers/auto_refresh_controller.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
// Atualiza o painel automaticamente, sem o usuário precisar apertar F5.
|
||||
//
|
||||
// A cada `interval` ms faz um Turbo.visit "replace" na própria URL: o Turbo
|
||||
// re-renderiza o corpo (KPIs, gráficos, mapa) e re-executa os <script> inline,
|
||||
// preservando os filtros — que vivem na query string. Pausa quando a aba está
|
||||
// oculta ou quando o usuário está com um campo em foco, e restaura a posição de
|
||||
// rolagem após cada atualização (para não "pular" para o topo).
|
||||
//
|
||||
// Uso:
|
||||
// <div data-controller="auto-refresh"
|
||||
// data-auto-refresh-interval-value="300000"> ... </div>
|
||||
export default class extends Controller {
|
||||
static values = { interval: { type: Number, default: 300000 } } // 5 min
|
||||
|
||||
connect() {
|
||||
// Restaura a rolagem salva antes da última atualização automática.
|
||||
const y = sessionStorage.getItem("autoRefreshScroll")
|
||||
if (y !== null) {
|
||||
window.scrollTo(0, parseInt(y, 10))
|
||||
sessionStorage.removeItem("autoRefreshScroll")
|
||||
}
|
||||
this.timer = setInterval(() => this.refresh(), this.intervalValue)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
|
||||
refresh() {
|
||||
// Não recarrega em aba oculta (economiza recursos) nem se o usuário estiver
|
||||
// digitando/selecionando algo (evita perder o que está fazendo).
|
||||
if (document.visibilityState !== "visible") return
|
||||
const ativo = document.activeElement
|
||||
if (ativo && ["INPUT", "SELECT", "TEXTAREA"].includes(ativo.tagName)) return
|
||||
|
||||
sessionStorage.setItem("autoRefreshScroll", String(window.scrollY))
|
||||
if (window.Turbo) {
|
||||
window.Turbo.visit(window.location.href, { action: "replace" })
|
||||
} else {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,9 @@
|
||||
<%# Parâmetros de operação a preservar ao trocar de modo/período %>
|
||||
<% ctx_ops = { operacao: @operacao, op_a: @op_a, op_b: @op_b }.compact %>
|
||||
|
||||
<div class="space-y-6">
|
||||
<%# Atualiza os dados sozinho a cada 5 min (data-auto-refresh-interval-value em
|
||||
ms), sem o operador apertar F5. Ver auto_refresh_controller.js. %>
|
||||
<div class="space-y-6" data-controller="auto-refresh" data-auto-refresh-interval-value="300000">
|
||||
|
||||
<%# ── Cabeçalho ─────────────────────────────────────────── %>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
@@ -58,7 +60,7 @@
|
||||
<% elsif @operacao %>
|
||||
<%= Operacao.label(@operacao) %>
|
||||
<% end %>
|
||||
· Atualizado às <%= Time.now.strftime('%H:%M') %>
|
||||
· Atualizado <%= ultima_atualizacao_label %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user