mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
Add customizable menu order for user accounts (#44)
* Add customizable menu order for user accounts Introduces a MenuOrder model and concern to allow users to select their preferred account ordering (by name or balance, ascending or descending). Adds a default_order field to users, updates user preferences UI, and applies the selected order to balance sheet account listings. * Rename MenuOrder to AccountOrder and update user order field Refactors the MenuOrder model to AccountOrder and updates all references accordingly. Replaces the user's default_order field with default_account_order, including migration changes, validations, and form fields. Updates localization and schema to reflect the new naming. * Update balance_sheet.rb * Fix for nil Current.user when rake runs in balance_sheet model --------- Signed-off-by: Aluisio Pereira <oaluiser@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
This commit is contained in:
14
app/controllers/concerns/orderable.rb
Normal file
14
app/controllers/concerns/orderable.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
module Orderable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_order
|
||||
end
|
||||
|
||||
private
|
||||
def set_order
|
||||
@order = AccountOrder.find(params[:order] || Current.user&.default_account_order)
|
||||
rescue ArgumentError
|
||||
@order = AccountOrder.default
|
||||
end
|
||||
end
|
||||
@@ -88,7 +88,7 @@ class UsersController < ApplicationController
|
||||
def user_params
|
||||
params.require(:user).permit(
|
||||
:first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at,
|
||||
:show_sidebar, :default_period, :show_ai_sidebar, :ai_enabled, :theme, :set_onboarding_preferences_at, :set_onboarding_goals_at,
|
||||
:show_sidebar, :default_period, :default_account_order, :show_ai_sidebar, :ai_enabled, :theme, :set_onboarding_preferences_at, :set_onboarding_goals_at,
|
||||
family_attributes: [ :name, :currency, :country, :locale, :date_format, :timezone, :id ],
|
||||
goals: []
|
||||
)
|
||||
|
||||
60
app/models/account_order.rb
Normal file
60
app/models/account_order.rb
Normal file
@@ -0,0 +1,60 @@
|
||||
class AccountOrder
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
|
||||
ORDERS = {
|
||||
"name_asc" => {
|
||||
label: "Name (A-Z)",
|
||||
label_short: "Name ↑",
|
||||
sql_order: "name ASC"
|
||||
},
|
||||
"name_desc" => {
|
||||
label: "Name (Z-A)",
|
||||
label_short: "Name ↓",
|
||||
sql_order: "name DESC"
|
||||
},
|
||||
"balance_asc" => {
|
||||
label: "Balance (Low to High)",
|
||||
label_short: "Balance ↑",
|
||||
sql_order: "balance ASC"
|
||||
},
|
||||
"balance_desc" => {
|
||||
label: "Balance (High to Low)",
|
||||
label_short: "Balance ↓",
|
||||
sql_order: "balance DESC"
|
||||
}
|
||||
}.freeze
|
||||
|
||||
attr_accessor :key
|
||||
|
||||
def initialize(key)
|
||||
@key = key.to_s
|
||||
raise ArgumentError, "Invalid order key: #{@key}" unless ORDERS.key?(@key)
|
||||
end
|
||||
|
||||
def label
|
||||
ORDERS.dig(key, :label)
|
||||
end
|
||||
|
||||
def label_short
|
||||
ORDERS.dig(key, :label_short)
|
||||
end
|
||||
|
||||
def sql_order
|
||||
ORDERS.dig(key, :sql_order)
|
||||
end
|
||||
|
||||
class << self
|
||||
def all
|
||||
ORDERS.keys.map { |key| new(key) }
|
||||
end
|
||||
|
||||
def find(key)
|
||||
new(key) if ORDERS.key?(key.to_s)
|
||||
end
|
||||
|
||||
def default
|
||||
new("name_asc")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,7 +13,7 @@ class BalanceSheet
|
||||
@assets ||= ClassificationGroup.new(
|
||||
classification: "asset",
|
||||
currency: family.currency,
|
||||
accounts: account_totals.asset_accounts.sort_by(&:name)
|
||||
accounts: sorted(account_totals.asset_accounts)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@ class BalanceSheet
|
||||
@liabilities ||= ClassificationGroup.new(
|
||||
classification: "liability",
|
||||
currency: family.currency,
|
||||
accounts: account_totals.liability_accounts.sort_by(&:name)
|
||||
accounts: sorted(account_totals.liability_accounts)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -61,4 +61,22 @@ class BalanceSheet
|
||||
def net_worth_series_builder
|
||||
@net_worth_series_builder ||= NetWorthSeriesBuilder.new(family)
|
||||
end
|
||||
|
||||
def sorted(accounts)
|
||||
account_order = Current.user&.account_order
|
||||
order_key = account_order&.key || "name_asc"
|
||||
|
||||
case order_key
|
||||
when "name_asc"
|
||||
accounts.sort_by(&:name)
|
||||
when "name_desc"
|
||||
accounts.sort_by(&:name).reverse
|
||||
when "balance_asc"
|
||||
accounts.sort_by(&:balance)
|
||||
when "balance_desc"
|
||||
accounts.sort_by(&:balance).reverse
|
||||
else
|
||||
accounts
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,7 @@ class User < ApplicationRecord
|
||||
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||
validate :ensure_valid_profile_image
|
||||
validates :default_period, inclusion: { in: Period::PERIODS.keys }
|
||||
validates :default_account_order, inclusion: { in: AccountOrder::ORDERS.keys }
|
||||
normalizes :email, with: ->(email) { email.strip.downcase }
|
||||
normalizes :unconfirmed_email, with: ->(email) { email&.strip&.downcase }
|
||||
|
||||
@@ -163,6 +164,10 @@ class User < ApplicationRecord
|
||||
!onboarded?
|
||||
end
|
||||
|
||||
def account_order
|
||||
AccountOrder.find(default_account_order) || AccountOrder.default
|
||||
end
|
||||
|
||||
private
|
||||
def ensure_valid_profile_image
|
||||
return unless profile_image.attached?
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
{ label: t(".default_period") },
|
||||
{ data: { auto_submit_form_target: "auto" } } %>
|
||||
|
||||
<%= form.select :default_account_order,
|
||||
AccountOrder.all.map { |order| [ order.label, order.key ] },
|
||||
{ label: t(".default_account_order") },
|
||||
{ data: { auto_submit_form_target: "auto" } } %>
|
||||
|
||||
<%= family_form.select :country,
|
||||
country_options,
|
||||
{ label: t(".country") },
|
||||
|
||||
@@ -14,6 +14,7 @@ en:
|
||||
general_subtitle: Configure your preferences
|
||||
general_title: General
|
||||
default_period: Default Period
|
||||
default_account_order: Default Account Order
|
||||
language: Language
|
||||
page_title: Preferences
|
||||
theme_dark: Dark
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddDefaultAccountOrderToUsers < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :users, :default_account_order, :string, default: "name_asc"
|
||||
end
|
||||
end
|
||||
5
db/schema.rb
generated
5
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_07_24_115507) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_07_31_134449) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
@@ -29,7 +29,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_07_24_115507) do
|
||||
t.uuid "accountable_id"
|
||||
t.decimal "balance", precision: 19, scale: 4
|
||||
t.string "currency"
|
||||
t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY ((ARRAY['Loan'::character varying, 'CreditCard'::character varying, 'OtherLiability'::character varying])::text[])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true
|
||||
t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY (ARRAY[('Loan'::character varying)::text, ('CreditCard'::character varying)::text, ('OtherLiability'::character varying)::text])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true
|
||||
t.uuid "import_id"
|
||||
t.uuid "plaid_account_id"
|
||||
t.decimal "cash_balance", precision: 19, scale: 4, default: "0.0"
|
||||
@@ -800,6 +800,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_07_24_115507) do
|
||||
t.text "goals", default: [], array: true
|
||||
t.datetime "set_onboarding_preferences_at"
|
||||
t.datetime "set_onboarding_goals_at"
|
||||
t.string "default_account_order", default: "name_asc"
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["family_id"], name: "index_users_on_family_id"
|
||||
t.index ["last_viewed_chat_id"], name: "index_users_on_last_viewed_chat_id"
|
||||
|
||||
Reference in New Issue
Block a user