mirror of
https://github.com/we-promise/sure.git
synced 2026-06-01 08:49:01 +00:00
ux(goals/show): balance-sheet-style funding widget; drop redundant stat row
Lower half of the goal detail used to be: (stat row: monthly pace +
total contributions) + (bottom row: contributions list + funding
breakdown card). Two of those four pieces were redundant:
- Total Contributions stat duplicated the count badge that already
sits beside the Contributions heading below.
- Monthly Pace stat repeated the same numbers the catch-up alert
surfaces above and the chart subtitle reads.
Adopt the dashboard Balance Sheet pattern (app/views/pages/dashboard/_
balance_sheet.html.erb) for the funding widget: inline header with
total ("Funding accounts · $13,250"), thin gap-separated segment bar,
color-dot legend with percent, and a bg-container-inset table with the
shared `pages/dashboard/group_weight` 5-stick weight indicator + value
column.
New show.html.erb bottom: just two full-width sections — funding
widget, then chronological contributions list. Both rendered only when
the goal has contributions (matches the empty-state branch added
earlier).
Locale: goals.show.funding_table.{name, weight, value}.
This commit is contained in:
@@ -1,27 +1,61 @@
|
||||
<% if total.zero? %>
|
||||
<p class="text-sm text-secondary"><%= t("goals.show.no_contributions_yet") %></p>
|
||||
<% else %>
|
||||
<div class="flex h-2 rounded-full overflow-hidden mb-4">
|
||||
<% rows.each do |row| %>
|
||||
<% next if row[:amount].to_d.zero? %>
|
||||
<div style="width: <%= percent_for(row[:amount]) %>%; background-color: <%= Goals::AvatarComponent.color_for(row[:account].name) %>;"
|
||||
title="<%= row[:account].name %>"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h2 class="text-lg font-medium inline-flex items-center gap-1.5">
|
||||
<%= t("goals.show.funding_accounts_heading") %>
|
||||
<span class="text-secondary">·</span>
|
||||
<span class="text-secondary font-medium text-lg privacy-sensitive tabular-nums"><%= Money.new(total, goal.currency).format(precision: 0) %></span>
|
||||
</h2>
|
||||
|
||||
<ul class="space-y-3">
|
||||
<% rows.each do |row| %>
|
||||
<li class="flex items-center gap-3">
|
||||
<%= render Goals::AvatarComponent.new(name: row[:account].name, color: Goals::AvatarComponent.color_for(row[:account].name), size: "sm") %>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-primary truncate"><%= row[:account].name %></p>
|
||||
<p class="text-[11px] text-subdued"><%= row[:account].subtype&.titleize || row[:account].accountable_type %> · <%= t("goals.show.funding_balance", amount: Money.new(row[:account].balance, row[:account].currency).format) %></p>
|
||||
<div class="flex gap-1">
|
||||
<% rows.each do |row| %>
|
||||
<% next if row[:amount].to_d.zero? %>
|
||||
<div class="h-1.5 rounded-sm" style="width: <%= percent_for(row[:amount]) %>%; background-color: <%= Goals::AvatarComponent.color_for(row[:account].name) %>;"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<% rows.each do |row| %>
|
||||
<% next if row[:amount].to_d.zero? %>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<div class="h-2.5 w-2.5 rounded-full" style="background-color: <%= Goals::AvatarComponent.color_for(row[:account].name) %>;"></div>
|
||||
<p class="text-secondary"><%= row[:account].name %></p>
|
||||
<p class="text-primary font-mono privacy-sensitive tabular-nums"><%= percent_for(row[:amount]) %>%</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-sm font-medium text-primary tabular-nums"><%= row[:money].format %></p>
|
||||
<p class="text-[10px] text-subdued tabular-nums"><%= percent_for(row[:amount]) %>% <%= t("goals.show.of_saved") %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="bg-container-inset rounded-xl p-1 overflow-x-auto">
|
||||
<div class="px-4 py-2 flex items-center uppercase text-xs font-medium text-secondary">
|
||||
<div class="flex-1 min-w-0"><%= t("goals.show.funding_table.name") %></div>
|
||||
<div class="ml-auto text-right flex items-center gap-2">
|
||||
<div class="w-20 shrink-0"><p><%= t("goals.show.funding_table.weight") %></p></div>
|
||||
<div class="w-24 shrink-0"><p><%= t("goals.show.funding_table.value") %></p></div>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg bg-container font-medium text-sm">
|
||||
<% rows.each_with_index do |row, idx| %>
|
||||
<div class="p-4 flex items-center justify-between gap-3">
|
||||
<div class="flex-1 min-w-0 flex items-center gap-2">
|
||||
<%= render Goals::AvatarComponent.new(name: row[:account].name, color: Goals::AvatarComponent.color_for(row[:account].name), size: "sm") %>
|
||||
<p class="truncate"><%= row[:account].name %></p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-right gap-2 shrink-0">
|
||||
<div class="w-20 shrink-0 flex items-center justify-end gap-2">
|
||||
<%= render "pages/dashboard/group_weight", weight: percent_for(row[:amount]), color: Goals::AvatarComponent.color_for(row[:account].name) %>
|
||||
</div>
|
||||
<div class="w-24 shrink-0">
|
||||
<p class="privacy-sensitive tabular-nums"><%= row[:money].format(precision: 0) %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if idx < rows.size - 1 %>
|
||||
<%= render "shared/ruler", classes: "mx-4" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -269,44 +269,15 @@
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<%# Stat row — combo pace card + contributions count. Reached, paused,
|
||||
or archived goals hide the pace combo since the comparison is moot
|
||||
or misleading. %>
|
||||
<% goal_reached = @goal.completed? || @goal.status == :reached %>
|
||||
<% hide_pace = goal_reached || @goal.archived? || @goal.paused? %>
|
||||
<section class="grid grid-cols-1 <%= hide_pace ? "" : "md:grid-cols-3" %> gap-3">
|
||||
<% unless hide_pace %>
|
||||
<%# 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], @goal.currency).format %></p>
|
||||
<p class="text-sm text-subdued tabular-nums">/mo</p>
|
||||
</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 %>
|
||||
<% if delta.positive? %>
|
||||
<p class="text-xs text-subdued mt-1 tabular-nums"><%= t(".stats.behind_by", amount: Money.new(delta, @goal.currency).format) %></p>
|
||||
<% else %>
|
||||
<p class="text-xs text-subdued 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>
|
||||
<% end %>
|
||||
<% unless @contributions.empty? %>
|
||||
<%# Funding breakdown — balance-sheet-style widget (heading · total /
|
||||
thin bar / dot legend / weight table). %>
|
||||
<section class="bg-container rounded-xl shadow-border-xs p-5">
|
||||
<%= render Goals::FundingAccountsBreakdownComponent.new(goal: @goal, rows: @funding_breakdown) %>
|
||||
</section>
|
||||
|
||||
<%# 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>
|
||||
</section>
|
||||
|
||||
<%# Bottom row: contributions + funding accounts %>
|
||||
<section class="grid grid-cols-1 lg:grid-cols-[minmax(0,1.6fr)_minmax(0,1fr)] gap-3">
|
||||
<div class="bg-container rounded-xl shadow-border-xs p-5">
|
||||
<%# Contributions — chronological list, full width. %>
|
||||
<section class="bg-container rounded-xl shadow-border-xs p-5">
|
||||
<div class="flex items-center mb-4">
|
||||
<h2 class="text-sm font-medium text-primary"><%= t(".contributions_heading") %></h2>
|
||||
<span class="ml-2 text-xs text-subdued tabular-nums"><%= @contributions.size %></span>
|
||||
@@ -314,13 +285,8 @@
|
||||
<div class="max-h-[420px] overflow-y-auto overflow-x-hidden scrollbar">
|
||||
<%= render "contributions_list", contributions: @contributions %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-container rounded-xl shadow-border-xs p-5">
|
||||
<h2 class="text-sm font-medium text-primary mb-3"><%= t(".funding_accounts_heading") %></h2>
|
||||
<%= render Goals::FundingAccountsBreakdownComponent.new(goal: @goal, rows: @funding_breakdown) %>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<% if @goal.notes.present? %>
|
||||
<section class="bg-container rounded-xl shadow-border-xs p-5">
|
||||
|
||||
@@ -94,6 +94,10 @@ en:
|
||||
contributions_heading: Contributions
|
||||
add_contribution: Add contribution
|
||||
funding_accounts_heading: Funding accounts
|
||||
funding_table:
|
||||
name: Name
|
||||
weight: Weight
|
||||
value: Value
|
||||
no_contributions_yet: No contributions yet.
|
||||
delete_contribution: Delete contribution
|
||||
confirm_delete_contribution: Delete this contribution?
|
||||
|
||||
Reference in New Issue
Block a user