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

@@ -101,6 +101,23 @@
</div>
<% else %>
<%# ── Filtros ativos (cross-filter por clique nas tabelas) ── %>
<% if @chips.any? && %w[operacao global].include?(@modo) %>
<div class="flex flex-wrap items-center gap-2">
<span class="text-xs text-gray-500 uppercase tracking-wider">Filtros ativos:</span>
<% @chips.each do |chip| %>
<span class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-orange-500/15 border border-orange-500/30 text-orange-300 text-xs">
<%= chip[:rotulo] %>: <strong class="text-white"><%= chip[:valor] %></strong>
<%= link_to '✕', operacoes_dashboard_path(request.query_parameters.except(chip[:param])),
data: { turbo: false }, class: 'hover:text-white font-bold', title: 'Remover filtro' %>
</span>
<% end %>
<%= link_to 'Limpar tudo',
operacoes_dashboard_path(request.query_parameters.except(*@chips.map { |c| c[:param] })),
data: { turbo: false }, class: 'text-xs text-gray-400 hover:text-orange-400 underline' %>
</div>
<% end %>
<%# ── Seletor de operação por modo ───────────────────── %>
<% if @modo == 'operacao' %>
<form method="get" action="<%= operacoes_dashboard_path %>" data-turbo="false" class="flex flex-wrap items-center gap-2">
@@ -120,7 +137,7 @@
</form>
<% if @metricas %>
<%= render 'painel', metricas: @metricas %>
<%= render 'painel', metricas: @metricas, filtravel: true %>
<%= render 'mapa', metricas: @metricas %>
<% end %>
@@ -130,7 +147,7 @@
<%= @tabelas_validas.size %> operações agregadas no período.
</div>
<% if @metricas %>
<%= render 'painel', metricas: @metricas %>
<%= render 'painel', metricas: @metricas, filtravel: true %>
<%= render 'mapa', metricas: @metricas %>
<% end %>
@@ -140,8 +157,8 @@
<input type="hidden" name="inicio" value="<%= ini_iso %>">
<input type="hidden" name="fim" value="<%= fim_iso %>">
<div class="flex items-center gap-2">
<label class="text-cyan-400 text-sm font-semibold">A:</label>
<select name="op_a" class="auto-submit flex-1 bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-cyan-500">
<label class="text-orange-400 text-sm font-semibold">A:</label>
<select name="op_a" class="auto-submit flex-1 bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500">
<% @operacoes_agrupadas.each do |titulo, ops| %>
<optgroup label="<%= titulo %>">
<% ops.each do |op| %>
@@ -152,8 +169,8 @@
</select>
</div>
<div class="flex items-center gap-2">
<label class="text-indigo-400 text-sm font-semibold">B:</label>
<select name="op_b" class="auto-submit flex-1 bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-indigo-500">
<label class="text-blue-400 text-sm font-semibold">B:</label>
<select name="op_b" class="auto-submit flex-1 bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-blue-500">
<% @operacoes_agrupadas.each do |titulo, ops| %>
<optgroup label="<%= titulo %>">
<% ops.each do |op| %>
@@ -165,22 +182,34 @@
</div>
</form>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
<div>
<% if @metricas_a %>
<%= render 'painel', metricas: @metricas_a, titulo: "A · #{@metricas_a.operacoes_label}" %>
<% else %>
<p class="text-gray-500 text-sm">Selecione a operação A.</p>
<% end %>
<%# Comparativo direto A x B (tabela + barras agrupadas) %>
<% if @metricas_a && @metricas_b %>
<%= render 'comparativo', a: @metricas_a, b: @metricas_b,
label_a: @metricas_a.operacoes_label, label_b: @metricas_b.operacoes_label %>
<% end %>
<%# Detalhe completo de cada operação (recolhível) %>
<details class="bg-[#1a1a1a] rounded-2xl border border-white/5">
<summary class="cursor-pointer px-6 py-4 text-sm text-gray-300 hover:text-white select-none">
🔎 Ver detalhe completo de cada operação
</summary>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6 p-4 pt-0">
<div>
<% if @metricas_a %>
<%= render 'painel', metricas: @metricas_a, titulo: "A · #{@metricas_a.operacoes_label}" %>
<% else %>
<p class="text-gray-500 text-sm">Selecione a operação A.</p>
<% end %>
</div>
<div>
<% if @metricas_b %>
<%= render 'painel', metricas: @metricas_b, titulo: "B · #{@metricas_b.operacoes_label}" %>
<% else %>
<p class="text-gray-500 text-sm">Selecione a operação B.</p>
<% end %>
</div>
</div>
<div>
<% if @metricas_b %>
<%= render 'painel', metricas: @metricas_b, titulo: "B · #{@metricas_b.operacoes_label}" %>
<% else %>
<p class="text-gray-500 text-sm">Selecione a operação B.</p>
<% end %>
</div>
</div>
</details>
<% end %>
<% end %>
</div>
@@ -231,9 +260,16 @@
});
}
// ── Charts (donut + barras) ──
// ── Cores da marca (laranja + vermelho p/ falha) ──
const COR_OK = '#f97316'; // laranja Reem (completas)
const COR_FAIL = '#ef4444'; // vermelho (falhas), igual ao card de Recusas
const COR_A = '#f97316'; // comparativo: A = laranja da marca
const COR_B = '#3b82f6'; // comparativo: B = azul (contraste)
// ── Charts (donut + barras + comparativo) ──
function initCharts() {
if (typeof Chart === 'undefined') return;
document.querySelectorAll('canvas.dash-donut').forEach(function (ctx) {
Chart.getChart(ctx)?.destroy();
new Chart(ctx, {
@@ -242,13 +278,14 @@
labels: ['Completas', 'Falhas'],
datasets: [{
data: [Number(ctx.dataset.completed), Number(ctx.dataset.failed)],
backgroundColor: ['#06b6d4', '#6366f1'],
backgroundColor: [COR_OK, COR_FAIL],
borderColor: '#1a1a1a', borderWidth: 2
}]
},
options: { responsive: true, maintainAspectRatio: false, cutout: '62%', plugins: { legend: { display: false } } }
});
});
document.querySelectorAll('canvas.dash-bars').forEach(function (ctx) {
Chart.getChart(ctx)?.destroy();
new Chart(ctx, {
@@ -256,8 +293,8 @@
data: {
labels: JSON.parse(ctx.dataset.labels),
datasets: [
{ label: 'Completas', data: JSON.parse(ctx.dataset.completed), backgroundColor: '#06b6d4' },
{ label: 'Falhas', data: JSON.parse(ctx.dataset.failed), backgroundColor: '#6366f1' }
{ label: 'Completas', data: JSON.parse(ctx.dataset.completed), backgroundColor: COR_OK },
{ label: 'Falhas', data: JSON.parse(ctx.dataset.failed), backgroundColor: COR_FAIL }
]
},
options: {
@@ -270,6 +307,29 @@
}
});
});
// Comparativo A x B (barras agrupadas)
document.querySelectorAll('canvas.cmp-bars').forEach(function (ctx) {
Chart.getChart(ctx)?.destroy();
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Total', 'Sucesso', 'Recusas', 'Pendentes'],
datasets: [
{ label: 'A · ' + (ctx.dataset.labelA || 'A'), data: JSON.parse(ctx.dataset.a), backgroundColor: COR_A },
{ label: 'B · ' + (ctx.dataset.labelB || 'B'), data: JSON.parse(ctx.dataset.b), backgroundColor: COR_B }
]
},
options: {
responsive: true, maintainAspectRatio: false,
scales: {
x: { ticks: { color: '#6b7280' }, grid: { color: 'rgba(255,255,255,0.04)' } },
y: { beginAtZero: true, ticks: { color: '#6b7280' }, grid: { color: 'rgba(255,255,255,0.04)' } }
},
plugins: { legend: { labels: { color: '#9ca3af' } } }
}
});
});
}
// ── Mapa de calor (Leaflet) ──