Arrumar a tela do dashboard e correção dos graficos
This commit is contained in:
@@ -122,6 +122,9 @@ class DashboardController < ApplicationController
|
|||||||
# Evolução diária no período (para Chart.js) — por data real (checkout)
|
# Evolução diária no período (para Chart.js) — por data real (checkout)
|
||||||
@grafico_diario = build_grafico_diario(pagas, config[:entrega])
|
@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
|
# Consolidações cujo período (data_inicio..data_fim) cruza o período do dashboard
|
||||||
# — independe de quando foram criadas (created_at).
|
# — independe de quando foram criadas (created_at).
|
||||||
@consolidacoes_mes = Consolidacao.ativas
|
@consolidacoes_mes = Consolidacao.ativas
|
||||||
@@ -241,6 +244,35 @@ class DashboardController < ApplicationController
|
|||||||
{ labels: labels, valores: valores, qtds: qtds }
|
{ labels: labels, valores: valores, qtds: qtds }
|
||||||
end
|
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)
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
# Meses ("YYYY-MM") do início ao fim do período selecionado, inclusive.
|
# Meses ("YYYY-MM") do início ao fim do período selecionado, inclusive.
|
||||||
def meses_no_periodo
|
def meses_no_periodo
|
||||||
meses = []
|
meses = []
|
||||||
|
|||||||
@@ -219,14 +219,16 @@
|
|||||||
<canvas id="grafico-diario"></canvas>
|
<canvas id="grafico-diario"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# Entregas por dia (volume) — complementa o valor acima %>
|
<%# 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">
|
<div class="flex items-center justify-between mt-6 mb-4 pt-6 border-t border-white/5">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold text-white">Entregas por dia</h2>
|
<h2 class="text-lg font-semibold text-white">Resultado por dia</h2>
|
||||||
<p class="text-gray-400 text-sm">Volume de entregas no período</p>
|
<p class="text-gray-400 text-sm">Entregues, falhadas e pendentes no período</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 text-xs text-gray-400">
|
<div class="flex items-center gap-3 text-xs text-gray-400">
|
||||||
<span class="inline-block w-3 h-2 bg-[#f97316]/60 rounded-sm"></span> Entregas
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative flex-1 min-h-48">
|
<div class="relative flex-1 min-h-48">
|
||||||
@@ -640,29 +642,36 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<%# Barras de entregas por dia (volume — complementa a evolução do custo) %>
|
<%# Barras empilhadas: resultado das entregas por dia (entregue/falhada/pendente) %>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const ctx = document.getElementById('grafico-entregas-dia');
|
const ctx = document.getElementById('grafico-entregas-dia');
|
||||||
if (!ctx || typeof Chart === 'undefined') return;
|
if (!ctx || typeof Chart === 'undefined') return;
|
||||||
Chart.getChart(ctx)?.destroy();
|
Chart.getChart(ctx)?.destroy();
|
||||||
|
|
||||||
const labels = <%= raw @grafico_diario[:labels].to_json %>;
|
const labels = <%= raw @grafico_status[:labels].to_json %>;
|
||||||
const qtds = <%= raw @grafico_diario[:qtds].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 barra = (label, data, cor) => ({
|
||||||
|
label: label,
|
||||||
|
data: data,
|
||||||
|
backgroundColor: cor,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderSkipped: false,
|
||||||
|
maxBarThickness: 28
|
||||||
|
});
|
||||||
|
|
||||||
new Chart(ctx, {
|
new Chart(ctx, {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: {
|
data: {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
datasets: [{
|
datasets: [
|
||||||
label: 'Entregas',
|
barra('Entregues', entregues, 'rgba(34, 197, 94, 0.75)'),
|
||||||
data: qtds,
|
barra('Falhadas', falhadas, 'rgba(239, 68, 68, 0.85)'),
|
||||||
backgroundColor: 'rgba(249, 115, 22, 0.55)',
|
barra('Pendentes', pendentes, 'rgba(234, 179, 8, 0.70)')
|
||||||
hoverBackgroundColor: '#f97316',
|
]
|
||||||
borderRadius: 6,
|
|
||||||
borderSkipped: false,
|
|
||||||
maxBarThickness: 28
|
|
||||||
}]
|
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
@@ -678,16 +687,24 @@
|
|||||||
bodyColor: '#ffffff',
|
bodyColor: '#ffffff',
|
||||||
padding: 12,
|
padding: 12,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
label: (c) => ` Entregas: ${c.parsed.y}`
|
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'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
|
stacked: true,
|
||||||
grid: { display: false, drawBorder: false },
|
grid: { display: false, drawBorder: false },
|
||||||
ticks: { color: '#6b7280', font: { size: 11 }, maxTicksLimit: 10 }
|
ticks: { color: '#6b7280', font: { size: 11 }, maxTicksLimit: 10 }
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
|
stacked: true,
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
grid: { color: 'rgba(255,255,255,0.04)', drawBorder: false },
|
grid: { color: 'rgba(255,255,255,0.04)', drawBorder: false },
|
||||||
ticks: { color: '#6b7280', font: { size: 11 }, precision: 0 }
|
ticks: { color: '#6b7280', font: { size: 11 }, precision: 0 }
|
||||||
|
|||||||
Reference in New Issue
Block a user