Correção e Adição do filtro ao vivo

This commit is contained in:
2026-06-29 20:50:07 -03:00
parent 7f21a16327
commit 6b899ebafa
9 changed files with 279 additions and 74 deletions

View File

@@ -20,16 +20,39 @@ module Analytics
# inicio/fim são OPCIONAIS: quando nil, não há filtro de data e o conjunto é a
# própria operação inteira (visão de operação única). O filtro de período só é
# usado na visão Global.
def initialize(tabelas:, inicio: nil, fim: nil)
#
# filtros: Hash de cross-filter (coluna => valor) aplicado em memória — clicar
# numa célula da tabela (motorista/STS/status/observação) refiltra TODO o
# dashboard por aquele valor. Ex.: { 'driver' => 'Carlos' }.
def initialize(tabelas:, inicio: nil, fim: nil, filtros: {})
@tabelas = Operacao.sanitizar(Array(tabelas))
@inicio = inicio&.to_date
@fim = fim&.to_date
@filtros = (filtros || {}).reject { |_, v| v.to_s.strip.empty? }
end
def operacoes_label
@tabelas.map { |t| Operacao.label(t) }.join(', ')
end
# Linhas após o cross-filter — base de TODAS as agregações/KPIs.
def linhas
@linhas ||= if @filtros.empty?
registros
else
registros.select { |r| @filtros.all? { |col, val| r[col].to_s == val.to_s } }
end
end
# Taxas (úteis no comparativo).
def taxa_sucesso
pct(sucesso, total)
end
def taxa_insucesso
pct(recusas, total)
end
# Faixa real das entregas carregadas (pela data de checkout) — para exibir o
# período natural da operação no cabeçalho/painel.
def data_inicio
@@ -51,15 +74,15 @@ module Analytics
# ── KPIs ─────────────────────────────────────────────────────
def total
registros.size
linhas.size
end
def sucesso
registros.count { |r| r['status'] == 'completed' }
linhas.count { |r| r['status'] == 'completed' }
end
def recusas
registros.count { |r| Entrega::STATUS_FALHA.include?(r['status']) }
linhas.count { |r| Entrega::STATUS_FALHA.include?(r['status']) }
end
# Em aberto: nem concluídas nem falhadas.
@@ -79,7 +102,7 @@ module Analytics
# Falhas agrupadas por observação (motivo), % sobre o total de falhas.
def indices_falha
falhas = registros.select { |r| Entrega::STATUS_FALHA.include?(r['status']) }
falhas = linhas.select { |r| Entrega::STATUS_FALHA.include?(r['status']) }
total_falhas = falhas.size
falhas.group_by { |r| r['observation'].presence || 'SEM OBSERVAÇÃO' }
.map { |obs, rows| { observation: obs, total: rows.size, pct: pct(rows.size, total_falhas) } }
@@ -88,25 +111,25 @@ module Analytics
# Contagem por status da tabela gade (RECORRENTE/NOVO/...).
def por_status_gade
registros.group_by { |r| r['status_gade'].presence || '—' }
.map { |st, rows| { status: st, total: rows.size } }
.sort_by { |h| -h[:total] }
linhas.group_by { |r| r['status_gade'].presence || '—' }
.map { |st, rows| { status: st, total: rows.size } }
.sort_by { |h| -h[:total] }
end
# NFs por motorista (todas as entregas), desc.
def por_motorista
contagem(registros, 'driver')
contagem(linhas, 'driver')
end
# Entregas CONCLUÍDAS por unidade (contact_name), desc.
def por_sts
contagem(registros.select { |r| r['status'] == 'completed' }, 'contact_name')
contagem(linhas.select { |r| r['status'] == 'completed' }, 'contact_name')
end
# Por dia (DATE do checkout): completas vs falhas — gráfico de barras.
def por_dia
por_data = Hash.new { |h, k| h[k] = { completed: 0, failed: 0 } }
registros.each do |r|
linhas.each do |r|
d = data_de(r['checkout'])
next unless d
if r['status'] == 'completed'
@@ -126,7 +149,7 @@ module Analytics
# Pontos [lat, lng] das entregas concluídas para o heatmap.
# Prefere as coordenadas de CHECKOUT (saída); cai para as de CHECKIN.
def pontos_mapa
@pontos_mapa ||= registros.filter_map do |r|
@pontos_mapa ||= linhas.filter_map do |r|
next unless r['status'] == 'completed'
lat = num(r['checkout_latitude']) || num(r['latitude'])
lng = num(r['checkout_longitude']) || num(r['longitude'])
@@ -142,7 +165,7 @@ module Analytics
private
def datas_checkout
@datas_checkout ||= registros.filter_map { |r| data_de(r['checkout']) }
@datas_checkout ||= linhas.filter_map { |r| data_de(r['checkout']) }
end
def contagem(rows, coluna)