06/05 Optimize app
This commit is contained in:
@@ -138,6 +138,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Financial Health Score -->
|
||||
<div id="health-score-card" class="pcard mb-4" style="display:none;">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
|
||||
<span class="pcard-title mb-0">Financial Health Score</span>
|
||||
<span id="hs-grade-badge" class="badge fw-bold" style="font-size:13px;padding:4px 12px;"></span>
|
||||
</div>
|
||||
<div class="row g-3 align-items-center">
|
||||
<!-- Score ring -->
|
||||
<div class="col-12 col-sm-auto d-flex justify-content-center">
|
||||
<div style="position:relative;width:96px;height:96px;">
|
||||
<svg viewBox="0 0 36 36" style="width:96px;height:96px;transform:rotate(-90deg);">
|
||||
<circle cx="18" cy="18" r="15.9155" fill="none" stroke="var(--border)" stroke-width="3"/>
|
||||
<circle id="hs-ring" cx="18" cy="18" r="15.9155" fill="none" stroke="#10b981" stroke-width="3"
|
||||
stroke-dasharray="0 100" stroke-linecap="round"
|
||||
style="transition:stroke-dasharray .8s ease, stroke .4s;"/>
|
||||
</svg>
|
||||
<div style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;">
|
||||
<div id="hs-score" class="mono fw-bold" style="font-size:22px;line-height:1;">—</div>
|
||||
<div style="font-size:10px;color:var(--muted);">/ 100</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Component bars -->
|
||||
<div class="col">
|
||||
<div id="hs-components" class="d-flex flex-column gap-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tips -->
|
||||
<div id="hs-tips" class="mt-3" style="display:none;">
|
||||
<div class="d-flex flex-wrap gap-2" id="hs-tips-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-xl-8">
|
||||
@@ -555,6 +588,71 @@ function refreshFxRate() {
|
||||
});
|
||||
})();
|
||||
|
||||
// ── Financial health score ───────────────────────────────────────────────────
|
||||
(function(){
|
||||
var card = document.getElementById('health-score-card');
|
||||
var ring = document.getElementById('hs-ring');
|
||||
var scoreEl = document.getElementById('hs-score');
|
||||
var gradeEl = document.getElementById('hs-grade-badge');
|
||||
var compsEl = document.getElementById('hs-components');
|
||||
var tipsWrap = document.getElementById('hs-tips');
|
||||
var tipsList = document.getElementById('hs-tips-list');
|
||||
if (!card) return;
|
||||
|
||||
const SYM = '{{ current_user.currency_symbol }}';
|
||||
|
||||
fetch('/api/health-score')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.error) return;
|
||||
card.style.display = '';
|
||||
|
||||
// Score ring
|
||||
var circ = 100;
|
||||
ring.style.strokeDasharray = (data.score / 100 * circ) + ' ' + circ;
|
||||
ring.style.stroke = data.color;
|
||||
scoreEl.textContent = data.score;
|
||||
scoreEl.style.color = data.color;
|
||||
|
||||
// Grade badge
|
||||
gradeEl.textContent = 'Grade ' + data.grade;
|
||||
gradeEl.style.background = data.color + '22';
|
||||
gradeEl.style.color = data.color;
|
||||
|
||||
// Component bars
|
||||
var icons = {
|
||||
savings: 'bi-piggy-bank',
|
||||
budgets: 'bi-pie-chart',
|
||||
goals: 'bi-bullseye',
|
||||
emergency_fund: 'bi-shield-check',
|
||||
};
|
||||
compsEl.innerHTML = data.components.map(c => `
|
||||
<div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-1" style="font-size:12px;">
|
||||
<span><i class="bi ${c.icon} me-1" style="color:var(--muted);"></i>${c.name}</span>
|
||||
<span class="mono" style="color:var(--muted);">${c.points}/${c.max}</span>
|
||||
</div>
|
||||
<div class="progress" style="height:5px;border-radius:3px;">
|
||||
<div class="progress-bar" role="progressbar"
|
||||
style="width:${c.pct}%;background:${c.pct>=80?'#10b981':c.pct>=50?'#3b82f6':c.pct>=25?'#f59e0b':'#ef4444'};transition:width .6s ease;">
|
||||
</div>
|
||||
</div>
|
||||
${c.label ? `<div style="font-size:10px;color:var(--muted);margin-top:1px;">${c.label}</div>` : ''}
|
||||
</div>`).join('');
|
||||
|
||||
// Tips
|
||||
if (data.tips && data.tips.length) {
|
||||
tipsWrap.style.display = '';
|
||||
tipsList.innerHTML = data.tips.map(t =>
|
||||
`<span style="font-size:11px;background:#fef3c7;color:#92400e;border-radius:20px;padding:3px 10px;display:inline-block;">
|
||||
<i class="bi bi-lightbulb me-1"></i>${t}
|
||||
</span>`
|
||||
).join('');
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
})();
|
||||
|
||||
// ── Spending anomalies ───────────────────────────────────────────────────────
|
||||
(function(){
|
||||
const card = document.getElementById('anomaly-card');
|
||||
|
||||
Reference in New Issue
Block a user