ux(goals/show): catch-up consolidation + confirm dialogs on archive / mark complete

A — Catch-up math triple-encoding. The catch-up amount (e.g. $1,531/mo)
was rendered verbatim in three places: the banner title, the projection
card subtitle, and the pace stat ("target $X/mo"). Only new information
anywhere was the buried "Behind by $Y/mo" delta in pace-card subtext.

- Banner body now carries the delta: "...currently $Y/mo behind."
- Projection sentence drops the "Bump to %{required}/mo" restatement;
  reduces to "At %{current}/mo you'll miss your target date." Chart
  aria-description benefits from the simpler phrasing too.
- Pace stat drops the "· target $X/mo" sub-line. Pair becomes
  "$avg/mo" + "Behind by $delta/mo" — same delta now in the banner,
  surfaced twice intentionally (alert vs at-a-glance stat).

K — Destructive transition confirms. Pause / Resume stay no-confirm
(recoverable). Mark complete (irreversible via UI; no may_uncomplete?
event exists) and Archive (goal disappears from active list) now wear
CustomConfirm. New locale keys: goals.show.confirm_{complete,archive}_
{title,body,cta}.

Locale catch_up body strings now interpolate %{delta} alongside %{date};
projection.behind drops %{required}. Controller#projection_summary still
passes both keys — extras are ignored by I18n.
This commit is contained in:
Guillem Arias
2026-05-11 20:22:45 +02:00
parent 8fc26b0666
commit 4be2ca2eeb
2 changed files with 37 additions and 10 deletions

View File

@@ -57,10 +57,32 @@
<% menu.with_item(variant: "button", text: t(".resume"), icon: "play", href: resume_goal_path(@goal), method: :patch) %>
<% end %>
<% if @goal.may_complete? %>
<% menu.with_item(variant: "button", text: t(".complete"), icon: "circle-check-big", href: complete_goal_path(@goal), method: :patch) %>
<% menu.with_item(
variant: "button",
text: t(".complete"),
icon: "circle-check-big",
href: complete_goal_path(@goal),
method: :patch,
confirm: CustomConfirm.new(
title: t(".confirm_complete_title"),
body: t(".confirm_complete_body"),
btn_text: t(".confirm_complete_cta")
)
) %>
<% end %>
<% if @goal.may_archive? %>
<% menu.with_item(variant: "button", text: t(".archive"), icon: "archive", href: archive_goal_path(@goal), method: :patch) %>
<% menu.with_item(
variant: "button",
text: t(".archive"),
icon: "archive",
href: archive_goal_path(@goal),
method: :patch,
confirm: CustomConfirm.new(
title: t(".confirm_archive_title"),
body: t(".confirm_archive_body"),
btn_text: t(".confirm_archive_cta")
)
) %>
<% end %>
<% if @goal.may_unarchive? %>
<% menu.with_item(variant: "button", text: t(".unarchive"), icon: "archive-restore", href: unarchive_goal_path(@goal), method: :patch) %>
@@ -113,12 +135,14 @@
<% elsif @goal.status == :behind && @goal.monthly_target_amount %>
<%# Catch-up callout %>
<% catch_up_money = Money.new(@goal.monthly_target_amount, @goal.currency) %>
<% catch_up_delta = @goal.monthly_target_amount.to_d - @stats[:avg_monthly].to_d %>
<% catch_up_delta_money = Money.new(catch_up_delta, @goal.currency) %>
<%= render DS::Alert.new(variant: "warning", title: t("goals.show.catch_up.title", amount: catch_up_money.format)) do %>
<p class="text-secondary">
<% if @goal.target_date %>
<%= t("goals.show.catch_up.body_with_date", date: I18n.l(@goal.target_date, format: :long)) %>
<%= t("goals.show.catch_up.body_with_date", date: I18n.l(@goal.target_date, format: :long), delta: catch_up_delta_money.format) %>
<% else %>
<%= t("goals.show.catch_up.body") %>
<%= t("goals.show.catch_up.body", delta: catch_up_delta_money.format) %>
<% end %>
</p>
<div class="mt-2">
@@ -240,9 +264,6 @@
<div class="flex items-baseline gap-2">
<p class="text-2xl font-medium text-primary tabular-nums"><%= Money.new(@stats[:avg_monthly], @goal.currency).format %></p>
<p class="text-sm text-subdued tabular-nums">/mo</p>
<% if @goal.monthly_target_amount && @goal.monthly_target_amount.to_d.positive? %>
<p class="text-sm text-subdued tabular-nums">· <%= t(".stats.target_of", amount: Money.new(@goal.monthly_target_amount, @goal.currency).format) %></p>
<% end %>
</div>
<% if @goal.monthly_target_amount && @goal.monthly_target_amount.to_d.positive? %>
<% delta = @goal.monthly_target_amount.to_d - @stats[:avg_monthly].to_d %>