Gonçalo Rodrigues 4cfe80e3d5 feat(finance): goal monthly funding status on dashboard
Each committed goal card now shows a "This month" section beneath the
overall progress bar with three states:
- ✓ On track (green) when funded >= monthly need
- partial (amber) showing shortfall + "Fund it →" link
- unfunded (red) with monthly amount needed + "Fund it →" link

"Fund it →" deep-links to /transactions?fund_goal=<id> which auto-opens
the Add Transaction modal with the goal pre-selected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 22:27:31 +01:00

446 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "content"}}
{{$d := .}}
<div style="display:flex; align-items:baseline; justify-content:space-between; margin-bottom:24px; flex-wrap:wrap; gap:8px;">
<h1>{{$d.T.Get "dashboard.title"}}</h1>
<span class="text-muted">{{if $d.Email}}{{$d.Email}}{{end}}</span>
</div>
{{if $d.Alerts}}
<div style="display:flex; flex-direction:column; gap:8px; margin-bottom:16px;">
{{range $d.Alerts}}
<div style="display:flex; align-items:flex-start; gap:10px; padding:12px 16px; border-radius:10px; font-size:13px;
{{if eq .Level "danger"}}background:rgba(248,113,113,0.08); border:1px solid rgba(248,113,113,0.25); color:var(--red);
{{else if eq .Level "warn"}}background:rgba(245,158,11,0.08); border:1px solid rgba(245,158,11,0.25); color:#f59e0b;
{{else}}background:rgba(105,121,248,0.08); border:1px solid rgba(105,121,248,0.25); color:var(--accent);{{end}}">
<span style="flex-shrink:0; font-size:15px;">{{if eq .Level "danger"}}🔴{{else if eq .Level "warn"}}⚠{{else}}{{end}}</span>
<span>{{.Message}}</span>
</div>
{{end}}
</div>
{{end}}
<!-- HERO: interactive cash flow waterfall -->
<div class="card animate-on-scroll" style="margin-bottom:16px; padding:24px 28px;">
<div style="font-size:12px; color:var(--text2); text-transform:uppercase; letter-spacing:.5px; margin-bottom:4px;">
{{$d.T.Get "dashboard.waterfall.title"}}
</div>
<!-- Income row -->
{{if $d.IncomeCats}}
<div class="wf-section">
<button class="wf-row" onclick="wfToggle('income')" aria-expanded="false">
<span class="wf-chevron" id="wf-chev-income"></span>
<span class="wf-row-label">{{$d.T.Get "dashboard.waterfall.income"}}</span>
<span class="wf-row-amt positive">+€{{cents $d.WaterfallIncome}}</span>
</button>
<div class="wf-detail" id="wf-income" style="display:none;">
{{range $i, $row := $d.IncomeCats}}
<div class="wf-cat-section">
<button class="wf-cat-row" onclick="wfToggleCat('ic{{$i}}')">
{{if $row.Color}}<span class="cat-dot" style="background:{{$row.Color}};"></span>{{end}}
<span style="flex:1; font-size:12px;">{{$row.Name}}</span>
<span class="wf-cat-chev" id="wf-cc-ic{{$i}}"></span>
<span style="font-size:12px; font-weight:500; color:var(--green);">+€{{cents $row.Cents}}</span>
</button>
<div class="wf-txn-list" id="wf-ic{{$i}}" style="display:none;">
{{range index $d.IncomeCatTxns $row.Name}}
<div class="wf-txn-row">
<span class="wf-txn-date">{{dateShort .Date}}</span>
<span class="wf-txn-desc">{{.Description}}</span>
<span class="wf-txn-amt positive">+€{{cents .AmountCents}}</span>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
{{else}}
<div class="wf-row" style="cursor:default;">
<span class="wf-row-label" style="padding-left:20px;">{{$d.T.Get "dashboard.waterfall.income"}}</span>
<span class="wf-row-amt positive">+€{{cents $d.WaterfallIncome}}</span>
</div>
{{end}}
<!-- Living expenses row -->
{{if $d.LivingCats}}
<div class="wf-section">
<button class="wf-row" onclick="wfToggle('living')" aria-expanded="false">
<span class="wf-chevron" id="wf-chev-living"></span>
<span class="wf-row-label">{{$d.T.Get "dashboard.waterfall.living"}}</span>
<span class="wf-row-amt" style="color:var(--red);">−€{{cents $d.WaterfallLiving}}</span>
</button>
<div class="wf-detail" id="wf-living" style="display:none;">
{{range $i, $row := $d.LivingCats}}
<div class="wf-cat-section">
<button class="wf-cat-row" onclick="wfToggleCat('lc{{$i}}')">
{{if $row.Color}}<span class="cat-dot" style="background:{{$row.Color}};"></span>{{end}}
<span style="flex:1; font-size:12px;">{{$row.Name}}</span>
<span class="wf-cat-chev" id="wf-cc-lc{{$i}}"></span>
<span style="font-size:12px; font-weight:500; color:var(--red);">−€{{cents $row.Cents}}</span>
</button>
<div class="wf-txn-list" id="wf-lc{{$i}}" style="display:none;">
{{range index $d.LivingCatTxns $row.Name}}
<div class="wf-txn-row">
<span class="wf-txn-date">{{dateShort .Date}}</span>
<span class="wf-txn-desc">{{.Description}}</span>
<span class="wf-txn-amt" style="color:var(--red);">−€{{cents (abs .AmountCents)}}</span>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
{{else if gt $d.WaterfallLiving 0}}
<div class="wf-row" style="cursor:default;">
<span class="wf-row-label" style="padding-left:20px;">{{$d.T.Get "dashboard.waterfall.living"}}</span>
<span class="wf-row-amt" style="color:var(--red);">−€{{cents $d.WaterfallLiving}}</span>
</div>
{{end}}
<!-- Goal contributions row -->
{{if gt $d.WaterfallGoals 0}}
<div class="wf-section">
{{if $d.DashGoals}}
<button class="wf-row" onclick="wfToggle('goals')" aria-expanded="false">
<span class="wf-chevron" id="wf-chev-goals"></span>
<span class="wf-row-label">{{$d.T.Get "dashboard.waterfall.goals"}}</span>
<span class="wf-row-amt" style="color:var(--accent);">−€{{cents $d.WaterfallGoals}}</span>
</button>
<div class="wf-detail" id="wf-goals" style="display:none;">
{{range $d.DashGoals}}
{{$funded := index $d.GoalFundedThisMonth .ID}}
{{if gt $funded 0}}
<div class="wf-cat-row" style="cursor:default;">
<span style="font-size:11px;">{{if eq .Type "once"}}🎯{{else if eq .Type "deposit"}}🏠{{else if eq .Type "emergency"}}🛡️{{else}}📈{{end}}</span>
<span style="flex:1; font-size:12px;">{{.Name}}</span>
<a href="/goals" style="font-size:11px; color:var(--accent); text-decoration:none; margin-right:8px;">{{$d.T.Get "dashboard.waterfall.goals_link"}}</a>
<span style="font-size:12px; font-weight:500; color:var(--accent);">−€{{cents $funded}}</span>
</div>
{{end}}
{{end}}
</div>
{{else}}
<div class="wf-row" style="cursor:default;">
<span class="wf-row-label" style="padding-left:20px; display:flex; align-items:center; gap:8px;">
{{$d.T.Get "dashboard.waterfall.goals"}}
<a href="/goals" style="font-size:11px; color:var(--accent); text-decoration:none;">{{$d.T.Get "dashboard.waterfall.goals_link"}}</a>
</span>
<span class="wf-row-amt" style="color:var(--accent);">−€{{cents $d.WaterfallGoals}}</span>
</div>
{{end}}
</div>
{{end}}
<!-- Free cash total -->
<div style="display:flex; justify-content:space-between; align-items:center; padding:14px 0 0 0; margin-top:4px; border-top:1px solid var(--border);">
<span style="font-size:14px; font-weight:600; color:var(--text);">{{$d.T.Get "dashboard.waterfall.free_cash"}}</span>
<div class="animate-counter {{if lt $d.WaterfallFreeCash 0}}negative{{else}}positive{{end}}"
style="font-size:32px; font-weight:500; letter-spacing:-1px; line-height:1;"
data-target="{{$d.WaterfallFreeCash}}" data-prefix="€">€0</div>
</div>
<!-- Month progress bar -->
<div style="margin-top:16px; padding-top:14px; border-top:1px solid var(--border);">
<div style="display:flex; justify-content:space-between; font-size:11px; color:var(--text3); margin-bottom:5px;">
<span>{{$d.T.Get "dashboard.waterfall.month_progress"}}</span>
<span>{{$d.MonthProgressPct}}%</span>
</div>
<div style="background:var(--bg3); border-radius:99px; height:4px; overflow:hidden;">
<div style="height:100%; border-radius:99px; width:{{$d.MonthProgressPct}}%;
background:var(--text3); transition:width 1s ease;"></div>
</div>
</div>
</div>
<style>
.wf-section { border-bottom: 1px solid var(--border); }
.wf-section:last-of-type { border-bottom: none; }
.wf-row {
display: flex; align-items: center; gap: 8px;
width: 100%; padding: 10px 0;
background: none; border: none; cursor: pointer; text-align: left;
color: var(--text);
}
.wf-row:hover .wf-row-label { color: var(--text); }
.wf-chevron {
font-size: 14px; color: var(--text3); width: 14px; flex-shrink: 0;
transition: transform 0.18s ease; display: inline-block;
transform-origin: center 45%;
}
.wf-chevron.open { transform: rotate(90deg); }
.wf-row-label { flex: 1; font-size: 13px; color: var(--text2); }
.wf-row-amt { font-size: 15px; font-weight: 600; white-space: nowrap; }
.wf-detail { padding: 4px 0 8px 22px; }
.wf-cat-section { }
.wf-cat-row {
display: flex; align-items: center; gap: 7px;
width: 100%; padding: 6px 0;
background: none; border: none; cursor: pointer; text-align: left;
color: var(--text2);
}
.wf-cat-chev {
font-size: 11px; color: var(--text3); width: 10px;
transition: transform 0.15s ease; display: inline-block;
transform-origin: center 45%;
}
.wf-cat-chev.open { transform: rotate(90deg); }
.cat-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.wf-txn-list { padding: 2px 0 6px 14px; }
.wf-txn-row {
display: flex; align-items: center; gap: 8px;
padding: 5px 0; border-bottom: 1px solid var(--border);
font-size: 12px;
}
.wf-txn-row:last-child { border-bottom: none; }
.wf-txn-date { color: var(--text3); white-space: nowrap; min-width: 44px; }
.wf-txn-desc { flex: 1; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wf-txn-amt { white-space: nowrap; font-weight: 500; }
</style>
<script>
function wfToggle(id) {
const detail = document.getElementById('wf-' + id);
const chev = document.getElementById('wf-chev-' + id);
const open = detail.style.display !== 'none';
detail.style.display = open ? 'none' : 'block';
if (chev) chev.classList.toggle('open', !open);
}
function wfToggleCat(id) {
const list = document.getElementById('wf-' + id);
const chev = document.getElementById('wf-cc-' + id);
const open = list.style.display !== 'none';
list.style.display = open ? 'none' : 'block';
if (chev) chev.classList.toggle('open', !open);
}
</script>
<!-- 3 diagnostic cards -->
<div class="grid" style="margin-bottom:16px;">
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "dashboard.cards.savings_rate"}}</h2>
<div class="value {{if gt $d.SavingsRatePct 0}}positive{{else}}negative{{end}}">{{$d.SavingsRatePct}}%</div>
{{if $d.LastMonthSavingsRatePct}}
<p style="font-size:12px; margin-top:6px; {{if gt $d.SavingsRatePct $d.LastMonthSavingsRatePct}}color:var(--green){{else}}color:var(--red){{end}};">
{{if gt $d.SavingsRatePct $d.LastMonthSavingsRatePct}}↑{{else}}↓{{end}} {{$d.T.Get "dashboard.alerts.vs_last_month_up"}} ({{$d.LastMonthSavingsRatePct}}%)
</p>
{{end}}
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "dashboard.cards.net_worth"}}</h2>
<div class="value animate-counter {{if lt $d.NetWorthCents 0}}negative{{else}}positive{{end}}"
data-target="{{$d.NetWorthCents}}" data-prefix="€">€0.00</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;"><a href="/networth" style="color:var(--accent);">{{$d.T.Get "dashboard.cards.net_worth_link"}}</a></p>
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "dashboard.cards.portfolio_today"}}</h2>
{{if $d.PortfolioHoldings}}
<div class="value animate-counter" data-target="{{$d.PortfolioValueCents}}" data-prefix="€" style="color:var(--text);">€0.00</div>
{{if $d.PortfolioPricesAvailable}}
<p style="font-size:12px; margin-top:6px; {{if ge $d.PortfolioPCLCents 0}}color:var(--green){{else}}color:var(--red){{end}};">
{{if ge $d.PortfolioPCLCents 0}}+{{else}}{{end}}€{{cents (centsAbs $d.PortfolioPCLCents)}} total P&L
</p>
{{else}}
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "dashboard.cards.portfolio_cost_basis"}}</p>
{{end}}
{{else}}
<div class="value" style="color:var(--text3); font-size:18px;">{{$d.T.Get "dashboard.cards.portfolio_no_trades"}}</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;"><a href="/import" style="color:var(--accent);">{{$d.T.Get "dashboard.cards.portfolio_import_link"}}</a></p>
{{end}}
</div>
</div>
<!-- Stocks + spending breakdown -->
<div class="grid-2" style="margin-bottom:16px;">
<div class="card animate-on-scroll">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "dashboard.stocks.section_title"}}</h2>
<a href="/portfolio" style="font-size:12px; color:var(--text3);">{{$d.T.Get "dashboard.stocks.portfolio_link"}}</a>
</div>
{{if $d.PortfolioHoldings}}
<div style="display:flex; flex-direction:column;">
{{range $d.PortfolioHoldings}}
<div style="display:flex; align-items:center; justify-content:space-between; padding:9px 0; border-bottom:1px solid var(--border);">
<div>
<div style="font-size:13px; font-weight:600; color:var(--text);">{{.Name}}</div>
<div style="font-size:11px; color:var(--text3);">{{printf "%.4f" .SharesOwned}} {{$d.T.Get "dashboard.stocks.shares_label"}}</div>
</div>
<div style="text-align:right;">
{{if $d.PortfolioPricesAvailable}}
<div style="font-size:13px; font-weight:500; color:var(--text);">€{{cents .CurrentValueCents}}</div>
<div style="font-size:12px; {{if ge .UnrealizedPCLCents 0}}color:var(--green){{else}}color:var(--red){{end}};">
{{pctSign .UnrealizedPCLPct}}{{printf "%.1f" .UnrealizedPCLPct}}%
</div>
{{else}}
<div style="font-size:13px; font-weight:500; color:var(--text);">€{{cents .TotalCostCents}}</div>
<div style="font-size:11px; color:var(--text3);">{{$d.T.Get "dashboard.stocks.cost_basis"}}</div>
{{end}}
</div>
</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);">{{$d.T.Get "dashboard.stocks.total_label"}}{{if not $d.PortfolioPricesAvailable}} {{$d.T.Get "dashboard.stocks.total_invested"}}{{end}}</span>
<span class="animate-counter" style="font-size:15px; font-weight:600; color:var(--text);"
data-target="{{$d.PortfolioValueCents}}" data-prefix="€">€0.00</span>
</div>
</div>
{{else}}
<div class="empty-state" style="padding:24px;">
<p>{{$d.T.Get "dashboard.stocks.no_holdings_msg"}}<br><a href="/import" style="color:var(--accent);">{{$d.T.Get "dashboard.stocks.import_link"}}</a></p>
</div>
{{end}}
</div>
</div>
<!-- Budget health + recent activity -->
<div class="grid-2">
{{if $d.CategoryBudgets}}
<div class="card animate-on-scroll">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "dashboard.budget_health.section_title"}}</h2>
<a href="/categories" style="font-size:12px; color:var(--text3);">{{$d.T.Get "dashboard.budget_health.categories_link"}}</a>
</div>
<div style="display:flex; flex-direction:column; gap:10px;">
{{range $cat, $budget := $d.CategoryBudgets}}
{{$spent := index $d.ThisMonth.ByCategory $cat}}
{{$spentAbs := centsAbs $spent}}
{{$color := index $d.CategoryColors $cat}}
{{$over := isOver $spentAbs $budget}}
{{$pct := clampPct $spentAbs $budget}}
<div>
<div style="display:flex; justify-content:space-between; margin-bottom:5px;">
<span style="font-size:12px; color:var(--text2); display:flex; align-items:center; gap:6px;">
{{if $color}}<span style="width:7px;height:7px;border-radius:50%;background:{{$color}};display:inline-block;"></span>{{end}}
{{$cat}}
</span>
<span style="font-size:11px; {{if $over}}color:var(--red); font-weight:600;{{else}}color:var(--text3);{{end}}">
{{$pct}}%{{if $over}} ⚠{{end}}
</span>
</div>
<div style="background:var(--bg3); border-radius:99px; height:5px; overflow:hidden;">
<div style="height:100%; border-radius:99px; width:{{$pct}}%;
background:{{if $over}}var(--red){{else if $color}}{{$color}}{{else}}var(--accent){{end}};
transition:width 1s ease;"></div>
</div>
</div>
{{end}}
</div>
</div>
{{end}}
<div class="card animate-on-scroll">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "dashboard.recent.section_title"}}</h2>
<a href="/transactions" style="font-size:12px; color:var(--text3);">{{$d.T.Get "dashboard.recent.all_txns_link"}}</a>
</div>
{{if $d.RecentTxns}}
<div style="display:flex; flex-direction:column;">
{{range $d.RecentTxns}}
{{$color := index $d.CategoryColors .Category}}
<div style="display:flex; align-items:center; justify-content:space-between; padding:9px 0; border-bottom:1px solid var(--border);">
<div style="display:flex; align-items:center; gap:10px; min-width:0;">
<div style="width:8px; height:8px; border-radius:50%; flex-shrink:0;
background:{{if $color}}{{$color}}{{else}}var(--text3){{end}};"></div>
<div style="min-width:0;">
<div style="font-size:13px; color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:180px;">{{.Description}}</div>
<div style="font-size:11px; color:var(--text3);">{{dateShort .Date}}</div>
</div>
</div>
<div style="font-size:13px; font-weight:500; white-space:nowrap; margin-left:12px;
{{if lt .AmountCents 0}}color:var(--red){{else}}color:var(--green){{end}};">
{{if lt .AmountCents 0}}{{else}}+{{end}}€{{cents (centsAbs .AmountCents)}}
</div>
</div>
{{end}}
</div>
{{else}}
<div class="empty-state" style="padding:24px;">
{{$d.T.Get "dashboard.recent.no_txns_msg"}} <a href="/import" style="color:var(--accent);">{{$d.T.Get "dashboard.recent.import_link"}}</a>
</div>
{{end}}
</div>
</div>
{{if $d.DashGoals}}
<div class="card animate-on-scroll" style="margin-top:16px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "dashboard.goals.section_title"}}</h2>
<a href="/goals" style="font-size:12px; color:var(--text3);">{{$d.T.Get "dashboard.goals.all_goals_link"}}</a>
</div>
<div style="display:flex; flex-direction:column; gap:18px;">
{{range $d.DashGoals}}
{{$funded := index $d.GoalFundedThisMonth .ID}}
{{$needed := .MonthlyCents}}
{{$met := ge $funded $needed}}
{{$partial := and (gt $funded 0) (not $met)}}
{{$monthPct := 0}}
{{if gt $needed 0}}{{$monthPct = clampPct $funded $needed}}{{end}}
<div>
<!-- name + overall progress -->
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:5px;">
<div style="display:flex; align-items:center; gap:8px; min-width:0;">
<span style="font-size:14px; flex-shrink:0;">{{if eq .Type "once"}}🎯{{else if eq .Type "deposit"}}🏠{{else if eq .Type "emergency"}}🛡️{{else}}📈{{end}}</span>
<span style="font-size:13px; font-weight:500; color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">{{.Name}}</span>
</div>
<div style="display:flex; align-items:center; gap:10px; flex-shrink:0; margin-left:12px;">
<span style="font-size:11px; color:var(--text3);">{{.MonthsLeft}}{{$d.T.Get "dashboard.goals.months_left"}}</span>
<span style="font-size:12px; font-weight:600; color:{{if .Feasible}}var(--green){{else}}var(--red){{end}};">{{.ProgressPct}}%</span>
</div>
</div>
<!-- overall progress bar -->
<div style="background:var(--bg3); border-radius:99px; height:4px; overflow:hidden; margin-bottom:3px;">
<div style="height:100%; border-radius:99px; width:{{.ProgressPct}}%;
background:{{if .Feasible}}var(--green){{else}}var(--accent){{end}};
transition:width 1s ease;"></div>
</div>
<div style="font-size:11px; color:var(--text3); margin-bottom:10px;">
€{{cents .SavedCents}} {{$d.T.Get "dashboard.goals.of"}} €{{cents .TargetCents}}
</div>
<!-- this-month funding status -->
{{if gt $needed 0}}
<div style="background:var(--bg2); border-radius:8px; padding:8px 12px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;">
<span style="font-size:11px; color:var(--text3); text-transform:uppercase; letter-spacing:.04em;">{{$d.T.Get "dashboard.goals.this_month"}}</span>
{{if $met}}
<span style="font-size:11px; font-weight:600; color:var(--green);">✓ {{$d.T.Get "dashboard.goals.funded"}}</span>
{{else if $partial}}
<a href="/transactions?fund_goal={{.ID}}" style="font-size:11px; color:var(--accent); text-decoration:none;">{{$d.T.Get "dashboard.goals.fund_link"}} →</a>
{{else}}
<a href="/transactions?fund_goal={{.ID}}" style="font-size:11px; color:var(--red); text-decoration:none; font-weight:500;">{{$d.T.Get "dashboard.goals.fund_link"}} →</a>
{{end}}
</div>
<div style="background:var(--bg3); border-radius:99px; height:3px; overflow:hidden; margin-bottom:5px;">
<div style="height:100%; border-radius:99px; width:{{$monthPct}}%;
background:{{if $met}}var(--green){{else if $partial}}var(--accent){{else}}var(--red){{end}};
transition:width 1s ease;"></div>
</div>
<div style="font-size:11px; {{if $met}}color:var(--green){{else if $partial}}color:var(--text2){{else}}color:var(--red){{end}};">
{{if $met}}
€{{cents $funded}} {{$d.T.Get "dashboard.goals.of"}} €{{cents $needed}} {{$d.T.Get "dashboard.goals.needed"}}
{{else if $partial}}
€{{cents $funded}} {{$d.T.Get "dashboard.goals.of"}} €{{cents $needed}} — €{{cents (sub $needed $funded)}} {{$d.T.Get "dashboard.goals.left_to_fund"}}
{{else}}
€{{cents $needed}} {{$d.T.Get "dashboard.goals.needed_this_month"}}
{{end}}
</div>
</div>
{{end}}
</div>
{{end}}
</div>
</div>
{{end}}
{{end}}