30 Commits

Author SHA1 Message Date
Gonçalo Rodrigues
1fce3b36aa feat: add checkable goals list to org events (#24)
Goals are stored as EventGoal items embedded in the event document.
During active fiscal years, members can check/uncheck goals inline.
Goals can be added and deleted while the event is not yet approved.

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:15:55 +01:00
Gonçalo Rodrigues
26a7236494 style: global dark form inputs + team emoji avatars (#23)
Form inputs:
- Add a catch-all CSS rule targeting all text-type inputs, selects, and
  textareas not already covered by .form-group or .form-input — sets
  background:--bg2, color:--text, focus ring. Fixes white-box appearance
  in transactions, goals, settings, portfolio, import, people, and tax
  templates without touching any HTML.

Team avatars:
- OrgTeam.Avatar string (emoji) — persisted in MongoDB, defaults to 👥
- OrgTeamCreate handler reads "avatar" form field
- org_teams.html: emoji picker (30 options) in new-team modal; preview
  updates live; selected emoji highlighted with accent border
- avatarEmojis() and teamAvatar() template functions registered in FuncMap
- Team badges in org_members, org_events, org_event_detail, org_report
  all show the emoji inline with the team name

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:08:29 +01:00
Gonçalo Rodrigues
ceeee2a46a style: add .form-input/.form-label CSS classes for dark-theme form fields (#22)
Org templates use class="form-input" and class="form-label" which had no
CSS definition — browsers fell back to white-box defaults, clashing with
the dark teal theme.

- .form-input: dark bg (--bg2), teal border, accent focus ring + glow,
  placeholder dimming, file input button styling
- .form-label: uppercase micro-label style (11px, --text3) matching the
  existing card section headers throughout the app
- textarea.form-input: resize:vertical, min-height, line-height
- select option / .form-input option: dark background on dropdown items
- Keep .form-group label unchanged so existing non-org templates are unaffected

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:03:45 +01:00
Gonçalo Rodrigues
07e3525dae fix(finance): org polish — broken links, upload handler, populated selects (#21)
- org_home.html: fix Requests/Ledger links (were pointing to non-existent
  /years/{id}/requests routes; corrected to /orgs/{slug}/requests and
  /orgs/{slug}/ledger). Add Analysis link. Add Close year button for admins.
- OrgRequestNew: pass events + teams to GET template so dropdowns are
  populated (were silently discarded with _ = events).
- OrgRequestDetailData: add NewEvents/NewTeams fields for new-request form.
- OrgRequestUpload: implement file upload handler — saves to
  /data/org-files/{org_id}/{req_id}/{id} and records metadata in MongoDB.
  Register POST /orgs/{slug}/requests/{req_id}/upload route.
- org_request_detail.html: show upload form in attachments section;
  populate event/team selects on new request form.

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:00:23 +01:00
Gonçalo Rodrigues
0f58a51c6d feat(finance): org Phases 2-5 — events, requests, ledger, analysis, report (#20)
Phase 2 — Event planning:
- Create/edit/delete/submit events per fiscal year
- Budget lines (income + expense) with planned amounts
- Admin review flow: approve / reject / comment
- Post-mortem feedback comments on closed years

Phase 3 — Transaction requests:
- 5 types: reimbursement, purchase_order, cash_advance, income, budget_transfer
- Full status machine with append-only StatusLog audit trail
- PO delivery sub-form (actual vendor, amount, invoice note)
- Cash advance settlement sub-form (spent + returned)
- Ledger view per fiscal year with income/expense summary
- Bank CSV import: parse → preview → confirm → ledger entries

Phase 4 — Plan vs actual analysis:
- Variance table by event (planned vs actual income/expense)
- Variance table by team

Phase 5 — Year-end report:
- Executive summary with net variance
- Per-event section: budget lines + team feedback comments
- Feedback form available on closed fiscal years

Supporting changes:
- New store methods: getLedgerEntries, createLedgerEntry, updateLedgerEntry,
  getAttachments, createAttachment
- New template funcs: dateInput, statusColor, varColor
- parseEuroAmount + parseBankCSV helpers in handler_org.go

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:58:47 +01:00
Gonçalo Rodrigues
6ed848a001 feat(finance): org management scaffold — Phase 1
Adds multi-tenant organisation support inside the existing finance namespace.
Users can create organisations, invite others via a copy-paste token link,
and manage teams/members with RBAC (admin, finance, member, viewer).
Fiscal year lifecycle is gated: activation requires all planned events to
be approved first. All org data lives in `org_`-prefixed MongoDB collections.

New files:
- models_org.go      — domain types (Org, OrgTeam, OrgMember, OrgInvite,
                       FiscalYear, OrgEvent, BudgetLine, EventComment,
                       TxRequest with full StatusLog audit trail, etc.)
- store_org.go       — MongoDB store methods for all org collections
- handler_org.go     — HTTP handlers + RegisterOrgRoutes(); join invite
                       route lives at /join/{token} to avoid ServeMux
                       conflict with /orgs/{slug}/... wildcard routes
- templates/org_*.html — list, create, home, teams, members, invite, join

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:43:48 +01:00
Gonçalo Rodrigues
a66941160a fix: portfolio value formula, Yahoo UA header, mobile nav hamburger
- Remove erroneous /100 in currentValue calculation (portfolio values
  were 100x too small, causing net worth card to show ~€0)
- Add User-Agent header to Yahoo Finance requests (avoids 429s)
- Wrap ES module chart body in if(total>0){} block (return at top
  level of a module is a SyntaxError — chart was silently broken)
- Add mobile hamburger menu: full-screen drawer at ≤720px with
  animated open/close, all nav links, scroll lock while open

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:33:12 +01:00
Gonçalo Rodrigues
09984272cd style: dark teal/cyan theme across the whole app
Replace indigo accent palette with deep black backgrounds and
cyan-teal accents (#00c9b8 dark, #00897b light). Borders, glows,
text muted tones and background gradients all updated to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:12:58 +01:00
Gonçalo Rodrigues
5c6752b4a3 style: blue-green palette for portfolio allocation chart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:09:02 +01:00
Gonçalo Rodrigues
6712c36081 feat: user-editable ISIN→ticker mappings for unrecognised holdings
When a holding has no price (ISIN not in the built-in map and Yahoo
rejects the raw ISIN), the portfolio page now shows an amber banner
listing each missing ISIN with an inline text input and a "Look up"
link to Yahoo Finance symbol search.

Submitting the form POSTs to /portfolio/ticker which upserts the mapping
into a finance_ticker_mappings collection keyed by (user_id, ISIN).
On the next page load custom mappings are resolved first, before the
hardcoded isinToTicker table, so user overrides always win.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:05:36 +01:00
Gonçalo Rodrigues
a2d3b60500 fix: allocation chart — illegal top-level return in ES module
`return` at the top level of a <script type="module"> is a SyntaxError
in strict mode, so the entire Three.js chart script was killed before
executing regardless of whether prices were available.

Replaced `if (total <= 0) return` with `if (total > 0) { ... }` wrapping
the full chart body. Also filter out holdings with value=0 before building
the arc geometry so a single un-priced ISIN can't produce a zero-arc slice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:55:38 +01:00
Gonçalo Rodrigues
437fb5a2df refactor: consolidate nav from 15 items to 7
- Merge Sharing + Household → /people (tab switcher: sharing | household)
- Merge Accounts + Categories → /settings (tab switcher: accounts | categories)
- Add Analysis dropdown in nav: Reports, Projections, Tax, Net Worth, What If
- Add Settings dropdown in nav: Accounts & Categories, Import CSV, Import Guide
- Legacy GET /sharing, /household, /accounts, /categories redirect 301 to new URLs
- Remove Import and Import Guide as standalone nav links
- New People handler consolidates all people-related mutations (_action field)
- New Settings handler renders both account and category lists in one page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:33:15 +01:00
Gonçalo Rodrigues
7f7e696b4d refactor: retire Auto Import schedules, add Import Guide + duplicate notice
- Auto Import page replaced with a static Import Guide: step-by-step
  instructions for CGD/Trade Republic/generic CSV export, duplicate
  detection explanation, and a curl example for headless automation
- Removed POST /auto-import and DELETE /auto-import/{id} routes and
  their handler logic; schedule CRUD was misleading since no bank
  exposes an unauthenticated CSV endpoint
- Nav label changed from "Auto Import" to "Import Guide"
- Transactions page now renders an amber banner when redirected with
  ?notice=all_duplicates (every row in the uploaded file was skipped)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:20:48 +01:00
Gonçalo Rodrigues
2170457528 feat: duplicate detection on CSV import
Compute a sha256 fingerprint (date|description|amount|account_id, first
16 hex chars) for every CSV row and store it as bank_ref. At preview
time, existing fingerprints are fetched and matching rows are shown
greyed out with a "duplicate" label. At confirm time, those rows are
silently skipped — only truly new transactions are inserted.

If every row is a duplicate the user is redirected with ?notice=all_duplicates
instead of inserting an empty batch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:15:23 +01:00
Gonçalo Rodrigues
1c2bac1d5f feat: implement Tax Summary, Household Mode, and Auto Import
- Tax Summary (/tax): annual gross income from Income transactions,
  FIFO capital gains/losses from trades, expenses by category, CSV export
- Household Mode (/household): link a partner account, combined
  income/expense/disposable view for current month, shared goals list
- Auto Import (/auto-import): schedule recurring CSV imports with
  account, format and optional source URL; delete schedules; webhook docs
- New store methods for households and import_schedules collections
- storeIface extended; mockStore updated; all tests still pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:28:22 +01:00
Gonçalo Rodrigues
995c6d89d6 feat: phase 6 — dashboard alerts and nudges
Adds server-computed alert banners to the dashboard:
- Budget exceeded (red): when a category spend is over 100% of budget
- Budget pace warning (amber): 80%+ of budget used but <80% of month elapsed
- Goal deadline risk (amber): avg savings rate too low to hit a goal on time
- Spend pace warning (amber): disposable spending is 20%+ ahead of month progress

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:03:12 +01:00
Gonçalo Rodrigues
39282ff550 feat: phase 5 — what-if simulator + savings rate history
Adds /simulator page with live sliders for income change, one-off
expenses, and fixed cost shifts. Goal timelines update in real time
showing months gained/lost. Includes a savings rate bar chart from
historical monthly data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:57:14 +01:00
Gonçalo Rodrigues
42f5b0df4d feat: phase 4 — net worth page + dashboard card
Adds /networth page with hero total, cash/portfolio breakdown cards,
and a month-by-month historical chart. Also shows net worth as a
value card on the dashboard with a link to the full breakdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:52:57 +01:00
Gonçalo Rodrigues
bfd5f62a7a 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>
2026-06-13 16:46:55 +01:00
Gonçalo Rodrigues
2324f62721 feat: add fixed costs panel to dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:41:48 +01:00
Gonçalo Rodrigues
3b041267ad feat: phase 3 — goals commit + plan
- Commit/uncommit button on each goal card
- Committed goals are deducted from disposable income on the dashboard
  (Available to spend now reflects reserved goal contributions)
- Conflict detection: warning banner when committed goals exceed
  disposable income, showing the shortfall
- Goals summary bar: disposable before goals, reserved per month,
  free to spend after committed goals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:37:45 +01:00
Gonçalo Rodrigues
99be71be8a fix: replace month input with month/year dropdowns in goal modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:28:58 +01:00
Gonçalo Rodrigues
be0c2bd89e feat: phase 2 — goals explore mode
Adds a Goals page where users can plan financial goals before committing
to them. Each goal shows:

- Required monthly contribution to hit the deadline
- Months remaining vs months at current savings rate
- Disposable income impact (what's left after the contribution)
- Feasibility banner (green if on track, red with month delta if not)
- Progress bar once savings are tracked

Goal types: one-off purchase, deposit/down-payment, emergency fund,
recurring investment — each with a description hint in the creation modal.

Data: Goal model + store CRUD in finance_goals collection.
Nav: Goals tab added between Portfolio and Sharing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:20:46 +01:00
Gonçalo Rodrigues
5412dda2ac fix: dashboard investment display and budget health
- Exclude FixedCategories (Housing, Utilities, Subscriptions, Investments)
  from budget health panel — they are committed costs, not variable spend
- Portfolio card and stocks panel now degrade gracefully to cost basis
  when Yahoo Finance prices are unavailable, instead of showing €0
- Stocks panel shows "cost basis" label per holding when live prices
  are not available

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:14:44 +01:00
Gonçalo Rodrigues
1d3aa764cb feat: phase 1 dashboard redesign — disposable income + smart panels
Replaces the old KPI/chart dashboard with a focused layout that
answers the three key questions immediately:

- Hero block: "Available to spend" = income − fixed costs − spent so far,
  with a progress bar showing % of disposable used vs month elapsed
- Bank math panel: detects recurring fixed expenses (Housing, Utilities,
  Subscriptions, Investments) from last 3 months and shows the minimum
  bank balance needed right now including a 2-week safety buffer
- Savings rate card with month-over-month delta
- Portfolio snapshot card with total value and P&L
- Stocks at a glance panel: per-holding value and P&L inline
- Budget health: thin bars per category, red when over limit
- Recent activity: last 5 transactions with category color dots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 15:51:46 +01:00
Gonçalo Rodrigues
2b98599bcf fix(finance): portfolio template float64 comparison error
ge/lt in Go templates cannot mix float64 and untyped int literals.
TotalPCLPct is float64, so replaced `ge $d.TotalPCLPct 0` with
`eq (pctSign $d.TotalPCLPct) "+"` which uses the existing pctSign
helper that already handles the sign check correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:50:31 +01:00
Gonçalo Rodrigues
c255d7f523 fix(finance): portfolio prices and projections template
Portfolio:
- fetchPrices was passing ISINs directly to Yahoo Finance, which only
  accepts ticker symbols (e.g. VWCE.DE, not IE00B3RBWM25). Added a
  hardcoded isinToTicker map covering common European ETFs (Vanguard,
  iShares, Xtrackers, Amundi). fetchPricesByISIN now resolves each ISIN
  to its ticker before calling Yahoo and keys the result by ISIN so
  computeHoldings can look up prices correctly. Renamed holdingsByISIN
  to uniqueISINs to reflect what it actually returns.

Projections:
- Template had a missing <tr> and a broken pace-bar width expression
  that mixed float64/int64 in the div template function, causing a
  template execution error that rendered the page blank. Moved all
  math server-side: the handler now pre-computes []catProjection with
  MonthlyAvg, AnnualTotal, and PacePct already calculated. Template
  is now simple range + printf. Chart switched to horizontal bar for
  better readability of long category names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:47:03 +01:00
Gonçalo Rodrigues
7a2cb10c79 feat(finance): dark mode UI overhaul + admin seed data
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>
2026-06-13 12:16:23 +01:00
Gonçalo Rodrigues
452f97e6d9 feat(finance): improve UX across dashboard, transactions, reports and categories
- Fix sortStrings no-op bug that caused balance trend chart to display in random date order
- Fix reports table referencing wrong scope for row totals ($.Totals → .Totals per row)
- Add income vs expense split cards on dashboard alongside net figure
- Add budget progress bars on dashboard using existing category budget_cents data
- Color-coded category badges throughout using each category's configured color
- Replace window.prompt() category editor in transactions with inline dropdown
- Replace window.prompt() budget editor in categories with inline input field
- Add manual transaction entry modal (POST /api/transactions) so users don't need CSV for every entry
- Show account names instead of raw MongoDB IDs in the transaction list
- Add clampPct and isOver template helpers for budget bar rendering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 11:33:39 +01:00
Gonçalo Rodrigues
13b7149614 First Commit 2026-06-13 11:25:23 +01:00