feat(retirement): PR4d what-if slider rail

Each lever (retire age / target spend / save per mo / real return) now
pairs a numeric input with a range slider; retirement_what_if_controller
mirrors the value across the pair (data-lever) and debounces the live
forecast preview. Birth year stays a plain numeric input.

(Skinned delete confirmations already render via Sure's global
Turbo.config.forms.confirm → DS::Dialog override, so the PR2 turbo_confirm
buttons are already styled — no change needed.)
This commit is contained in:
Guillem Arias
2026-05-29 12:38:24 +02:00
parent ec023cfe71
commit 01118b858f
2 changed files with 37 additions and 15 deletions

View File

@@ -7,6 +7,18 @@ export default class extends Controller {
static targets = ["form"]
static values = { url: String, debounce: { type: Number, default: 300 } }
// Mirror a lever's value across its paired number + range inputs (matched
// by data-lever), then debounce a preview.
sync(event) {
const lever = event.target.dataset.lever
if (lever) {
this.element.querySelectorAll(`[data-lever="${lever}"]`).forEach((el) => {
if (el !== event.target) el.value = event.target.value
})
}
this.preview()
}
preview() {
clearTimeout(this.timer)
this.timer = setTimeout(() => this.fetchPreview(), this.debounceValue)

View File

@@ -48,21 +48,31 @@
</div>
<%= form_with url: retirement_path, method: :patch, data: { retirement_what_if_target: "form" }, class: "space-y-3" do |form| %>
<div class="grid grid-cols-2 sm:grid-cols-5 gap-3">
<% [
[ "birth_year", @plan.birth_year ],
[ "retire_age", @plan.retire_age ],
[ "target_spend", @plan.target_spend ],
[ "monthly_savings", @plan.monthly_savings ],
[ "real_return_pct", @plan.real_return_pct ]
].each do |field, value| %>
<label class="text-xs text-secondary space-y-1">
<span class="block"><%= t("retirement.what_if.fields.#{field}") %></span>
<%= number_field_tag "retirement[#{field}]", value, step: "any",
autocomplete: "off",
class: "form-field__input w-full",
data: { action: "input->retirement-what-if#preview" } %>
</label>
<% levers = {
"retire_age" => { min: 40, max: 75, step: 1 },
"target_spend" => { min: 0, max: 20_000, step: 50 },
"monthly_savings" => { min: 0, max: 20_000, step: 50 },
"real_return_pct" => { min: 0, max: 10, step: 0.1 }
} %>
<div class="grid grid-cols-1 sm:grid-cols-5 gap-4">
<label class="text-xs text-secondary space-y-1">
<span class="block"><%= t("retirement.what_if.fields.birth_year") %></span>
<%= number_field_tag "retirement[birth_year]", @plan.birth_year, step: 1,
autocomplete: "off", class: "form-field__input w-full",
data: { action: "input->retirement-what-if#preview" } %>
</label>
<% levers.each do |field, cfg| %>
<% value = @plan.public_send(field) %>
<div class="space-y-1.5">
<label class="text-xs text-secondary block" for="rwi_<%= field %>"><%= t("retirement.what_if.fields.#{field}") %></label>
<%= number_field_tag "retirement[#{field}]", value, step: cfg[:step], id: "rwi_#{field}",
autocomplete: "off", class: "form-field__input w-full",
data: { lever: field, action: "input->retirement-what-if#sync" } %>
<%= range_field_tag "rwi_#{field}_slider", value, min: cfg[:min], max: cfg[:max], step: cfg[:step],
class: "w-full accent-green-600 cursor-pointer",
data: { lever: field, action: "input->retirement-what-if#sync" } %>
</div>
<% end %>
</div>
<%= form.submit t("retirement.what_if.save"), class: "text-sm font-medium text-primary underline cursor-pointer" %>