feat(savings_goals/show): combo monthly-pace stat card

D7 — Merge the separate Avg-monthly and Target-pace cards into one
wider "Monthly pace" card spanning 2/3 of the stat row. Shows actual
$/mo + target $/mo inline, with a delta line below:
- behind → "Behind by $X/mo" (text-warning)
- on/ahead → "Above target pace" (text-success)
- no target_date → "No required pace"

Total contributions stays as a separate, smaller card at 1/3 width.
The action pyramid finally points at the actionable stat — pace is
visually primary, raw count secondary.
This commit is contained in:
Guillem Arias
2026-05-11 16:06:29 +02:00
parent bb81bc895c
commit 7954a01ed1
2 changed files with 26 additions and 12 deletions

View File

@@ -202,25 +202,36 @@
<% end %>
</section>
<%# Stat row %>
<%# Stat row — combo pace card + contributions count %>
<section class="grid grid-cols-1 md:grid-cols-3 gap-3">
<div class="bg-container rounded-xl shadow-border-xs px-5 py-4">
<p class="text-[11px] text-secondary mb-1"><%= t(".stats.avg_monthly") %></p>
<p class="text-lg font-medium text-primary tabular-nums"><%= Money.new(@stats[:avg_monthly], @savings_goal.currency).format %></p>
<p class="text-[11px] text-subdued mt-1"><%= @stats[:avg_monthly_sub] %></p>
<%# Combo: Avg vs Target pace %>
<div class="md:col-span-2 bg-container rounded-xl shadow-border-xs px-5 py-4">
<p class="text-[11px] text-secondary mb-2"><%= t(".stats.monthly_pace") %></p>
<div class="flex items-baseline gap-2">
<p class="text-2xl font-medium text-primary tabular-nums"><%= Money.new(@stats[:avg_monthly], @savings_goal.currency).format %></p>
<p class="text-sm text-subdued tabular-nums">/mo</p>
<% if @savings_goal.monthly_target_amount %>
<p class="text-sm text-subdued tabular-nums">· <%= t(".stats.target_of", amount: Money.new(@savings_goal.monthly_target_amount, @savings_goal.currency).format) %></p>
<% end %>
</div>
<% if @savings_goal.monthly_target_amount %>
<% delta = @savings_goal.monthly_target_amount.to_d - @stats[:avg_monthly].to_d %>
<% if delta.positive? %>
<p class="text-xs text-warning mt-1 tabular-nums"><%= t(".stats.behind_by", amount: Money.new(delta, @savings_goal.currency).format) %></p>
<% else %>
<p class="text-xs text-success mt-1 tabular-nums"><%= t(".stats.above_target_pace") %></p>
<% end %>
<% else %>
<p class="text-xs text-subdued mt-1"><%= t(".stats.no_required_pace") %></p>
<% end %>
</div>
<%# Total contributions %>
<div class="bg-container rounded-xl shadow-border-xs px-5 py-4">
<p class="text-[11px] text-secondary mb-1"><%= t(".stats.total_contributions") %></p>
<p class="text-lg font-medium text-primary tabular-nums"><%= @stats[:contributions_count] %></p>
<p class="text-[11px] text-subdued mt-1"><%= t(".stats.across_all_accounts") %></p>
</div>
<div class="bg-container rounded-xl shadow-border-xs px-5 py-4">
<p class="text-[11px] text-secondary mb-1"><%= t(".stats.target_pace") %></p>
<p class="text-lg font-medium text-primary tabular-nums">
<%= @savings_goal.monthly_target_amount ? Money.new(@savings_goal.monthly_target_amount, @savings_goal.currency).format + "/mo" : t(".stats.no_required_pace") %>
</p>
<p class="text-[11px] text-subdued mt-1"><%= @stats[:monthly_target_sub] %></p>
</div>
</section>
<%# Bottom row: contributions + funding accounts %>

View File

@@ -148,6 +148,9 @@ en:
no_required_pace: No required pace
needs_per_month: "Needs %{amount}/mo"
above_target_pace: Above target pace
monthly_pace: Monthly pace
target_of: "target %{amount}/mo"
behind_by: "Behind by %{amount}/mo"
states:
active: Active
paused: Paused