mirror of
https://github.com/we-promise/sure.git
synced 2026-06-01 16:59:03 +00:00
Surfaces the forecast on the page and makes the levers live. - KPI cards (_kpis): Freedom date, Coast FIRE, Money-lasts-to + terminal value, with a "set your birth year" prompt until a plan is projectable. Wrapped in #retirement_kpis for Turbo Stream replacement; money carries privacy-sensitive. - What-if form: birth_year / retire_age / target_spend / monthly_savings / real_return_pct. On input, retirement_what_if_controller debounces and POSTs the current values to PATCH /retirement/forecast, which recomputes against transient inputs and streams the KPI cards back WITHOUT persisting. "Save plan" submits to #update to persist retirement_params. - RetirementController gains #update (persist) and #forecast (transient recompute → turbo_stream). Both reuse merged_plan_params, which drops blank fields so a partial what-if doesn't clobber stored values. Tests: KPI section renders; update persists params; forecast streams #retirement_kpis without writing the slider value back. Rubocop + erb_lint + biome clean. PR4 replaces this minimal form with the designed slider rail + glide chart; the #forecast endpoint and the engine stay.
33 lines
1.8 KiB
Plaintext
33 lines
1.8 KiB
Plaintext
<%# locals: (plan:) %>
|
|
<div id="retirement_kpis" class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<% forecast = plan.forecast %>
|
|
<% if forecast.nil? %>
|
|
<div class="sm:col-span-3 bg-container shadow-border-xs rounded-xl p-5">
|
|
<p class="text-primary text-sm font-medium"><%= t("retirement.kpis.set_birth_year_heading") %></p>
|
|
<p class="text-secondary text-sm"><%= t("retirement.kpis.set_birth_year_body") %></p>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-container shadow-border-xs rounded-xl p-5">
|
|
<p class="text-secondary text-xs uppercase tracking-wide"><%= t("retirement.kpis.freedom_date") %></p>
|
|
<p class="text-primary text-2xl font-semibold"><%= plan.freedom_date&.year || "—" %></p>
|
|
<p class="text-secondary text-xs"><%= t("retirement.kpis.retire_at_age", age: plan.effective_retire_age) %></p>
|
|
</div>
|
|
|
|
<div class="bg-container shadow-border-xs rounded-xl p-5">
|
|
<p class="text-secondary text-xs uppercase tracking-wide"><%= t("retirement.kpis.coast_fire") %></p>
|
|
<p class="text-primary text-2xl font-semibold"><%= plan.coast_fire_date&.year || t("retirement.kpis.not_yet") %></p>
|
|
<p class="text-secondary text-xs"><%= t("retirement.kpis.coast_hint") %></p>
|
|
</div>
|
|
|
|
<div class="bg-container shadow-border-xs rounded-xl p-5">
|
|
<p class="text-secondary text-xs uppercase tracking-wide"><%= t("retirement.kpis.money_lasts_to") %></p>
|
|
<p class="text-primary text-2xl font-semibold">
|
|
<%= forecast.lasts_past_terminal? ? t("retirement.kpis.past_age", age: forecast.terminal_age) : t("retirement.kpis.age", age: forecast.money_lasts_to_age) %>
|
|
</p>
|
|
<p class="text-secondary text-xs privacy-sensitive">
|
|
<%= t("retirement.kpis.terminal", amount: Money.new(forecast.terminal_value, plan.currency).format(precision: 0)) %>
|
|
</p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|