1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-24 07:39:39 +02:00

Add nice formatting for subtypes on account list (#2138)
Some checks are pending
Publish Docker image / ci (push) Waiting to run
Publish Docker image / Build docker image (push) Blocked by required conditions

* Add nice formatting for subtypes on account list

* Fix rubocop linting errors

* Implement better mapping

* Fix rubocop linting

* Add short and long versions of subtypes

* Simplify subtype reference

Co-authored-by: Zach Gollwitzer <zach.gollwitzer@gmail.com>
Signed-off-by: Alex Hatzenbuhler <hatz@hey.com>

* Simplify reference logic, add a small test

* Fix test

* Fix tests

---------

Signed-off-by: Alex Hatzenbuhler <hatz@hey.com>
Co-authored-by: Zach Gollwitzer <zach.gollwitzer@gmail.com>
This commit is contained in:
Alex Hatzenbuhler 2025-04-22 13:10:50 -05:00 committed by GitHub
parent db34f6d7a2
commit 47aeaf8cea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 83 additions and 30 deletions

View file

@ -155,6 +155,16 @@ class Account < ApplicationRecord
first_valuation&.amount_money || balance_money
end
# Get short version of the subtype label
def short_subtype_label
accountable_class.short_subtype_label_for(subtype) || accountable_class.display_name
end
# Get long version of the subtype label
def long_subtype_label
accountable_class.long_subtype_label_for(subtype) || accountable_class.display_name
end
private
def sync_balances
strategy = linked? ? :reverse : :forward

View file

@ -59,6 +59,7 @@ class BalanceSheet
account.define_singleton_method(:weight) do
classification_total.zero? ? 0 : account.converted_balance / classification_total.to_d * 100
end
account
end.sort_by(&:weight).reverse
)

View file

@ -3,6 +3,9 @@ module Accountable
TYPES = %w[Depository Investment Crypto Property Vehicle OtherAsset CreditCard Loan OtherLiability]
# Define empty hash to ensure all accountables have this defined
SUBTYPES = {}.freeze
def self.from_type(type)
return nil unless TYPES.include?(type)
type.constantize
@ -27,6 +30,24 @@ module Accountable
raise NotImplementedError, "Accountable must implement #color"
end
# Given a subtype, look up the label for this accountable type
def subtype_label_for(subtype, format: :short)
return nil if subtype.nil?
label_type = format == :long ? :long : :short
self::SUBTYPES[subtype]&.fetch(label_type, nil)
end
# Convenience method for getting the short label
def short_subtype_label_for(subtype)
subtype_label_for(subtype, format: :short)
end
# Convenience method for getting the long label
def long_subtype_label_for(subtype)
subtype_label_for(subtype, format: :long)
end
def favorable_direction
classification == "asset" ? "up" : "down"
end

View file

@ -1,10 +1,10 @@
class Depository < ApplicationRecord
include Accountable
SUBTYPES = [
[ "Checking", "checking" ],
[ "Savings", "savings" ]
].freeze
SUBTYPES = {
"checking" => { short: "Checking", long: "Checking" },
"savings" => { short: "Savings", long: "Savings" }
}.freeze
class << self
def display_name

View file

@ -1,20 +1,20 @@
class Investment < ApplicationRecord
include Accountable
SUBTYPES = [
[ "Brokerage", "brokerage" ],
[ "Pension", "pension" ],
[ "Retirement", "retirement" ],
[ "401(k)", "401k" ],
[ "Traditional 401(k)", "traditional_401k" ],
[ "Roth 401(k)", "roth_401k" ],
[ "529 Plan", "529_plan" ],
[ "Health Savings Account", "hsa" ],
[ "Mutual Fund", "mutual_fund" ],
[ "Traditional IRA", "traditional_ira" ],
[ "Roth IRA", "roth_ira" ],
[ "Angel", "angel" ]
].freeze
SUBTYPES = {
"brokerage" => { short: "Brokerage", long: "Brokerage" },
"pension" => { short: "Pension", long: "Pension" },
"retirement" => { short: "Retirement", long: "Retirement" },
"401k" => { short: "401(k)", long: "401(k)" },
"traditional_401k" => { short: "Traditional 401(k)", long: "Traditional 401(k)" },
"roth_401k" => { short: "Roth 401(k)", long: "Roth 401(k)" },
"529_plan" => { short: "529 Plan", long: "529 Plan" },
"hsa" => { short: "HSA", long: "Health Savings Account" },
"mutual_fund" => { short: "Mutual Fund", long: "Mutual Fund" },
"traditional_ira" => { short: "Traditional IRA", long: "Traditional IRA" },
"roth_ira" => { short: "Roth IRA", long: "Roth IRA" },
"angel" => { short: "Angel", long: "Angel" }
}.freeze
class << self
def color

View file

@ -1,14 +1,14 @@
class Property < ApplicationRecord
include Accountable
SUBTYPES = [
[ "Single Family Home", "single_family_home" ],
[ "Multi-Family Home", "multi_family_home" ],
[ "Condominium", "condominium" ],
[ "Townhouse", "townhouse" ],
[ "Investment Property", "investment_property" ],
[ "Second Home", "second_home" ]
]
SUBTYPES = {
"single_family_home" => { short: "Single Family Home", long: "Single Family Home" },
"multi_family_home" => { short: "Multi-Family Home", long: "Multi-Family Home" },
"condominium" => { short: "Condo", long: "Condominium" },
"townhouse" => { short: "Townhouse", long: "Townhouse" },
"investment_property" => { short: "Investment Property", long: "Investment Property" },
"second_home" => { short: "Second Home", long: "Second Home" }
}.freeze
has_one :address, as: :addressable, dependent: :destroy

View file

@ -17,6 +17,9 @@
</p>
<% else %>
<%= link_to account.name, account, class: [(account.is_active ? "text-primary" : "text-subdued"), "text-sm font-medium hover:underline"], data: { turbo_frame: "_top" } %>
<% if account.long_subtype_label %>
<p class="text-sm text-secondary truncate"><%= account.long_subtype_label %></p>
<% end %>
<% end %>
</div>

View file

@ -24,7 +24,7 @@
<div class="min-w-0 grow">
<%= tag.p account.name, class: "text-sm text-primary font-medium mb-0.5 truncate" %>
<%= tag.p account.subtype&.humanize.presence || account_group.name, class: "text-sm text-secondary truncate" %>
<%= tag.p account.short_subtype_label, class: "text-sm text-secondary truncate" %>
</div>
<div class="ml-auto text-right grow h-10">

View file

@ -2,6 +2,6 @@
<%= render "accounts/form", account: account, url: url do |form| %>
<%= form.select :subtype,
Depository::SUBTYPES,
Depository::SUBTYPES.map { |k, v| [v[:long], k] },
{ label: true, prompt: t("depositories.form.subtype_prompt"), include_blank: t("depositories.form.none") } %>
<% end %>

View file

@ -2,6 +2,6 @@
<%= render "accounts/form", account: account, url: url do |form| %>
<%= form.select :subtype,
Investment::SUBTYPES,
Investment::SUBTYPES.map { |k, v| [v[:long], k] },
{ label: true, prompt: t("investments.form.subtype_prompt"), include_blank: t("investments.form.none") } %>
<% end %>

View file

@ -2,7 +2,7 @@
<%= render "accounts/form", account: account, url: url do |form| %>
<%= form.select :subtype,
Property::SUBTYPES,
Property::SUBTYPES.map { |k, v| [v[:long], k] },
{ label: true, prompt: t("properties.form.subtype_prompt"), include_blank: t("properties.form.none") } %>
<hr class="my-4">

View file

@ -13,4 +13,22 @@ class AccountTest < ActiveSupport::TestCase
@account.destroy
end
end
test "gets short/long subtype label" do
account = @family.accounts.create!(
name: "Test Investment",
balance: 1000,
currency: "USD",
subtype: "hsa",
accountable: Investment.new
)
assert_equal "HSA", account.short_subtype_label
assert_equal "Health Savings Account", account.long_subtype_label
# Test with nil subtype
account.update!(subtype: nil)
assert_equal "Investments", account.short_subtype_label
assert_equal "Investments", account.long_subtype_label
end
end