diff --git a/app/components/savings/status_pill_component.rb b/app/components/savings/status_pill_component.rb index 0d43e3ef8..c1d42b824 100644 --- a/app/components/savings/status_pill_component.rb +++ b/app/components/savings/status_pill_component.rb @@ -9,7 +9,8 @@ class Savings::StatusPillComponent < ApplicationComponent behind: { classes: "bg-yellow-500/10 text-yellow-700", icon: "triangle-alert" }, reached: { classes: "bg-green-500/10 text-green-700", icon: "star" }, no_target_date: { classes: "bg-surface-inset text-gray-700", icon: "infinity" }, - paused: { classes: "bg-surface-inset text-gray-700", icon: "pause" } + paused: { classes: "bg-surface-inset text-gray-700", icon: "pause" }, + archived: { classes: "bg-surface-inset text-gray-700", icon: "archive" } }.freeze def initialize(goal:) @@ -17,8 +18,7 @@ class Savings::StatusPillComponent < ApplicationComponent end def status_key - return :paused if @goal.paused? - @goal.status + @goal.display_status end def variant diff --git a/app/models/savings_goal.rb b/app/models/savings_goal.rb index ea5280548..3696fb443 100644 --- a/app/models/savings_goal.rb +++ b/app/models/savings_goal.rb @@ -162,6 +162,21 @@ class SavingsGoal < ApplicationRecord } end + # Display-layer status. Prefers AASM state for inactive goals so the UI + # doesn't compute a misleading "Behind / On track" verdict against a goal + # that isn't accepting contributions anymore. + def display_status + return @display_status if defined?(@display_status) + + @display_status = if archived? + :archived + elsif paused? + :paused + else + status + end + end + # :reached → progress_percent >= 100 # :on_track → has target_date and current pace >= required monthly pace # :behind → has target_date and current pace < required monthly pace diff --git a/app/views/savings_goals/show.html.erb b/app/views/savings_goals/show.html.erb index 7779e791c..c44a403c3 100644 --- a/app/views/savings_goals/show.html.erb +++ b/app/views/savings_goals/show.html.erb @@ -145,7 +145,20 @@

- <% if @savings_goal.completed? || @savings_goal.status == :reached %> + <% if @savings_goal.archived? || @savings_goal.paused? %> + <%# Paused / archived: pace + projection are misleading. Show a static recap card. %> +
+
+ <%= icon(@savings_goal.archived? ? "archive" : "pause", size: "2xl") %> +
+

+ <%= t(@savings_goal.archived? ? ".inactive.heading_archived" : ".inactive.heading_paused") %> +

+

+ <%= t(".inactive.body", saved: @savings_goal.current_balance_money.format, target: @savings_goal.target_amount_money.format) %> +

+
+ <% elsif @savings_goal.completed? || @savings_goal.status == :reached %> <%# Reached celebration card %>
@@ -212,11 +225,13 @@ <% end %> - <%# Stat row — combo pace card + contributions count. Reached goals - hide the pace combo since the comparison is moot. %> + <%# 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 = @savings_goal.completed? || @savings_goal.status == :reached %> -
gap-3"> - <% unless goal_reached %> + <% hide_pace = goal_reached || @savings_goal.archived? || @savings_goal.paused? %> +
gap-3"> + <% unless hide_pace %> <%# Combo: Avg vs Target pace %>

<%= t(".stats.monthly_pace") %>

diff --git a/config/locales/views/savings_goals/en.yml b/config/locales/views/savings_goals/en.yml index 87a87113b..e6a49031f 100644 --- a/config/locales/views/savings_goals/en.yml +++ b/config/locales/views/savings_goals/en.yml @@ -137,6 +137,10 @@ en: heading: Goal reached. Nice work. body: "You hit your %{amount} target. Keep the goal as a record, or archive it now." archive_cta: Archive goal + inactive: + heading_paused: This goal is paused + heading_archived: This goal is archived + body: "%{saved} of %{target} saved so far." no_target_date: heading: Add a target date body: Set a deadline to project a finish line and track required pace. @@ -168,6 +172,7 @@ en: reached: Reached no_target_date: No date paused: Paused + archived: Archived empty_state: heading: No goals yet body: Set a target, link the accounts you save into, and watch your progress add up. Goals can pull from multiple accounts. diff --git a/test/models/savings_goal_test.rb b/test/models/savings_goal_test.rb index e78eceace..d4ee5cd4d 100644 --- a/test/models/savings_goal_test.rb +++ b/test/models/savings_goal_test.rb @@ -126,6 +126,23 @@ class SavingsGoalTest < ActiveSupport::TestCase assert_equal :no_target_date, @goal.status end + test "display_status returns :archived for archived goal regardless of progress" do + @goal.save! + @goal.archive! + assert_equal :archived, @goal.display_status + end + + test "display_status returns :paused for paused goal regardless of progress" do + @goal.save! + @goal.pause! + assert_equal :paused, @goal.display_status + end + + test "display_status falls through to status for active goals" do + @goal.target_amount = 1 + assert_equal :reached, @goal.display_status + end + test "advisory_lock_key_for is stable per family" do k1 = SavingsGoal.advisory_lock_key_for(@family.id) k2 = SavingsGoal.advisory_lock_key_for(@family.id)