mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-23 15:19:38 +02:00
Add nice formatting for subtypes on account list (#2138)
* 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:
parent
db34f6d7a2
commit
47aeaf8cea
12 changed files with 83 additions and 30 deletions
|
@ -155,6 +155,16 @@ class Account < ApplicationRecord
|
||||||
first_valuation&.amount_money || balance_money
|
first_valuation&.amount_money || balance_money
|
||||||
end
|
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
|
private
|
||||||
def sync_balances
|
def sync_balances
|
||||||
strategy = linked? ? :reverse : :forward
|
strategy = linked? ? :reverse : :forward
|
||||||
|
|
|
@ -59,6 +59,7 @@ class BalanceSheet
|
||||||
account.define_singleton_method(:weight) do
|
account.define_singleton_method(:weight) do
|
||||||
classification_total.zero? ? 0 : account.converted_balance / classification_total.to_d * 100
|
classification_total.zero? ? 0 : account.converted_balance / classification_total.to_d * 100
|
||||||
end
|
end
|
||||||
|
|
||||||
account
|
account
|
||||||
end.sort_by(&:weight).reverse
|
end.sort_by(&:weight).reverse
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,9 @@ module Accountable
|
||||||
|
|
||||||
TYPES = %w[Depository Investment Crypto Property Vehicle OtherAsset CreditCard Loan OtherLiability]
|
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)
|
def self.from_type(type)
|
||||||
return nil unless TYPES.include?(type)
|
return nil unless TYPES.include?(type)
|
||||||
type.constantize
|
type.constantize
|
||||||
|
@ -27,6 +30,24 @@ module Accountable
|
||||||
raise NotImplementedError, "Accountable must implement #color"
|
raise NotImplementedError, "Accountable must implement #color"
|
||||||
end
|
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
|
def favorable_direction
|
||||||
classification == "asset" ? "up" : "down"
|
classification == "asset" ? "up" : "down"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
class Depository < ApplicationRecord
|
class Depository < ApplicationRecord
|
||||||
include Accountable
|
include Accountable
|
||||||
|
|
||||||
SUBTYPES = [
|
SUBTYPES = {
|
||||||
[ "Checking", "checking" ],
|
"checking" => { short: "Checking", long: "Checking" },
|
||||||
[ "Savings", "savings" ]
|
"savings" => { short: "Savings", long: "Savings" }
|
||||||
].freeze
|
}.freeze
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def display_name
|
def display_name
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
class Investment < ApplicationRecord
|
class Investment < ApplicationRecord
|
||||||
include Accountable
|
include Accountable
|
||||||
|
|
||||||
SUBTYPES = [
|
SUBTYPES = {
|
||||||
[ "Brokerage", "brokerage" ],
|
"brokerage" => { short: "Brokerage", long: "Brokerage" },
|
||||||
[ "Pension", "pension" ],
|
"pension" => { short: "Pension", long: "Pension" },
|
||||||
[ "Retirement", "retirement" ],
|
"retirement" => { short: "Retirement", long: "Retirement" },
|
||||||
[ "401(k)", "401k" ],
|
"401k" => { short: "401(k)", long: "401(k)" },
|
||||||
[ "Traditional 401(k)", "traditional_401k" ],
|
"traditional_401k" => { short: "Traditional 401(k)", long: "Traditional 401(k)" },
|
||||||
[ "Roth 401(k)", "roth_401k" ],
|
"roth_401k" => { short: "Roth 401(k)", long: "Roth 401(k)" },
|
||||||
[ "529 Plan", "529_plan" ],
|
"529_plan" => { short: "529 Plan", long: "529 Plan" },
|
||||||
[ "Health Savings Account", "hsa" ],
|
"hsa" => { short: "HSA", long: "Health Savings Account" },
|
||||||
[ "Mutual Fund", "mutual_fund" ],
|
"mutual_fund" => { short: "Mutual Fund", long: "Mutual Fund" },
|
||||||
[ "Traditional IRA", "traditional_ira" ],
|
"traditional_ira" => { short: "Traditional IRA", long: "Traditional IRA" },
|
||||||
[ "Roth IRA", "roth_ira" ],
|
"roth_ira" => { short: "Roth IRA", long: "Roth IRA" },
|
||||||
[ "Angel", "angel" ]
|
"angel" => { short: "Angel", long: "Angel" }
|
||||||
].freeze
|
}.freeze
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def color
|
def color
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
class Property < ApplicationRecord
|
class Property < ApplicationRecord
|
||||||
include Accountable
|
include Accountable
|
||||||
|
|
||||||
SUBTYPES = [
|
SUBTYPES = {
|
||||||
[ "Single Family Home", "single_family_home" ],
|
"single_family_home" => { short: "Single Family Home", long: "Single Family Home" },
|
||||||
[ "Multi-Family Home", "multi_family_home" ],
|
"multi_family_home" => { short: "Multi-Family Home", long: "Multi-Family Home" },
|
||||||
[ "Condominium", "condominium" ],
|
"condominium" => { short: "Condo", long: "Condominium" },
|
||||||
[ "Townhouse", "townhouse" ],
|
"townhouse" => { short: "Townhouse", long: "Townhouse" },
|
||||||
[ "Investment Property", "investment_property" ],
|
"investment_property" => { short: "Investment Property", long: "Investment Property" },
|
||||||
[ "Second Home", "second_home" ]
|
"second_home" => { short: "Second Home", long: "Second Home" }
|
||||||
]
|
}.freeze
|
||||||
|
|
||||||
has_one :address, as: :addressable, dependent: :destroy
|
has_one :address, as: :addressable, dependent: :destroy
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= link_to account.name, account, class: [(account.is_active ? "text-primary" : "text-subdued"), "text-sm font-medium hover:underline"], data: { turbo_frame: "_top" } %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
<div class="min-w-0 grow">
|
<div class="min-w-0 grow">
|
||||||
<%= tag.p account.name, class: "text-sm text-primary font-medium mb-0.5 truncate" %>
|
<%= 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>
|
||||||
|
|
||||||
<div class="ml-auto text-right grow h-10">
|
<div class="ml-auto text-right grow h-10">
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
<%= render "accounts/form", account: account, url: url do |form| %>
|
<%= render "accounts/form", account: account, url: url do |form| %>
|
||||||
<%= form.select :subtype,
|
<%= 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") } %>
|
{ label: true, prompt: t("depositories.form.subtype_prompt"), include_blank: t("depositories.form.none") } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
<%= render "accounts/form", account: account, url: url do |form| %>
|
<%= render "accounts/form", account: account, url: url do |form| %>
|
||||||
<%= form.select :subtype,
|
<%= 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") } %>
|
{ label: true, prompt: t("investments.form.subtype_prompt"), include_blank: t("investments.form.none") } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<%= render "accounts/form", account: account, url: url do |form| %>
|
<%= render "accounts/form", account: account, url: url do |form| %>
|
||||||
<%= form.select :subtype,
|
<%= 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") } %>
|
{ label: true, prompt: t("properties.form.subtype_prompt"), include_blank: t("properties.form.none") } %>
|
||||||
|
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
|
|
|
@ -13,4 +13,22 @@ class AccountTest < ActiveSupport::TestCase
|
||||||
@account.destroy
|
@account.destroy
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue