feat: skeleton shimmer for Client 360 + fix Chart.js event error on BI
- Add skeleton loading to admin-cliente.js: profile card, 12 hero KPIs, health/risk/netting values, 9 charts, transaction table - Fix BI Chart.js handleEvent crash: use absolute-positioned skeleton overlays instead of display:none on canvases - Fix setPreset null pointer on preset button querySelector Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -812,7 +812,7 @@ function buildAdminBIHTML(user) {
|
||||
.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-chart { height: 100%; width: 100%; min-height: 180px; border-radius: 12px; position: absolute; top: 0; left: 0; z-index: 2; }
|
||||
.skel-row { height: 32px; margin: 6px 0; width: 100%; }
|
||||
.skel-gauge { height: 40px; width: 90px; }
|
||||
`;
|
||||
@@ -909,11 +909,11 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
|
||||
<div class="charts-row">
|
||||
<div class="chart-card">
|
||||
<h3>Revenue por Corredor</h3>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelDonut"></div><canvas id="chartDonut" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelDonut"></div><canvas id="chartDonut"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>Spread Medio + Volume Diario <span class="badge" id="spreadBadge">--</span></h3>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelSpreadTrend"></div><canvas id="chartSpreadTrend" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelSpreadTrend"></div><canvas id="chartSpreadTrend"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -928,7 +928,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
|
||||
<button id="btnResetClients" style="display:none;margin-left:auto;" class="preset-btn"
|
||||
onclick="_resetClientFilter()">Mostrar Todos</button>
|
||||
</h3>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelTopClients"></div><canvas id="chartTopClients" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelTopClients"></div><canvas id="chartTopClients"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card" style="display:flex;flex-direction:column;gap:20px;">
|
||||
<div class="metric-card" style="box-shadow:none;border:1px solid var(--border);padding:16px;">
|
||||
@@ -968,7 +968,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
|
||||
<div class="charts-row equal">
|
||||
<div class="chart-card">
|
||||
<h3>Volume por Corredor</h3>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelVolFlow"></div><canvas id="chartVolFlow" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelVolFlow"></div><canvas id="chartVolFlow"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>Ranking Agentes</h3>
|
||||
@@ -991,7 +991,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
|
||||
<h3 style="margin:0;">Forecast: Historical + Predicted Volume</h3>
|
||||
<span class="period-info" id="forecastInfo">Loading forecast...</span>
|
||||
</div>
|
||||
<div style="height:300px;"><div class="skel skel-chart" id="skelForecast"></div><canvas id="forecastChart" style="display:none"></canvas></div>
|
||||
<div style="height:300px;position:relative;"><div class="skel skel-chart" id="skelForecast"></div><canvas id="forecastChart"></canvas></div>
|
||||
</div>
|
||||
|
||||
<!-- Section: Netting & Balanco -->
|
||||
@@ -1009,7 +1009,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
|
||||
<button class="netting-gran-btn" data-netting-gran="M">M</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelNetting"></div><canvas id="chartNetting" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelNetting"></div><canvas id="chartNetting"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card" style="display:flex;flex-direction:column;gap:16px;">
|
||||
<h3 style="font-size:14px;font-weight:700;color:var(--text);margin:0;">Resumo Netting</h3>
|
||||
@@ -1077,11 +1077,11 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
|
||||
<div class="charts-row equal">
|
||||
<div class="chart-card">
|
||||
<h3>Receita por Produto ao Longo do Tempo</h3>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelRevTimeline"></div><canvas id="chartRevTimeline" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelRevTimeline"></div><canvas id="chartRevTimeline"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>Composicao de Receita por Produto</h3>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelRevDonut"></div><canvas id="chartRevDonut" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelRevDonut"></div><canvas id="chartRevDonut"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1131,11 +1131,11 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
|
||||
<div class="charts-row equal" style="margin-bottom:28px;">
|
||||
<div class="chart-card">
|
||||
<h3>Waterfall de Receita</h3>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelWaterfall"></div><canvas id="chartWaterfall" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelWaterfall"></div><canvas id="chartWaterfall"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>Distribuicao por Segmento</h3>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelExpDonut"></div><canvas id="chartExpDonut" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelExpDonut"></div><canvas id="chartExpDonut"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1147,12 +1147,12 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'bi' })}
|
||||
<div class="segment-grid">
|
||||
<div class="segment-card">
|
||||
<h3>Cross-sell <span class="badge">CambioPay vs Checkout</span></h3>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelCrossSell"></div><canvas id="chartCrossSell" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelCrossSell"></div><canvas id="chartCrossSell"></canvas></div>
|
||||
<div class="segment-bars" id="crossSellBars" style="margin-top:16px;"></div>
|
||||
</div>
|
||||
<div class="segment-card">
|
||||
<h3>Maturidade de Clientes</h3>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelMaturity"></div><canvas id="chartMaturity" style="display:none"></canvas></div>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelMaturity"></div><canvas id="chartMaturity"></canvas></div>
|
||||
<div class="segment-bars" id="maturityBars" style="margin-top:16px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1231,11 +1231,9 @@ const fmtPct = (curr, prev) => {
|
||||
};
|
||||
|
||||
// === Skeleton helpers ===
|
||||
function showChart(skelId, canvasId) {
|
||||
function showChart(skelId) {
|
||||
var s = document.getElementById(skelId);
|
||||
if (s) s.remove();
|
||||
var c = document.getElementById(canvasId);
|
||||
if (c) c.style.display = '';
|
||||
}
|
||||
|
||||
// === Date & Filter Logic ===
|
||||
@@ -1260,8 +1258,9 @@ function setPreset(preset) {
|
||||
}
|
||||
document.getElementById('dateStart').value = start;
|
||||
document.getElementById('dateEnd').value = today;
|
||||
document.querySelectorAll('.preset-btn').forEach(b => b.classList.remove('active'));
|
||||
document.querySelector('[data-preset="'+preset+'"]').classList.add('active');
|
||||
document.querySelectorAll('.preset-btn[data-preset]').forEach(b => b.classList.remove('active'));
|
||||
var _pb = document.querySelector('[data-preset="'+preset+'"]');
|
||||
if (_pb) _pb.classList.add('active');
|
||||
currentStart = start; currentEnd = today;
|
||||
loadAll();
|
||||
}
|
||||
|
||||
@@ -330,6 +330,28 @@ function buildAdminClienteHTML(user) {
|
||||
[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; position: absolute; top: 0; left: 0; z-index: 2; }
|
||||
.skel-row { height: 32px; margin: 6px 0; width: 100%; }
|
||||
.skel-gauge { height: 40px; width: 90px; }
|
||||
.skel-avatar { width: 56px; height: 56px; border-radius: 50%; }
|
||||
.skel-name { height: 22px; width: 160px; }
|
||||
.skel-stat { height: 18px; width: 70px; }
|
||||
|
||||
/* === Merchant / Checkout === */
|
||||
.merchant-badge {
|
||||
display: none; padding: 4px 12px; border-radius: 8px; font-size: 11px; font-weight: 800;
|
||||
@@ -397,23 +419,23 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
<!-- Profile Card with Health Score -->
|
||||
<div class="profile-card" id="profileCard">
|
||||
<div class="profile-left">
|
||||
<div class="profile-avatar" id="profileAvatar">--</div>
|
||||
<div class="profile-avatar" id="profileAvatar"><div class="skel skel-avatar"></div></div>
|
||||
<div>
|
||||
<div class="profile-name" id="profileName">--</div>
|
||||
<div class="profile-id" id="profileId">--</div>
|
||||
<div class="profile-name" id="profileName"><div class="skel skel-name"></div></div>
|
||||
<div class="profile-id" id="profileId"><div class="skel skel-text"></div></div>
|
||||
<div class="merchant-badge" id="merchantBadge">MERCHANT</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-stats">
|
||||
<div class="profile-stat"><div class="profile-stat-label">Primeira Op</div><div class="profile-stat-value" id="profileFirstOp">--</div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Ultima Op</div><div class="profile-stat-value" id="profileLastOp">--</div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Meses Ativo</div><div class="profile-stat-value" id="profileMonths">--</div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">LTV (Receita)</div><div class="profile-stat-value" id="profileLTV">--</div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Vol. Lifetime</div><div class="profile-stat-value" id="profileVolume">--</div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Ops Lifetime</div><div class="profile-stat-value" id="profileOps">--</div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Primeira Op</div><div class="profile-stat-value" id="profileFirstOp"><div class="skel skel-stat"></div></div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Ultima Op</div><div class="profile-stat-value" id="profileLastOp"><div class="skel skel-stat"></div></div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Meses Ativo</div><div class="profile-stat-value" id="profileMonths"><div class="skel skel-stat"></div></div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">LTV (Receita)</div><div class="profile-stat-value" id="profileLTV"><div class="skel skel-stat"></div></div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Vol. Lifetime</div><div class="profile-stat-value" id="profileVolume"><div class="skel skel-stat"></div></div></div>
|
||||
<div class="profile-stat"><div class="profile-stat-label">Ops Lifetime</div><div class="profile-stat-value" id="profileOps"><div class="skel skel-stat"></div></div></div>
|
||||
</div>
|
||||
<div class="health-score-box" id="healthScoreBox">
|
||||
<div class="health-score-number" id="healthScoreNum">--</div>
|
||||
<div class="health-score-number" id="healthScoreNum"><div class="skel skel-gauge"></div></div>
|
||||
<div class="health-score-label" id="healthScoreLabel">Health</div>
|
||||
</div>
|
||||
<div id="churnRisk" style="padding:0 16px 16px;"></div>
|
||||
@@ -440,12 +462,12 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
|
||||
<!-- Hero KPIs (6) -->
|
||||
<div class="hero-grid" id="heroGrid">
|
||||
<div class="hero-card volume"><div class="hero-label">Volume USD</div><div class="hero-value" id="kpiVolume">--</div><span class="hero-badge neutral" id="kpiVolBadge">--</span></div>
|
||||
<div class="hero-card transactions"><div class="hero-label">Transacoes</div><div class="hero-value" id="kpiTx">--</div><span class="hero-badge neutral" id="kpiTxBadge">--</span></div>
|
||||
<div class="hero-card spread"><div class="hero-label">Receita USD</div><div class="hero-value" id="kpiSpread">--</div><span class="hero-badge neutral" id="kpiSpreadBadge">--</span></div>
|
||||
<div class="hero-card ticket"><div class="hero-label">Ticket Medio</div><div class="hero-value" id="kpiTicket">--</div><div class="hero-sub">USD / operacao</div></div>
|
||||
<div class="hero-card arpa"><div class="hero-label">ARPA</div><div class="hero-value" id="kpiARPA">--</div><div class="hero-sub">receita / mes</div></div>
|
||||
<div class="hero-card avgspread"><div class="hero-label">Spread Medio</div><div class="hero-value" id="kpiAvgSpread">--</div><div class="hero-sub">% ponderado</div></div>
|
||||
<div class="hero-card volume"><div class="hero-label">Volume USD</div><div class="hero-value" id="kpiVolume"><div class="skel skel-value"></div></div><span class="hero-badge neutral" id="kpiVolBadge"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="hero-card transactions"><div class="hero-label">Transacoes</div><div class="hero-value" id="kpiTx"><div class="skel skel-value"></div></div><span class="hero-badge neutral" id="kpiTxBadge"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="hero-card spread"><div class="hero-label">Receita USD</div><div class="hero-value" id="kpiSpread"><div class="skel skel-value"></div></div><span class="hero-badge neutral" id="kpiSpreadBadge"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="hero-card ticket"><div class="hero-label">Ticket Medio</div><div class="hero-value" id="kpiTicket"><div class="skel skel-value"></div></div><div class="hero-sub">USD / operacao</div></div>
|
||||
<div class="hero-card arpa"><div class="hero-label">ARPA</div><div class="hero-value" id="kpiARPA"><div class="skel skel-value"></div></div><div class="hero-sub">receita / mes</div></div>
|
||||
<div class="hero-card avgspread"><div class="hero-label">Spread Medio</div><div class="hero-value" id="kpiAvgSpread"><div class="skel skel-value"></div></div><div class="hero-sub">% ponderado</div></div>
|
||||
</div>
|
||||
|
||||
<!-- Checkout KPIs (merchant only) -->
|
||||
@@ -455,12 +477,12 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
CambioCheckout (Merchant)
|
||||
</div>
|
||||
<div class="hero-grid" id="checkoutHeroGrid">
|
||||
<div class="hero-card checkout"><div class="hero-label">Checkout Volume</div><div class="hero-value" id="ckVolume">--</div><span class="hero-badge neutral" id="ckVolBadge">--</span></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Payers Unicos</div><div class="hero-value" id="ckPayers">--</div><div class="hero-sub">pagadores distintos</div></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Receita Checkout</div><div class="hero-value" id="ckRevenue">--</div><span class="hero-badge neutral" id="ckRevBadge">--</span></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Checkout Ops</div><div class="hero-value" id="ckOps">--</div><span class="hero-badge neutral" id="ckOpsBadge">--</span></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Ticket Checkout</div><div class="hero-value" id="ckTicket">--</div><div class="hero-sub">USD / operacao</div></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Spread Checkout</div><div class="hero-value" id="ckSpread">--</div><div class="hero-sub">% medio</div></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Checkout Volume</div><div class="hero-value" id="ckVolume"><div class="skel skel-value"></div></div><span class="hero-badge neutral" id="ckVolBadge"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Payers Unicos</div><div class="hero-value" id="ckPayers"><div class="skel skel-value"></div></div><div class="hero-sub">pagadores distintos</div></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Receita Checkout</div><div class="hero-value" id="ckRevenue"><div class="skel skel-value"></div></div><span class="hero-badge neutral" id="ckRevBadge"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Checkout Ops</div><div class="hero-value" id="ckOps"><div class="skel skel-value"></div></div><span class="hero-badge neutral" id="ckOpsBadge"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Ticket Checkout</div><div class="hero-value" id="ckTicket"><div class="skel skel-value"></div></div><div class="hero-sub">USD / operacao</div></div>
|
||||
<div class="hero-card checkout"><div class="hero-label">Spread Checkout</div><div class="hero-value" id="ckSpread"><div class="skel skel-value"></div></div><div class="hero-sub">% medio</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -469,7 +491,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
<div class="charts-row wide-left">
|
||||
<div class="chart-card">
|
||||
<h3>Checkout Mensal: Volume + Payers</h3>
|
||||
<div class="chart-wrap"><canvas id="chartCheckoutMonthly"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelCheckoutMonthly"></div><canvas id="chartCheckoutMonthly"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>Top 10 Pagadores</h3>
|
||||
@@ -489,7 +511,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
<div class="health-gauge-wrap">
|
||||
<div class="health-gauge">
|
||||
<svg viewBox="0 0 140 140"><circle class="health-gauge-bg" cx="70" cy="70" r="60"/><circle class="health-gauge-fill" id="healthGaugeFill" cx="70" cy="70" r="60" stroke-dasharray="377" stroke-dashoffset="377"/></svg>
|
||||
<div class="health-gauge-center"><div class="health-gauge-score" id="healthGaugeScore">--</div><div class="health-gauge-label" id="healthGaugeLabel">--</div></div>
|
||||
<div class="health-gauge-center"><div class="health-gauge-score" id="healthGaugeScore"><div class="skel skel-gauge"></div></div><div class="health-gauge-label" id="healthGaugeLabel"><div class="skel skel-text"></div></div></div>
|
||||
</div>
|
||||
<div class="health-breakdown" id="healthBreakdown">
|
||||
<div class="health-row"><span class="health-row-label">Volume</span><div class="health-row-bar"><div class="health-row-fill" id="hbVol" style="width:0%;background:var(--blue);"></div></div><span class="health-row-val" id="hbVolVal">0</span></div>
|
||||
@@ -502,22 +524,22 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
|
||||
<div class="intel-card">
|
||||
<h3>Indicadores de Churn</h3>
|
||||
<div style="margin-bottom:12px;text-align:center;"><span class="risk-level-badge" id="riskBadge">--</span></div>
|
||||
<div style="margin-bottom:12px;text-align:center;"><span class="risk-level-badge" id="riskBadge"><div class="skel skel-badge"></div></span></div>
|
||||
<div id="riskIndicators">
|
||||
<div class="risk-row"><span class="risk-row-label">Variacao Volume</span><span class="risk-row-value neutral" id="riskVol">--</span></div>
|
||||
<div class="risk-row"><span class="risk-row-label">Variacao Transacoes</span><span class="risk-row-value neutral" id="riskTx">--</span></div>
|
||||
<div class="risk-row"><span class="risk-row-label">Dias Inativo</span><span class="risk-row-value neutral" id="riskDays">--</span></div>
|
||||
<div class="risk-row"><span class="risk-row-label">Intervalo Medio</span><span class="risk-row-value neutral" id="riskInterval">--</span></div>
|
||||
<div class="risk-row"><span class="risk-row-label">Variacao Volume</span><span class="risk-row-value neutral" id="riskVol"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="risk-row"><span class="risk-row-label">Variacao Transacoes</span><span class="risk-row-value neutral" id="riskTx"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="risk-row"><span class="risk-row-label">Dias Inativo</span><span class="risk-row-value neutral" id="riskDays"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="risk-row"><span class="risk-row-label">Intervalo Medio</span><span class="risk-row-value neutral" id="riskInterval"><div class="skel skel-badge"></div></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="intel-card">
|
||||
<h3>Netting & Posicao</h3>
|
||||
<div id="nettingContent">
|
||||
<div class="netting-row"><span class="netting-label">Saida (BRL→USD)</span><span class="netting-value red" id="netOut">--</span></div>
|
||||
<div class="netting-row"><span class="netting-label">Entrada (USD→BRL)</span><span class="netting-value green" id="netIn">--</span></div>
|
||||
<div class="netting-row" style="border-top:2px solid var(--border);padding-top:12px;"><span class="netting-label" style="font-weight:700;">Posicao Liquida</span><span class="netting-value" id="netPos">--</span></div>
|
||||
<div style="margin-top:12px;"><div style="font-size:11px;color:var(--text-muted);margin-bottom:4px;">Eficiencia Netting</div><div class="netting-value blue" id="netEff" style="font-size:24px;">--%</div><div class="gauge-bar"><div class="gauge-bar-fill blue" id="netEffBar" style="width:0%"></div></div></div>
|
||||
<div class="netting-row"><span class="netting-label">Saida (BRL→USD)</span><span class="netting-value red" id="netOut"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="netting-row"><span class="netting-label">Entrada (USD→BRL)</span><span class="netting-value green" id="netIn"><div class="skel skel-badge"></div></span></div>
|
||||
<div class="netting-row" style="border-top:2px solid var(--border);padding-top:12px;"><span class="netting-label" style="font-weight:700;">Posicao Liquida</span><span class="netting-value" id="netPos"><div class="skel skel-badge"></div></span></div>
|
||||
<div style="margin-top:12px;"><div style="font-size:11px;color:var(--text-muted);margin-bottom:4px;">Eficiencia Netting</div><div class="netting-value blue" id="netEff" style="font-size:24px;"><div class="skel skel-gauge"></div></div><div class="gauge-bar"><div class="gauge-bar-fill blue" id="netEffBar" style="width:0%"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -530,11 +552,11 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
<div class="charts-row">
|
||||
<div class="chart-card">
|
||||
<h3>Receita USD Mensal + Volume</h3>
|
||||
<div class="chart-wrap"><canvas id="chartMonthlyRev"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelMonthlyRev"></div><canvas id="chartMonthlyRev"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>Crescimento MoM %</h3>
|
||||
<div class="chart-wrap"><canvas id="chartMoM"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelMoM"></div><canvas id="chartMoM"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -552,7 +574,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
<button class="gran-btn" data-tl-gran="M">M</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-wrap"><canvas id="chartTimeline"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelTimeline"></div><canvas id="chartTimeline"></canvas></div>
|
||||
</div>
|
||||
|
||||
<!-- Section: Flow Analysis -->
|
||||
@@ -563,11 +585,11 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
<div class="charts-row">
|
||||
<div class="chart-card">
|
||||
<h3>BRL→USD vs USD→BRL</h3>
|
||||
<div class="chart-wrap short"><canvas id="chartFlowDonut"></canvas></div>
|
||||
<div class="chart-wrap short"><div class="skel skel-chart" id="skelFlowDonut"></div><canvas id="chartFlowDonut"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>Tendencia Spread % por Fluxo</h3>
|
||||
<div class="chart-wrap"><canvas id="chartSpreadTrend"></canvas></div>
|
||||
<div class="chart-wrap"><div class="skel skel-chart" id="skelSpreadTrend"></div><canvas id="chartSpreadTrend"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -601,7 +623,7 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
<th data-col="status">Status <span class="sort-arrow">▲</span></th>
|
||||
<th data-col="provider">Provider <span class="sort-arrow">▲</span></th>
|
||||
</tr></thead>
|
||||
<tbody id="txTableBody"><tr><td colspan="10" style="text-align:center;color:var(--text-muted);">--</td></tr></tbody>
|
||||
<tbody id="txTableBody"><tr class="skel-tr"><td colspan="10"><div class="skel skel-row"></div></td></tr><tr class="skel-tr"><td colspan="10"><div class="skel skel-row"></div></td></tr><tr class="skel-tr"><td colspan="10"><div class="skel skel-row"></div></td></tr></tbody>
|
||||
<tfoot id="txTableFoot"></tfoot>
|
||||
</table>
|
||||
</div>
|
||||
@@ -613,9 +635,9 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente' })}
|
||||
Insights Comportamentais
|
||||
</div>
|
||||
<div class="charts-row triple">
|
||||
<div class="chart-card"><h3>Atividade por Dia da Semana</h3><div class="chart-wrap short"><canvas id="chartDow"></canvas></div></div>
|
||||
<div class="chart-card"><h3>Ticket Medio Mensal</h3><div class="chart-wrap short"><canvas id="chartAvgSize"></canvas></div></div>
|
||||
<div class="chart-card"><h3>Providers / Metodos</h3><div class="chart-wrap short"><canvas id="chartProvider"></canvas></div></div>
|
||||
<div class="chart-card"><h3>Atividade por Dia da Semana</h3><div class="chart-wrap short"><div class="skel skel-chart" id="skelDow"></div><canvas id="chartDow"></canvas></div></div>
|
||||
<div class="chart-card"><h3>Ticket Medio Mensal</h3><div class="chart-wrap short"><div class="skel skel-chart" id="skelAvgSize"></div><canvas id="chartAvgSize"></canvas></div></div>
|
||||
<div class="chart-card"><h3>Providers / Metodos</h3><div class="chart-wrap short"><div class="skel skel-chart" id="skelProvider"></div><canvas id="chartProvider"></canvas></div></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -686,6 +708,12 @@ function applyChartDefaults(t) {
|
||||
Chart.defaults.plugins.tooltip.borderWidth = 1;
|
||||
}
|
||||
|
||||
// === Skeleton helpers ===
|
||||
function showChart(skelId) {
|
||||
var s = document.getElementById(skelId);
|
||||
if (s) s.remove();
|
||||
}
|
||||
|
||||
// === Charts ===
|
||||
var chartTimeline, chartFlowDonut, chartSpreadTrend, chartDow, chartAvgSize, chartProvider, chartMonthlyRev, chartMoM, chartCheckoutMonthly;
|
||||
function destroyAllCharts() {
|
||||
@@ -1015,6 +1043,10 @@ function renderIntelligence(d, t) {
|
||||
function renderCharts(d, t) {
|
||||
if (typeof Chart === 'undefined') return;
|
||||
destroyAllCharts();
|
||||
showChart('skelMonthlyRev'); showChart('skelMoM');
|
||||
showChart('skelTimeline'); showChart('skelFlowDonut');
|
||||
showChart('skelSpreadTrend'); showChart('skelDow');
|
||||
showChart('skelAvgSize'); showChart('skelProvider');
|
||||
renderMonthlyRevenue(d, t);
|
||||
renderMoMChart(d, t);
|
||||
renderTimeline(d, t);
|
||||
@@ -1196,6 +1228,7 @@ function renderCheckoutKPIs(d) {
|
||||
document.getElementById('ckOpsBadge').className = 'hero-badge ' + bo.cls; document.getElementById('ckOpsBadge').textContent = bo.text;
|
||||
}
|
||||
function renderCheckoutMonthly(d, t) {
|
||||
showChart('skelCheckoutMonthly');
|
||||
if (!d.merchant || !d.merchant.monthly || !d.merchant.monthly.length) return;
|
||||
var m = d.merchant.monthly;
|
||||
if (chartCheckoutMonthly) chartCheckoutMonthly.destroy();
|
||||
|
||||
Reference in New Issue
Block a user