feat: show committed goals in fixed costs panel

Committed goals now appear in the Fixed Costs panel on the dashboard
with a "committed goal" label, and the total line uses TotalCommittedCents
(fixed costs + goal contributions) instead of total monthly expenses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gonçalo Rodrigues 2026-06-13 16:46:55 +01:00
parent 2324f62721
commit bfd5f62a7a
3 changed files with 24 additions and 9 deletions

View File

@ -324,7 +324,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
// disposable income = income - fixed recurring
disposableIncome := thisMonthIncome - totalFixedCents
// deduct committed goal contributions from disposable
// deduct committed goal contributions from disposable and add to fixed costs list
committedGoalsCents := int64(0)
if goals, err := h.store.getGoals(ctx, a.UserID); err == nil {
now2 := time.Now()
@ -340,10 +340,17 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
if ml < 1 {
ml = 1
}
committedGoalsCents += remaining / ml
monthly := remaining / ml
committedGoalsCents += monthly
recurringExpenses = append(recurringExpenses, RecurringExpense{
Category: g.Name,
MonthlyCents: monthly,
IsGoal: true,
})
}
}
disposableIncome -= committedGoalsCents
totalCommittedCents := totalFixedCents + committedGoalsCents
// variable spend so far this month (non-fixed categories, expenses only)
variableSpent := int64(0)
@ -450,6 +457,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
RecurringExpenses: recurringExpenses,
BankShouldBe: bankShouldBe,
SafetyBufferCents: safetyBuffer,
TotalCommittedCents: totalCommittedCents,
SavingsRatePct: savingsRatePct,
LastMonthSavingsRatePct: lastMonthSavingsRatePct,
PortfolioValueCents: portfolioValueCents,

View File

@ -115,6 +115,7 @@ var FixedCategories = map[string]bool{
type RecurringExpense struct {
Category string
MonthlyCents int64
IsGoal bool // true when this entry comes from a committed goal
}
type DashboardData struct {
@ -140,8 +141,9 @@ type DashboardData struct {
MonthSpentPct int // % of disposable already spent
RecurringExpenses []RecurringExpense
BankShouldBe int64 // sum of upcoming fixed costs + safety buffer
BankShouldBe int64
SafetyBufferCents int64
TotalCommittedCents int64 // sum of all fixed costs + committed goals
SavingsRatePct int // savings / income * 100 this month
LastMonthSavingsRatePct int

View File

@ -236,10 +236,14 @@
{{$color := index $d.CategoryColors .Category}}
<div style="display:flex; align-items:center; justify-content:space-between; padding:10px 0; border-bottom:1px solid var(--border);">
<div style="display:flex; align-items:center; gap:10px;">
{{if $color}}<span style="width:9px; height:9px; border-radius:50%; background:{{$color}}; flex-shrink:0; display:inline-block;"></span>{{end}}
{{if .IsGoal}}
<span style="width:9px; height:9px; border-radius:50%; background:var(--accent); flex-shrink:0; display:inline-block;"></span>
{{else if $color}}
<span style="width:9px; height:9px; border-radius:50%; background:{{$color}}; flex-shrink:0; display:inline-block;"></span>
{{end}}
<div>
<div style="font-size:13px; font-weight:500; color:var(--text);">{{.Category}}</div>
<div style="font-size:11px; color:var(--text3);">committed monthly cost</div>
<div style="font-size:11px; color:var(--text3);">{{if .IsGoal}}committed goal{{else}}recurring expense{{end}}</div>
</div>
</div>
<div style="text-align:right;">
@ -249,8 +253,9 @@
</div>
{{end}}
<div style="display:flex; justify-content:space-between; align-items:center; padding:12px 0 0 0;">
<span style="font-size:13px; font-weight:500; color:var(--text);">Total fixed</span>
<span style="font-size:15px; font-weight:600; color:var(--red);"> €{{cents $d.BankShouldBe}}</span>
<span style="font-size:13px; font-weight:500; color:var(--text);">Total committed</span>
<span class="animate-counter" style="font-size:15px; font-weight:600; color:var(--red);"
data-target="{{$d.TotalCommittedCents}}" data-prefix="€">€0</span>
</div>
</div>
</div>