256 lines
8.5 KiB
HTML
Executable File
256 lines
8.5 KiB
HTML
Executable File
{% extends "index.html" %}
|
|
{% block title %}Dashboard{% endblock %}
|
|
{% block content %}
|
|
|
|
<div id="loading" class="loading-backdrop">
|
|
<div class="spinner"></div>
|
|
<div class="loading-msg">Carregando dados…</div>
|
|
</div>
|
|
|
|
|
|
<style>
|
|
/* ---- Combobox estilizado ---- */
|
|
.combo {
|
|
appearance: none;
|
|
-moz-appearance: none;
|
|
-webkit-appearance: none;
|
|
background: #fff;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 12px;
|
|
padding: 12px 44px 12px 14px;
|
|
font-size: 14px;
|
|
line-height: 1.2;
|
|
color: #111827;
|
|
box-shadow: 0 6px 20px rgba(0,0,0,.06);
|
|
transition: box-shadow .2s ease, border-color .2s ease, transform .2s ease;
|
|
}
|
|
.combo:focus { outline: none; border-color: #2563eb; box-shadow: 0 8px 28px rgba(37,99,235,.18); }
|
|
.combo-wrap { position: relative; display: inline-flex; align-items: center; gap: 8px; }
|
|
.combo-wrap:after {
|
|
content: "▾"; position: absolute; right: 12px; pointer-events: none; color:#6b7280; font-size: 12px;
|
|
}
|
|
|
|
/* ---- Cards ---- */
|
|
.cards { display: grid; grid-template-columns: repeat(12, 1fr); gap: 18px; margin: 22px 0 32px; }
|
|
.card {
|
|
grid-column: span 12;
|
|
display: grid; grid-template-columns: 82px 1fr; align-items: start;
|
|
background: #1f2937; /* cinza escuro */
|
|
color: #f9fafb; /* texto claro */
|
|
border-radius: 18px; padding: 18px;
|
|
box-shadow: 0 12px 34px rgba(0,0,0,.08);
|
|
position: relative; overflow: hidden;
|
|
transition: transform .18s ease, box-shadow .18s ease;
|
|
animation: pop .35s ease both;
|
|
}
|
|
.card:hover { transform: translateY(-2px); box-shadow: 0 18px 44px rgba(0,0,0,.1); }
|
|
@keyframes pop { from{ transform: scale(.98); opacity:.0 } to{ transform: scale(1); opacity:1 } }
|
|
|
|
.card .icon {
|
|
width: 72px; height: 72px; border-radius: 16px;
|
|
display: grid; place-items: center; font-size: 38px; color: #fff;
|
|
box-shadow: inset 0 0 40px rgba(255,255,255,.2);
|
|
}
|
|
.icon.blue { background: linear-gradient(135deg, #2563eb, #3b82f6); }
|
|
.icon.green { background: linear-gradient(135deg, #059669, #10b981); }
|
|
.icon.amber { background: linear-gradient(135deg, #d97706, #f59e0b); }
|
|
|
|
.metrics { padding-left: 16px; }
|
|
.value { font-size: 30px; font-weight: 800; color: #f9fafb; text-align: right; }
|
|
.label { margin-top: 6px; font-size: 13px; color: #d1d5db; text-align: right; }
|
|
|
|
/* Responsivo */
|
|
@media (min-width: 640px) { .card { grid-column: span 6; } }
|
|
@media (min-width: 1024px){ .card { grid-column: span 4; } }
|
|
|
|
.loading-backdrop{
|
|
position:fixed; inset:0; z-index:9999;
|
|
background:rgba(17,24,39,.55); backdrop-filter: blur(2px);
|
|
display:flex; align-items:center; justify-content:center; gap:12px;
|
|
transition:opacity .25s ease; opacity:1; pointer-events:auto;
|
|
}
|
|
.loading-backdrop.hide{ opacity:0; pointer-events:none; }
|
|
.spinner{
|
|
width:40px; height:40px; border:4px solid rgba(255,255,255,.3);
|
|
border-top-color:#60a5fa; border-radius:50%; animation:spin 1s linear infinite;
|
|
}
|
|
@keyframes spin{ to{ transform:rotate(360deg) } }
|
|
.loading-msg{ color:#fff; font-weight:600; }
|
|
|
|
/* Card simples para gráficos */
|
|
.panel{
|
|
background:#1f2937; /* mesmo fundo dos cards */
|
|
color:#f9fafb;
|
|
border-radius:18px;
|
|
padding:16px 18px 22px;
|
|
box-shadow:0 12px 34px rgba(0,0,0,.08);
|
|
margin-top:10px;
|
|
}
|
|
.panel-title{
|
|
margin:0 0 10px 0;
|
|
font-weight:700;
|
|
display:flex;align-items:center;gap:10px;
|
|
}
|
|
</style>
|
|
|
|
<h1 style="display:flex;align-items:center;gap:10px;margin-bottom:10px">
|
|
<i class="fas fa-chart-line"></i> Dashboard de Faturas
|
|
</h1>
|
|
|
|
<form method="get" action="/" style="margin: 6px 0 18px">
|
|
<div class="combo-wrap">
|
|
<label for="cliente" style="font-size:13px;color:#374151">Selecionar Cliente:</label>
|
|
<select class="combo" name="cliente" id="cliente">
|
|
<option value="">Todos</option>
|
|
{% for c in clientes %}
|
|
<option value="{{ c }}" {% if c == cliente_atual %}selected{% endif %}>{{ c }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</form>
|
|
|
|
<script>
|
|
document.getElementById('cliente').addEventListener('change', function () {
|
|
const u = new URL(window.location);
|
|
if (this.value) u.searchParams.set('cliente', this.value);
|
|
else u.searchParams.delete('cliente');
|
|
u.pathname = "/"; // garante que fica na raiz
|
|
window.location = u.toString();
|
|
});
|
|
</script>
|
|
|
|
|
|
<!-- Cards -->
|
|
<div class="cards">
|
|
<!-- Total de Clientes -->
|
|
<div class="card">
|
|
<div class="icon" style="background: linear-gradient(135deg,#7c3aed,#a78bfa)"><i class="fas fa-users"></i></div>
|
|
<div class="metrics">
|
|
<div class="value">{{ '{:,}'.format(total_clientes or 0).replace(',', '.') }}</div>
|
|
<div class="label">Total de clientes</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Total de Faturas -->
|
|
<div class="card">
|
|
<div class="icon blue"><i class="fas fa-file-invoice"></i></div>
|
|
<div class="metrics">
|
|
<div class="value">{{ '{:,}'.format(total_faturas or 0).replace(',', '.') }}</div>
|
|
<div class="label">Total de faturas processadas</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Restituição Corrigida -->
|
|
<div class="card">
|
|
<div class="icon green"><i class="fas fa-hand-holding-usd"></i></div>
|
|
<div class="metrics">
|
|
<div class="value">R$ {{ '{:,.2f}'.format(valor_restituicao_corrigida or 0).replace(',', 'X').replace('.', ',').replace('X', '.') }}</div>
|
|
<div class="label">Restituição corrigida (PIS+COFINS sobre ICMS)</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- % ICMS na Base -->
|
|
<div class="card">
|
|
<div class="icon amber"><i class="fas fa-percentage"></i></div>
|
|
<div class="metrics">
|
|
<div class="value">{{ '{:.1f}%'.format(percentual_icms_base or 0) }}</div>
|
|
<div class="label">% de faturas com ICMS na base do PIS/COFINS</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Valor médio por fatura com ICMS na base -->
|
|
<div class="card">
|
|
<div class="icon" style="background: linear-gradient(135deg,#ef4444,#f97316)">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
</div>
|
|
<div class="metrics">
|
|
<div class="value">R$ {{ '{:,.2f}'.format(valor_medio_com_icms or 0).replace(',', 'X').replace('.', ',').replace('X', '.') }}</div>
|
|
<div class="label">Valor médio (PIS+COFINS sobre ICMS) por fatura</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Evolução mensal (card) -->
|
|
<div class="panel">
|
|
<h2 class="panel-title">
|
|
<i class="fas fa-chart-line"></i>
|
|
Evolução mensal do valor passível de recuperação
|
|
</h2>
|
|
<canvas id="graficoEvolucao" style="max-height:360px"></canvas>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
|
|
<script>
|
|
const ctxE = document.getElementById('graficoEvolucao').getContext('2d');
|
|
|
|
const evoLabels = {{ serie_mensal_labels | tojson }};
|
|
const evoValores = {{ serie_mensal_valores | tojson }};
|
|
|
|
new Chart(ctxE, {
|
|
type: 'line',
|
|
data: {
|
|
labels: evoLabels,
|
|
datasets: [{
|
|
label: 'Valor corrigido (R$)',
|
|
data: evoValores,
|
|
fill: false,
|
|
tension: 0.25,
|
|
borderWidth: 3,
|
|
pointRadius: 4,
|
|
pointHoverRadius: 5
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
usePointStyle: true, // legenda com “linha”, não retângulo
|
|
pointStyle: 'line'
|
|
}
|
|
},
|
|
datalabels: {
|
|
align: 'top',
|
|
anchor: 'end',
|
|
color: '#e5e7eb',
|
|
font: { weight: 600, size: 11 },
|
|
formatter: (v) => 'R$ ' + Number(v).toLocaleString('pt-BR', {
|
|
minimumFractionDigits: 2, maximumFractionDigits: 2
|
|
})
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: (ctx) => {
|
|
const v = ctx.parsed.y ?? 0;
|
|
return 'R$ ' + Number(v).toLocaleString('pt-BR', {
|
|
minimumFractionDigits: 2, maximumFractionDigits: 2
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: { grid: { display: false } }, // remove linhas do fundo
|
|
y: {
|
|
grid: { display: false },
|
|
ticks: { callback: v => 'R$ ' + Number(v).toLocaleString('pt-BR') }
|
|
}
|
|
}
|
|
},
|
|
plugins: [ChartDataLabels] // ativa o plugin de rótulos
|
|
});
|
|
|
|
// Mostra overlay ao iniciar; esconde quando tudo carregar
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const el = document.getElementById('loading');
|
|
// garante visível até 'load'
|
|
el.classList.remove('hide');
|
|
});
|
|
window.addEventListener('load', () => {
|
|
const el = document.getElementById('loading');
|
|
el.classList.add('hide');
|
|
});
|
|
</script>
|
|
{% endblock %}
|