implantação da fase 2 e 3
This commit is contained in:
234
app/views/dashboard/index.html.erb
Normal file
234
app/views/dashboard/index.html.erb
Normal file
@@ -0,0 +1,234 @@
|
||||
<%# app/views/dashboard/index.html.erb %>
|
||||
<%# Fase 3: Dashboard com cards, gráfico Chart.js e cálculo ao vivo %>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
||||
<%# Header + filtro de mês %>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-white">Dashboard</h1>
|
||||
<p class="text-gray-400 text-sm mt-0.5">
|
||||
<%= @data_referencia.strftime('%B de %Y').capitalize %> · Atualizado às <%= Time.now.strftime('%H:%M') %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<%# Navegação de mês %>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= link_to dashboard_path(data: (@data_referencia - 1.month).to_s),
|
||||
class: 'p-2.5 bg-[#1a1a1a] hover:bg-white/10 border border-white/10 rounded-xl
|
||||
text-gray-400 hover:text-white transition-colors' do %>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
<% end %>
|
||||
|
||||
<div class="px-4 py-2.5 bg-[#1a1a1a] border border-white/10 rounded-xl text-white font-medium min-w-[140px] text-center">
|
||||
<%= @data_referencia.strftime('%b %Y').capitalize %>
|
||||
</div>
|
||||
|
||||
<% unless @data_referencia >= Date.today.beginning_of_month %>
|
||||
<%= link_to dashboard_path(data: (@data_referencia + 1.month).to_s),
|
||||
class: 'p-2.5 bg-[#1a1a1a] hover:bg-white/10 border border-white/10 rounded-xl
|
||||
text-gray-400 hover:text-white transition-colors' do %>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# CARDS GRANDES — KPIs %>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||
|
||||
<%# Card 1: Valor estimado %>
|
||||
<div class="sm:col-span-2 xl:col-span-1 bg-[#f97316] rounded-2xl p-6 relative overflow-hidden">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-orange-400/20 to-transparent"></div>
|
||||
<div class="relative">
|
||||
<p class="text-orange-100 text-sm font-medium mb-2">💰 Valor Estimado</p>
|
||||
<p class="text-4xl font-black text-white mb-1">
|
||||
<%= moeda(@valor_estimado) %>
|
||||
</p>
|
||||
<p class="text-orange-200 text-sm">
|
||||
<%= @entregas_pagas %> entregas pagas
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Card 2: Total de entregas %>
|
||||
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
||||
<p class="text-gray-400 text-sm font-medium mb-2">📦 Total Entregas</p>
|
||||
<p class="text-4xl font-black text-white mb-1"><%= @total_entregas %></p>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-green-400"><%= @entregas_pagas %> pagas</span>
|
||||
<span class="text-gray-600">·</span>
|
||||
<span class="text-yellow-500"><%= @entregas_pendentes %> pendentes</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Card 3: Consolidações %>
|
||||
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
||||
<p class="text-gray-400 text-sm font-medium mb-2">📋 Consolidações</p>
|
||||
<p class="text-4xl font-black text-white mb-1"><%= @consolidacoes_mes.count %></p>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-yellow-500"><%= @consolidacoes_abertas %> abertas</span>
|
||||
<span class="text-gray-600">·</span>
|
||||
<span class="text-green-400"><%= @consolidacoes_fechadas %> fechadas</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Card 4: Motoristas ativos %>
|
||||
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
||||
<p class="text-gray-400 text-sm font-medium mb-2">🚗 Motoristas</p>
|
||||
<p class="text-4xl font-black text-white mb-1"><%= @motoristas.count %></p>
|
||||
<p class="text-gray-500 text-sm">com entregas no mês</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# GRÁFICO + MOTORISTAS (lado a lado no desktop) %>
|
||||
<div class="grid grid-cols-1 xl:grid-cols-3 gap-4">
|
||||
|
||||
<%# Gráfico de linha diário (Chart.js) %>
|
||||
<div class="xl:col-span-2 bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-white">Evolução Diária</h2>
|
||||
<p class="text-gray-400 text-sm">Valor estimado por dia no mês</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs text-gray-500">
|
||||
<span class="inline-block w-3 h-0.5 bg-[#f97316] rounded"></span> Valor (R$)
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative h-64">
|
||||
<canvas id="grafico-diario"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Top motoristas %>
|
||||
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">🏆 Top Motoristas</h2>
|
||||
|
||||
<% if @motoristas.any? %>
|
||||
<div class="space-y-3">
|
||||
<% @motoristas.each_with_index do |m, i| %>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-xs font-bold w-5 text-center
|
||||
<%= i == 0 ? 'text-yellow-400' : i == 1 ? 'text-gray-300' : i == 2 ? 'text-orange-600' : 'text-gray-600' %>">
|
||||
<%= i + 1 %>
|
||||
</span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-white text-sm font-medium truncate"><%= m[:nome].split.first %></span>
|
||||
<span class="text-[#f97316] text-sm font-semibold ml-2 flex-shrink-0">
|
||||
<%= moeda(m[:valor]) %>
|
||||
</span>
|
||||
</div>
|
||||
<%# Barra de progresso %>
|
||||
<div class="w-full bg-white/5 rounded-full h-1.5">
|
||||
<% max = @motoristas.first[:entregas].to_f %>
|
||||
<div class="h-1.5 rounded-full bg-[#f97316]"
|
||||
style="width: <%= [(m[:entregas] / max * 100).round, 100].min %>%"></div>
|
||||
</div>
|
||||
<span class="text-gray-500 text-xs"><%= m[:entregas] %> entregas</span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-center py-8 text-gray-500">
|
||||
<div class="text-3xl mb-2">🚚</div>
|
||||
<p class="text-sm">Nenhuma entrega paga neste mês</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Por operação %>
|
||||
<% if @por_operacao.any? %>
|
||||
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">🏥 Entregas por Local</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<% total_op = @por_operacao.values.sum.to_f %>
|
||||
<% @por_operacao.each do |local, qtd| %>
|
||||
<div class="bg-[#0a0a0a] rounded-xl p-4 border border-white/5 text-center">
|
||||
<p class="text-2xl font-bold text-[#f97316]"><%= qtd %></p>
|
||||
<p class="text-gray-400 text-xs mt-1 leading-tight"><%= local.to_s.truncate(20) %></p>
|
||||
<p class="text-gray-600 text-xs mt-1"><%= ((qtd / total_op) * 100).round %>%</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
<%# Chart.js via CDN %>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
|
||||
<script>
|
||||
const ctx = document.getElementById('grafico-diario');
|
||||
|
||||
const labels = <%= raw @grafico_diario[:labels].to_json %>;
|
||||
const valores = <%= raw @grafico_diario[:valores].to_json %>;
|
||||
const qtds = <%= raw @grafico_diario[:qtds].to_json %>;
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Valor (R$)',
|
||||
data: valores,
|
||||
borderColor: '#f97316',
|
||||
backgroundColor: 'rgba(249, 115, 22, 0.08)',
|
||||
borderWidth: 2.5,
|
||||
pointBackgroundColor: '#f97316',
|
||||
pointBorderColor: '#1a1a1a',
|
||||
pointBorderWidth: 2,
|
||||
pointRadius: 4,
|
||||
pointHoverRadius: 6,
|
||||
fill: true,
|
||||
tension: 0.4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: '#0a0a0a',
|
||||
borderColor: '#f97316',
|
||||
borderWidth: 1,
|
||||
titleColor: '#9ca3af',
|
||||
bodyColor: '#ffffff',
|
||||
padding: 12,
|
||||
callbacks: {
|
||||
label: (ctx) => {
|
||||
const idx = ctx.dataIndex;
|
||||
return [
|
||||
` Valor: R$ ${ctx.parsed.y.toFixed(2).replace('.', ',')}`,
|
||||
` Entregas: ${qtds[idx]}`
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: 'rgba(255,255,255,0.04)', drawBorder: false },
|
||||
ticks: { color: '#6b7280', font: { size: 11 }, maxTicksLimit: 10 }
|
||||
},
|
||||
y: {
|
||||
grid: { color: 'rgba(255,255,255,0.04)', drawBorder: false },
|
||||
ticks: {
|
||||
color: '#6b7280',
|
||||
font: { size: 11 },
|
||||
callback: (v) => `R$ ${v.toFixed(0)}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user