UI — full dark/light theme system: - CSS custom-property token system (--bg, --surface, --accent, --green, --red, etc.) with complete light-mode overrides via [data-theme="light"] - Sticky frosted-glass nav with animated brand icon, theme toggle persisted to localStorage, respects prefers-color-scheme on first visit - Cards with layered shadows, glass backdrop-filter, shimmer accent stripe on value cards - Glowing category color dots, colored P&L badges, budget bars with glow effect - All Chart.js instances use CSS-variable-aware grid/text colours - Scroll-reveal animations and animated money counters on every KPI card - 3-D donut portfolio chart recoloured to match palette; hover lifts the hovered slice - Accounts page shows type emoji icons; delete removes row in-place - Sharing page search dropdown themed with var() colours - Import preview: colour-coded left border on category select driven by category colour - Projections: second KPI card (monthly avg) + pace bars per category Seed data (seed.go): - SeedAdmin() runs in a goroutine at startup; idempotent (skips if transactions exist) - Resolves admin user ID via internal users service GET /admin/users?search=<email> (SEED_USER_EMAIL env var, defaults to admin@homelab.local) - Seeds 4 accounts (CGD Checking, CGD Savings, Visa Credit, Trade Republic) - Seeds 14 categories with colours and monthly budgets - Seeds ~65 realistic Portuguese household transactions spread across 6 months - Seeds 7 ETF buy trades across VWCE, SXR8 (S&P 500), EUNL (MSCI World) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
93 lines
3.1 KiB
HTML
93 lines
3.1 KiB
HTML
{{define "content"}}
|
|
{{$d := .}}
|
|
<h1 style="margin-bottom:24px;">Projections</h1>
|
|
|
|
<div class="grid">
|
|
<div class="card value-card animate-on-scroll">
|
|
<h2>Projected Annual Spend</h2>
|
|
<div class="value negative animate-counter" data-target="{{$d.AnnualTotal}}" data-prefix="€">€0.00</div>
|
|
<p class="text-muted" style="margin-top:8px;">Based on 6-month average</p>
|
|
</div>
|
|
<div class="card value-card animate-on-scroll">
|
|
<h2>Projected Monthly Spend</h2>
|
|
{{$monthly := div $d.AnnualTotal 12}}
|
|
<div class="value negative animate-counter" data-target="{{$monthly}}" data-prefix="€">€0.00</div>
|
|
<p class="text-muted" style="margin-top:8px;">Average across categories</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card animate-on-scroll">
|
|
<h2 style="margin-bottom:12px;">Monthly Average by Category — Last 6 Months</h2>
|
|
<div style="padding-top:4px;">
|
|
<canvas id="projChart" height="260"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card animate-on-scroll">
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Category</th>
|
|
<th class="text-right">Monthly Avg</th>
|
|
<th class="text-right">Projected Annual</th>
|
|
<th style="width:200px;">Pace</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range $cat, $avg := $d.MonthlyAvg}}
|
|
<td style="font-weight:500;">{{$cat}}</td>
|
|
<td class="cents negative">€{{printf "%.2f" $avg}}</td>
|
|
<td class="cents negative">€{{printf "%.2f" (mul $avg 12)}}</td>
|
|
<td>
|
|
<div style="background:var(--bg3); border-radius:6px; height:6px; overflow:hidden;">
|
|
<div style="height:100%; border-radius:6px; background:var(--accent);
|
|
width:{{round (mul (div (round (mul $avg 100)) (div $d.AnnualTotal 12)) 100)}}%;
|
|
max-width:100%;"></div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const isDark = () => document.documentElement.getAttribute('data-theme') === 'dark';
|
|
const gridC = () => isDark() ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.06)';
|
|
const textC = () => isDark() ? '#5c6585' : '#8a92b0';
|
|
|
|
const catColors = [
|
|
'#6979f8','#f87171','#fbbf24','#34d399','#a78bfa',
|
|
'#f472b6','#38bdf8','#fb923c','#4ade80','#e879f9',
|
|
'#94a3b8','#22d3ee','#f97316','#a3e635',
|
|
];
|
|
|
|
new Chart(document.getElementById('projChart'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: [{{range $cat, $_ := $d.MonthlyAvg}}"{{$cat}}",{{end}}],
|
|
datasets: [{
|
|
label: 'Monthly Avg (€)',
|
|
data: [{{range $_, $avg := $d.MonthlyAvg}}{{$avg}},{{end}}],
|
|
backgroundColor: catColors.map(c => c + '99'),
|
|
borderColor: catColors,
|
|
borderWidth: 1.5,
|
|
borderRadius: 6,
|
|
borderSkipped: false,
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
animation: { duration: 900, easing: 'easeOutQuart' },
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: { beginAtZero: true, grid: { color: gridC() }, ticks: { color: textC(), callback: v => '€' + v } },
|
|
x: { grid: { display: false }, ticks: { color: textC() } }
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
{{end}}
|