diff --git a/src/admin-bi.js b/src/admin-bi.js index a559915..0a203c2 100644 --- a/src/admin-bi.js +++ b/src/admin-bi.js @@ -796,6 +796,25 @@ function buildAdminBIHTML(user) { [data-theme="dark"] .gran-btn:hover { border-color: #F9A825; color: #F9A825; } [data-theme="dark"] .date-inputs input[type="date"] { background: var(--card); color: var(--text); border-color: var(--border); } [data-theme="dark"] .data-table tr:hover td { background: rgba(255,255,255,0.03); } + + /* === Skeleton Loading Shimmer === */ + @keyframes shimmer { + 0% { background-position: -400px 0; } + 100% { background-position: 400px 0; } + } + .skel { + background: linear-gradient(90deg, var(--card-bg,var(--card)) 25%, var(--border) 50%, var(--card-bg,var(--card)) 75%); + background-size: 800px 100%; + animation: shimmer 1.5s infinite ease-in-out; + border-radius: 6px; + display: inline-block; + } + .skel-value { height: 32px; width: 55%; } + .skel-badge { height: 18px; width: 70px; } + .skel-text { height: 14px; width: 80%; margin: 4px 0; } + .skel-chart { height: 100%; width: 100%; min-height: 180px; border-radius: 12px; } + .skel-row { height: 32px; margin: 6px 0; width: 100%; } + .skel-gauge { height: 40px; width: 90px; } `; return ` @@ -854,30 +873,30 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
Receita USD (Spread)
-
--
- -- +
+
vs periodo anterior
Volume Processado
-
--
- -- +
+
USD total movimentado
Transacoes
-
--
- -- +
+
operacoes no periodo
Clientes Ativos
-
--
+
clientes unicos no periodo
Ticket Medio
-
--
+
USD por operacao
@@ -890,11 +909,11 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}

Revenue por Corredor

-
+

Spread Medio + Volume Diario --

-
+
@@ -909,14 +928,14 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })} -
+

Taxa de Retencao

-
--%
+
-
-- de -- clientes retidos
+
+0 novos -0 perdidos @@ -935,7 +954,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
- +
ClienteUltima OpDiasVolume USD
Carregando...
@@ -949,14 +968,14 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}

Volume por Corredor

-
+

Ranking Agentes

- +
#AgenteVolumeOpsSpread R$Clientes
Carregando...
@@ -972,7 +991,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}

Forecast: Historical + Predicted Volume

Loading forecast...
-
+
@@ -990,26 +1009,26 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })} -
+

Resumo Netting

Saida (BRL→USD) - -- +
Entrada (USD→BRL) - -- +
Posicao Liquida - -- +
Eficiencia Netting
-
--%
+
@@ -1035,22 +1054,22 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
Receita Total (P&L)
-
--
-
-- operacoes
+
+
BR → US
-
--
+
Checkout + CambioTransfer
US → BR
-
--
+
Spread + Fees
Receita / Operacao
-
--
+
ticket medio de receita
@@ -1058,11 +1077,11 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}

Receita por Produto ao Longo do Tempo

-
+

Composicao de Receita por Produto

-
+
@@ -1071,7 +1090,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
- +
PeriodoProdutoReceitaOperacoesReceita/Op
Carregando...
@@ -1084,39 +1103,39 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
Novos
-
--
-
--
+
+
Expansao
-
--
-
--
+
+
Estavel
-
--
-
--
+
+
Contracao
-
--
-
--
+
+
Churned
-
--
-
--
+
+

Waterfall de Receita

-
+

Distribuicao por Segmento

-
+
@@ -1128,12 +1147,12 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}

Cross-sell CambioPay vs Checkout

-
+

Maturidade de Clientes

-
+
@@ -1148,7 +1167,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
- +
CohortClientes
Carregando...
@@ -1211,6 +1230,14 @@ const fmtPct = (curr, prev) => { return { text: (pct > 0 ? '+' : '') + pct + '%', cls: pct >= 0 ? 'up' : 'down' }; }; +// === Skeleton helpers === +function showChart(skelId, canvasId) { + var s = document.getElementById(skelId); + if (s) s.remove(); + var c = document.getElementById(canvasId); + if (c) c.style.display = ''; +} + // === Date & Filter Logic === let currentStart = '${thirtyDaysAgo}'; let currentEnd = '${today}'; @@ -1325,6 +1352,11 @@ function _renderBICharts(d) { if (typeof Chart === 'undefined') return; var _ct = getChartTheme(); try { destroyCharts(); } catch(e) {} + showChart('skelDonut', 'chartDonut'); + showChart('skelSpreadTrend', 'chartSpreadTrend'); + showChart('skelTopClients', 'chartTopClients'); + showChart('skelVolFlow', 'chartVolFlow'); + showChart('skelNetting', 'chartNetting'); // Donut: Revenue por Corredor try { @@ -1759,6 +1791,8 @@ function _renderRevCharts(d) { if (typeof Chart === 'undefined' || !d.timeline || !d.totals) return; var _ct = getChartTheme(); try { destroyRevCharts(); } catch(e) {} + showChart('skelRevTimeline', 'chartRevTimeline'); + showChart('skelRevDonut', 'chartRevDonut'); var periods = [...new Set(d.timeline.map(function(r){return r.periodo_label;}))].sort(); var products = [...new Set(d.timeline.map(function(r){return r.produto;}))].sort(); @@ -1944,6 +1978,8 @@ function renderStrategic(d) { // --- Expansion / Contraction --- function renderExpansion(exp, t) { + showChart('skelWaterfall', 'chartWaterfall'); + showChart('skelExpDonut', 'chartExpDonut'); document.getElementById('wfNewCount').textContent = exp.new_clients.count; document.getElementById('wfNewRev').textContent = '+' + fmtUSD(exp.new_clients.revenue); document.getElementById('wfExpCount').textContent = exp.expansion.count; @@ -1991,6 +2027,7 @@ function renderExpansion(exp, t) { // --- Cross-sell --- function renderCrossSell(cs, t) { + showChart('skelCrossSell', 'chartCrossSell'); if (typeof Chart === 'undefined') return; try { if (chartCrossSell) chartCrossSell.destroy(); } catch(e){} @@ -2024,6 +2061,7 @@ function renderCrossSell(cs, t) { // --- Client Maturity --- function renderMaturity(mat, t) { + showChart('skelMaturity', 'chartMaturity'); if (typeof Chart === 'undefined') return; try { if (chartMaturity) chartMaturity.destroy(); } catch(e){} @@ -2120,6 +2158,7 @@ async function loadForecast() { var resp = await fetch('/admin/api/bi/forecast?metric=volume&days=30'); var data = await resp.json(); if (!data.historical || data.historical.length === 0) { + showChart('skelForecast', 'forecastChart'); document.getElementById('forecastInfo').textContent = 'No data'; return; } @@ -2134,6 +2173,7 @@ async function loadForecast() { // Extend historical with nulls for prediction period var histFull = histValues.concat(new Array(predLabels.length).fill(null)); + showChart('skelForecast', 'forecastChart'); if (_forecastChart) _forecastChart.destroy(); var ctx = document.getElementById('forecastChart'); if (!ctx) return; @@ -2164,6 +2204,7 @@ async function loadForecast() { document.getElementById('forecastInfo').textContent = data.predicted.length + '-day forecast'; } catch (e) { console.error('Forecast error:', e); + showChart('skelForecast', 'forecastChart'); document.getElementById('forecastInfo').textContent = 'Forecast error'; } }