Commit Graph

8 Commits

Author SHA1 Message Date
Guillem Arias
fe9e2dccbe feat(savings): add "ONGOING · N" + "COMPLETED · N" section dividers
Same pattern as the bank-providers page's `AVAILABLE · 3` header (see
`app/views/settings/providers/_search_filters.html.erb` references):
small uppercase tracking-wide secondary label, separator dot, tabular
count. Replaces the prior "Completed · 1" inline label with a more
consistent treatment and adds an "Ongoing · N" header above the active
goals grid.

Name choice: "Ongoing" rather than "Active" because the grid includes
both `active` and `paused` AASM states; "ongoing" reads as still-in-
progress for both. Parallel to the existing "Completed" sibling.
2026-05-11 12:35:25 +02:00
Guillem Arias
f70e908043 fix(savings): add bottom padding so last card clears the mobile fixed bottom-nav 2026-05-11 12:34:43 +02:00
Guillem Arias
9b70f0385c fix(savings): refine hero spacing, goal/account card padding, sparkline negative range
- Sparkline (`savings-sparkline` controller): dropped the `Math.max(0,
  yMin)` clamp on the y-axis domain so negative balances (or any series
  that dips into negative territory) render fully instead of being
  cropped off the canvas.
- Hero card: padding `p-6` → `p-7`, column ratio
  `[minmax(0,1fr)_minmax(0,1.6fr)]` so the chart breathes, min height
  bumped to 220px, sparkline container `h-full min-h-[200px]` so it
  fills the card vertically. Stats row now sits at the bottom of the
  text column via `mt-auto pt-6`; labels promoted to `text-xs`, values
  to `text-lg`.
- Section vertical rhythm: outer `space-y-6` → `space-y-8`.
- Goal card: padding `p-[18px]` → `p-6`. Internal gap from header row
  to amount line `mt-3.5` → `mt-5`. Account-row gap `mt-3` → `mt-4`.
- Account card: padding `p-5` → `p-6`.
- Status pill "Behind" dot: `bg-yellow-500` → `bg-yellow-600` for a
  warmer/ambery tone matching the Claude Design reference.
- Goal card donut "behind" stroke: `var(--color-yellow-500)` →
  `var(--color-yellow-600)` to match the pill.
2026-05-11 12:28:34 +02:00
Guillem Arias
dad9cf70b6 feat(savings): rebuild index to match Claude Design
- Page header: title "Savings" + "Your savings accounts and the goals
  you're working toward." Removed the top-right New goal button (moves
  into the Goals section).
- Hero card: "Total in savings" with sum-of-savings-subtype balance,
  30-day delta vs last 30 days (Family#savings_balance_30d_delta),
  3-stat sub-row (Accounts / Active goals / Saved toward goals), and a
  D3 sparkline area chart on the right (new
  `savings-sparkline` Stimulus controller, sourced from
  Family#savings_balance_series).
- Accounts section: lists Depository accounts with subtype = "savings"
  as cards (blue avatar, name, subtype, balance, "Funds N goals"). New
  Savings::AccountCardComponent.
- Goals section header: "Goals" + "Save toward what matters." + "New
  goal" button right-aligned to the section (not the page header).
- Removed state-filter pill nav. Active goals render in the main grid;
  Completed goals get a "Completed · N" divider w/ check-circle icon
  and their own grid below.
- Goal card layout reworked: horizontal bar replaced with a 64px donut
  ring on the right side of the card header (ring colour tracks
  goal.status — yellow=behind, primary=on-track, green=reached). Pill
  is inline with the goal name.
- Status pill copy: "Behind pace" → "Behind".
- Filter bar (copied from settings/providers): search input + status
  chips (All / On track / Behind / No date). Hidden when ≤ 6 active
  goals. Powered by `savings-goals-filter` Stimulus controller —
  toggles `.hidden` on cards by goal name + status.
- Family#savings_subtype_accounts, total_savings_balance,
  savings_balance_series, savings_balance_30d_delta helpers; controller
  computes hero payload + account-goal counts for the cards.
2026-05-11 12:18:57 +02:00
Guillem Arias
696fbc0b43 feat(savings): match Claude Design — projection chart, target-icon modal, grouped funding accounts
Brings the savings goals UI closer to the Claude Design reference shared
by the user. Changes:

- Sidebar nav label: "Savings goals" → "Savings".
- Status pill copy: "Behind" → "Behind pace" (matches Pill component
  from GoalsCommon.jsx).
- Empty state rewritten with a large target icon, "No goals yet"
  heading, and the descriptive body copy from the design.

Goal detail page (matches GoalDetail.jsx):
- New "← All goals" back link above the header.
- 2-column hero: ring card on the left (320px column), Projection card
  on the right.
- Projection card uses a new D3 Stimulus controller
  (`savings-goal-projection-chart`) that draws:
    · saved area + line from goal creation → today (solid, primary)
    · dashed projection segment from today → target date (yellow when
      behind, green when on track)
    · horizontal dashed target line with label
    · today marker (vertical dashed line + dot)
  Data shape comes from `SavingsGoal#projection_payload`.
- Card subtitle generates a contextual sentence ("At $X/mo you'll fall
  short. Bump to $Y/mo to hit it on time." / "At your current pace
  you'll reach this goal around Month YYYY." / "Goal reached. Nice
  work.") with a strong tag highlighting the actionable figure.
- Stat row now shows Linked balance (sum across linked accounts) +
  "N accounts" sub-caption instead of duplicate "Target date" stat.

New goal modal (matches the design images 2 + 3):
- DS::Dialog custom header: DS::FilledIcon target glyph + title + step
  subtitle ("Step 1 of 2 · Goal details" / "Step 2 of 2 · Review &
  start") that updates as the user advances.
- Connected stepper at top of body: numbered circles connected by a
  bar, step-1 circle flips to ✓ when complete.
- Step 1 heading "What are you saving for?" + supporting copy.
- Name field paired with a target glyph affordance on its left.
- Target amount + Target date in a 2-col grid.
- Funding accounts list now grouped by account subtype with uppercase
  section headers (CHECKING / SAVINGS / HSA / CD / MONEY MARKET /
  OTHER), each row showing avatar + name + subtype + balance.
- Step 2 heading "Looks good?" + Review card (goal target + funding
  accounts summary + suggested monthly = target/months_remaining), and
  a disclosure for the optional initial contribution.
- Footer: "Cancel" left text-button (closes modal) / "Back" left text
  when on step 2; "Continue →" or "Create goal →" right arrow button.

Demo generator: Depository accounts now set `subtype` ("checking" /
"savings") on the accountable so they group correctly in the modal.

Tests: all green, 35 runs in the savings suite, 92 assertions.
2026-05-11 12:08:47 +02:00
Guillem Arias
39743a9ec4 feat(savings): rebuild UI to match Claude Design + adopt shared donut-chart
Previous savings goals UI looked nothing like the Claude Design output
(see sure-design-context/design/savings-goals/project/goals/*.jsx) and
the hand-rolled ring did not match the segmented D3 donut used at
app/views/budgets/_budget_donut.html.erb. This rewires the surface end
to end.

Donut chart:
- SavingsGoal#to_donut_segments_json returns the same segment shape as
  Budget#to_donut_segments_json: filled portion in goal color, unused
  remainder as `var(--budget-unallocated-fill)`. Visual identity is now
  the same: segmented arc with cornerRadius and gap, courtesy of the
  shared `donut-chart` Stimulus controller and D3.
- ProgressRingComponent renders a `data-controller="donut-chart"` div
  with the same default-content/inner-text pattern as `_budget_donut`.

Index page (matches GoalsIndex.jsx):
- Page header: title + "Save toward what matters." subtitle + "New goal"
  primary CTA right-aligned.
- Summary strip card: total saved / target, overall bar, active goals,
  on-track ratio, behind count.
- State filter rendered as DS::Tabs-style pill nav (`bg-surface-inset
  p-1 rounded-lg`, white-pill active state).
- Cards rebuilt: avatar (44px, rounded-xl, white initial on goal color)
  + name + secondary line ("N days left · by date" / "No target date" /
  "Completed" / "Past due"), status pill with leading dot, big
  $current/$target line + percent, bar in status colour, AccountStack
  (overlapping initials) + "N accounts" + "to go".

Goal detail (matches GoalDetail.jsx):
- Header: 64px avatar + h1 name + status pill + "Target $X by date ·
  N days left" subline + Edit (outline) + Add contribution (primary) +
  kebab (DS::Menu for AASM transitions).
- Donut-chart ring card with stats overlay.
- 4-col stat row (Avg monthly, Total contributions, Target date,
  Started) with mono numerals and "Needs $X/mo" / "Above target pace"
  sub-captions where relevant.
- Two-col bottom: contributions list (avatar + account · date · source
  · green +$amount) and funding accounts breakdown (stacked bar +
  per-account row with $ and % of saved).

New components: Savings::AccountStackComponent (overlapping account
initials with ring-2 ring-container). StatusPillComponent now uses a
leading colored dot instead of an icon. GoalAvatarComponent radii
match Claude Design (rounded-md/lg/xl/2xl) and white initial.

Locale: new keys under savings_goals.{index.subtitle, index.summary.*,
goal_card.{accounts,days_left,completed,past_due,no_target_date},
show.header.*, show.ring.{of,to_go}, show.stats.*, show.funding_balance,
show.of_saved, show.notes}.
2026-05-11 11:52:35 +02:00
Guillem Arias
8a9f4b1a67 fix(savings): DS conformance pass on stepper, ring, card, status pill
- StatusPill: use functional `text-success` / `text-warning` tokens with
  matching icon colors and `px-2 py-1`, mirroring
  `app/views/budget_categories/_budget_category.html.erb:29-43`.
- ProgressRing: rework center text to match `_budget_donut.html.erb`
  (small "Saved" label, `text-3xl font-medium` headline, "of $X"
  underline). Stroke color now derives from goal.status (yellow when
  behind, blue on track, green reached, gray for no-date).
- GoalCard bar: track height + transition match budget category bar
  (`h-1.5`, `transition-all duration-500`, `inline-size`).
- Index/show layouts: render page header inline (`<h1>` + actions). The
  default application layout doesn't yield `:page_actions`, so the
  CTA + kebab menu wouldn't appear when emitted via `content_for`.
- Stepper review summary: target the actual form inputs by `name`
  rather than relying on the `data-target` Stimulus attribute, since
  `money_field` puts the attribute on the wrapper. Step 1 validation
  scoped to the step 1 panel.
- Demo generator: filter Depository accounts via
  `where(accountable_type: "Depository")` — Rails delegated_type
  generates the `depository?` predicate, not a `.depository` scope.
2026-05-11 11:39:13 +02:00
Guillem Arias
77660d2ee4 feat(savings): add savings goals
Adds a standalone Savings goals feature: a piggy-bank style tracker that
lets a family set a target, link one or more Depository accounts as
funding sources, and log manual contributions over time. Supersedes #1569
(closed) — same intent, redesigned per reviewer + Discord feedback.

What this adds:

- New `/savings_goals` sidebar entry (piggy-bank icon) with index, show,
  state-filtered tabs (all/active/paused/completed/archived), and a
  2-step modal stepper for creation (Identity → Review).
- Multi-account funding via a `SavingsGoalAccount` join: a goal requires
  ≥1 linked Depository account (checking/savings/HSA/CD/money-market),
  and all linked accounts must share the goal's currency.
- Tracker balance model: goal balance = SUM(contributions.amount). No
  auto-flow from account balances. Contributions are pure logical
  records and don't move money between accounts.
- Manual contributions modal scoped to the goal's linked accounts.
  Initial contributions seeded at creation can't be deleted; manual
  ones can.
- AASM lifecycle: active / paused / completed / archived.
  Hard-delete only after archive.
- Status pills (On track / Behind / Reached / No date) derived from
  pace vs target_date.
- AI Assistant tool `create_savings_goal` lets the sidebar chat create
  a goal end-to-end from a natural-language prompt; soft errors carry
  the available-accounts list back to the LLM (mirrors the existing
  `import_bank_statement` pattern).
- Family-scoped throughout (`Current.family`-only access, account
  family-scoping enforced both in controllers and the AI tool).
- Demo data seed wires up 4 sample goals across the Depository accounts.

Intentionally out of scope (separate PRs / v1.1):

- Auto-fund from budget surplus + Sidekiq cron + budget-show card.
- Dashboard "Savings goals" widget.
- "Behind pace" projection chart on the detail page.
- `evaluate_savings_goal_feasibility` LLM tool (level-setting before
  create_savings_goal).
- Spend-less goals inside Budgets.
- Family-member-private goals (deferred investigation).
2026-05-11 11:20:37 +02:00