From 75d82227c9884a90b2cab969549aab1f90c1a34c16e19ea94987e1cb6981ff69 Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 13 Jul 2026 18:12:04 -0300 Subject: [PATCH] =?UTF-8?q?Inclus=C3=A3o=20de=20uma=20tabela=20de=20acompa?= =?UTF-8?q?nhamento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../operacoes_dashboard_controller.rb | 18 +++ app/services/analytics/operacao_metricas.rb | 12 ++ .../operacoes_dashboard/_espelho.html.erb | 139 ++++++++++++++++++ app/views/operacoes_dashboard/index.html.erb | 2 + 4 files changed, 171 insertions(+) create mode 100644 app/views/operacoes_dashboard/_espelho.html.erb diff --git a/app/controllers/operacoes_dashboard_controller.rb b/app/controllers/operacoes_dashboard_controller.rb index 930cb5c..710d102 100644 --- a/app/controllers/operacoes_dashboard_controller.rb +++ b/app/controllers/operacoes_dashboard_controller.rb @@ -7,6 +7,9 @@ class OperacoesDashboardController < ApplicationController MODOS = %w[operacao global comparar].freeze + # Linhas por página da tabela espelho (planilha da operação) + ESPELHO_POR_PAGINA = 15 + def index authorize :dashboard, :operacoes? @@ -32,10 +35,25 @@ class OperacoesDashboardController < ApplicationController @operacao = Operacao.sanitizar([params[:operacao]]).first || @tabelas_validas.first @metricas = montar([@operacao], filtros: @filtros, data: @data_filtro) if @operacao end + + montar_tabela_espelho if @metricas && %w[operacao global].include?(@modo) end private + # Tabela espelho da planilha da operação: busca textual (q) + paginação (pg) + # sobre as linhas já cross-filtradas. Ordena pelas mais recentes (checkout; + # sem checkout vai para o fim). + def montar_tabela_espelho + @busca = params[:q].to_s.strip + linhas = @metricas.buscar(@busca).sort_by { |r| r['checkout'].to_s }.reverse + + @espelho_total = linhas.size + @espelho_paginas = [(@espelho_total / ESPELHO_POR_PAGINA.to_f).ceil, 1].max + @espelho_pagina = params[:pg].to_i.clamp(1, @espelho_paginas) + @espelho_linhas = linhas[(@espelho_pagina - 1) * ESPELHO_POR_PAGINA, ESPELHO_POR_PAGINA] || [] + end + def montar(tabelas, inicio: nil, fim: nil, filtros: {}, data: nil) Analytics::OperacaoMetricas.new(tabelas: tabelas, inicio: inicio, fim: fim, filtros: filtros, data: data) end diff --git a/app/services/analytics/operacao_metricas.rb b/app/services/analytics/operacao_metricas.rb index 97c2834..e05bf31 100644 --- a/app/services/analytics/operacao_metricas.rb +++ b/app/services/analytics/operacao_metricas.rb @@ -45,6 +45,18 @@ module Analytics end end + # Campos pesquisáveis da tabela espelho (planilha da operação). + BUSCA_CAMPOS = %w[reference_id driver vehicle status observation contact_name + address nome_completo endereco_completo status_gade operacao].freeze + + # Busca textual da tabela espelho: filtra as linhas (já pós cross-filter) + # por qualquer campo exibido na planilha. + def buscar(texto) + termo = texto.to_s.strip.downcase + return linhas if termo.empty? + linhas.select { |r| BUSCA_CAMPOS.any? { |c| r[c].to_s.downcase.include?(termo) } } + end + # Taxas (úteis no comparativo). def taxa_sucesso pct(sucesso, total) diff --git a/app/views/operacoes_dashboard/_espelho.html.erb b/app/views/operacoes_dashboard/_espelho.html.erb new file mode 100644 index 0000000..0bf19d9 --- /dev/null +++ b/app/views/operacoes_dashboard/_espelho.html.erb @@ -0,0 +1,139 @@ +<%# app/views/operacoes_dashboard/_espelho.html.erb %> +<%# Tabela espelho da planilha da operação: uma linha por NF com resultado, + ocorrência, motorista etc. Busca textual (param q) e paginação (param pg) + são server-side; o restante da URL (modo/operação/período/cross-filters) + é preservado nos links e no form. %> + +<% pg_path = ->(n) { operacoes_dashboard_path(request.query_parameters.merge('pg' => n)) } %> +<% resultado_badge = ->(status) { + if status == 'completed' + ['Entregue', 'bg-green-500/10 text-green-400 border-green-500/30'] + elsif Entrega::STATUS_FALHA.include?(status) + ['Falha', 'bg-red-500/10 text-red-400 border-red-500/30'] + else + ['Pendente', 'bg-yellow-500/10 text-yellow-400 border-yellow-500/30'] + end + } %> + +
+
+
+

📋 Planilha da operação

+

Espelho por NF — pesquise por nota, motorista, unidade, ocorrência, veículo...

+
+ +
+ <% request.query_parameters.except('q', 'pg').each do |k, v| %> + + <% end %> +
+ 🔎 + +
+ + <% if @busca.present? %> + <%= link_to '✕ Limpar', operacoes_dashboard_path(request.query_parameters.except('q', 'pg')), + data: { turbo: false }, class: 'text-xs text-orange-400 hover:text-orange-300 whitespace-nowrap' %> + <% end %> +
+
+ + <% if @espelho_linhas.blank? %> +

+ <%= @busca.present? ? %(Nenhuma entrega encontrada para "#{@busca}".) : 'Nenhuma entrega para exibir.' %> +

+ <% else %> +
+ + + + + + <% if @modo == 'global' %> + + <% end %> + + + + + + + + + + + <% @espelho_linhas.each do |r| %> + <% rotulo, badge_css = resultado_badge.(r['status']) %> + <% data = r['checkout'] || r['planned_date'] %> + <% endereco = r['endereco_completo'].presence || r['address'].presence %> + + + + <% if @modo == 'global' %> + + <% end %> + + + + + + + + + <% end %> + +
DataNFOperaçãoDestinatárioUnidadeMotoristaVeículoResultadoOcorrênciaStatus
+ <%= data.respond_to?(:strftime) ? data.strftime('%d/%m/%y %H:%M') : data.to_s.first(16) %> + <%= r['reference_id'] %><%= r['operacao'] %> + <%= (r['nome_completo'].presence || '—').truncate(28) %> + <%= r['contact_name'].to_s.truncate(24).presence || '—' %><%= r['driver'].presence || '—' %><%= r['vehicle'].presence || '—' %> + + <%= rotulo %> + + + <%= r['observation'].to_s.truncate(32).presence || '—' %> + <%= r['status_gade'].presence || '—' %>
+
+ + <%# Paginação: « 1 … janela em torno da atual … última » %> +
+

+ <%= (@espelho_pagina - 1) * OperacoesDashboardController::ESPELHO_POR_PAGINA + 1 %>–<%= + [(@espelho_pagina) * OperacoesDashboardController::ESPELHO_POR_PAGINA, @espelho_total].min %> + de <%= @espelho_total %> entregas +

+ <% if @espelho_paginas > 1 %> +
+ <% if @espelho_pagina > 1 %> + <%= link_to '‹', pg_path.(@espelho_pagina - 1), data: { turbo: false }, + class: 'px-3 py-1.5 rounded-lg text-xs font-semibold bg-[#0a0a0a] text-gray-400 border border-white/5 hover:text-white' %> + <% end %> + <% janela = ([1, @espelho_pagina - 2].max..[@espelho_paginas, @espelho_pagina + 2].min).to_a %> + <% paginas = ([1] + janela + [@espelho_paginas]).uniq %> + <% anterior = 0 %> + <% paginas.each do |p| %> + <% if p > anterior + 1 %> + + <% end %> + <% anterior = p %> + <% if p == @espelho_pagina %> + <%= p %> + <% else %> + <%= link_to p, pg_path.(p), data: { turbo: false }, + class: 'px-3 py-1.5 rounded-lg text-xs font-semibold bg-[#0a0a0a] text-gray-400 border border-white/5 hover:text-white' %> + <% end %> + <% end %> + <% if @espelho_pagina < @espelho_paginas %> + <%= link_to '›', pg_path.(@espelho_pagina + 1), data: { turbo: false }, + class: 'px-3 py-1.5 rounded-lg text-xs font-semibold bg-[#0a0a0a] text-gray-400 border border-white/5 hover:text-white' %> + <% end %> +
+ <% end %> +
+ <% end %> +
diff --git a/app/views/operacoes_dashboard/index.html.erb b/app/views/operacoes_dashboard/index.html.erb index 46f2f56..7118ad5 100644 --- a/app/views/operacoes_dashboard/index.html.erb +++ b/app/views/operacoes_dashboard/index.html.erb @@ -144,6 +144,7 @@ <% if @metricas %> <%= render 'painel', metricas: @metricas, filtravel: true %> <%= render 'mapa', metricas: @metricas %> + <%= render 'espelho' %> <% end %> <% elsif @modo == 'global' %> @@ -154,6 +155,7 @@ <% if @metricas %> <%= render 'painel', metricas: @metricas, filtravel: true %> <%= render 'mapa', metricas: @metricas %> + <%= render 'espelho' %> <% end %> <% else # comparar %>