mirror of
https://github.com/we-promise/sure.git
synced 2026-06-01 00:39:01 +00:00
feat(savings_goals): status pill icons + paused variant, attention-first sort, paused chip, rename "No date" to "Open-ended"
P4: status pills now carry an icon alongside the colored tint (circle-check / triangle-alert / star / infinity / pause), so color is no longer the sole signal. Drop the redundant dot. P4: default sort on the active goals list becomes attention-first — behind → on_track → no_target_date → paused, alphabetical within bucket. The user opens the page and lands on the goals that need them. P5: add a Paused filter chip + render paused goal cards with opacity-75 so they read as inactive at a glance. Rename "No date" chip to "Open-ended" — clearer to non-jargon readers.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<%= link_to savings_goal_path(goal),
|
||||
class: "group block bg-container rounded-xl shadow-border-xs hover:bg-surface-hover transition-colors p-6",
|
||||
class: "group block bg-container rounded-xl shadow-border-xs hover:bg-surface-hover transition-colors p-6 #{"opacity-75" if goal.paused?}",
|
||||
data: {
|
||||
savings_goals_filter_target: "card",
|
||||
goal_name: goal.name,
|
||||
goal_status: goal.status
|
||||
goal_status: goal.paused? ? "paused" : goal.status
|
||||
} do %>
|
||||
<div class="flex items-start gap-3">
|
||||
<%= render Savings::GoalAvatarComponent.new(goal: goal, size: "lg") %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium whitespace-nowrap <%= classes %>">
|
||||
<span class="w-1.5 h-1.5 rounded-full <%= dot_classes %>"></span>
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium whitespace-nowrap <%= classes %>" aria-label="<%= label %>">
|
||||
<%= helpers.icon(icon_name, size: "xs") %>
|
||||
<%= label %>
|
||||
</span>
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
class Savings::StatusPillComponent < ApplicationComponent
|
||||
VARIANTS = {
|
||||
on_track: { classes: "bg-green-500/10 text-success", dot: "bg-green-600" },
|
||||
behind: { classes: "bg-yellow-500/10 text-warning", dot: "bg-yellow-600" },
|
||||
reached: { classes: "bg-green-500/10 text-success", dot: "bg-green-600" },
|
||||
no_target_date: { classes: "bg-surface-inset text-secondary", dot: "bg-gray-400" }
|
||||
on_track: { classes: "bg-green-500/10 text-success", icon: "circle-check" },
|
||||
behind: { classes: "bg-yellow-500/10 text-warning", icon: "triangle-alert" },
|
||||
reached: { classes: "bg-green-500/10 text-success", icon: "star" },
|
||||
no_target_date: { classes: "bg-surface-inset text-secondary", icon: "infinity" },
|
||||
paused: { classes: "bg-surface-inset text-secondary", icon: "pause" }
|
||||
}.freeze
|
||||
|
||||
def initialize(goal:)
|
||||
@goal = goal
|
||||
end
|
||||
|
||||
def status
|
||||
def status_key
|
||||
return :paused if @goal.paused?
|
||||
@goal.status
|
||||
end
|
||||
|
||||
def variant
|
||||
VARIANTS.fetch(status, VARIANTS[:no_target_date])
|
||||
VARIANTS.fetch(status_key, VARIANTS[:no_target_date])
|
||||
end
|
||||
|
||||
def label
|
||||
I18n.t("savings_goals.status.#{status}")
|
||||
I18n.t("savings_goals.status.#{status_key}")
|
||||
end
|
||||
|
||||
def classes
|
||||
variant[:classes]
|
||||
end
|
||||
|
||||
def dot_classes
|
||||
variant[:dot]
|
||||
def icon_name
|
||||
variant[:icon]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@ class SavingsGoalsController < ApplicationController
|
||||
before_action :set_savings_goal, only: %i[show edit update destroy pause resume complete archive unarchive]
|
||||
|
||||
STATE_FILTERS = %w[all active paused completed archived].freeze
|
||||
ACTIVE_STATUS_RANK = { behind: 0, on_track: 1, no_target_date: 2 }.freeze
|
||||
|
||||
def index
|
||||
@counts = STATE_FILTERS.each_with_object({}) do |state, h|
|
||||
@@ -10,6 +11,7 @@ class SavingsGoalsController < ApplicationController
|
||||
|
||||
all_goals = Current.family.savings_goals.with_current_balance.alphabetically.to_a
|
||||
@active_goals = all_goals.reject { |g| %w[completed archived].include?(g.state) }
|
||||
.sort_by { |g| [ g.paused? ? 3 : ACTIVE_STATUS_RANK.fetch(g.status, 4), g.name.downcase ] }
|
||||
@completed_goals = all_goals.select { |g| g.state == "completed" }
|
||||
|
||||
@linkable_account_count = Current.family.accounts.where(accountable_type: "Depository").visible.count
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-flex items-center gap-1 p-1 bg-surface-inset rounded-xl">
|
||||
<% %w[all on_track behind no_target_date].each do |status| %>
|
||||
<% %w[all on_track behind no_target_date paused].each do |status| %>
|
||||
<% active = status == "all" %>
|
||||
<button type="button"
|
||||
data-savings-goals-filter-target="chip"
|
||||
|
||||
@@ -53,7 +53,8 @@ en:
|
||||
all: All
|
||||
on_track: On track
|
||||
behind: Behind
|
||||
no_target_date: No date
|
||||
no_target_date: Open-ended
|
||||
paused: Paused
|
||||
account_card:
|
||||
funds:
|
||||
one: Funds 1 goal
|
||||
@@ -151,6 +152,7 @@ en:
|
||||
behind: Behind
|
||||
reached: Reached
|
||||
no_target_date: No date
|
||||
paused: Paused
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user