Atualização geral de inconsistencias da UX
This commit is contained in:
@@ -122,9 +122,6 @@ class DashboardController < ApplicationController
|
||||
# Evolução diária no período (para Chart.js) — por data real (checkout)
|
||||
@grafico_diario = build_grafico_diario(pagas, config[:entrega])
|
||||
|
||||
# Resultado por dia (entregue/falhada/pendente) — barras empilhadas
|
||||
@grafico_status = build_grafico_status(pagas, falhadas, pendentes)
|
||||
|
||||
# Consolidações cujo período (data_inicio..data_fim) cruza o período do dashboard
|
||||
# — independe de quando foram criadas (created_at).
|
||||
@consolidacoes_mes = Consolidacao.ativas
|
||||
@@ -168,6 +165,9 @@ class DashboardController < ApplicationController
|
||||
|
||||
@pagamentos_feitos = pagos.includes(:consolidacao, :pagador).order(pago_em: :desc)
|
||||
@pagamentos_pendentes = pendentes.includes(:consolidacao).order(consolidacao_id: :desc)
|
||||
|
||||
# Fluxo de pagamentos por dia (valor pago + acumulado) para o gráfico
|
||||
@grafico_pagamentos_dia = build_grafico_pagamentos(pagos)
|
||||
end
|
||||
|
||||
# Visão financeira (custo) das consolidações finalizadas do período, já com o
|
||||
@@ -244,33 +244,26 @@ class DashboardController < ApplicationController
|
||||
{ labels: labels, valores: valores, qtds: qtds }
|
||||
end
|
||||
|
||||
# Entregas por dia separadas por resultado, para o gráfico empilhado.
|
||||
# Concluídas pela data real (checkout); falhadas e pendentes não têm
|
||||
# checkout, então usam a data planejada (planned_date).
|
||||
def build_grafico_status(pagas, falhadas, pendentes)
|
||||
# Valor pago por dia (pela data do pagamento) + acumulado no período,
|
||||
# para o gráfico de fluxo de pagamentos.
|
||||
def build_grafico_pagamentos(pagos)
|
||||
dias = (@periodo_inicio..@periodo_fim).to_a
|
||||
|
||||
if dias.size <= MAX_DIAS_GRAFICO
|
||||
ok = pagas.group("DATE(checkout)").count.transform_keys(&:to_s)
|
||||
falhas = falhadas.group("DATE(planned_date)").count.transform_keys(&:to_s)
|
||||
pend = pendentes.group("DATE(planned_date)").count.transform_keys(&:to_s)
|
||||
soma = pagos.group("DATE(pago_em)").sum(:valor_total).transform_keys(&:to_s)
|
||||
labels = dias.map { |d| d.strftime('%d/%m') }
|
||||
chaves = dias.map(&:to_s)
|
||||
else
|
||||
ok = pagas.group("TO_CHAR(checkout, 'YYYY-MM')").count
|
||||
falhas = falhadas.group("TO_CHAR(planned_date, 'YYYY-MM')").count
|
||||
pend = pendentes.group("TO_CHAR(planned_date, 'YYYY-MM')").count
|
||||
soma = pagos.group("TO_CHAR(pago_em, 'YYYY-MM')").sum(:valor_total)
|
||||
meses = meses_no_periodo
|
||||
labels = meses.map { |m| Date.strptime(m, '%Y-%m').strftime('%m/%Y') }
|
||||
chaves = meses
|
||||
end
|
||||
|
||||
{
|
||||
labels: labels,
|
||||
entregues: chaves.map { |k| ok[k] || 0 },
|
||||
falhadas: chaves.map { |k| falhas[k] || 0 },
|
||||
pendentes: chaves.map { |k| pend[k] || 0 }
|
||||
}
|
||||
valores = chaves.map { |k| soma[k].to_f.round(2) }
|
||||
acumulado = 0
|
||||
{ labels: labels, valores: valores,
|
||||
acumulado: valores.map { |v| (acumulado += v).round(2) } }
|
||||
end
|
||||
|
||||
# Meses ("YYYY-MM") do início ao fim do período selecionado, inclusive.
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
|
||||
<%# Gráficos diários (Chart.js) — flex-col p/ preencher a altura do card de motoristas %>
|
||||
<div class="xl:col-span-2 bg-[#1a1a1a] rounded-2xl border border-white/5 p-6 flex flex-col">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 mb-6">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-white">Evolução do custo</h2>
|
||||
<p class="text-gray-400 text-sm">Valor estimado por dia no período<%= ' · operação filtrada' if @operacao_filtro.present? %></p>
|
||||
@@ -219,20 +219,19 @@
|
||||
<canvas id="grafico-diario"></canvas>
|
||||
</div>
|
||||
|
||||
<%# Resultado por dia (entregue x falhada x pendente) — mostra a taxa de insucesso %>
|
||||
<div class="flex items-center justify-between mt-6 mb-4 pt-6 border-t border-white/5">
|
||||
<%# Pagamentos por dia (fluxo de caixa) — quando o dinheiro saiu de fato %>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 mt-6 mb-4 pt-6 border-t border-white/5">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-white">Resultado por dia</h2>
|
||||
<p class="text-gray-400 text-sm">Entregues, falhadas e pendentes no período</p>
|
||||
<h2 class="text-lg font-semibold text-white">Pagamentos por dia</h2>
|
||||
<p class="text-gray-400 text-sm">Valor pago aos motoristas por data de pagamento</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-xs text-gray-400">
|
||||
<span class="flex items-center gap-1.5"><span class="inline-block w-3 h-2 bg-green-500 rounded-sm"></span> Entregues</span>
|
||||
<span class="flex items-center gap-1.5"><span class="inline-block w-3 h-2 bg-red-500 rounded-sm"></span> Falhadas</span>
|
||||
<span class="flex items-center gap-1.5"><span class="inline-block w-3 h-2 bg-yellow-500 rounded-sm"></span> Pendentes</span>
|
||||
<div class="flex flex-wrap items-center gap-3 text-xs text-gray-400">
|
||||
<span class="flex items-center gap-1.5"><span class="inline-block w-3 h-2 bg-green-500 rounded-sm"></span> Pago no dia</span>
|
||||
<span class="flex items-center gap-1.5"><span class="inline-block w-3 h-0.5 bg-gray-300 rounded"></span> Acumulado</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex-1 min-h-48">
|
||||
<canvas id="grafico-entregas-dia"></canvas>
|
||||
<canvas id="grafico-pagamentos-dia"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -642,35 +641,46 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<%# Barras empilhadas: resultado das entregas por dia (entregue/falhada/pendente) %>
|
||||
<%# Fluxo de pagamentos: barras (pago no dia) + linha (acumulado no período) %>
|
||||
<script>
|
||||
(function () {
|
||||
const ctx = document.getElementById('grafico-entregas-dia');
|
||||
const ctx = document.getElementById('grafico-pagamentos-dia');
|
||||
if (!ctx || typeof Chart === 'undefined') return;
|
||||
Chart.getChart(ctx)?.destroy();
|
||||
|
||||
const labels = <%= raw @grafico_status[:labels].to_json %>;
|
||||
const entregues = <%= raw @grafico_status[:entregues].to_json %>;
|
||||
const falhadas = <%= raw @grafico_status[:falhadas].to_json %>;
|
||||
const pendentes = <%= raw @grafico_status[:pendentes].to_json %>;
|
||||
const labels = <%= raw @grafico_pagamentos_dia[:labels].to_json %>;
|
||||
const valores = <%= raw @grafico_pagamentos_dia[:valores].to_json %>;
|
||||
const acumulado = <%= raw @grafico_pagamentos_dia[:acumulado].to_json %>;
|
||||
|
||||
const barra = (label, data, cor) => ({
|
||||
label: label,
|
||||
data: data,
|
||||
backgroundColor: cor,
|
||||
borderRadius: 4,
|
||||
borderSkipped: false,
|
||||
maxBarThickness: 28
|
||||
});
|
||||
const brl = (v) => `R$ ${v.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
barra('Entregues', entregues, 'rgba(34, 197, 94, 0.75)'),
|
||||
barra('Falhadas', falhadas, 'rgba(239, 68, 68, 0.85)'),
|
||||
barra('Pendentes', pendentes, 'rgba(234, 179, 8, 0.70)')
|
||||
{
|
||||
label: 'Pago no dia',
|
||||
data: valores,
|
||||
backgroundColor: 'rgba(34, 197, 94, 0.65)',
|
||||
hoverBackgroundColor: '#22c55e',
|
||||
borderRadius: 4,
|
||||
borderSkipped: false,
|
||||
maxBarThickness: 28,
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
label: 'Acumulado',
|
||||
data: acumulado,
|
||||
type: 'line',
|
||||
borderColor: '#d1d5db',
|
||||
borderWidth: 2,
|
||||
borderDash: [5, 4],
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
tension: 0.3,
|
||||
order: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
@@ -681,33 +691,25 @@
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: '#0a0a0a',
|
||||
borderColor: '#f97316',
|
||||
borderColor: '#22c55e',
|
||||
borderWidth: 1,
|
||||
titleColor: '#9ca3af',
|
||||
bodyColor: '#ffffff',
|
||||
padding: 12,
|
||||
callbacks: {
|
||||
label: (c) => ` ${c.dataset.label}: ${c.parsed.y}`,
|
||||
footer: (items) => {
|
||||
const total = items.reduce((s, i) => s + i.parsed.y, 0);
|
||||
const falha = items.find(i => i.dataset.label === 'Falhadas')?.parsed.y || 0;
|
||||
return total > 0 ? `Total: ${total} · Insucesso: ${(falha / total * 100).toFixed(1)}%` : '';
|
||||
}
|
||||
},
|
||||
footerColor: '#9ca3af'
|
||||
label: (c) => ` ${c.dataset.label}: ${brl(c.parsed.y)}`
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: { display: false, drawBorder: false },
|
||||
ticks: { color: '#6b7280', font: { size: 11 }, maxTicksLimit: 10 }
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(255,255,255,0.04)', drawBorder: false },
|
||||
ticks: { color: '#6b7280', font: { size: 11 }, precision: 0 }
|
||||
ticks: { color: '#6b7280', font: { size: 11 }, callback: (v) => `R$ ${v.toFixed(0)}` }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,10 @@
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-[#0a0a0a] text-white min-h-screen">
|
||||
<%# overflow-x-hidden: conteúdo largo demais não pode criar scroll horizontal na
|
||||
página (aparecia como uma barra laranja "quebrando" o rodapé no celular);
|
||||
tabelas largas já rolam nos próprios contêineres overflow-x-auto. %>
|
||||
<body class="bg-[#0a0a0a] text-white min-h-screen overflow-x-hidden">
|
||||
|
||||
<%# ── Animação de abertura (uma vez, logo após o login) ── %>
|
||||
<% if @mostrar_splash %>
|
||||
|
||||
Reference in New Issue
Block a user