feat(goals): pending-pledge dot on card avatar

Goals with open + unexpired pledges now carry a small amber DS::Pill
dot at the top-right of the avatar on the index card. Same primitive
+ position pattern as the beta gate dot on the sidebar nav, so the
'small marker' affordance reads consistently across the app.

Pledges are preloaded via the existing .includes(:open_pledges, ...) on
the index query, so the indicator is free at request time.
This commit is contained in:
Guillem Arias
2026-05-18 22:19:44 +02:00
parent 82e3ba8ef7
commit 25eb18d9af
3 changed files with 17 additions and 2 deletions

View File

@@ -1,9 +1,16 @@
<div class="group relative bg-container rounded-xl shadow-border-xs hover:shadow-sm transition-shadow p-6 <%= "opacity-75" if goal.paused? || goal.archived? %>"
<% if filterable %>data-goals-filter-target="card"<% end %>
<% if filterable %> data-goals-filter-target="card"<% end %>
data-goal-name="<%= goal.name %>"
data-goal-status="<%= goal.display_status %>">
<div class="flex items-start gap-3">
<%= render Goals::AvatarComponent.new(goal: goal, size: "lg") %>
<div class="relative shrink-0">
<%= render Goals::AvatarComponent.new(goal: goal, size: "lg") %>
<% if has_pending_pledge? %>
<span class="absolute -top-0.5 -right-0.5">
<%= render DS::Pill.new(tone: :amber, size: :md, dot_only: true, title: t("goals.goal_card.pending_pledge")) %>
</span>
<% end %>
</div>
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2 mb-0.5">
<p class="text-base font-medium text-primary truncate">

View File

@@ -26,6 +26,13 @@ class Goals::CardComponent < ApplicationComponent
@linked_accounts ||= goal.linked_accounts.to_a
end
# Open + unexpired pledges are preloaded on the index via the
# `.includes(:open_pledges, ...)` chain in GoalsController#index, so
# this is a hit on the in-memory association — no N+1.
def has_pending_pledge?
goal.open_pledges.any?
end
def linked_accounts_count_label
I18n.t("goals.goal_card.accounts", count: linked_accounts.size)
end

View File

@@ -233,6 +233,7 @@ en:
footer_reached: Goal reached
footer_catch_up: "Save %{amount}/mo to catch up"
footer_no_deadline: Open
pending_pledge: Pending pledge
footer_no_pledges: No matched pledges yet
footer_last_today: Last pledge matched today
footer_last_days: