diff --git a/app/controllers/account/entries_controller.rb b/app/controllers/account/entries_controller.rb
index fb394f2d..d78cb62c 100644
--- a/app/controllers/account/entries_controller.rb
+++ b/app/controllers/account/entries_controller.rb
@@ -4,13 +4,21 @@ class Account::EntriesController < ApplicationController
before_action :set_account
before_action :set_entry, only: %i[edit update show destroy]
+ def index
+ @q = search_params
+ @pagy, @entries = pagy(@account.entries.search(@q).reverse_chronological, limit: params[:per_page] || "10")
+ end
+
def edit
render entryable_view_path(:edit)
end
def update
+ prev_amount = @entry.amount
+ prev_date = @entry.date
+
@entry.update!(entry_params)
- @entry.sync_account_later
+ @entry.sync_account_later if prev_amount != @entry.amount || prev_date != @entry.date
respond_to do |format|
format.html { redirect_to account_entry_path(@account, @entry), notice: t(".success") }
@@ -43,6 +51,11 @@ class Account::EntriesController < ApplicationController
end
def entry_params
- params.require(:account_entry).permit(:name, :date, :amount, :currency)
+ params.require(:account_entry).permit(:name, :date, :amount, :currency, :notes)
+ end
+
+ def search_params
+ params.fetch(:q, {})
+ .permit(:search)
end
end
diff --git a/app/controllers/account/trades_controller.rb b/app/controllers/account/trades_controller.rb
index 0b8bc99d..e0897c9f 100644
--- a/app/controllers/account/trades_controller.rb
+++ b/app/controllers/account/trades_controller.rb
@@ -17,10 +17,10 @@ class Account::TradesController < ApplicationController
if entry = @builder.save
entry.sync_account_later
- redirect_to account_path(@account), notice: t(".success")
+ redirect_to @account, notice: t(".success")
else
flash[:alert] = t(".failure")
- redirect_back_or_to account_path(@account)
+ redirect_back_or_to @account
end
end
diff --git a/app/controllers/account/transactions_controller.rb b/app/controllers/account/transactions_controller.rb
index 2e3073e6..26c9e62d 100644
--- a/app/controllers/account/transactions_controller.rb
+++ b/app/controllers/account/transactions_controller.rb
@@ -12,16 +12,25 @@ class Account::TransactionsController < ApplicationController
end
def update
- @entry.update!(entry_params)
+ prev_amount = @entry.amount
+ prev_date = @entry.date
+
+ @entry.update!(entry_params.except(:origin))
+ @entry.sync_account_later if prev_amount != @entry.amount || prev_date != @entry.date
respond_to do |format|
format.html { redirect_to account_entry_path(@account, @entry), notice: t(".success") }
- format.turbo_stream { render turbo_stream: turbo_stream.replace(@entry) }
+ format.turbo_stream do
+ render turbo_stream: turbo_stream.replace(
+ @entry,
+ partial: "account/entries/entry",
+ locals: entry_locals.merge(entry: @entry)
+ )
+ end
end
end
private
-
def set_account
@account = Current.family.accounts.find(params[:account_id])
end
@@ -30,10 +39,18 @@ class Account::TransactionsController < ApplicationController
@entry = @account.entries.find(params[:id])
end
+ def entry_locals
+ {
+ selectable: entry_params[:origin].present?,
+ show_balance: entry_params[:origin] == "account",
+ origin: entry_params[:origin]
+ }
+ end
+
def entry_params
params.require(:account_entry)
.permit(
- :name, :date, :amount, :currency, :excluded, :notes, :entryable_type, :nature,
+ :name, :date, :amount, :currency, :excluded, :notes, :entryable_type, :nature, :origin,
entryable_attributes: [
:id,
:category_id,
diff --git a/app/controllers/account/transfers_controller.rb b/app/controllers/account/transfers_controller.rb
index bdd611f9..cdcca52d 100644
--- a/app/controllers/account/transfers_controller.rb
+++ b/app/controllers/account/transfers_controller.rb
@@ -1,12 +1,15 @@
class Account::TransfersController < ApplicationController
layout :with_sidebar
- before_action :set_transfer, only: :destroy
+ before_action :set_transfer, only: %i[destroy show update]
def new
@transfer = Account::Transfer.new
end
+ def show
+ end
+
def create
from_account = Current.family.accounts.find(transfer_params[:from_account_id])
to_account = Current.family.accounts.find(transfer_params[:to_account_id])
@@ -27,18 +30,33 @@ class Account::TransfersController < ApplicationController
end
end
+ def update
+ @transfer.update_entries!(transfer_update_params)
+ redirect_back_or_to transactions_url, notice: t(".success")
+ end
+
def destroy
- @transfer.destroy_and_remove_marks!
+ @transfer.destroy!
redirect_back_or_to transactions_url, notice: t(".success")
end
private
def set_transfer
- @transfer = Account::Transfer.find(params[:id])
+ record = Account::Transfer.find(params[:id])
+
+ unless record.entries.all? { |entry| Current.family.accounts.include?(entry.account) }
+ raise ActiveRecord::RecordNotFound
+ end
+
+ @transfer = record
end
def transfer_params
- params.require(:account_transfer).permit(:from_account_id, :to_account_id, :amount, :date, :name)
+ params.require(:account_transfer).permit(:from_account_id, :to_account_id, :amount, :date, :name, :excluded)
+ end
+
+ def transfer_update_params
+ params.require(:account_transfer).permit(:excluded, :notes)
end
end
diff --git a/app/controllers/account/valuations_controller.rb b/app/controllers/account/valuations_controller.rb
index e3015bde..577dfbf4 100644
--- a/app/controllers/account/valuations_controller.rb
+++ b/app/controllers/account/valuations_controller.rb
@@ -15,7 +15,7 @@ class Account::ValuationsController < ApplicationController
redirect_back_or_to account_valuations_path(@account), notice: t(".success")
else
flash[:alert] = @entry.errors.full_messages.to_sentence
- redirect_to account_path(@account)
+ redirect_to @account
end
end
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 21419365..5c463b21 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -1,8 +1,7 @@
class AccountsController < ApplicationController
layout :with_sidebar
- include Filterable
- before_action :set_account, only: %i[edit show destroy sync update]
+ before_action :set_account, only: %i[sync]
def index
@institutions = Current.family.institutions
@@ -10,6 +9,7 @@ class AccountsController < ApplicationController
end
def summary
+ @period = Period.from_param(params[:period])
snapshot = Current.family.snapshot(@period)
@net_worth_series = snapshot[:net_worth_series]
@asset_series = snapshot[:asset_series]
@@ -22,45 +22,6 @@ class AccountsController < ApplicationController
render layout: false
end
- def new
- @account = Account.new(currency: Current.family.currency)
- @account.accountable = Accountable.from_type(params[:type])&.new if params[:type].present?
- @account.accountable.address = Address.new if @account.accountable.is_a?(Property)
-
- if params[:institution_id]
- @account.institution = Current.family.institutions.find_by(id: params[:institution_id])
- end
- end
-
- def show
- end
-
- def edit
- @account.accountable.build_address if @account.accountable.is_a?(Property) && @account.accountable.address.blank?
- end
-
- def update
- @account.update_with_sync!(account_params)
- redirect_back_or_to account_path(@account), notice: t(".success")
- end
-
- def create
- @account = Current.family
- .accounts
- .create_with_optional_start_balance! \
- attributes: account_params.except(:start_date, :start_balance),
- start_date: account_params[:start_date],
- start_balance: account_params[:start_balance]
- @account.sync_later
-
- redirect_back_or_to account_path(@account), notice: t(".success")
- end
-
- def destroy
- @account.destroy!
- redirect_to accounts_path, notice: t(".success")
- end
-
def sync
unless @account.syncing?
@account.sync_later
@@ -73,12 +34,7 @@ class AccountsController < ApplicationController
end
private
-
def set_account
@account = Current.family.accounts.find(params[:id])
end
-
- def account_params
- params.require(:account).permit(:name, :accountable_type, :mode, :balance, :start_date, :start_balance, :currency, :subtype, :is_active, :institution_id)
- end
end
diff --git a/app/controllers/concerns/accountable_resource.rb b/app/controllers/concerns/accountable_resource.rb
new file mode 100644
index 00000000..29ab519d
--- /dev/null
+++ b/app/controllers/concerns/accountable_resource.rb
@@ -0,0 +1,60 @@
+module AccountableResource
+ extend ActiveSupport::Concern
+
+ included do
+ layout :with_sidebar
+ before_action :set_account, only: [ :show, :edit, :update, :destroy ]
+ end
+
+ class_methods do
+ def permitted_accountable_attributes(*attrs)
+ @permitted_accountable_attributes = attrs if attrs.any?
+ @permitted_accountable_attributes ||= [ :id ]
+ end
+ end
+
+ def new
+ @account = Current.family.accounts.build(
+ currency: Current.family.currency,
+ accountable: accountable_type.new,
+ institution_id: params[:institution_id]
+ )
+ end
+
+ def show
+ end
+
+ def edit
+ end
+
+ def create
+ @account = Current.family.accounts.create_and_sync(account_params.except(:return_to))
+ redirect_to account_params[:return_to].presence || @account, notice: t(".success")
+ end
+
+ def update
+ @account.update_with_sync!(account_params.except(:return_to))
+ redirect_back_or_to @account, notice: t(".success")
+ end
+
+ def destroy
+ @account.destroy!
+ redirect_to accounts_path, notice: t(".success")
+ end
+
+ private
+ def accountable_type
+ controller_name.classify.constantize
+ end
+
+ def set_account
+ @account = Current.family.accounts.find(params[:id])
+ end
+
+ def account_params
+ params.require(:account).permit(
+ :name, :is_active, :balance, :subtype, :currency, :institution_id, :accountable_type, :return_to,
+ accountable_attributes: self.class.permitted_accountable_attributes
+ )
+ end
+end
diff --git a/app/controllers/concerns/filterable.rb b/app/controllers/concerns/filterable.rb
deleted file mode 100644
index 4fe42f45..00000000
--- a/app/controllers/concerns/filterable.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module Filterable
- extend ActiveSupport::Concern
-
- included do
- before_action :set_period
- end
-
- private
-
- def set_period
- @period = Period.find_by_name(params[:period])
- if @period.nil?
- start_date = params[:start_date].presence&.to_date
- end_date = params[:end_date].presence&.to_date
- if start_date.is_a?(Date) && end_date.is_a?(Date) && start_date <= end_date
- @period = Period.new(name: "custom", date_range: start_date..end_date)
- else
- params[:period] = "last_30_days"
- @period = Period.find_by_name(params[:period])
- end
- end
- end
-end
diff --git a/app/controllers/credit_cards_controller.rb b/app/controllers/credit_cards_controller.rb
index bb416ce7..4d0f5659 100644
--- a/app/controllers/credit_cards_controller.rb
+++ b/app/controllers/credit_cards_controller.rb
@@ -1,41 +1,12 @@
class CreditCardsController < ApplicationController
- before_action :set_account, only: :update
+ include AccountableResource
- def create
- account = Current.family
- .accounts
- .create_with_optional_start_balance! \
- attributes: account_params.except(:start_date, :start_balance),
- start_date: account_params[:start_date],
- start_balance: account_params[:start_balance]
-
- account.sync_later
- redirect_to account, notice: t(".success")
- end
-
- def update
- @account.update_with_sync!(account_params)
- redirect_to @account, notice: t(".success")
- end
-
- private
-
- def set_account
- @account = Current.family.accounts.find(params[:id])
- end
-
- def account_params
- params.require(:account)
- .permit(
- :name, :balance, :institution_id, :mode, :start_date, :start_balance, :currency, :accountable_type,
- accountable_attributes: [
- :id,
- :available_credit,
- :minimum_payment,
- :apr,
- :annual_fee,
- :expiration_date
- ]
- )
- end
+ permitted_accountable_attributes(
+ :id,
+ :available_credit,
+ :minimum_payment,
+ :apr,
+ :annual_fee,
+ :expiration_date
+ )
end
diff --git a/app/controllers/cryptos_controller.rb b/app/controllers/cryptos_controller.rb
new file mode 100644
index 00000000..2df7d041
--- /dev/null
+++ b/app/controllers/cryptos_controller.rb
@@ -0,0 +1,3 @@
+class CryptosController < ApplicationController
+ include AccountableResource
+end
diff --git a/app/controllers/depositories_controller.rb b/app/controllers/depositories_controller.rb
new file mode 100644
index 00000000..34a2842d
--- /dev/null
+++ b/app/controllers/depositories_controller.rb
@@ -0,0 +1,3 @@
+class DepositoriesController < ApplicationController
+ include AccountableResource
+end
diff --git a/app/controllers/investments_controller.rb b/app/controllers/investments_controller.rb
new file mode 100644
index 00000000..1ef7d144
--- /dev/null
+++ b/app/controllers/investments_controller.rb
@@ -0,0 +1,3 @@
+class InvestmentsController < ApplicationController
+ include AccountableResource
+end
diff --git a/app/controllers/issue/exchange_rate_provider_missings_controller.rb b/app/controllers/issue/exchange_rate_provider_missings_controller.rb
index 91cac7f5..7e53f4df 100644
--- a/app/controllers/issue/exchange_rate_provider_missings_controller.rb
+++ b/app/controllers/issue/exchange_rate_provider_missings_controller.rb
@@ -3,8 +3,9 @@ class Issue::ExchangeRateProviderMissingsController < ApplicationController
def update
Setting.synth_api_key = exchange_rate_params[:synth_api_key]
- @issue.issuable.sync_later
- redirect_back_or_to account_path(@issue.issuable)
+ account = @issue.issuable
+ account.sync_later
+ redirect_back_or_to account
end
private
diff --git a/app/controllers/loans_controller.rb b/app/controllers/loans_controller.rb
index 1b704290..b9968faf 100644
--- a/app/controllers/loans_controller.rb
+++ b/app/controllers/loans_controller.rb
@@ -1,39 +1,7 @@
class LoansController < ApplicationController
- before_action :set_account, only: :update
+ include AccountableResource
- def create
- account = Current.family
- .accounts
- .create_with_optional_start_balance! \
- attributes: account_params.except(:start_date, :start_balance),
- start_date: account_params[:start_date],
- start_balance: account_params[:start_balance]
-
- account.sync_later
- redirect_to account, notice: t(".success")
- end
-
- def update
- @account.update_with_sync!(account_params)
- redirect_to @account, notice: t(".success")
- end
-
- private
-
- def set_account
- @account = Current.family.accounts.find(params[:id])
- end
-
- def account_params
- params.require(:account)
- .permit(
- :name, :balance, :institution_id, :start_date, :mode, :start_balance, :currency, :accountable_type,
- accountable_attributes: [
- :id,
- :rate_type,
- :interest_rate,
- :term_months
- ]
- )
- end
+ permitted_accountable_attributes(
+ :id, :rate_type, :interest_rate, :term_months
+ )
end
diff --git a/app/controllers/other_assets_controller.rb b/app/controllers/other_assets_controller.rb
new file mode 100644
index 00000000..409cca27
--- /dev/null
+++ b/app/controllers/other_assets_controller.rb
@@ -0,0 +1,3 @@
+class OtherAssetsController < ApplicationController
+ include AccountableResource
+end
diff --git a/app/controllers/other_liabilities_controller.rb b/app/controllers/other_liabilities_controller.rb
new file mode 100644
index 00000000..a5054172
--- /dev/null
+++ b/app/controllers/other_liabilities_controller.rb
@@ -0,0 +1,3 @@
+class OtherLiabilitiesController < ApplicationController
+ include AccountableResource
+end
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index f495d465..9af9d663 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -2,9 +2,8 @@ class PagesController < ApplicationController
skip_before_action :authenticate_user!, only: %i[early_access]
layout :with_sidebar, except: %i[early_access]
- include Filterable
-
def dashboard
+ @period = Period.from_param(params[:period])
snapshot = Current.family.snapshot(@period)
@net_worth_series = snapshot[:net_worth_series]
@asset_series = snapshot[:asset_series]
diff --git a/app/controllers/properties_controller.rb b/app/controllers/properties_controller.rb
index e37344d4..d1cd21cf 100644
--- a/app/controllers/properties_controller.rb
+++ b/app/controllers/properties_controller.rb
@@ -1,40 +1,22 @@
class PropertiesController < ApplicationController
- before_action :set_account, only: :update
+ include AccountableResource
- def create
- account = Current.family
- .accounts
- .create_with_optional_start_balance! \
- attributes: account_params.except(:start_date, :start_balance),
- start_date: account_params[:start_date],
- start_balance: account_params[:start_balance]
+ permitted_accountable_attributes(
+ :id, :year_built, :area_unit, :area_value,
+ address_attributes: [ :line1, :line2, :locality, :region, :country, :postal_code ]
+ )
- account.sync_later
- redirect_to account, notice: t(".success")
+ def new
+ @account = Current.family.accounts.build(
+ currency: Current.family.currency,
+ accountable: Property.new(
+ address: Address.new
+ ),
+ institution_id: params[:institution_id]
+ )
end
- def update
- @account.update_with_sync!(account_params)
- redirect_to @account, notice: t(".success")
+ def edit
+ @account.accountable.address ||= Address.new
end
-
- private
-
- def set_account
- @account = Current.family.accounts.find(params[:id])
- end
-
- def account_params
- params.require(:account)
- .permit(
- :name, :balance, :institution_id, :start_date, :mode, :start_balance, :currency, :accountable_type,
- accountable_attributes: [
- :id,
- :year_built,
- :area_unit,
- :area_value,
- address_attributes: [ :line1, :line2, :locality, :region, :country, :postal_code ]
- ]
- )
- end
end
diff --git a/app/controllers/transactions_controller.rb b/app/controllers/transactions_controller.rb
index ed8f579d..6360bd19 100644
--- a/app/controllers/transactions_controller.rb
+++ b/app/controllers/transactions_controller.rb
@@ -32,7 +32,7 @@ class TransactionsController < ApplicationController
.create!(transaction_entry_params.merge(amount: amount))
@entry.sync_account_later
- redirect_back_or_to account_path(@entry.account), notice: t(".success")
+ redirect_back_or_to @entry.account, notice: t(".success")
end
def bulk_delete
diff --git a/app/controllers/vehicles_controller.rb b/app/controllers/vehicles_controller.rb
index fe41a42f..487f1439 100644
--- a/app/controllers/vehicles_controller.rb
+++ b/app/controllers/vehicles_controller.rb
@@ -1,41 +1,7 @@
class VehiclesController < ApplicationController
- before_action :set_account, only: :update
+ include AccountableResource
- def create
- account = Current.family
- .accounts
- .create_with_optional_start_balance! \
- attributes: account_params.except(:start_date, :start_balance),
- start_date: account_params[:start_date],
- start_balance: account_params[:start_balance]
-
- account.sync_later
- redirect_to account, notice: t(".success")
- end
-
- def update
- @account.update_with_sync!(account_params)
- redirect_to @account, notice: t(".success")
- end
-
- private
-
- def set_account
- @account = Current.family.accounts.find(params[:id])
- end
-
- def account_params
- params.require(:account)
- .permit(
- :name, :balance, :institution_id, :start_date, :mode, :start_balance, :currency, :accountable_type,
- accountable_attributes: [
- :id,
- :make,
- :model,
- :year,
- :mileage_value,
- :mileage_unit
- ]
- )
- end
+ permitted_accountable_attributes(
+ :id, :make, :model, :year, :mileage_value, :mileage_unit
+ )
end
diff --git a/app/helpers/account/entries_helper.rb b/app/helpers/account/entries_helper.rb
index 0f2aff52..360dae4a 100644
--- a/app/helpers/account/entries_helper.rb
+++ b/app/helpers/account/entries_helper.rb
@@ -12,43 +12,18 @@ module Account::EntriesHelper
transfers.map(&:transfer).uniq
end
- def entry_icon(entry, is_oldest: false)
- if is_oldest
- "keyboard"
- elsif entry.trend.direction.up?
- "arrow-up"
- elsif entry.trend.direction.down?
- "arrow-down"
- else
- "minus"
- end
- end
-
- def entry_style(entry, is_oldest: false)
- color = is_oldest ? "#D444F1" : entry.trend.color
-
- mixed_hex_styles(color)
- end
-
- def entry_name(entry)
- if entry.account_trade?
- trade = entry.account_trade
- prefix = trade.sell? ? "Sell " : "Buy "
- generated = prefix + "#{trade.qty.abs} shares of #{trade.security.ticker}"
- name = entry.name || generated
- name
- else
- entry.name || "Transaction"
- end
- end
-
- def entries_by_date(entries, selectable: true)
+ def entries_by_date(entries, selectable: true, totals: false)
entries.group_by(&:date).map do |date, grouped_entries|
- content = capture do
- yield grouped_entries
+ # Valuations always go first, then sort by created_at
+ sorted_entries = grouped_entries.sort_by do |entry|
+ [ entry.account_valuation? ? 0 : 1, entry.created_at ]
end
- render partial: "account/entries/entry_group", locals: { date:, entries: grouped_entries, content:, selectable: }
+ content = capture do
+ yield sorted_entries
+ end
+
+ render partial: "account/entries/entry_group", locals: { date:, entries: sorted_entries, content:, selectable:, totals: }
end.join.html_safe
end
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
index b65033c5..357ec1da 100644
--- a/app/helpers/accounts_helper.rb
+++ b/app/helpers/accounts_helper.rb
@@ -1,12 +1,25 @@
module AccountsHelper
- def permitted_accountable_partial(account, name = nil)
- permitted_names = %w[tooltip header tabs form]
- folder = account.accountable_type.underscore
- name ||= account.accountable_type.underscore
+ def period_label(period)
+ return "since account creation" if period.date_range.begin.nil?
+ start_date, end_date = period.date_range.first, period.date_range.last
- raise "Unpermitted accountable partial: #{name}" unless permitted_names.include?(name)
+ return "Starting from #{start_date.strftime('%b %d, %Y')}" if end_date.nil?
+ return "Ending at #{end_date.strftime('%b %d, %Y')}" if start_date.nil?
- "accounts/accountables/#{folder}/#{name}"
+ days_apart = (end_date - start_date).to_i
+
+ case days_apart
+ when 1
+ "vs. yesterday"
+ when 7
+ "vs. last week"
+ when 30, 31
+ "vs. last month"
+ when 365, 366
+ "vs. last year"
+ else
+ "from #{start_date.strftime('%b %d, %Y')} to #{end_date.strftime('%b %d, %Y')}"
+ end
end
def summary_card(title:, &block)
@@ -38,62 +51,6 @@ module AccountsHelper
class_mapping(accountable_type)[:hex]
end
- # Eventually, we'll have an accountable form for each type of accountable, so
- # this helper is a convenience for now to reuse common logic in the accounts controller
- def new_account_form_url(account)
- case account.accountable_type
- when "Property"
- properties_path
- when "Vehicle"
- vehicles_path
- when "Loan"
- loans_path
- when "CreditCard"
- credit_cards_path
- else
- accounts_path
- end
- end
-
- def edit_account_form_url(account)
- case account.accountable_type
- when "Property"
- property_path(account)
- when "Vehicle"
- vehicle_path(account)
- when "Loan"
- loan_path(account)
- when "CreditCard"
- credit_card_path(account)
- else
- account_path(account)
- end
- end
-
- def account_tabs(account)
- overview_tab = { key: "overview", label: t("accounts.show.overview"), path: account_path(account, tab: "overview"), partial_path: "accounts/overview" }
- holdings_tab = { key: "holdings", label: t("accounts.show.holdings"), path: account_path(account, tab: "holdings"), route: account_holdings_path(account) }
- cash_tab = { key: "cash", label: t("accounts.show.cash"), path: account_path(account, tab: "cash"), route: account_cashes_path(account) }
- value_tab = { key: "valuations", label: t("accounts.show.value"), path: account_path(account, tab: "valuations"), route: account_valuations_path(account) }
- transactions_tab = { key: "transactions", label: t("accounts.show.transactions"), path: account_path(account, tab: "transactions"), route: account_transactions_path(account) }
- trades_tab = { key: "trades", label: t("accounts.show.trades"), path: account_path(account, tab: "trades"), route: account_trades_path(account) }
-
- return [ value_tab ] if account.other_asset? || account.other_liability?
- return [ overview_tab, value_tab ] if account.property? || account.vehicle?
- return [ holdings_tab, cash_tab, trades_tab, value_tab ] if account.investment?
- return [ overview_tab, value_tab, transactions_tab ] if account.loan? || account.credit_card?
-
- [ value_tab, transactions_tab ]
- end
-
- def selected_account_tab(account)
- available_tabs = account_tabs(account)
-
- tab = available_tabs.find { |tab| tab[:key] == params[:tab] }
-
- tab || available_tabs.first
- end
-
def account_groups(period: nil)
assets, liabilities = Current.family.accounts.by_group(currency: Current.family.currency, period: period || Period.last_30_days).values_at(:assets, :liabilities)
[ assets.children.sort_by(&:name), liabilities.children.sort_by(&:name) ].flatten
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index ca83e38d..f1194764 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -122,29 +122,6 @@ module ApplicationHelper
{ bg_class: bg_class, text_class: text_class, symbol: symbol, icon: icon }
end
- def period_label(period)
- return "since account creation" if period.date_range.begin.nil?
- start_date, end_date = period.date_range.first, period.date_range.last
-
- return "Starting from #{start_date.strftime('%b %d, %Y')}" if end_date.nil?
- return "Ending at #{end_date.strftime('%b %d, %Y')}" if start_date.nil?
-
- days_apart = (end_date - start_date).to_i
-
- case days_apart
- when 1
- "vs. yesterday"
- when 7
- "vs. last week"
- when 30, 31
- "vs. last month"
- when 365, 366
- "vs. last year"
- else
- "from #{start_date.strftime('%b %d, %Y')} to #{end_date.strftime('%b %d, %Y')}"
- end
- end
-
# Wrapper around I18n.l to support custom date formats
def format_date(object, format = :default, options = {})
date = object.to_date
diff --git a/app/javascript/controllers/bulk_select_controller.js b/app/javascript/controllers/bulk_select_controller.js
index 104d2aa6..023e97da 100644
--- a/app/javascript/controllers/bulk_select_controller.js
+++ b/app/javascript/controllers/bulk_select_controller.js
@@ -10,7 +10,8 @@ export default class extends Controller {
"bulkEditDrawerTitle",
];
static values = {
- resource: String,
+ singularLabel: String,
+ pluralLabel: String,
selectedIds: { type: Array, default: [] },
};
@@ -132,9 +133,11 @@ export default class extends Controller {
}
_pluralizedResourceName() {
- return `${this.resourceValue}${
- this.selectedIdsValue.length === 1 ? "" : "s"
- }`;
+ if (this.selectedIdsValue.length === 1) {
+ return this.singularLabelValue;
+ }
+
+ return this.pluralLabelValue;
}
_updateGroups() {
diff --git a/app/javascript/controllers/time_series_chart_controller.js b/app/javascript/controllers/time_series_chart_controller.js
index 7660f3f4..428060ae 100644
--- a/app/javascript/controllers/time_series_chart_controller.js
+++ b/app/javascript/controllers/time_series_chart_controller.js
@@ -535,7 +535,7 @@ export default class extends Controller {
}
get _d3YScale() {
- const reductionPercent = this.useLabelsValue ? 0.15 : 0.05;
+ const reductionPercent = this.useLabelsValue ? 0.3 : 0.05;
const dataMin = d3.min(this._normalDataPoints, (d) => d.value);
const dataMax = d3.max(this._normalDataPoints, (d) => d.value);
const padding = (dataMax - dataMin) * reductionPercent;
diff --git a/app/models/account.rb b/app/models/account.rb
index c2c91f37..bc4f7123 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -1,10 +1,7 @@
class Account < ApplicationRecord
- VALUE_MODES = %w[balance transactions]
-
include Syncable, Monetizable, Issuable
validates :name, :balance, :currency, presence: true
- validates :mode, inclusion: { in: VALUE_MODES }, allow_nil: true
belongs_to :family
belongs_to :institution, optional: true
@@ -34,7 +31,7 @@ class Account < ApplicationRecord
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
- accepts_nested_attributes_for :accountable
+ accepts_nested_attributes_for :accountable, update_only: true
delegate :value, :series, to: :accountable
@@ -61,29 +58,32 @@ class Account < ApplicationRecord
grouped_accounts
end
- def create_with_optional_start_balance!(attributes:, start_date: nil, start_balance: nil)
- transaction do
- attributes[:accountable_attributes] ||= {} # Ensure accountable is created
- account = new(attributes)
+ def create_and_sync(attributes)
+ attributes[:accountable_attributes] ||= {} # Ensure accountable is created, even if empty
+ account = new(attributes)
- # Always initialize an account with a valuation entry to begin tracking value history
- account.entries.build \
+ transaction do
+ # Create 2 valuations for new accounts to establish a value history for users to see
+ account.entries.build(
+ name: "Current Balance",
date: Date.current,
amount: account.balance,
currency: account.currency,
entryable: Account::Valuation.new
-
- if start_date.present? && start_balance.present?
- account.entries.build \
- date: start_date,
- amount: start_balance,
- currency: account.currency,
- entryable: Account::Valuation.new
- end
+ )
+ account.entries.build(
+ name: "Initial Balance",
+ date: 1.day.ago.to_date,
+ amount: 0,
+ currency: account.currency,
+ entryable: Account::Valuation.new
+ )
account.save!
- account
end
+
+ account.sync_later
+ account
end
end
diff --git a/app/models/account/entry.rb b/app/models/account/entry.rb
index e04756f1..85a05850 100644
--- a/app/models/account/entry.rb
+++ b/app/models/account/entry.rb
@@ -46,22 +46,40 @@ class Account::Entry < ApplicationRecord
amount > 0 && account_transaction?
end
- def first_of_type?
- first_entry = account
- .entries
- .where("entryable_type = ?", entryable_type)
- .order(:date)
- .first
-
- first_entry&.id == id
- end
-
def entryable_name_short
entryable_type.demodulize.underscore
end
+ def prior_balance
+ account.balances.find_by(date: date - 1)&.balance || 0
+ end
+
+ def balance_after_entry
+ if account_valuation?
+ Money.new(amount, currency)
+ else
+ new_balance = prior_balance
+ entries_on_entry_date.each do |e|
+ change = e.amount
+ change = account.liability? ? change : -change
+ new_balance += change
+ break if e == self
+ end
+
+ Money.new(new_balance, currency)
+ end
+ end
+
def trend
- @trend ||= create_trend
+ TimeSeries::Trend.new(
+ current: balance_after_entry,
+ previous: Money.new(prior_balance, currency),
+ favorable_direction: account.favorable_direction
+ )
+ end
+
+ def entries_on_entry_date
+ account.entries.where(date: date).order(created_at: :desc)
end
class << self
@@ -216,11 +234,4 @@ class Account::Entry < ApplicationRecord
.order(date: :desc)
.first
end
-
- def create_trend
- TimeSeries::Trend.new \
- current: amount_money,
- previous: previous_entry&.amount_money,
- favorable_direction: account.favorable_direction
- end
end
diff --git a/app/models/account/trade.rb b/app/models/account/trade.rb
index a594ee16..17e0b070 100644
--- a/app/models/account/trade.rb
+++ b/app/models/account/trade.rb
@@ -26,6 +26,12 @@ class Account::Trade < ApplicationRecord
qty > 0
end
+ def name
+ prefix = sell? ? "Sell " : "Buy "
+ generated = prefix + "#{qty.abs} shares of #{security.ticker}"
+ entry.name || generated
+ end
+
def unrealized_gain_loss
return nil if sell?
current_price = security.current_price
diff --git a/app/models/account/transaction.rb b/app/models/account/transaction.rb
index 52a0f9bd..91f24f70 100644
--- a/app/models/account/transaction.rb
+++ b/app/models/account/transaction.rb
@@ -48,12 +48,20 @@ class Account::Transaction < ApplicationRecord
end
end
+ def name
+ entry.name || "(no description)"
+ end
+
+ def eod_balance
+ entry.amount_money
+ end
+
private
- def previous_transaction_date
- self.account
- .transactions
- .where("date < ?", date)
- .order(date: :desc)
- .first&.date
+ def account
+ entry.account
+ end
+
+ def daily_transactions
+ account.entries.account_transactions
end
end
diff --git a/app/models/account/transfer.rb b/app/models/account/transfer.rb
index 9cd78ac1..ec464003 100644
--- a/app/models/account/transfer.rb
+++ b/app/models/account/transfer.rb
@@ -1,5 +1,5 @@
class Account::Transfer < ApplicationRecord
- has_many :entries, dependent: :nullify
+ has_many :entries, dependent: :destroy
validate :net_zero_flows, if: :single_currency_transfer?
validate :transaction_count, :from_different_accounts, :all_transactions_marked
@@ -13,17 +13,25 @@ class Account::Transfer < ApplicationRecord
end
def from_name
- outflow_transaction&.account&.name || I18n.t("account/transfer.from_fallback_name")
+ from_account&.name || I18n.t("account/transfer.from_fallback_name")
end
def to_name
- inflow_transaction&.account&.name || I18n.t("account/transfer.to_fallback_name")
+ to_account&.name || I18n.t("account/transfer.to_fallback_name")
end
def name
I18n.t("account/transfer.name", from_account: from_name, to_account: to_name)
end
+ def from_account
+ outflow_transaction&.account
+ end
+
+ def to_account
+ inflow_transaction&.account
+ end
+
def inflow_transaction
entries.find { |e| e.inflow? }
end
@@ -32,13 +40,11 @@ class Account::Transfer < ApplicationRecord
entries.find { |e| e.outflow? }
end
- def destroy_and_remove_marks!
+ def update_entries!(params)
transaction do
- entries.each do |e|
- e.update! marked_as_transfer: false
+ entries.each do |entry|
+ entry.update!(params)
end
-
- destroy!
end
end
diff --git a/app/models/account/valuation.rb b/app/models/account/valuation.rb
index 93ebf5ff..f5f5aa50 100644
--- a/app/models/account/valuation.rb
+++ b/app/models/account/valuation.rb
@@ -10,4 +10,44 @@ class Account::Valuation < ApplicationRecord
false
end
end
+
+ def name
+ oldest? ? "Initial balance" : entry.name || "Balance update"
+ end
+
+ def trend
+ @trend ||= create_trend
+ end
+
+ def icon
+ oldest? ? "plus" : entry.trend.icon
+ end
+
+ def color
+ oldest? ? "#D444F1" : entry.trend.color
+ end
+
+ private
+ def oldest?
+ @oldest ||= account.entries.where("date < ?", entry.date).empty?
+ end
+
+ def account
+ @account ||= entry.account
+ end
+
+ def create_trend
+ TimeSeries::Trend.new(
+ current: entry.amount_money,
+ previous: prior_balance&.balance_money,
+ favorable_direction: account.favorable_direction
+ )
+ end
+
+ def prior_balance
+ @prior_balance ||= account.balances
+ .where("date < ?", entry.date)
+ .order(date: :desc)
+ .first
+ end
end
diff --git a/app/models/concerns/accountable.rb b/app/models/concerns/accountable.rb
index 5f5ce93d..a7a10284 100644
--- a/app/models/concerns/accountable.rb
+++ b/app/models/concerns/accountable.rb
@@ -33,8 +33,4 @@ module Accountable
rescue Money::ConversionError
TimeSeries.new([])
end
-
- def mode_required?
- true
- end
end
diff --git a/app/models/credit_card.rb b/app/models/credit_card.rb
index bdde91c8..dc269516 100644
--- a/app/models/credit_card.rb
+++ b/app/models/credit_card.rb
@@ -16,4 +16,8 @@ class CreditCard < ApplicationRecord
def color
"#F13636"
end
+
+ def icon
+ "credit-card"
+ end
end
diff --git a/app/models/crypto.rb b/app/models/crypto.rb
index e4f81ae4..1aba075e 100644
--- a/app/models/crypto.rb
+++ b/app/models/crypto.rb
@@ -4,4 +4,8 @@ class Crypto < ApplicationRecord
def color
"#737373"
end
+
+ def icon
+ "bitcoin"
+ end
end
diff --git a/app/models/demo/generator.rb b/app/models/demo/generator.rb
index b56ef1bd..48a28d2e 100644
--- a/app/models/demo/generator.rb
+++ b/app/models/demo/generator.rb
@@ -34,6 +34,7 @@ class Demo::Generator
create_investment_account!
create_house_and_mortgage!
create_car_and_loan!
+ create_other_accounts!
puts "accounts created"
puts "Demo data loaded successfully!"
@@ -50,7 +51,7 @@ class Demo::Generator
family = Family.find_by(id: family_id)
family.destroy! if family
- Family.create!(id: family_id, name: "Demo Family").tap(&:reload)
+ Family.create!(id: family_id, name: "Demo Family", stripe_subscription_status: "active").tap(&:reload)
end
def clear_data!
@@ -273,6 +274,20 @@ class Demo::Generator
currency: "USD"
end
+ def create_other_accounts!
+ family.accounts.create! \
+ accountable: OtherAsset.new,
+ name: "Other Asset",
+ balance: 10000,
+ currency: "USD"
+
+ family.accounts.create! \
+ accountable: OtherLiability.new,
+ name: "Other Liability",
+ balance: 5000,
+ currency: "USD"
+ end
+
def create_transaction!(attributes = {})
entry_attributes = attributes.except(:category, :tags, :merchant)
transaction_attributes = attributes.slice(:category, :tags, :merchant)
diff --git a/app/models/depository.rb b/app/models/depository.rb
index 90abe087..3b818f43 100644
--- a/app/models/depository.rb
+++ b/app/models/depository.rb
@@ -1,7 +1,16 @@
class Depository < ApplicationRecord
include Accountable
+ SUBTYPES = [
+ [ "Checking", "checking" ],
+ [ "Savings", "savings" ]
+ ].freeze
+
def color
"#875BF7"
end
+
+ def icon
+ "landmark"
+ end
end
diff --git a/app/models/investment.rb b/app/models/investment.rb
index 1912899f..e9df9a4f 100644
--- a/app/models/investment.rb
+++ b/app/models/investment.rb
@@ -50,4 +50,8 @@ class Investment < ApplicationRecord
def color
"#1570EF"
end
+
+ def icon
+ "line-chart"
+ end
end
diff --git a/app/models/loan.rb b/app/models/loan.rb
index 5051b69b..41bd4d7b 100644
--- a/app/models/loan.rb
+++ b/app/models/loan.rb
@@ -20,4 +20,8 @@ class Loan < ApplicationRecord
def color
"#D444F1"
end
+
+ def icon
+ "hand-coins"
+ end
end
diff --git a/app/models/other_asset.rb b/app/models/other_asset.rb
index 29b2048a..68d3915e 100644
--- a/app/models/other_asset.rb
+++ b/app/models/other_asset.rb
@@ -5,7 +5,7 @@ class OtherAsset < ApplicationRecord
"#12B76A"
end
- def mode_required?
- false
+ def icon
+ "plus"
end
end
diff --git a/app/models/other_liability.rb b/app/models/other_liability.rb
index 4be8e38c..18e44171 100644
--- a/app/models/other_liability.rb
+++ b/app/models/other_liability.rb
@@ -5,7 +5,7 @@ class OtherLiability < ApplicationRecord
"#737373"
end
- def mode_required?
- false
+ def icon
+ "minus"
end
end
diff --git a/app/models/period.rb b/app/models/period.rb
index 4a8a3ffa..4a1e8762 100644
--- a/app/models/period.rb
+++ b/app/models/period.rb
@@ -1,12 +1,18 @@
class Period
attr_reader :name, :date_range
- def self.find_by_name(name)
- INDEX[name]
- end
+ class << self
+ def from_param(param)
+ find_by_name(param) || self.last_30_days
+ end
- def self.names
- INDEX.keys.sort
+ def find_by_name(name)
+ INDEX[name]
+ end
+
+ def names
+ INDEX.keys.sort
+ end
end
def initialize(name: "custom", date_range:)
diff --git a/app/models/property.rb b/app/models/property.rb
index 02f4b848..c173e2cb 100644
--- a/app/models/property.rb
+++ b/app/models/property.rb
@@ -1,6 +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" ]
+ ]
+
has_one :address, as: :addressable, dependent: :destroy
accepts_nested_attributes_for :address
@@ -23,8 +31,8 @@ class Property < ApplicationRecord
"#06AED4"
end
- def mode_required?
- false
+ def icon
+ "home"
end
private
diff --git a/app/models/time_series/trend.rb b/app/models/time_series/trend.rb
index b93db2bd..c6638cbe 100644
--- a/app/models/time_series/trend.rb
+++ b/app/models/time_series/trend.rb
@@ -35,9 +35,19 @@ class TimeSeries::Trend
end
end
+ def icon
+ if direction.flat?
+ "minus"
+ elsif direction.up?
+ "arrow-up"
+ else
+ "arrow-down"
+ end
+ end
+
def value
if previous.nil?
- current.is_a?(Money) ? Money.new(0) : 0
+ current.is_a?(Money) ? Money.new(0, current.currency) : 0
else
current - previous
end
diff --git a/app/models/vehicle.rb b/app/models/vehicle.rb
index 92c5b940..70f20457 100644
--- a/app/models/vehicle.rb
+++ b/app/models/vehicle.rb
@@ -19,8 +19,8 @@ class Vehicle < ApplicationRecord
"#F23E94"
end
- def mode_required?
- false
+ def icon
+ "car-front"
end
private
diff --git a/app/views/account/entries/_entry.html.erb b/app/views/account/entries/_entry.html.erb
index ab081dff..4b839916 100644
--- a/app/views/account/entries/_entry.html.erb
+++ b/app/views/account/entries/_entry.html.erb
@@ -1,5 +1,5 @@
-<%# locals: (entry:, **opts) %>
+<%# locals: (entry:, selectable: true, show_balance: false, origin: nil) %>
<%= turbo_frame_tag dom_id(entry) do %>
- <%= render partial: entry.entryable.to_partial_path, locals: { entry: entry, **opts } %>
+ <%= render partial: entry.entryable.to_partial_path, locals: { entry:, selectable:, show_balance:, origin: } %>
<% end %>
diff --git a/app/views/account/entries/_entry_group.html.erb b/app/views/account/entries/_entry_group.html.erb
index 11489c2a..a58d0f15 100644
--- a/app/views/account/entries/_entry_group.html.erb
+++ b/app/views/account/entries/_entry_group.html.erb
@@ -1,4 +1,4 @@
-<%# locals: (date:, entries:, content:, selectable:) %>
+<%# locals: (date:, entries:, content:, selectable:, totals: false) %>
@@ -16,7 +16,9 @@
- <%= totals_by_currency(collection: entries, money_method: :amount_money, negate: true) %>
+ <% if totals %>
+ <%= totals_by_currency(collection: entries, money_method: :amount_money, negate: true) %>
+ <% end %>
<%= content %>
diff --git a/app/views/account/entries/_selection_bar.html.erb b/app/views/account/entries/_selection_bar.html.erb
new file mode 100644
index 00000000..f4f7208e
--- /dev/null
+++ b/app/views/account/entries/_selection_bar.html.erb
@@ -0,0 +1,15 @@
+
+
+ <%= check_box_tag "entry_selection", 1, true, class: "maybe-checkbox maybe-checkbox--dark", data: { action: "bulk-select#deselectAll" } %>
+
+
+
+
+
+ <%= form_with url: bulk_delete_transactions_path, data: { turbo_confirm: true, turbo_frame: "_top" } do %>
+
+ <%= lucide_icon "trash-2", class: "w-5 group-hover:text-white" %>
+
+ <% end %>
+
+
diff --git a/app/views/account/entries/index.html.erb b/app/views/account/entries/index.html.erb
new file mode 100644
index 00000000..30e02686
--- /dev/null
+++ b/app/views/account/entries/index.html.erb
@@ -0,0 +1,88 @@
+<%= turbo_frame_tag dom_id(@account, "entries") do %>
+
+
+ <%= tag.h2 t(".title"), class: "font-medium text-lg" %>
+
+
+ <%= lucide_icon("plus", class: "w-4 h-4") %>
+ <%= tag.span t(".new") %>
+
+
+ <%= link_to new_account_valuation_path(@account), data: { turbo_frame: :modal }, class: "block p-2 rounded-lg hover:bg-gray-50 flex items-center gap-2" do %>
+ <%= lucide_icon("circle-dollar-sign", class: "text-gray-500 w-5 h-5") %>
+ <%= tag.span t(".new_balance"), class: "text-sm" %>
+ <% end %>
+
+ <%= link_to @account.investment? ? new_account_trade_path(@account) : new_transaction_path(account_id: @account.id), data: { turbo_frame: :modal }, class: "block p-2 rounded-lg hover:bg-gray-50 flex items-center gap-2" do %>
+ <%= lucide_icon("credit-card", class: "text-gray-500 w-5 h-5") %>
+ <%= tag.span t(".new_transaction"), class: "text-sm" %>
+ <% end %>
+
+
+
+
+ <% if @entries.empty? %>
+
<%= t(".no_entries") %>
+ <% else %>
+
+
+ <%= form_with url: account_entries_path(@account),
+ id: "entries-search",
+ scope: :q,
+ method: :get,
+ data: { controller: "auto-submit-form" } do |form| %>
+
+
+
+ <%= lucide_icon("search", class: "w-5 h-5 text-gray-500") %>
+ <%= form.search_field :search,
+ placeholder: "Search entries by name",
+ value: @q[:search],
+ class: "form-field__input placeholder:text-sm placeholder:text-gray-500",
+ "data-auto-submit-form-target": "auto" %>
+
+
+
+ <% end %>
+
+
+ <%= tag.div id: dom_id(@account, "entries_bulk_select"),
+ data: {
+ controller: "bulk-select",
+ bulk_select_singular_label_value: t(".entry"),
+ bulk_select_plural_label_value: t(".entries")
+ } do %>
+
+ <%= render "account/entries/selection_bar" %>
+
+
+
+
+ <%= check_box_tag "selection_entry",
+ class: "maybe-checkbox maybe-checkbox--light",
+ data: { action: "bulk-select#togglePageSelection" } %>
+
<%= t(".date") %>
+
+ <%= tag.p t(".amount"), class: "col-span-2 justify-self-end" %>
+ <%= tag.p t(".balance"), class: "col-span-2 justify-self-end" %>
+
+
+
+
+
+
+ <%= entries_by_date(@entries) do |entries| %>
+ <%= render entries, show_balance: true, origin: "account" %>
+ <% end %>
+
+
+
+
+
+ <%= render "pagination", pagy: @pagy %>
+
+
+ <% end %>
+ <% end %>
+
+<% end %>
diff --git a/app/views/account/trades/_security.turbo_stream.erb b/app/views/account/trades/_security.turbo_stream.erb
index f270b028..34bdebd3 100644
--- a/app/views/account/trades/_security.turbo_stream.erb
+++ b/app/views/account/trades/_security.turbo_stream.erb
@@ -8,4 +8,4 @@
<%= "#{security.symbol} (#{security.exchange_acronym})" %>
-
\ No newline at end of file
+
diff --git a/app/views/account/trades/_trade.html.erb b/app/views/account/trades/_trade.html.erb
index 273ec184..f7974079 100644
--- a/app/views/account/trades/_trade.html.erb
+++ b/app/views/account/trades/_trade.html.erb
@@ -1,4 +1,4 @@
-<%# locals: (entry:, selectable: true, **opts) %>
+<%# locals: (entry:, selectable: true, show_balance: false, origin: nil) %>
<% trade, account = entry.account_trade, entry.account %>
@@ -13,14 +13,14 @@
<%= tag.div class: ["flex items-center gap-2"] do %>
- <%= entry_name(entry).first.upcase %>
+ <%= trade.name.first.upcase %>
<% if entry.new_record? %>
- <%= content_tag :p, entry_name(entry) %>
+ <%= content_tag :p, trade.name %>
<% else %>
- <%= link_to entry_name(entry),
+ <%= link_to trade.name,
account_entry_path(account, entry),
data: { turbo_frame: "drawer", turbo_prefetch: false },
class: "hover:underline hover:text-gray-800" %>
diff --git a/app/views/account/trades/securities.turbo_stream.erb b/app/views/account/trades/securities.turbo_stream.erb
index a3128c77..a3225939 100644
--- a/app/views/account/trades/securities.turbo_stream.erb
+++ b/app/views/account/trades/securities.turbo_stream.erb
@@ -1,2 +1,2 @@
<%= async_combobox_options @securities,
- render_in: { partial: "account/trades/security" } %>
\ No newline at end of file
+ render_in: { partial: "account/trades/security" } %>
diff --git a/app/views/account/transactions/_transaction.html.erb b/app/views/account/transactions/_transaction.html.erb
index 4e033f9a..a87c5bdf 100644
--- a/app/views/account/transactions/_transaction.html.erb
+++ b/app/views/account/transactions/_transaction.html.erb
@@ -1,9 +1,8 @@
-<%# locals: (entry:, selectable: true, editable: true, short: false, show_tags: false, **opts) %>
+<%# locals: (entry:, selectable: true, show_balance: false, origin: nil) %>
<% transaction, account = entry.account_transaction, entry.account %>
-
- <% name_col_span = unconfirmed_transfer?(entry) ? "col-span-10" : short ? "col-span-6" : "col-span-4" %>
-
+
text-sm font-medium p-4">
+
<% if selectable %>
<%= check_box_tag dom_id(entry, "selection"),
class: "maybe-checkbox maybe-checkbox--light",
@@ -13,15 +12,15 @@
<%= content_tag :div, class: ["flex items-center gap-2"] do %>
- <%= entry_name(entry).first.upcase %>
+ <%= transaction.name.first.upcase %>
-
- <% if entry.new_record? || !editable %>
- <%= content_tag :p, entry.name %>
+
+ <% if entry.new_record? %>
+ <%= content_tag :p, transaction.name %>
<% else %>
- <%= link_to entry_name(entry),
- account_entry_path(account, entry),
+ <%= link_to transaction.name,
+ entry.transfer.present? ? account_transfer_path(entry.transfer, origin:) : account_entry_path(account, entry, origin:),
data: { turbo_frame: "drawer", turbo_prefetch: false },
class: "hover:underline hover:text-gray-800" %>
<% end %>
@@ -30,46 +29,25 @@
<% if unconfirmed_transfer?(entry) %>
- <% if editable %>
- <%= form_with url: unmark_transfers_transactions_path, class: "flex items-center", data: {
- turbo_confirm: {
- title: t(".remove_transfer"),
- body: t(".remove_transfer_body"),
- accept: t(".remove_transfer_confirm"),
- },
- turbo_frame: "_top"
- } do |f| %>
- <%= f.hidden_field "bulk_update[entry_ids][]", value: entry.id %>
- <%= f.button class: "flex items-center justify-center group", title: "Remove transfer" do %>
- <%= lucide_icon "arrow-left-right", class: "group-hover:hidden text-gray-500 w-4 h-4" %>
- <%= lucide_icon "unlink", class: "hidden group-hover:inline-block text-gray-900 w-4 h-4" %>
- <% end %>
- <% end %>
- <% else %>
- <%= lucide_icon "arrow-left-right", class: "text-gray-500 w-4 h-4" %>
- <% end %>
+ <%= render "account/transfers/transfer_toggle", entry: entry %>
<% end %>
- <% unless entry.marked_as_transfer? %>
- <% unless short %>
-
">
- <% if editable %>
- <%= render "categories/menu", transaction: transaction %>
- <% else %>
- <%= render "categories/badge", category: transaction.category %>
- <% end %>
-
- <% if show_tags %>
- <% transaction.tags.each do |tag| %>
- <%= render partial: "tags/badge", locals: { tag: tag } %>
- <% end %>
- <% end %>
-
+ <% if entry.transfer.present? %>
+ <% unless show_balance %>
+
<% end %>
- <% unless show_tags %>
- <%= tag.div class: short ? "col-span-4" : "col-span-3" do %>
+
+ <%= render "account/transfers/account_logos", transfer: entry.transfer, outflow: entry.outflow? %>
+
+ <% else %>
+
+ <%= render "categories/menu", transaction: transaction, origin: origin %>
+
+
+ <% unless show_balance %>
+ <%= tag.div class: "col-span-2" do %>
<% if entry.new_record? %>
<%= tag.p account.name %>
<% else %>
@@ -87,4 +65,10 @@
format_money(-entry.amount_money),
class: ["text-green-600": entry.inflow?] %>
+
+ <% if show_balance %>
+
+ <%= tag.p format_money(entry.trend.current), class: "font-medium text-sm text-gray-900" %>
+
+ <% end %>
diff --git a/app/views/account/transactions/show.html.erb b/app/views/account/transactions/show.html.erb
index ecf22131..fe75a2f0 100644
--- a/app/views/account/transactions/show.html.erb
+++ b/app/views/account/transactions/show.html.erb
@@ -1,5 +1,7 @@
<% entry, transaction, account = @entry, @entry.account_transaction, @entry.account %>
+<% origin = params[:origin] %>
+
<%= drawer do %>
@@ -31,6 +33,7 @@
url: account_transaction_path(account, entry),
class: "space-y-2",
data: { controller: "auto-submit-form" } do |f| %>
+ <%= f.hidden_field :origin, value: origin %>
<%= f.text_field :name,
label: t(".name_label"),
"data-auto-submit-form-target": "auto" %>
@@ -73,6 +76,7 @@
url: account_transaction_path(account, entry),
class: "space-y-2",
data: { controller: "auto-submit-form" } do |f| %>
+ <%= f.hidden_field :origin, value: origin %>
<%= f.fields_for :entryable do |ef| %>
<% unless entry.marked_as_transfer? %>
<%= ef.collection_select :category_id,
@@ -110,6 +114,7 @@
url: account_transaction_path(account, entry),
class: "space-y-2",
data: { controller: "auto-submit-form" } do |f| %>
+ <%= f.hidden_field :origin, value: origin %>
<%= f.text_area :notes,
label: t(".note_label"),
placeholder: t(".note_placeholder"),
@@ -128,6 +133,7 @@
url: account_transaction_path(account, entry),
class: "p-3",
data: { controller: "auto-submit-form" } do |f| %>
+ <%= f.hidden_field :origin, value: origin %>
<%= t(".exclude_title") %>
@@ -138,28 +144,26 @@
<%= f.check_box :excluded,
class: "sr-only peer",
"data-auto-submit-form-target": "auto" %>
-
<% end %>
- <% unless entry.marked_as_transfer? %>
-
-
-
<%= t(".delete_title") %>
-
<%= t(".delete_subtitle") %>
-
+
+
+
<%= t(".delete_title") %>
+
<%= t(".delete_subtitle") %>
+
- <%= button_to t(".delete"),
+ <%= button_to t(".delete"),
account_entry_path(account, entry),
method: :delete,
class: "rounded-lg px-3 py-2 text-red-500 text-sm
font-medium border border-alpha-black-200",
data: { turbo_confirm: true, turbo_frame: "_top" } %>
-
- <% end %>
+
<% end %>
diff --git a/app/views/account/transfers/_account_logos.html.erb b/app/views/account/transfers/_account_logos.html.erb
new file mode 100644
index 00000000..2d668e33
--- /dev/null
+++ b/app/views/account/transfers/_account_logos.html.erb
@@ -0,0 +1,25 @@
+<%# locals: (transfer:, outflow: false) %>
+
+
+ <% if outflow %>
+ <%= link_to transfer.from_account, data: { turbo_frame: :_top }, class: "hover:opacity-90" do %>
+ <%= circle_logo(transfer.from_name[0].upcase, size: "sm") %>
+ <% end %>
+
+ <%= lucide_icon "arrow-right", class: "text-gray-500 w-4 h-4" %>
+
+ <%= link_to transfer.to_account, data: { turbo_frame: :_top }, class: "hover:opacity-90" do %>
+ <%= circle_logo(transfer.to_name[0].upcase, size: "sm") %>
+ <% end %>
+ <% else %>
+ <%= link_to transfer.to_account, data: { turbo_frame: :_top }, class: "hover:opacity-90" do %>
+ <%= circle_logo(transfer.to_name[0].upcase, size: "sm") %>
+ <% end %>
+
+ <%= lucide_icon "arrow-left", class: "text-gray-500 w-4 h-4" %>
+
+ <%= link_to transfer.from_account, data: { turbo_frame: :_top }, class: "hover:opacity-90" do %>
+ <%= circle_logo(transfer.from_name[0].upcase, size: "sm") %>
+ <% end %>
+ <% end %>
+
diff --git a/app/views/account/transfers/_transfer.html.erb b/app/views/account/transfers/_transfer.html.erb
deleted file mode 100644
index 1948b965..00000000
--- a/app/views/account/transfers/_transfer.html.erb
+++ /dev/null
@@ -1,49 +0,0 @@
-<%# locals: (transfer:, selectable: true, editable: true, short: false, **opts) %>
-
-<%= turbo_frame_tag dom_id(transfer) do %>
-
-
- <% if selectable %>
- <%= check_box_tag dom_id(transfer, "selection"),
- disabled: true,
- class: "mr-3 cursor-not-allowed maybe-checkbox maybe-checkbox--light" %>
- <% end %>
-
- <%= tag.div class: short ? "max-w-[250px]" : "max-w-[325px]" do %>
-
">
- <%= circle_logo(transfer.from_name[0].upcase) %>
-
- <%= tag.p transfer.name, class: "truncate text-gray-900" %>
-
- <% end %>
-
- <%= button_to account_transfer_path(transfer),
- method: :delete,
- class: "ml-2 flex items-center group/transfer hover:bg-gray-50 rounded-md p-1",
- data: {
- turbo_frame: "_top",
- turbo_confirm: {
- title: t(".remove_title"),
- body: t(".remove_body"),
- confirm: t(".remove_confirm")
- }
- } do %>
-
- <%= lucide_icon "link-2", class: "group-hover/transfer:hidden w-4 h-4 text-gray-500" %>
- <%= lucide_icon "unlink", class: "group-hover/transfer:inline-block hidden w-4 h-4 text-gray-500" %>
- <% end %>
-
-
- <% unless short %>
-
- <%= circle_logo(transfer.from_name[0].upcase, size: "sm") %>
- →
- <%= circle_logo(transfer.to_name[0].upcase, size: "sm") %>
-
- <% end %>
-
-
">
- <%= tag.p format_money(transfer.amount_money), class: "font-medium" %>
-
-
-<% end %>
diff --git a/app/views/account/transfers/_transfer_toggle.html.erb b/app/views/account/transfers/_transfer_toggle.html.erb
new file mode 100644
index 00000000..4b3b5e5a
--- /dev/null
+++ b/app/views/account/transfers/_transfer_toggle.html.erb
@@ -0,0 +1,16 @@
+<%# locals: (entry:) %>
+
+<%= form_with url: unmark_transfers_transactions_path, class: "flex items-center", data: {
+ turbo_confirm: {
+ title: t(".remove_transfer"),
+ body: t(".remove_transfer_body"),
+ accept: t(".remove_transfer_confirm"),
+ },
+ turbo_frame: "_top"
+ } do |f| %>
+ <%= f.hidden_field "bulk_update[entry_ids][]", value: entry.id %>
+ <%= f.button class: "flex items-center justify-center group", title: "Remove transfer" do %>
+ <%= lucide_icon "arrow-left-right", class: "group-hover:hidden text-gray-500 w-4 h-4" %>
+ <%= lucide_icon "unlink", class: "hidden group-hover:inline-block text-gray-900 w-4 h-4" %>
+ <% end %>
+<% end %>
diff --git a/app/views/account/transfers/show.html.erb b/app/views/account/transfers/show.html.erb
new file mode 100644
index 00000000..53e37ecf
--- /dev/null
+++ b/app/views/account/transfers/show.html.erb
@@ -0,0 +1,121 @@
+<%= drawer do %>
+
+
+
+
+ <%= disclosure t(".overview") do %>
+
+
+
+ To
+
+ <%= render "accounts/logo", account: @transfer.inflow_transaction.account, size: "sm" %>
+ <%= @transfer.to_name %>
+
+
+
+
+ Date
+ <%= l(@transfer.date, format: :long) %>
+
+
+
+ Amount
+ <%= format_money -@transfer.amount_money %>
+
+
+
+
+
+
+
+ From
+
+ <%= render "accounts/logo", account: @transfer.outflow_transaction.account, size: "sm" %>
+ <%= @transfer.from_name %>
+
+
+
+
+ Date
+ <%= l(@transfer.date, format: :long) %>
+
+
+
+ Amount
+ +<%= format_money @transfer.amount_money %>
+
+
+
+ <% end %>
+
+
+ <%= disclosure t(".details") do %>
+ <%= styled_form_with model: @transfer,
+ data: { controller: "auto-submit-form" } do |f| %>
+ <%= f.text_area :notes,
+ label: t(".note_label"),
+ placeholder: t(".note_placeholder"),
+ value: @transfer.outflow_transaction.notes,
+ rows: 5,
+ "data-auto-submit-form-target": "auto" %>
+ <% end %>
+ <% end %>
+
+
+ <%= disclosure t(".settings") do %>
+
+ <%= styled_form_with model: @transfer,
+ class: "p-3", data: { controller: "auto-submit-form" } do |f| %>
+
+
+
<%= t(".exclude_title") %>
+
<%= t(".exclude_subtitle") %>
+
+
+
+ <%= f.check_box :excluded,
+ checked: @transfer.inflow_transaction.excluded,
+ class: "sr-only peer",
+ "data-auto-submit-form-target": "auto" %>
+
+
+
+ <% end %>
+
+
+
+
<%= t(".delete_title") %>
+
<%= t(".delete_subtitle") %>
+
+
+ <%= button_to t(".delete"),
+ account_transfer_path(@transfer),
+ method: :delete,
+ class: "rounded-lg px-3 py-2 text-red-500 text-sm
+ font-medium border border-alpha-black-200",
+ data: { turbo_confirm: true, turbo_frame: "_top" } %>
+
+
+ <% end %>
+
+<% end %>
diff --git a/app/views/account/valuations/_form.html.erb b/app/views/account/valuations/_form.html.erb
index c28597c5..6eba543e 100644
--- a/app/views/account/valuations/_form.html.erb
+++ b/app/views/account/valuations/_form.html.erb
@@ -1,23 +1,13 @@
<%# locals: (entry:) %>
-<%= form_with model: [entry.account, entry],
- data: { turbo_frame: "_top" },
- url: entry.new_record? ? account_valuations_path(entry.account) : account_entry_path(entry.account, entry) do |f| %>
-
-
-
- <%= lucide_icon("pencil-line", class: "w-4 h-4 text-gray-500") %>
-
-
- <%= f.date_field :date, required: "required", min: Account::Entry.min_supported_date, max: Date.current, value: Date.current, class: "border border-alpha-black-200 bg-white rounded-lg shadow-xs min-w-[200px] px-3 py-1.5 text-gray-900 text-sm" %>
- <%= f.number_field :amount, required: "required", placeholder: "0.00", step: "0.01", class: "bg-white border border-alpha-black-200 rounded-lg shadow-xs text-gray-900 text-sm px-3 py-1.5 text-right" %>
- <%= f.hidden_field :currency, value: entry.account.currency %>
-
-
-
-
- <%= link_to t(".cancel"), account_valuations_path(entry.account), class: "text-sm text-gray-900 hover:text-gray-800 font-medium px-3 py-1.5" %>
- <%= f.submit class: "bg-gray-50 rounded-lg font-medium px-3 py-1.5 cursor-pointer hover:bg-gray-100 text-sm" %>
-
+<%= styled_form_with model: [entry.account, entry],
+ url: entry.new_record? ? account_valuations_path(entry.account) : account_entry_path(entry.account, entry),
+ class: "space-y-4",
+ data: { turbo: false } do |form| %>
+
+ <%= form.date_field :date, label: true, required: true, value: Date.current, min: Account::Entry.min_supported_date, max: Date.current %>
+ <%= form.money_field :amount, label: t(".amount"), required: true, default_currency: Current.family.currency %>
+
+ <%= form.submit t(".submit") %>
<% end %>
diff --git a/app/views/account/valuations/_valuation.html.erb b/app/views/account/valuations/_valuation.html.erb
index 895c5c9c..a0402be5 100644
--- a/app/views/account/valuations/_valuation.html.erb
+++ b/app/views/account/valuations/_valuation.html.erb
@@ -1,50 +1,39 @@
-<%# locals: (entry:, **opts) %>
+<%# locals: (entry:, selectable: true, show_balance: false, origin: nil) %>
<% account = entry.account %>
+<% valuation = entry.account_valuation %>
-<%= turbo_frame_tag dom_id(entry) do %>
- <% is_oldest = entry.first_of_type? %>
+
+
+ <% if selectable %>
+ <%= check_box_tag dom_id(entry, "selection"),
+ class: "maybe-checkbox maybe-checkbox--light",
+ data: { id: entry.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
+ <% end %>
-
-
- <%= tag.div class: "w-8 h-8 rounded-full p-1.5 flex items-center justify-center", style: entry_style(entry, is_oldest:).html_safe do %>
- <%= lucide_icon entry_icon(entry, is_oldest:), class: "w-4 h-4" %>
+
+ <%= tag.div class: "w-8 h-8 rounded-full p-1.5 flex items-center justify-center", style: mixed_hex_styles(valuation.color) do %>
+ <%= lucide_icon valuation.icon, class: "w-4 h-4" %>
<% end %>
-
- <%= tag.p entry.date, class: "text-gray-900 font-medium" %>
- <%= tag.p is_oldest ? t(".start_balance") : t(".value_update"), class: "text-gray-500" %>
+
+ <% if entry.new_record? %>
+ <%= content_tag :p, entry.name %>
+ <% else %>
+ <%= link_to valuation.name,
+ account_entry_path(account, entry),
+ data: { turbo_frame: "drawer", turbo_prefetch: false },
+ class: "hover:underline hover:text-gray-800" %>
+ <% end %>
-
-
- <%= tag.p format_money(entry.amount_money), class: "font-medium text-sm text-gray-900" %>
-
-
-
- <% if entry.trend.direction.flat? %>
- <%= tag.span t(".no_change"), class: "text-gray-500" %>
- <% else %>
- <%= tag.span format_money(entry.trend.value) %>
- <%= tag.span "(#{entry.trend.percent}%)" %>
- <% end %>
-
-
-
- <%= contextual_menu do %>
-
- <%= contextual_menu_modal_action_item t(".edit_entry"), edit_account_entry_path(account, entry), turbo_frame: dom_id(entry) %>
-
- <%= contextual_menu_destructive_item t(".delete_entry"),
- account_entry_path(account, entry),
- turbo_frame: "_top",
- turbo_confirm: {
- title: t(".confirm_title"),
- body: t(".confirm_body_html"),
- accept: t(".confirm_accept")
- } %>
-
- <% end %>
-
-<% end %>
+
+
+ <%= tag.span format_money(entry.trend.value) %>
+
+
+
+ <%= tag.p format_money(entry.amount_money), class: "font-medium text-sm text-gray-900" %>
+
+
diff --git a/app/views/account/valuations/new.html.erb b/app/views/account/valuations/new.html.erb
index b74a04c7..ee05f06b 100644
--- a/app/views/account/valuations/new.html.erb
+++ b/app/views/account/valuations/new.html.erb
@@ -1,4 +1,3 @@
-<%= turbo_frame_tag dom_id(@entry) do %>
- <%= render "account/valuations/form", entry: @entry %>
-
+<%= modal_form_wrapper title: t(".title") do %>
+ <%= render "form", entry: @entry %>
<% end %>
diff --git a/app/views/account/valuations/show.html.erb b/app/views/account/valuations/show.html.erb
index d8efac24..d6684795 100644
--- a/app/views/account/valuations/show.html.erb
+++ b/app/views/account/valuations/show.html.erb
@@ -1,3 +1,83 @@
-<% entry = @entry %>
+<% entry, account = @entry, @entry.account %>
-<%= render "account/valuations/valuation", entry: entry %>
+<%= drawer do %>
+
+
+
+
+ <%= disclosure t(".overview") do %>
+
+ <%= styled_form_with model: [account, entry],
+ url: account_entry_path(account, entry),
+ class: "space-y-2",
+ data: { controller: "auto-submit-form" } do |f| %>
+ <%= f.text_field :name,
+ label: t(".name_label"),
+ placeholder: t(".name_placeholder"),
+ "data-auto-submit-form-target": "auto" %>
+
+ <%= f.date_field :date,
+ label: t(".date_label"),
+ max: Date.current,
+ "data-auto-submit-form-target": "auto" %>
+
+ <%= f.money_field :amount,
+ label: t(".amount"),
+ auto_submit: true,
+ disable_currency: true %>
+ <% end %>
+
+ <% end %>
+
+
+ <%= disclosure t(".details") do %>
+
+ <%= styled_form_with model: [account, entry],
+ url: account_entry_path(account, entry),
+ class: "space-y-2",
+ data: { controller: "auto-submit-form" } do |f| %>
+ <%= f.text_area :notes,
+ label: t(".note_label"),
+ placeholder: t(".note_placeholder"),
+ rows: 5,
+ "data-auto-submit-form-target": "auto" %>
+ <% end %>
+
+ <% end %>
+
+
+ <%= disclosure t(".settings") do %>
+
+
+
+
+
<%= t(".delete_title") %>
+
<%= t(".delete_subtitle") %>
+
+
+ <%= button_to t(".delete"),
+ account_entry_path(account, entry),
+ method: :delete,
+ class: "rounded-lg px-3 py-2 text-red-500 text-sm font-medium border border-alpha-black-200",
+ data: { turbo_confirm: true, turbo_frame: "_top" } %>
+
+
+ <% end %>
+
+<% end %>
diff --git a/app/views/accounts/_account.html.erb b/app/views/accounts/_account.html.erb
index e8627e69..c0bb2904 100644
--- a/app/views/accounts/_account.html.erb
+++ b/app/views/accounts/_account.html.erb
@@ -1,3 +1,5 @@
+<%# locals: (account:, return_to: nil) %>
+
<%= turbo_frame_tag dom_id(account) do %>
@@ -16,7 +18,7 @@
<% end %>
- <%= link_to edit_account_path(account), data: { turbo_frame: :modal }, class: "group-hover/account:flex hidden hover:opacity-80 items-center justify-center" do %>
+ <%= link_to edit_account_path(account, return_to: return_to), data: { turbo_frame: :modal }, class: "group-hover/account:flex hidden hover:opacity-80 items-center justify-center" do %>
<%= lucide_icon "pencil-line", class: "w-4 h-4 text-gray-500" %>
<% end %>
diff --git a/app/views/accounts/_account_list.html.erb b/app/views/accounts/_account_list.html.erb
index 46e8004b..d86fe8d3 100644
--- a/app/views/accounts/_account_list.html.erb
+++ b/app/views/accounts/_account_list.html.erb
@@ -1,35 +1,37 @@
<%# locals: (group:) -%>
<% type = Accountable.from_type(group.name) %>
<% if group && group.children.any? %>
-
-
- <%= lucide_icon("chevron-down", class: "hidden group-open:block text-gray-500 w-5 h-5") %>
- <%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
+ <% group_trend = group.series.trend %>
+
+
+
+ <%= lucide_icon("chevron-down",
+ class: "hidden group-open:block text-gray-500 w-5 h-5") %>
+ <%= lucide_icon("chevron-right",
+ class: "group-open:hidden text-gray-500 w-5 h-5") %>
+
<%= type.model_name.human %>
+
<%= format_money group.sum %>
+
- <%=
- tag.div(
- id: "#{group.name}_sparkline",
- class: "h-3 w-8 ml-auto",
- data: {
- controller: "time-series-chart",
- "time-series-chart-data-value": group.series.to_json,
- "time-series-chart-stroke-width-value": 1,
- "time-series-chart-use-labels-value": false,
- "time-series-chart-use-tooltip-value": false
- }
- )
- %>
- <% styles = trend_styles(group.series.trend) %>
-
<%= sprintf("%+.2f", group.series.trend.percent) %>%
+
+ <%= render "shared/sparkline", series: group.series, id: "#{group.name}_sparkline" %>
+
+
+
<%= group_trend.value.positive? ? "+" : "" %><%= group_trend.percent.infinite? ? "∞" : number_to_percentage(group_trend.percent, precision: 0) %>
<% group.children.sort_by(&:name).each do |account_value_node| %>
<% account = account_value_node.original %>
- <%= link_to account_path(account), class: "flex items-center w-full gap-3 px-3 py-2 mb-1 hover:bg-gray-100 rounded-[10px]" do %>
+ <% account_trend = account_value_node.series.trend %>
+ <%= link_to account, class: "flex items-center w-full gap-3 px-3 py-2 mb-1 hover:bg-gray-100 rounded-[10px]" do %>
<%= render "accounts/logo", account: account, size: "sm" %>
<%= account_value_node.name %>
@@ -37,31 +39,21 @@
<%= account.subtype&.humanize %>
<% end %>
-
+
<%= format_money account.balance_money %>
- <% unless account_value_node.series.trend.direction.flat? %>
-
- <%=
- tag.div(
- id: dom_id(account, :list_sparkline),
- class: "h-3 w-8 ml-auto",
- data: {
- controller: "time-series-chart",
- "time-series-chart-data-value": account_value_node.series.to_json,
- "time-series-chart-stroke-width-value": 1,
- "time-series-chart-use-labels-value": false,
- "time-series-chart-use-tooltip-value": false
- }
- )
- %>
- <% styles = trend_styles(account_value_node.series.trend) %>
-
<%= sprintf("%+.2f", account_value_node.series.trend.percent) %>%
+
+
+ <%= render "shared/sparkline", series: account_value_node.series, id: dom_id(account, :list_sparkline) %>
- <% end %>
+
+
+ <%= account_trend.value.positive? ? "+" : "" %><%= account_trend.percent.infinite? ? "∞" : number_to_percentage(account_trend.percent, precision: 0) %>
+
+
<% end %>
<% end %>
- <%= link_to new_account_path(step: "method", type: type.name.demodulize), class: "flex items-center min-h-10 gap-4 px-3 py-2 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_polymorphic_path(type, step: "method_select"), class: "flex items-center min-h-10 gap-4 px-3 py-2 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new_account", type: type.model_name.human.downcase) %>
<% end %>
diff --git a/app/views/accounts/_account_type.html.erb b/app/views/accounts/_account_type.html.erb
index d4b69527..a82915c4 100644
--- a/app/views/accounts/_account_type.html.erb
+++ b/app/views/accounts/_account_type.html.erb
@@ -1,10 +1,9 @@
-<%= link_to new_account_path(
- type: type.class.name.demodulize,
- institution_id: params[:institution_id]
- ),
+<%# locals: (accountable:) %>
+
+<%= link_to new_polymorphic_path(accountable, institution_id: params[:institution_id], step: "method_select", return_to: params[:return_to]),
class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-alpha-black-25 hover:bg-alpha-black-25 border border-transparent block px-2 rounded-lg p-2" do %>
-
- <%= lucide_icon(icon, class: "#{text_color} w-5 h-5") %>
+
+ <%= lucide_icon(accountable.icon, style: "color: #{accountable.color}", class: "w-5 h-5") %>
- <%= type.model_name.human %>
+ <%= accountable.model_name.human %>
<% end %>
diff --git a/app/views/accounts/_empty.html.erb b/app/views/accounts/_empty.html.erb
index 68c64316..e938c863 100644
--- a/app/views/accounts/_empty.html.erb
+++ b/app/views/accounts/_empty.html.erb
@@ -3,7 +3,7 @@
<%= tag.p t(".no_accounts"), class: "text-gray-900 mb-1 font-medium" %>
<%= tag.p t(".empty_message"), class: "text-gray-500 mb-4" %>
- <%= link_to new_account_path(step: "method"), class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2 pr-3", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path, class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2 pr-3", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new_account") %>
<% end %>
diff --git a/app/views/accounts/_entry_method.html.erb b/app/views/accounts/_entry_method.html.erb
deleted file mode 100644
index 1e9b6971..00000000
--- a/app/views/accounts/_entry_method.html.erb
+++ /dev/null
@@ -1,17 +0,0 @@
-<%# locals: (text:, icon:, disabled: false) %>
-
-<% if disabled %>
-
-
- <%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
-
- <%= text %>
-
-<% else %>
- <%= link_to new_account_path(institution_id: params[:institution_id]), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
-
- <%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
-
- <%= text %>
- <% end %>
-<% end %>
diff --git a/app/views/accounts/_form.html.erb b/app/views/accounts/_form.html.erb
index b5d17930..88e30bea 100644
--- a/app/views/accounts/_form.html.erb
+++ b/app/views/accounts/_form.html.erb
@@ -1,28 +1,21 @@
<%# locals: (account:, url:) %>
-<%= styled_form_with model: account, url: url, scope: :account, class: "flex flex-col gap-4 justify-between grow", data: { turbo: false } do |f| %>
+<%= styled_form_with model: account, url: url, scope: :account, data: { turbo: false }, class: "flex flex-col gap-4 justify-between grow" do |form| %>
- <% unless account.new_record? %>
- <% if account.accountable.mode_required? %>
- <%= f.select :mode, Account::VALUE_MODES.map { |mode| [mode.titleize, mode] }, { label: t(".mode"), prompt: t(".mode_prompt") }, required: true %>
- <% end %>
- <% end %>
-
- <%= f.select :accountable_type, Accountable::TYPES.map { |type| [type.titleize, type] }, { label: t(".accountable_type"), prompt: t(".type_prompt") }, required: true, autofocus: true %>
- <%= f.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label") %>
+ <%= form.hidden_field :accountable_type %>
+ <%= form.hidden_field :return_to, value: params[:return_to] %>
<% if account.new_record? %>
- <%= f.hidden_field :institution_id %>
+ <%= form.hidden_field :institution_id %>
<% else %>
- <%= f.collection_select :institution_id, Current.family.institutions.alphabetically, :id, :name, { include_blank: t(".ungrouped"), label: t(".institution") } %>
+ <%= form.collection_select :institution_id, Current.family.institutions.alphabetically, :id, :name, { include_blank: t(".ungrouped"), label: t(".institution") } %>
<% end %>
- <%= f.money_field :balance, label: t(".balance"), required: true, default_currency: Current.family.currency %>
+ <%= form.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label") %>
+ <%= form.money_field :balance, label: t(".balance"), required: true, default_currency: Current.family.currency %>
- <% if account.accountable %>
- <%= render permitted_accountable_partial(account, "form"), f: f %>
- <% end %>
+ <%= yield form %>
- <%= f.submit %>
+ <%= form.submit %>
<% end %>
diff --git a/app/views/accounts/_overview.html.erb b/app/views/accounts/_overview.html.erb
deleted file mode 100644
index d586e780..00000000
--- a/app/views/accounts/_overview.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<%# locals: (account:) %>
-
-<%= render partial: "accounts/accountables/#{account.accountable_type.underscore}/overview", locals: { account: account } %>
diff --git a/app/views/accounts/accountables/_default_header.html.erb b/app/views/accounts/accountables/_default_header.html.erb
deleted file mode 100644
index 5feae417..00000000
--- a/app/views/accounts/accountables/_default_header.html.erb
+++ /dev/null
@@ -1,7 +0,0 @@
-
- <%= render "accounts/logo", account: account %>
-
-
-
<%= account.name %>
-
-
diff --git a/app/views/accounts/accountables/_default_tabs.html.erb b/app/views/accounts/accountables/_default_tabs.html.erb
deleted file mode 100644
index 9b6274ef..00000000
--- a/app/views/accounts/accountables/_default_tabs.html.erb
+++ /dev/null
@@ -1,13 +0,0 @@
-<%# locals: (account:, selected_tab:) %>
-
-<% if account.mode.nil? %>
- <%= render "accounts/accountables/value_onboarding", account: account %>
-<% else %>
-
- <% if account.mode == "transactions" %>
- <%= render "accounts/accountables/transactions", account: account %>
- <% else %>
- <%= render "accounts/accountables/valuations", account: account %>
- <% end %>
-
-<% end %>
diff --git a/app/views/accounts/accountables/_transactions.html.erb b/app/views/accounts/accountables/_transactions.html.erb
deleted file mode 100644
index d6943303..00000000
--- a/app/views/accounts/accountables/_transactions.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<%# locals: (account:) %>
-
-<%= turbo_frame_tag dom_id(account, :transactions), src: account_transactions_path(account) do %>
- <%= render "account/entries/loading" %>
-<% end %>
diff --git a/app/views/accounts/accountables/_valuations.html.erb b/app/views/accounts/accountables/_valuations.html.erb
deleted file mode 100644
index b4bf1fc3..00000000
--- a/app/views/accounts/accountables/_valuations.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<%# locals: (account:) %>
-
-<%= turbo_frame_tag dom_id(account, :valuations), src: account_valuations_path(account) do %>
- <%= render "account/entries/loading" %>
-<% end %>
diff --git a/app/views/accounts/accountables/_value_onboarding.html.erb b/app/views/accounts/accountables/_value_onboarding.html.erb
deleted file mode 100644
index 27798add..00000000
--- a/app/views/accounts/accountables/_value_onboarding.html.erb
+++ /dev/null
@@ -1,16 +0,0 @@
-<%# locals: (account:) %>
-
-
-
How would you like to track value for this account?
-
We will use this to determine what data to show for this account.
-
- <%= button_to account_path(account, { account: { mode: "balance" } }), method: :put, class: "btn btn--outline", data: { controller: "tooltip", turbo: false } do %>
- <%= render partial: "shared/text_tooltip", locals: { tooltip_text: "Choose this if you only need to track the historical value of this account over time and do not plan on importing any transactions." } %>
- Balance only
- <% end %>
- <%= button_to account_path(account, { account: { mode: "transactions" } }), method: :put, class: "btn btn--primary", data: { controller: "tooltip", turbo: false } do %>
- <%= render partial: "shared/text_tooltip", locals: { tooltip_text: "Choose this if you plan on importing transactions into this account for budgeting and other analytics." } %>
- Transactions
- <% end %>
-
-
diff --git a/app/views/accounts/accountables/credit_card/_form.html.erb b/app/views/accounts/accountables/credit_card/_form.html.erb
deleted file mode 100644
index 7e3d573a..00000000
--- a/app/views/accounts/accountables/credit_card/_form.html.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
- <%= f.fields_for :accountable do |credit_card_form| %>
-
- <%= credit_card_form.number_field :available_credit, label: t(".available_credit"), placeholder: t(".available_credit_placeholder"), min: 0 %>
-
-
-
- <%= credit_card_form.number_field :minimum_payment, label: t(".minimum_payment"), placeholder: t(".minimum_payment_placeholder"), min: 0 %>
- <%= credit_card_form.number_field :apr, label: t(".apr"), placeholder: t(".apr_placeholder"), min: 0, step: 0.01 %>
-
-
-
- <%= credit_card_form.date_field :expiration_date, label: t(".expiration_date") %>
- <%= credit_card_form.number_field :annual_fee, label: t(".annual_fee"), placeholder: t(".annual_fee_placeholder"), min: 0 %>
-
- <% end %>
-
-
diff --git a/app/views/accounts/accountables/credit_card/_header.html.erb b/app/views/accounts/accountables/credit_card/_header.html.erb
deleted file mode 100644
index 51889135..00000000
--- a/app/views/accounts/accountables/credit_card/_header.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/credit_card/_tabs.html.erb b/app/views/accounts/accountables/credit_card/_tabs.html.erb
deleted file mode 100644
index d08ff8e2..00000000
--- a/app/views/accounts/accountables/credit_card/_tabs.html.erb
+++ /dev/null
@@ -1,26 +0,0 @@
-<%# locals: (account:, selected_tab:) %>
-
-<% if account.mode.nil? %>
- <%= render "accounts/accountables/value_onboarding", account: account %>
-<% else %>
-
- <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
-
- <% if account.mode == "transactions" %>
- <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
- <% else %>
- <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
- <% end %>
-
-
-
- <% case selected_tab %>
- <% when nil, "overview" %>
- <%= render "accounts/accountables/credit_card/overview", account: account %>
- <% when "transactions" %>
- <%= render "accounts/accountables/transactions", account: account %>
- <% when "value" %>
- <%= render "accounts/accountables/valuations", account: account %>
- <% end %>
-
-<% end %>
diff --git a/app/views/accounts/accountables/crypto/_form.html.erb b/app/views/accounts/accountables/crypto/_form.html.erb
deleted file mode 100644
index e69de29b..00000000
diff --git a/app/views/accounts/accountables/crypto/_header.html.erb b/app/views/accounts/accountables/crypto/_header.html.erb
deleted file mode 100644
index 51889135..00000000
--- a/app/views/accounts/accountables/crypto/_header.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/crypto/_tabs.html.erb b/app/views/accounts/accountables/crypto/_tabs.html.erb
deleted file mode 100644
index be74694e..00000000
--- a/app/views/accounts/accountables/crypto/_tabs.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_tabs", account: account, selected_tab: selected_tab %>
diff --git a/app/views/accounts/accountables/depository/_form.html.erb b/app/views/accounts/accountables/depository/_form.html.erb
deleted file mode 100644
index 0504400a..00000000
--- a/app/views/accounts/accountables/depository/_form.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= f.select :subtype, [["Checking", "checking"], ["Savings", "savings"]], { label: true, prompt: t(".prompt"), include_blank: t(".none") } %>
diff --git a/app/views/accounts/accountables/depository/_header.html.erb b/app/views/accounts/accountables/depository/_header.html.erb
deleted file mode 100644
index 51889135..00000000
--- a/app/views/accounts/accountables/depository/_header.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/depository/_tabs.html.erb b/app/views/accounts/accountables/depository/_tabs.html.erb
deleted file mode 100644
index be74694e..00000000
--- a/app/views/accounts/accountables/depository/_tabs.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_tabs", account: account, selected_tab: selected_tab %>
diff --git a/app/views/accounts/accountables/investment/_form.html.erb b/app/views/accounts/accountables/investment/_form.html.erb
deleted file mode 100644
index 86355b07..00000000
--- a/app/views/accounts/accountables/investment/_form.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= f.select :subtype, Investment::SUBTYPES, { label: true, prompt: t(".prompt"), include_blank: t(".none") } %>
diff --git a/app/views/accounts/accountables/investment/_header.html.erb b/app/views/accounts/accountables/investment/_header.html.erb
deleted file mode 100644
index 51889135..00000000
--- a/app/views/accounts/accountables/investment/_header.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/investment/_tabs.html.erb b/app/views/accounts/accountables/investment/_tabs.html.erb
deleted file mode 100644
index bff1d3ce..00000000
--- a/app/views/accounts/accountables/investment/_tabs.html.erb
+++ /dev/null
@@ -1,34 +0,0 @@
-<%# locals: (account:, selected_tab:) %>
-
-<% if account.mode.nil? %>
- <%= render "accounts/accountables/value_onboarding", account: account %>
-<% else %>
-
- <% if account.mode == "transactions" %>
- <%= render "accounts/accountables/tab", account: account, key: "holdings", is_selected: selected_tab.in?([nil, "holdings"]) %>
- <%= render "accounts/accountables/tab", account: account, key: "cash", is_selected: selected_tab == "cash" %>
- <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
- <% end %>
-
-
-
- <% if account.mode == "transactions" %>
- <% case selected_tab %>
- <% when nil, "holdings" %>
- <%= turbo_frame_tag dom_id(account, :holdings), src: account_holdings_path(account) do %>
- <%= render "account/entries/loading" %>
- <% end %>
- <% when "cash" %>
- <%= turbo_frame_tag dom_id(account, :cash), src: account_cashes_path(account) do %>
- <%= render "account/entries/loading" %>
- <% end %>
- <% when "transactions" %>
- <%= turbo_frame_tag dom_id(account, :trades), src: account_trades_path(account) do %>
- <%= render "account/entries/loading" %>
- <% end %>
- <% end %>
- <% else %>
- <%= render "accounts/accountables/valuations", account: account %>
- <% end %>
-
-<% end %>
diff --git a/app/views/accounts/accountables/loan/_form.html.erb b/app/views/accounts/accountables/loan/_form.html.erb
deleted file mode 100644
index d44b1037..00000000
--- a/app/views/accounts/accountables/loan/_form.html.erb
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
- <%= f.fields_for :accountable do |loan_form| %>
-
- <%= loan_form.number_field :interest_rate, label: t(".interest_rate"), placeholder: t(".interest_rate_placeholder"), min: 0, step: 0.01 %>
- <%= loan_form.select :rate_type, options_for_select([["Fixed", "fixed"], ["Variable", "variable"], ["Adjustable", "adjustable"]]), { label: t(".rate_type") } %>
-
-
-
- <%= loan_form.number_field :term_months, label: t(".term_months"), placeholder: t(".term_months_placeholder") %>
-
- <% end %>
-
-
diff --git a/app/views/accounts/accountables/loan/_header.html.erb b/app/views/accounts/accountables/loan/_header.html.erb
deleted file mode 100644
index 51889135..00000000
--- a/app/views/accounts/accountables/loan/_header.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/loan/_tabs.html.erb b/app/views/accounts/accountables/loan/_tabs.html.erb
deleted file mode 100644
index 8c5ca4fe..00000000
--- a/app/views/accounts/accountables/loan/_tabs.html.erb
+++ /dev/null
@@ -1,25 +0,0 @@
-<%# locals: (account:, selected_tab:) %>
-
-<% if account.mode.nil? %>
- <%= render "accounts/accountables/value_onboarding", account: account %>
-<% else %>
-
- <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
- <% if account.mode == "transactions" %>
- <%= render "accounts/accountables/tab", account: account, key: "transactions", is_selected: selected_tab == "transactions" %>
- <% else %>
- <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
- <% end %>
-
-
-
- <% case selected_tab %>
- <% when nil, "overview" %>
- <%= render "accounts/accountables/loan/overview", account: account %>
- <% when "transactions" %>
- <%= render "accounts/accountables/transactions", account: account %>
- <% when "value" %>
- <%= render "accounts/accountables/valuations", account: account %>
- <% end %>
-
-<% end %>
diff --git a/app/views/accounts/accountables/other_asset/_form.html.erb b/app/views/accounts/accountables/other_asset/_form.html.erb
deleted file mode 100644
index e69de29b..00000000
diff --git a/app/views/accounts/accountables/other_asset/_header.html.erb b/app/views/accounts/accountables/other_asset/_header.html.erb
deleted file mode 100644
index 51889135..00000000
--- a/app/views/accounts/accountables/other_asset/_header.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/other_asset/_tabs.html.erb b/app/views/accounts/accountables/other_asset/_tabs.html.erb
deleted file mode 100644
index 480136bf..00000000
--- a/app/views/accounts/accountables/other_asset/_tabs.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/valuations", account: account %>
diff --git a/app/views/accounts/accountables/other_liability/_form.html.erb b/app/views/accounts/accountables/other_liability/_form.html.erb
deleted file mode 100644
index e69de29b..00000000
diff --git a/app/views/accounts/accountables/other_liability/_header.html.erb b/app/views/accounts/accountables/other_liability/_header.html.erb
deleted file mode 100644
index 51889135..00000000
--- a/app/views/accounts/accountables/other_liability/_header.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/other_liability/_tabs.html.erb b/app/views/accounts/accountables/other_liability/_tabs.html.erb
deleted file mode 100644
index 480136bf..00000000
--- a/app/views/accounts/accountables/other_liability/_tabs.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/valuations", account: account %>
diff --git a/app/views/accounts/accountables/property/_form.html.erb b/app/views/accounts/accountables/property/_form.html.erb
deleted file mode 100644
index 67ccc042..00000000
--- a/app/views/accounts/accountables/property/_form.html.erb
+++ /dev/null
@@ -1,36 +0,0 @@
-<%# locals: (f:) %>
-
-
-
-
-
<%= t(".additional_info") %> (<%= t(".optional") %>)
-
-
- <%= f.fields_for :accountable do |af| %>
-
- <%= af.number_field :year_built, label: t(".year_built"), placeholder: 2005, min: 1700, max: Time.current.year %>
- <%= af.number_field :area_value, label: t(".area_value"), placeholder: 2000, min: 1 %>
- <%= af.select :area_unit,
- [["Square feet", "sqft"], ["Square meters", "sqm"]],
- { label: t(".area_unit") } %>
-
-
- <%= af.fields_for :address do |address_form| %>
-
- <%= address_form.text_field :line1, label: t(".line1"), placeholder: "123 Main St" %>
- <%= address_form.text_field :line2, label: t(".line2"), placeholder: "Apt 1" %>
-
-
-
- <%= address_form.text_field :locality, label: t(".city"), placeholder: "Sacramento" %>
- <%= address_form.text_field :region, label: t(".state"), placeholder: "CA" %>
-
-
-
- <%= address_form.text_field :postal_code, label: t(".postal_code"), placeholder: "95814" %>
- <%= address_form.text_field :country, label: t(".country"), placeholder: "USA" %>
-
- <% end %>
- <% end %>
-
-
diff --git a/app/views/accounts/accountables/property/_header.html.erb b/app/views/accounts/accountables/property/_header.html.erb
deleted file mode 100644
index 27701588..00000000
--- a/app/views/accounts/accountables/property/_header.html.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-
- <%= render "accounts/logo", account: account %>
-
-
-
<%= account.name %>
-
- <% if account.property.address&.line1.present? %>
-
<%= account.property.address %>
- <% end %>
-
-
diff --git a/app/views/accounts/accountables/property/_tabs.html.erb b/app/views/accounts/accountables/property/_tabs.html.erb
deleted file mode 100644
index 07ff76a6..00000000
--- a/app/views/accounts/accountables/property/_tabs.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<%# locals: (account:, selected_tab:) %>
-
-
- <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
- <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
-
-
-
- <% case selected_tab %>
- <% when nil, "overview" %>
- <%= render "accounts/accountables/property/overview", account: account %>
- <% when "value" %>
- <%= render "accounts/accountables/valuations", account: account %>
- <% end %>
-
diff --git a/app/views/accounts/accountables/vehicle/_form.html.erb b/app/views/accounts/accountables/vehicle/_form.html.erb
deleted file mode 100644
index b8baeed1..00000000
--- a/app/views/accounts/accountables/vehicle/_form.html.erb
+++ /dev/null
@@ -1,23 +0,0 @@
-<%# locals: (f:) %>
-
-
-
-
-
- <%= f.fields_for :accountable do |vehicle_form| %>
-
-
- <%= vehicle_form.text_field :make, label: t(".make"), placeholder: t(".make_placeholder") %>
- <%= vehicle_form.text_field :model, label: t(".model"), placeholder: t(".model_placeholder") %>
-
-
-
- <%= vehicle_form.number_field :year, label: t(".year"), placeholder: t(".year_placeholder"), min: 1900, max: Time.current.year %>
- <%= vehicle_form.number_field :mileage_value, label: t(".mileage"), placeholder: t(".mileage_placeholder"), min: 0 %>
- <%= vehicle_form.select :mileage_unit,
- [["Miles", "mi"], ["Kilometers", "km"]],
- { label: t(".mileage_unit") } %>
-
- <% end %>
-
-
diff --git a/app/views/accounts/accountables/vehicle/_header.html.erb b/app/views/accounts/accountables/vehicle/_header.html.erb
deleted file mode 100644
index 51889135..00000000
--- a/app/views/accounts/accountables/vehicle/_header.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "accounts/accountables/default_header", account: account %>
diff --git a/app/views/accounts/accountables/vehicle/_tabs.html.erb b/app/views/accounts/accountables/vehicle/_tabs.html.erb
deleted file mode 100644
index a79c89c2..00000000
--- a/app/views/accounts/accountables/vehicle/_tabs.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<%# locals: (account:, selected_tab:) %>
-
-
- <%= render "accounts/accountables/tab", account: account, key: "overview", is_selected: selected_tab.in?([nil, "overview"]) %>
- <%= render "accounts/accountables/tab", account: account, key: "value", is_selected: selected_tab == "value" %>
-
-
-
- <% case selected_tab %>
- <% when nil, "overview" %>
- <%= render "accounts/accountables/vehicle/overview", account: account %>
- <% when "value" %>
- <%= render "accounts/accountables/valuations", account: account %>
- <% end %>
-
diff --git a/app/views/accounts/index.html.erb b/app/views/accounts/index.html.erb
index fedf238e..431fe4cd 100644
--- a/app/views/accounts/index.html.erb
+++ b/app/views/accounts/index.html.erb
@@ -20,7 +20,7 @@
<%= render "sync_all_button" %>
- <%= link_to new_account_path(step: "method"),
+ <%= link_to new_account_path(return_to: accounts_path),
data: { turbo_frame: "modal" },
class: "btn btn--primary flex items-center gap-1" do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
@@ -35,11 +35,11 @@
<% else %>
<% @institutions.each do |institution| %>
- <%= render "institution_accounts", institution: %>
+ <%= render "accounts/index/institution_accounts", institution: %>
<% end %>
<% if @accounts.any? %>
- <%= render "institutionless_accounts", accounts: @accounts %>
+ <%= render "accounts/index/institutionless_accounts", accounts: @accounts %>
<% end %>
<% end %>
diff --git a/app/views/accounts/_institution_accounts.html.erb b/app/views/accounts/index/_institution_accounts.html.erb
similarity index 95%
rename from app/views/accounts/_institution_accounts.html.erb
rename to app/views/accounts/index/_institution_accounts.html.erb
index 9554f1f4..a1127077 100644
--- a/app/views/accounts/_institution_accounts.html.erb
+++ b/app/views/accounts/index/_institution_accounts.html.erb
@@ -40,7 +40,7 @@
<%= contextual_menu do %>
- <%= link_to new_account_path(institution_id: institution.id),
+ <%= link_to new_account_path(institution_id: institution.id, return_to: accounts_path),
class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg",
data: { turbo_frame: :modal } do %>
<%= lucide_icon "plus", class: "w-5 h-5 text-gray-500" %>
@@ -81,7 +81,7 @@
<% else %>
There are no accounts in this financial institution
- <%= link_to new_account_path(institution_id: institution.id), class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-1.5 pr-2", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path(institution_id: institution.id, return_to: accounts_path), class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-1.5 pr-2", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-4 h-4") %>
<%= t(".new_account") %>
<% end %>
diff --git a/app/views/accounts/_institutionless_accounts.html.erb b/app/views/accounts/index/_institutionless_accounts.html.erb
similarity index 100%
rename from app/views/accounts/_institutionless_accounts.html.erb
rename to app/views/accounts/index/_institutionless_accounts.html.erb
diff --git a/app/views/accounts/new.html.erb b/app/views/accounts/new.html.erb
index 9e8a562f..02c1b3f8 100644
--- a/app/views/accounts/new.html.erb
+++ b/app/views/accounts/new.html.erb
@@ -1,53 +1,24 @@
-
<%= t(".title") %>
-<%= modal do %>
-
- <% if params[:step] == 'method' %>
-
- How would you like to add it?
-
-
-
Previous
-
Next
+<%= render layout: "accounts/new/container", locals: { title: t(".title") } do %>
+
+ <%= render "account_type", accountable: Depository.new %>
+ <%= render "account_type", accountable: Investment.new %>
+ <%= render "account_type", accountable: Crypto.new %>
+ <%= render "account_type", accountable: Property.new %>
+ <%= render "account_type", accountable: Vehicle.new %>
+ <%= render "account_type", accountable: CreditCard.new %>
+ <%= render "account_type", accountable: Loan.new %>
+ <%= render "account_type", accountable: OtherAsset.new %>
+ <%= render "account_type", accountable: OtherLiability.new %>
- <%= render "entry_method", text: t(".manual_entry"), icon: "keyboard" %>
-
- <%= link_to new_import_path(import: { type: "AccountImport" }), class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
-
- <%= lucide_icon("sheet", class: "text-gray-500 w-5 h-5") %>
-
- <%= t(".csv_entry") %>
- <% end %>
-
- <%= render "entry_method", text: t(".connected_entry"), icon: "link-2", disabled: true %>
-
-
-
-
- Select
- <%= lucide_icon("corner-down-left", class: "inline w-3 h-3") %>
-
-
- Navigate
- <%= lucide_icon("arrow-up", class: "inline w-3 h-3") %>
- <%= lucide_icon("arrow-down", class: "inline w-3 h-3") %>
-
-
-
- Close
- ESC
-
-
- <% else %>
-
- <%= link_to new_account_path(step: "method"), class: "flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 focus:outline-gray-300 focus:outline" do %>
- <%= lucide_icon("arrow-left", class: "text-gray-500 w-5 h-5") %>
- <% end %>
- Add account
-
-
-
- <%= render "form", account: @account, url: new_account_form_url(@account) %>
-
+ <% unless params[:return_to].present? %>
+ <%= button_to imports_path(import: { type: "AccountImport" }),
+ data: { turbo_frame: :_top },
+ class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-alpha-black-25 hover:bg-alpha-black-25 border border-transparent block px-2 rounded-lg p-2" do %>
+
+ <%= lucide_icon("download", style: "color: #F79009", class: "w-5 h-5") %>
+
+ <%= t("accounts.new.import_accounts") %>
+ <% end %>
<% end %>
<% end %>
diff --git a/app/views/accounts/new/_container.html.erb b/app/views/accounts/new/_container.html.erb
new file mode 100644
index 00000000..2ef85d8a
--- /dev/null
+++ b/app/views/accounts/new/_container.html.erb
@@ -0,0 +1,40 @@
+<%# locals: (title:, back_path: nil) %>
+
+<%= modal do %>
+
+
+ <% if back_path %>
+ <%= link_to back_path, class: "flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 focus:outline-gray-300 focus:outline" do %>
+ <%= lucide_icon("arrow-left", class: "text-gray-500 w-5 h-5") %>
+ <% end %>
+ <% end %>
+
+ <%= title %>
+
+
+
+ Previous
+ Next
+
+ <%= yield %>
+
+
+
+
+
+ Select
+ <%= lucide_icon("corner-down-left", class: "inline w-3 h-3") %>
+
+
+ Navigate
+ <%= lucide_icon("arrow-up", class: "inline w-3 h-3") %>
+ <%= lucide_icon("arrow-down", class: "inline w-3 h-3") %>
+
+
+
+ Close
+ ESC
+
+
+
+<% end %>
diff --git a/app/views/accounts/new/_method_selector.html.erb b/app/views/accounts/new/_method_selector.html.erb
new file mode 100644
index 00000000..2760ff4f
--- /dev/null
+++ b/app/views/accounts/new/_method_selector.html.erb
@@ -0,0 +1,19 @@
+<%# locals: (path:) %>
+
+<%= render layout: "accounts/new/container", locals: { title: t(".title"), back_path: new_account_path } do %>
+
+ <%= link_to path, class: "flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2" do %>
+
+ <%= lucide_icon("keyboard", class: "text-gray-500 w-5 h-5") %>
+
+ <%= t("accounts.new.method_selector.manual_entry") %>
+ <% end %>
+
+
+
+ <%= lucide_icon("link-2", class: "text-gray-500 w-5 h-5") %>
+
+ <%= t("accounts.new.method_selector.connected_entry") %>
+
+
+<% end %>
diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb
deleted file mode 100644
index d250a38f..00000000
--- a/app/views/accounts/show.html.erb
+++ /dev/null
@@ -1,65 +0,0 @@
-<%= turbo_stream_from @account %>
-
-<% series = @account.series(period: @period) %>
-<% trend = series.trend %>
-
-<%= tag.div id: dom_id(@account), class: "space-y-4" do %>
-
- <%= render permitted_accountable_partial(@account, "header"), account: @account %>
-
-
- <%= button_to sync_account_path(@account), method: :post, class: "flex items-center gap-2", title: "Sync Account" do %>
- <%= lucide_icon "refresh-cw", class: "w-4 h-4 text-gray-500 hover:text-gray-400" %>
- <% end %>
-
- <%= render "menu", account: @account %>
-
-
-
- <% if @account.highest_priority_issue %>
- <%= render partial: "issues/issue", locals: { issue: @account.highest_priority_issue } %>
- <% end %>
-
-
-
-
-
-
- <% if @account.asset? %>
- <%= tag.p t(".total_value"), class: "text-sm font-medium text-gray-500" %>
- <% else %>
- <%= tag.p t(".total_owed"), class: "text-sm font-medium text-gray-500" %>
- <% end %>
-
-
- <%= render permitted_accountable_partial(@account, "tooltip"), account: @account if @account.investment? %>
-
-
- <%= tag.p format_money(@account.value), class: "text-gray-900 text-3xl font-medium" %>
-
-
- <% if trend.direction.flat? %>
- <%= tag.span t(".no_change"), class: "text-gray-500" %>
- <% else %>
- <%= tag.span format_money(trend.value), style: "color: #{trend.color}" %>
- <%= tag.span "(#{trend.percent}%)", style: "color: #{trend.color}" %>
- <% end %>
-
- <%= tag.span period_label(@period), class: "text-gray-500" %>
-
-
-
- <%= form_with url: account_path(@account), method: :get, data: { controller: "auto-submit-form" } do |form| %>
- <%= period_select form: form, selected: @period.name %>
- <% end %>
-
-
-
- <%= render "shared/line_chart", series: @account.series(period: @period) %>
-
-
-
-
- <%= render permitted_accountable_partial(@account, "tabs"), account: @account, selected_tab: params[:tab] %>
-
-<% end %>
diff --git a/app/views/accounts/show/_activity.html.erb b/app/views/accounts/show/_activity.html.erb
new file mode 100644
index 00000000..4bf4e2ed
--- /dev/null
+++ b/app/views/accounts/show/_activity.html.erb
@@ -0,0 +1,5 @@
+<%# locals: (account:) %>
+
+<%= turbo_frame_tag dom_id(account, :entries), src: account_entries_path(account) do %>
+ <%= render "account/entries/loading" %>
+<% end %>
diff --git a/app/views/accounts/show/_chart.html.erb b/app/views/accounts/show/_chart.html.erb
new file mode 100644
index 00000000..c3762c62
--- /dev/null
+++ b/app/views/accounts/show/_chart.html.erb
@@ -0,0 +1,40 @@
+<%# locals: (account:, title: nil, tooltip: nil) %>
+
+<% period = Period.from_param(params[:period]) %>
+<% series = account.series(period: period) %>
+<% trend = series.trend %>
+<% default_value_title = account.asset? ? t(".balance") : t(".owed") %>
+
+
+
+
+
+ <%= tag.p title || default_value_title, class: "text-sm font-medium text-gray-500" %>
+ <%= tooltip %>
+
+
+ <%= tag.p format_money(account.value), class: "text-gray-900 text-3xl font-medium" %>
+
+
+ <% if trend.direction.flat? %>
+ <%= tag.span t(".no_change"), class: "text-gray-500" %>
+ <% else %>
+ <%= tag.span "#{trend.value.positive? ? "+" : ""}#{format_money(trend.value)}", style: "color: #{trend.color}" %>
+ <% unless trend.percent.infinite? %>
+ <%= tag.span "(#{trend.percent}%)", style: "color: #{trend.color}" %>
+ <% end %>
+ <% end %>
+
+ <%= tag.span period_label(period), class: "text-gray-500" %>
+
+
+
+ <%= form_with url: request.path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
+ <%= period_select form: form, selected: period.name %>
+ <% end %>
+
+
+
+ <%= render "shared/line_chart", series: series %>
+
+
diff --git a/app/views/accounts/show/_header.html.erb b/app/views/accounts/show/_header.html.erb
new file mode 100644
index 00000000..c41f2d48
--- /dev/null
+++ b/app/views/accounts/show/_header.html.erb
@@ -0,0 +1,34 @@
+<%# locals: (account:, title: nil, subtitle: nil) %>
+
+
+
+ <% content = yield %>
+
+ <% if content.present? %>
+ <%= content %>
+ <% else %>
+
+ <%= render "accounts/logo", account: account %>
+
+
+
<%= title || account.name %>
+ <% if subtitle.present? %>
+
<%= subtitle %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= button_to sync_account_path(account), method: :post, class: "flex items-center gap-2", title: "Sync Account" do %>
+ <%= lucide_icon "refresh-cw", class: "w-4 h-4 text-gray-500 hover:text-gray-400" %>
+ <% end %>
+
+ <%= render "accounts/show/menu", account: account %>
+
+
+
+ <% if account.highest_priority_issue %>
+ <%= render partial: "issues/issue", locals: { issue: account.highest_priority_issue } %>
+ <% end %>
+
diff --git a/app/views/accounts/show/_loading.html.erb b/app/views/accounts/show/_loading.html.erb
new file mode 100644
index 00000000..a4254988
--- /dev/null
+++ b/app/views/accounts/show/_loading.html.erb
@@ -0,0 +1,3 @@
+
diff --git a/app/views/accounts/_menu.html.erb b/app/views/accounts/show/_menu.html.erb
similarity index 93%
rename from app/views/accounts/_menu.html.erb
rename to app/views/accounts/show/_menu.html.erb
index 8ebb53f1..4ff93e02 100644
--- a/app/views/accounts/_menu.html.erb
+++ b/app/views/accounts/show/_menu.html.erb
@@ -11,6 +11,7 @@
<% end %>
<%= link_to new_import_path,
+ data: { turbo_frame: :modal },
class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg" do %>
<%= lucide_icon "download", class: "w-5 h-5 text-gray-500" %>
@@ -21,6 +22,7 @@
method: :delete,
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
data: {
+ turbo_frame: :_top,
turbo_confirm: {
title: t(".confirm_title"),
body: t(".confirm_body_html"),
diff --git a/app/views/accounts/accountables/_tab.html.erb b/app/views/accounts/show/_tab.html.erb
similarity index 100%
rename from app/views/accounts/accountables/_tab.html.erb
rename to app/views/accounts/show/_tab.html.erb
diff --git a/app/views/accounts/show/_tabs.html.erb b/app/views/accounts/show/_tabs.html.erb
new file mode 100644
index 00000000..6c69d803
--- /dev/null
+++ b/app/views/accounts/show/_tabs.html.erb
@@ -0,0 +1,11 @@
+<%# locals: (account:, tabs:) %>
+
+<% selected_tab = tabs.find { |tab| tab[:key] == params[:tab] } || tabs.first %>
+
+
+ <% tabs.each do |tab| %>
+ <%= render "accounts/show/tab", account: account, key: tab[:key], is_selected: selected_tab[:key] == tab[:key] %>
+ <% end %>
+
+
+<%= selected_tab[:contents] %>
diff --git a/app/views/accounts/show/_template.html.erb b/app/views/accounts/show/_template.html.erb
new file mode 100644
index 00000000..7d17e80c
--- /dev/null
+++ b/app/views/accounts/show/_template.html.erb
@@ -0,0 +1,27 @@
+<%# locals: (account:, header: nil, chart: nil, tabs: nil) %>
+
+<%= turbo_stream_from account %>
+
+<%= turbo_frame_tag dom_id(account) do %>
+ <%= tag.div class: "space-y-4" do %>
+ <% if header.present? %>
+ <%= header %>
+ <% else %>
+ <%= render "accounts/show/header", account: account %>
+ <% end %>
+
+ <% if chart.present? %>
+ <%= chart %>
+ <% else %>
+ <%= render "accounts/show/chart", account: account %>
+ <% end %>
+
+
+ <% if tabs.present? %>
+ <%= tabs %>
+ <% else %>
+ <%= render "accounts/show/activity", account: account %>
+ <% end %>
+
+ <% end %>
+<% end %>
diff --git a/app/views/accounts/summary.html.erb b/app/views/accounts/summary.html.erb
index db9ab322..0894ee34 100644
--- a/app/views/accounts/summary.html.erb
+++ b/app/views/accounts/summary.html.erb
@@ -1,13 +1,15 @@
+<% period = Period.from_param(params[:period]) %>
+
- <%= render "header" %>
+ <%= render "accounts/summary/header" %>
<%= render partial: "shared/value_heading", locals: {
label: "Assets",
- period: @period,
+ period: period,
value: Current.family.assets,
trend: @asset_series.trend
} %>
@@ -23,7 +25,7 @@
<%= render partial: "shared/value_heading", locals: {
label: "Liabilities",
- period: @period,
+ period: period,
size: "md",
value: Current.family.liabilities,
trend: @liability_series.trend
@@ -41,12 +43,12 @@
Assets
- <%= link_to new_account_path(step: "method"), class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path, class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
<%= t(".new") %>
<% end %>
<%= form_with url: summary_accounts_path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
- <%= period_select form: form, selected: @period.name %>
+ <%= period_select form: form, selected: period.name %>
<% end %>
@@ -66,12 +68,12 @@
Liabilities
- <%= link_to new_account_path(step: "method"), class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path, class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
<%= t(".new") %>
<% end %>
<%= form_with url: summary_accounts_path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
- <%= period_select form: form, selected: @period.name %>
+ <%= period_select form: form, selected: period.name %>
<% end %>
diff --git a/app/views/accounts/_header.html.erb b/app/views/accounts/summary/_header.html.erb
similarity index 81%
rename from app/views/accounts/_header.html.erb
rename to app/views/accounts/summary/_header.html.erb
index 348fc9b9..f8c8eb3e 100644
--- a/app/views/accounts/_header.html.erb
+++ b/app/views/accounts/summary/_header.html.erb
@@ -13,7 +13,7 @@
<% end %>
- <%= link_to new_account_path(step: "method"), class: "rounded-lg bg-gray-900 text-white flex items-center gap-1 justify-center hover:bg-gray-700 px-3 py-2", data: { turbo_frame: :modal } do %>
+ <%= link_to new_account_path, class: "rounded-lg bg-gray-900 text-white flex items-center gap-1 justify-center hover:bg-gray-700 px-3 py-2", data: { turbo_frame: :modal } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new") %>
<% end %>
diff --git a/app/views/categories/_menu.html.erb b/app/views/categories/_menu.html.erb
index 6c632596..9235a581 100644
--- a/app/views/categories/_menu.html.erb
+++ b/app/views/categories/_menu.html.erb
@@ -1,11 +1,11 @@
-<%# locals: (transaction:) %>
+<%# locals: (transaction:, origin: nil) %>
<%= render partial: "categories/badge", locals: { category: transaction.category } %>
- <%= turbo_frame_tag "category_dropdown", src: category_dropdown_path(category_id: transaction.category_id, transaction_id: transaction.id), loading: :lazy do %>
+ <%= turbo_frame_tag "category_dropdown", src: category_dropdown_path(category_id: transaction.category_id, transaction_id: transaction.id, origin: origin), loading: :lazy do %>
diff --git a/app/views/category/dropdowns/_row.html.erb b/app/views/category/dropdowns/_row.html.erb
index b4b6971b..bf8c2eae 100644
--- a/app/views/category/dropdowns/_row.html.erb
+++ b/app/views/category/dropdowns/_row.html.erb
@@ -1,8 +1,8 @@
-<%# locals: (category:) %>
+<%# locals: (category:, origin: nil) %>
<% is_selected = category.id === @selected_category&.id %>
<%= content_tag :div, class: ["filterable-item flex justify-between items-center border-none rounded-lg px-2 py-1 group w-full", { "bg-gray-25": is_selected }], data: { filter_name: category.name } do %>
- <%= button_to account_transaction_path(@transaction.entry.account, @transaction.entry, account_entry: { entryable_type: "Account::Transaction", entryable_attributes: { id: @transaction.id, category_id: category.id } }), method: :patch, data: { turbo_frame: dom_id(@transaction.entry) }, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
+ <%= button_to account_transaction_path(@transaction.entry.account, @transaction.entry, account_entry: { origin: origin,entryable_type: "Account::Transaction", entryable_attributes: { id: @transaction.id, category_id: category.id } }), method: :patch, data: { turbo_frame: dom_id(@transaction.entry) }, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
<%= lucide_icon("check", class: "w-5 h-5 text-gray-500") if is_selected %>
diff --git a/app/views/category/dropdowns/show.html.erb b/app/views/category/dropdowns/show.html.erb
index 4f3baa63..f486c9c2 100644
--- a/app/views/category/dropdowns/show.html.erb
+++ b/app/views/category/dropdowns/show.html.erb
@@ -11,7 +11,7 @@
<%= t(".no_categories") %>
<% @categories.each do |category| %>
- <%= render partial: "category/dropdowns/row", locals: { category: } %>
+ <%= render partial: "category/dropdowns/row", locals: { category:, origin: params[:origin] } %>
<% end %>
diff --git a/app/views/credit_cards/_form.html.erb b/app/views/credit_cards/_form.html.erb
new file mode 100644
index 00000000..ff9e7582
--- /dev/null
+++ b/app/views/credit_cards/_form.html.erb
@@ -0,0 +1,37 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url do |form| %>
+
+
+
+ <%= form.fields_for :accountable do |credit_card_form| %>
+
+ <%= credit_card_form.number_field :available_credit,
+ label: t("credit_cards.form.available_credit"),
+ placeholder: t("credit_cards.form.available_credit_placeholder"),
+ min: 0 %>
+
+
+
+ <%= credit_card_form.number_field :minimum_payment,
+ label: t("credit_cards.form.minimum_payment"),
+ placeholder: t("credit_cards.form.minimum_payment_placeholder"),
+ min: 0 %>
+ <%= credit_card_form.number_field :apr,
+ label: t("credit_cards.form.apr"),
+ placeholder: t("credit_cards.form.apr_placeholder"),
+ min: 0,
+ step: 0.01 %>
+
+
+
+ <%= credit_card_form.date_field :expiration_date,
+ label: t("credit_cards.form.expiration_date") %>
+ <%= credit_card_form.number_field :annual_fee,
+ label: t("credit_cards.form.annual_fee"),
+ placeholder: t("credit_cards.form.annual_fee_placeholder"),
+ min: 0 %>
+
+ <% end %>
+
+<% end %>
diff --git a/app/views/accounts/accountables/credit_card/_overview.html.erb b/app/views/credit_cards/_overview.html.erb
similarity index 89%
rename from app/views/accounts/accountables/credit_card/_overview.html.erb
rename to app/views/credit_cards/_overview.html.erb
index 032d8b52..85ce6527 100644
--- a/app/views/accounts/accountables/credit_card/_overview.html.erb
+++ b/app/views/credit_cards/_overview.html.erb
@@ -27,5 +27,5 @@
- <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
+ <%= link_to "Edit account details", edit_credit_card_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
diff --git a/app/views/accounts/edit.html.erb b/app/views/credit_cards/edit.html.erb
similarity index 50%
rename from app/views/accounts/edit.html.erb
rename to app/views/credit_cards/edit.html.erb
index 26eff37d..fc97a7a8 100644
--- a/app/views/accounts/edit.html.erb
+++ b/app/views/credit_cards/edit.html.erb
@@ -1,3 +1,3 @@
<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
- <%= render "form", account: @account, url: edit_account_form_url(@account) %>
+ <%= render "form", account: @account, url: credit_card_path(@account) %>
<% end %>
diff --git a/app/views/credit_cards/new.html.erb b/app/views/credit_cards/new.html.erb
new file mode 100644
index 00000000..0e13c2a5
--- /dev/null
+++ b/app/views/credit_cards/new.html.erb
@@ -0,0 +1,7 @@
+<% if params[:step] == "method_select" %>
+ <%= render "accounts/new/method_selector", path: new_credit_card_path(institution_id: params[:institution_id], return_to: params[:return_to]) %>
+<% else %>
+ <%= modal_form_wrapper title: t(".title") do %>
+ <%= render "credit_cards/form", account: @account, url: credit_cards_path %>
+ <% end %>
+<% end %>
diff --git a/app/views/credit_cards/show.html.erb b/app/views/credit_cards/show.html.erb
new file mode 100644
index 00000000..2135dee9
--- /dev/null
+++ b/app/views/credit_cards/show.html.erb
@@ -0,0 +1,6 @@
+<%= render "accounts/show/template",
+ account: @account,
+ tabs: render("accounts/show/tabs", account: @account, tabs: [
+ { key: "overview", contents: render("credit_cards/overview", account: @account) },
+ { key: "activity", contents: render("accounts/show/activity", account: @account) }
+ ]) %>
diff --git a/app/views/cryptos/_form.html.erb b/app/views/cryptos/_form.html.erb
new file mode 100644
index 00000000..8895b27a
--- /dev/null
+++ b/app/views/cryptos/_form.html.erb
@@ -0,0 +1,3 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url %>
diff --git a/app/views/cryptos/edit.html.erb b/app/views/cryptos/edit.html.erb
new file mode 100644
index 00000000..5f5b4981
--- /dev/null
+++ b/app/views/cryptos/edit.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
+ <%= render "form", account: @account, url: crypto_path(@account) %>
+<% end %>
diff --git a/app/views/cryptos/new.html.erb b/app/views/cryptos/new.html.erb
new file mode 100644
index 00000000..38cb112b
--- /dev/null
+++ b/app/views/cryptos/new.html.erb
@@ -0,0 +1,7 @@
+<% if params[:step] == "method_select" %>
+ <%= render "accounts/new/method_selector", path: new_crypto_path(institution_id: params[:institution_id], return_to: params[:return_to]) %>
+<% else %>
+ <%= modal_form_wrapper title: t(".title") do %>
+ <%= render "cryptos/form", account: @account, url: cryptos_path %>
+ <% end %>
+<% end %>
diff --git a/app/views/cryptos/show.html.erb b/app/views/cryptos/show.html.erb
new file mode 100644
index 00000000..0c3e6e97
--- /dev/null
+++ b/app/views/cryptos/show.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/show/template", account: @account %>
diff --git a/app/views/depositories/_form.html.erb b/app/views/depositories/_form.html.erb
new file mode 100644
index 00000000..1cb9d1aa
--- /dev/null
+++ b/app/views/depositories/_form.html.erb
@@ -0,0 +1,7 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url do |form| %>
+ <%= form.select :subtype,
+ Depository::SUBTYPES,
+ { label: true, prompt: t("depositories.form.subtype_prompt"), include_blank: t("depositories.form.none") } %>
+<% end %>
diff --git a/app/views/depositories/edit.html.erb b/app/views/depositories/edit.html.erb
new file mode 100644
index 00000000..0c61040d
--- /dev/null
+++ b/app/views/depositories/edit.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
+ <%= render "form", account: @account, url: depository_path(@account) %>
+<% end %>
diff --git a/app/views/depositories/new.html.erb b/app/views/depositories/new.html.erb
new file mode 100644
index 00000000..db8f4ff5
--- /dev/null
+++ b/app/views/depositories/new.html.erb
@@ -0,0 +1,7 @@
+<% if params[:step] == "method_select" %>
+ <%= render "accounts/new/method_selector", path: new_depository_path(institution_id: params[:institution_id], return_to: params[:return_to]) %>
+<% else %>
+ <%= modal_form_wrapper title: t(".title") do %>
+ <%= render "depositories/form", account: @account, url: depositories_path %>
+ <% end %>
+<% end %>
diff --git a/app/views/depositories/show.html.erb b/app/views/depositories/show.html.erb
new file mode 100644
index 00000000..0c3e6e97
--- /dev/null
+++ b/app/views/depositories/show.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/show/template", account: @account %>
diff --git a/app/views/import/confirms/_mappings.html.erb b/app/views/import/confirms/_mappings.html.erb
index 75f50532..8e37172c 100644
--- a/app/views/import/confirms/_mappings.html.erb
+++ b/app/views/import/confirms/_mappings.html.erb
@@ -8,13 +8,13 @@
<%= tag.p t(".no_accounts"), class: "text-sm" %>
- <%= link_to t(".create_account"), new_account_path, class: "btn btn--primary whitespace-nowrap", data: { turbo_frame: :modal } %>
+ <%= link_to t(".create_account"), new_account_path(return_to: import_confirm_path(import)), class: "btn btn--primary whitespace-nowrap", data: { turbo_frame: :modal } %>
<% elsif import.has_unassigned_account? %>
<%= tag.p t(".unassigned_account"), class: "text-sm" %>
- <%= link_to t(".create_account"), new_account_path, class: "btn btn--primary whitespace-nowrap", data: { turbo_frame: :modal } %>
+ <%= link_to t(".create_account"), new_account_path(return_to: import_confirm_path(import)), class: "btn btn--primary whitespace-nowrap", data: { turbo_frame: :modal } %>
<% end %>
<% end %>
diff --git a/app/views/investments/_cash_tab.html.erb b/app/views/investments/_cash_tab.html.erb
new file mode 100644
index 00000000..f5e51f28
--- /dev/null
+++ b/app/views/investments/_cash_tab.html.erb
@@ -0,0 +1,5 @@
+<%# locals: (account:) %>
+
+<%= turbo_frame_tag dom_id(account, :cash), src: account_cashes_path(account) do %>
+ <%= render "account/entries/loading" %>
+<% end %>
diff --git a/app/views/investments/_chart.html.erb b/app/views/investments/_chart.html.erb
new file mode 100644
index 00000000..bb96b3bc
--- /dev/null
+++ b/app/views/investments/_chart.html.erb
@@ -0,0 +1 @@
+<%# locals: (account:) %>
diff --git a/app/views/investments/_form.html.erb b/app/views/investments/_form.html.erb
new file mode 100644
index 00000000..e6b2dc46
--- /dev/null
+++ b/app/views/investments/_form.html.erb
@@ -0,0 +1,7 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url do |form| %>
+ <%= form.select :subtype,
+ Investment::SUBTYPES,
+ { label: true, prompt: t("investments.form.subtype_prompt"), include_blank: t("investments.form.none") } %>
+<% end %>
diff --git a/app/views/investments/_holdings_tab.html.erb b/app/views/investments/_holdings_tab.html.erb
new file mode 100644
index 00000000..c61c3a17
--- /dev/null
+++ b/app/views/investments/_holdings_tab.html.erb
@@ -0,0 +1,5 @@
+<%# locals: (account:) %>
+
+<%= turbo_frame_tag dom_id(account, :holdings), src: account_holdings_path(account) do %>
+ <%= render "account/entries/loading" %>
+<% end %>
diff --git a/app/views/accounts/accountables/investment/_tooltip.html.erb b/app/views/investments/_value_tooltip.html.erb
similarity index 80%
rename from app/views/accounts/accountables/investment/_tooltip.html.erb
rename to app/views/investments/_value_tooltip.html.erb
index 83432ebc..c624afbc 100644
--- a/app/views/accounts/accountables/investment/_tooltip.html.erb
+++ b/app/views/investments/_value_tooltip.html.erb
@@ -1,4 +1,4 @@
-<%# locals: (account:) -%>
+<%# locals: (value:, cash:) %>
<%= lucide_icon("info", class: "w-4 h-4 shrink-0 text-gray-500") %>
@@ -11,7 +11,7 @@
<%= t(".holdings") %>
- <%= tag.p format_money(account.investment.holdings_value, precision: 0) %>
+ <%= tag.p format_money(value, precision: 0) %>
@@ -19,7 +19,7 @@
<%= t(".cash") %>
- <%= tag.p format_money(account.balance_money, precision: 0) %>
+ <%= tag.p format_money(cash, precision: 0) %>
diff --git a/app/views/investments/edit.html.erb b/app/views/investments/edit.html.erb
new file mode 100644
index 00000000..c91d9eb2
--- /dev/null
+++ b/app/views/investments/edit.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
+ <%= render "investments/form", account: @account, url: investment_path(@account) %>
+<% end %>
diff --git a/app/views/investments/new.html.erb b/app/views/investments/new.html.erb
new file mode 100644
index 00000000..5e83350d
--- /dev/null
+++ b/app/views/investments/new.html.erb
@@ -0,0 +1,7 @@
+<% if params[:step] == "method_select" %>
+ <%= render "accounts/new/method_selector", path: new_investment_path(institution_id: params[:institution_id], return_to: params[:return_to]) %>
+<% else %>
+ <%= modal_form_wrapper title: t(".title") do %>
+ <%= render "investments/form", account: @account, url: investments_path %>
+ <% end %>
+<% end %>
diff --git a/app/views/investments/show.html.erb b/app/views/investments/show.html.erb
new file mode 100644
index 00000000..4833badd
--- /dev/null
+++ b/app/views/investments/show.html.erb
@@ -0,0 +1,24 @@
+<%= turbo_stream_from @account %>
+
+<%= turbo_frame_tag dom_id(@account) do %>
+ <%= tag.div class: "space-y-4" do %>
+ <%= render "accounts/show/header", account: @account %>
+
+ <%= render "accounts/show/chart",
+ account: @account,
+ title: t(".chart_title"),
+ tooltip: render(
+ "investments/value_tooltip",
+ value: @account.value,
+ cash: @account.balance_money
+ ) %>
+
+
+ <%= render "accounts/show/tabs", account: @account, tabs: [
+ { key: "activity", contents: render("accounts/show/activity", account: @account) },
+ { key: "holdings", contents: render("investments/holdings_tab", account: @account) },
+ { key: "cash", contents: render("investments/cash_tab", account: @account) }
+ ] %>
+
+ <% end %>
+<% end %>
diff --git a/app/views/invitation_mailer/invite_email.html.erb b/app/views/invitation_mailer/invite_email.html.erb
index c57be0a9..04a70291 100644
--- a/app/views/invitation_mailer/invite_email.html.erb
+++ b/app/views/invitation_mailer/invite_email.html.erb
@@ -1,11 +1,11 @@
<%= t(".greeting") %>
- <%= t(".body",
+ <%= t(".body",
inviter: @invitation.inviter.display_name,
family: @invitation.family.name).html_safe %>
<%= link_to t(".accept_button"), @accept_url, class: "button" %>
-
\ No newline at end of file
+
diff --git a/app/views/invitations/new.html.erb b/app/views/invitations/new.html.erb
index 5b6a4550..5d50e516 100644
--- a/app/views/invitations/new.html.erb
+++ b/app/views/invitations/new.html.erb
@@ -1,6 +1,6 @@
<%= modal_form_wrapper title: t(".title"), subtitle: t(".subtitle") do %>
<%= styled_form_with model: @invitation, class: "space-y-4", data: { turbo: false } do |form| %>
- <%= form.email_field :email,
+ <%= form.email_field :email,
required: true,
placeholder: t(".email_placeholder"),
label: t(".email_label") %>
@@ -17,4 +17,4 @@
<%= form.submit t(".submit"), class: "bg-gray-900 text-white rounded-lg px-4 py-2 w-full" %>
<% end %>
-<% end %>
\ No newline at end of file
+<% end %>
diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb
index 8c997673..c274dc75 100644
--- a/app/views/layouts/_sidebar.html.erb
+++ b/app/views/layouts/_sidebar.html.erb
@@ -95,7 +95,7 @@
<%= period_select form: form, selected: "last_30_days", classes: "w-full border-none pl-2 pr-7 text-xs bg-transparent gap-1 cursor-pointer font-semibold tracking-wide focus:outline-none focus:ring-0" %>
<% end %>
- <%= link_to new_account_path(step: "method"), id: "sidebar-new-account", class: "block hover:bg-gray-100 font-semibold text-gray-900 flex items-center rounded p-1", title: t(".new_account"), data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path, id: "sidebar-new-account", class: "block hover:bg-gray-100 font-semibold text-gray-900 flex items-center rounded p-1", title: t(".new_account"), data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
<% end %>
@@ -106,7 +106,7 @@
<%= render "accounts/account_list", group: group %>
<% end %>
<% else %>
- <%= link_to new_account_path(step: "method"), class: "flex items-center min-h-10 gap-4 px-3 py-2 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path, class: "flex items-center min-h-10 gap-4 px-3 py-2 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= tag.p t(".new_account") %>
<% end %>
diff --git a/app/views/loans/_form.html.erb b/app/views/loans/_form.html.erb
new file mode 100644
index 00000000..bfadda90
--- /dev/null
+++ b/app/views/loans/_form.html.erb
@@ -0,0 +1,26 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url do |form| %>
+
+
+
+ <%= form.fields_for :accountable do |loan_form| %>
+
+ <%= loan_form.number_field :interest_rate,
+ label: t("loans.form.interest_rate"),
+ placeholder: t("loans.form.interest_rate_placeholder"),
+ min: 0,
+ step: 0.01 %>
+ <%= loan_form.select :rate_type,
+ [["Fixed", "fixed"], ["Variable", "variable"], ["Adjustable", "adjustable"]],
+ { label: t("loans.form.rate_type") } %>
+
+
+
+ <%= loan_form.number_field :term_months,
+ label: t("loans.form.term_months"),
+ placeholder: t("loans.form.term_months_placeholder") %>
+
+ <% end %>
+
+<% end %>
diff --git a/app/views/accounts/accountables/loan/_overview.html.erb b/app/views/loans/_overview.html.erb
similarity index 92%
rename from app/views/accounts/accountables/loan/_overview.html.erb
rename to app/views/loans/_overview.html.erb
index 7d1b19b5..db6824fb 100644
--- a/app/views/accounts/accountables/loan/_overview.html.erb
+++ b/app/views/loans/_overview.html.erb
@@ -45,5 +45,5 @@
- <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
+ <%= link_to "Edit loan details", edit_loan_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
diff --git a/app/views/loans/edit.html.erb b/app/views/loans/edit.html.erb
new file mode 100644
index 00000000..5fb3b13e
--- /dev/null
+++ b/app/views/loans/edit.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
+ <%= render "form", account: @account, url: loan_path(@account) %>
+<% end %>
diff --git a/app/views/loans/new.html.erb b/app/views/loans/new.html.erb
new file mode 100644
index 00000000..407e648d
--- /dev/null
+++ b/app/views/loans/new.html.erb
@@ -0,0 +1,7 @@
+<% if params[:step] == "method_select" %>
+ <%= render "accounts/new/method_selector", path: new_loan_path(institution_id: params[:institution_id], return_to: params[:return_to]) %>
+<% else %>
+ <%= modal_form_wrapper title: t(".title") do %>
+ <%= render "loans/form", account: @account, url: loans_path %>
+ <% end %>
+<% end %>
diff --git a/app/views/loans/show.html.erb b/app/views/loans/show.html.erb
new file mode 100644
index 00000000..64e9403c
--- /dev/null
+++ b/app/views/loans/show.html.erb
@@ -0,0 +1,6 @@
+<%= render "accounts/show/template",
+ account: @account,
+ tabs: render("accounts/show/tabs", account: @account, tabs: [
+ { key: "overview", contents: render("loans/overview", account: @account) },
+ { key: "activity", contents: render("accounts/show/activity", account: @account) }
+ ]) %>
diff --git a/app/views/other_assets/_form.html.erb b/app/views/other_assets/_form.html.erb
new file mode 100644
index 00000000..8895b27a
--- /dev/null
+++ b/app/views/other_assets/_form.html.erb
@@ -0,0 +1,3 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url %>
diff --git a/app/views/other_assets/edit.html.erb b/app/views/other_assets/edit.html.erb
new file mode 100644
index 00000000..4c38d223
--- /dev/null
+++ b/app/views/other_assets/edit.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
+ <%= render "other_assets/form", account: @account, url: other_asset_path(@account) %>
+<% end %>
diff --git a/app/views/other_assets/new.html.erb b/app/views/other_assets/new.html.erb
new file mode 100644
index 00000000..bff7face
--- /dev/null
+++ b/app/views/other_assets/new.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".title") do %>
+ <%= render "other_assets/form", account: @account, url: other_assets_path(return_to: params[:return_to]) %>
+<% end %>
diff --git a/app/views/other_assets/show.html.erb b/app/views/other_assets/show.html.erb
new file mode 100644
index 00000000..0c3e6e97
--- /dev/null
+++ b/app/views/other_assets/show.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/show/template", account: @account %>
diff --git a/app/views/other_liabilities/_form.html.erb b/app/views/other_liabilities/_form.html.erb
new file mode 100644
index 00000000..8895b27a
--- /dev/null
+++ b/app/views/other_liabilities/_form.html.erb
@@ -0,0 +1,3 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url %>
diff --git a/app/views/other_liabilities/edit.html.erb b/app/views/other_liabilities/edit.html.erb
new file mode 100644
index 00000000..4473faff
--- /dev/null
+++ b/app/views/other_liabilities/edit.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
+ <%= render "form", account: @account, url: other_liability_path(@account) %>
+<% end %>
diff --git a/app/views/other_liabilities/new.html.erb b/app/views/other_liabilities/new.html.erb
new file mode 100644
index 00000000..364a5ecf
--- /dev/null
+++ b/app/views/other_liabilities/new.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".title") do %>
+ <%= render "other_liabilities/form", account: @account, url: other_liabilities_path(return_to: params[:return_to]) %>
+<% end %>
diff --git a/app/views/other_liabilities/show.html.erb b/app/views/other_liabilities/show.html.erb
new file mode 100644
index 00000000..0c3e6e97
--- /dev/null
+++ b/app/views/other_liabilities/show.html.erb
@@ -0,0 +1 @@
+<%= render "accounts/show/template", account: @account %>
diff --git a/app/views/pages/dashboard.html.erb b/app/views/pages/dashboard.html.erb
index f3846a4a..666cf509 100644
--- a/app/views/pages/dashboard.html.erb
+++ b/app/views/pages/dashboard.html.erb
@@ -17,7 +17,7 @@
<% end %>
- <%= link_to new_account_path(step: "method"), class: "flex items-center gap-1 btn btn--primary", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path, class: "flex items-center gap-1 btn btn--primary", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new") %>
<% end %>
@@ -180,7 +180,7 @@
<% else %>
<%= entries_by_date(@transaction_entries, selectable: false) do |entries| %>
- <%= render entries, selectable: false, editable: false %>
+ <%= render entries, selectable: false %>
<% end %>
<%= link_to t(".view_all"), transactions_path %>
diff --git a/app/views/properties/_form.html.erb b/app/views/properties/_form.html.erb
new file mode 100644
index 00000000..c26c71ba
--- /dev/null
+++ b/app/views/properties/_form.html.erb
@@ -0,0 +1,54 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url do |form| %>
+ <%= form.select :subtype,
+ Property::SUBTYPES,
+ { label: true, prompt: t("properties.form.subtype_prompt"), include_blank: t("properties.form.none") } %>
+
+
+
+
+ <%= form.fields_for :accountable do |property_form| %>
+
+ <%= property_form.number_field :year_built,
+ label: t("properties.form.year_built"),
+ placeholder: t("properties.form.year_built_placeholder"),
+ min: 1800,
+ max: Time.current.year %>
+
+
+
+ <%= property_form.number_field :area_value,
+ label: t("properties.form.area"),
+ placeholder: t("properties.form.area_placeholder"),
+ min: 0 %>
+ <%= property_form.select :area_unit,
+ [["Square Feet", "sqft"], ["Square Meters", "sqm"]],
+ { label: t("properties.form.area_unit") } %>
+
+
+ <%= property_form.fields_for :address do |address_form| %>
+ <%= address_form.text_field :line1,
+ label: t("properties.form.address_line1"),
+ placeholder: t("properties.form.address_line1_placeholder") %>
+
+ <%= address_form.text_field :locality,
+ label: t("properties.form.locality"),
+ placeholder: t("properties.form.locality_placeholder") %>
+ <%= address_form.text_field :region,
+ label: t("properties.form.region"),
+ placeholder: t("properties.form.region_placeholder") %>
+
+
+
+ <%= address_form.text_field :postal_code,
+ label: t("properties.form.postal_code"),
+ placeholder: t("properties.form.postal_code_placeholder") %>
+ <%= address_form.text_field :country,
+ label: t("properties.form.country"),
+ placeholder: t("properties.form.country_placeholder") %>
+
+ <% end %>
+ <% end %>
+
+<% end %>
diff --git a/app/views/accounts/accountables/property/_overview.html.erb b/app/views/properties/_overview.html.erb
similarity index 88%
rename from app/views/accounts/accountables/property/_overview.html.erb
rename to app/views/properties/_overview.html.erb
index c0fde9f9..95d42c8e 100644
--- a/app/views/accounts/accountables/property/_overview.html.erb
+++ b/app/views/properties/_overview.html.erb
@@ -29,5 +29,5 @@
- <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
+ <%= link_to "Edit account details", edit_property_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
diff --git a/app/views/properties/edit.html.erb b/app/views/properties/edit.html.erb
new file mode 100644
index 00000000..187208b6
--- /dev/null
+++ b/app/views/properties/edit.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
+ <%= render "form", account: @account, url: property_path(@account) %>
+<% end %>
diff --git a/app/views/properties/new.html.erb b/app/views/properties/new.html.erb
new file mode 100644
index 00000000..5cddb5eb
--- /dev/null
+++ b/app/views/properties/new.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".title") do %>
+ <%= render "properties/form", account: @account, url: properties_path(return_to: params[:return_to]) %>
+<% end %>
diff --git a/app/views/properties/show.html.erb b/app/views/properties/show.html.erb
new file mode 100644
index 00000000..24d42147
--- /dev/null
+++ b/app/views/properties/show.html.erb
@@ -0,0 +1,7 @@
+<%= render "accounts/show/template",
+ account: @account,
+ header: render("accounts/show/header", account: @account, subtitle: @account.property.address),
+ tabs: render("accounts/show/tabs", account: @account, tabs: [
+ { key: "overview", contents: render("properties/overview", account: @account) },
+ { key: "activity", contents: render("accounts/show/activity", account: @account) }
+ ]) %>
diff --git a/app/views/registrations/new.html.erb b/app/views/registrations/new.html.erb
index 6ad1f066..1c0f3fe7 100644
--- a/app/views/registrations/new.html.erb
+++ b/app/views/registrations/new.html.erb
@@ -10,7 +10,7 @@
<% elsif @invitation %>
- <%= t(".invitation_message",
+ <%= t(".invitation_message",
inviter: @invitation.inviter.display_name,
role: t(".role_#{@invitation.role}")) %>
@@ -18,8 +18,8 @@
<% end %>
<%= styled_form_with model: @user, url: registration_path, class: "space-y-4" do |form| %>
- <%= form.email_field :email,
- autofocus: false,
+ <%= form.email_field :email,
+ autofocus: false,
autocomplete: "email",
required: "required",
placeholder: "you@example.com",
diff --git a/app/views/settings/profiles/show.html.erb b/app/views/settings/profiles/show.html.erb
index dde691a2..c21775d6 100644
--- a/app/views/settings/profiles/show.html.erb
+++ b/app/views/settings/profiles/show.html.erb
@@ -63,9 +63,10 @@
<%= t(".invitation_link") %>
<%= accept_invitation_url(invitation.token) %>
-
diff --git a/app/views/shared/_modal_form.html.erb b/app/views/shared/_modal_form.html.erb
index 68a462ee..e0e69e01 100644
--- a/app/views/shared/_modal_form.html.erb
+++ b/app/views/shared/_modal_form.html.erb
@@ -3,9 +3,9 @@
<%= modal do %>
- <%= link_to new_account_path(step: "method"), class: "btn btn--primary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
+ <%= link_to new_account_path, class: "btn btn--primary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<%= t(".new_account") %>
<% end %>
diff --git a/app/views/shared/_sparkline.html.erb b/app/views/shared/_sparkline.html.erb
new file mode 100644
index 00000000..719103d9
--- /dev/null
+++ b/app/views/shared/_sparkline.html.erb
@@ -0,0 +1,13 @@
+<%# locals: (series:, id:, stroke_width: 1) %>
+
+<%= tag.div(
+ id: id,
+ class: "h-full w-full",
+ data: {
+ controller: "time-series-chart",
+ "time-series-chart-data-value": series.to_json,
+ "time-series-chart-stroke-width-value": stroke_width,
+ "time-series-chart-use-labels-value": false,
+ "time-series-chart-use-tooltip-value": false
+ }
+) %>
diff --git a/app/views/transactions/index.html.erb b/app/views/transactions/index.html.erb
index 1055496f..c2663da3 100644
--- a/app/views/transactions/index.html.erb
+++ b/app/views/transactions/index.html.erb
@@ -5,7 +5,8 @@
"
+ data-bulk-select-singular-label-value="<%= t(".transaction") %>"
+ data-bulk-select-plural-label-value="<%= t(".transactions") %>"
class="overflow-y-auto flex flex-col bg-white rounded-xl border border-alpha-black-25 shadow-xs p-4">
<%= render "transactions/searches/search" %>
@@ -15,21 +16,20 @@
-
+
<%= check_box_tag "selection_entry",
class: "maybe-checkbox maybe-checkbox--light",
data: { action: "bulk-select#togglePageSelection" } %>
transaction
-
category
-
account
+
category
+
account
amount
- <%= entries_by_date(@transaction_entries) do |entries| %>
- <%= render entries.reject { |e| e.transfer_id.present? }, selectable: true %>
- <%= render transfer_entries(entries), selectable: false %>
+ <%= entries_by_date(@transaction_entries, totals: true) do |entries| %>
+ <%= render entries, origin: "transactions" %>
<% end %>
diff --git a/app/views/vehicles/_form.html.erb b/app/views/vehicles/_form.html.erb
new file mode 100644
index 00000000..97dd7944
--- /dev/null
+++ b/app/views/vehicles/_form.html.erb
@@ -0,0 +1,33 @@
+<%# locals: (account:, url:) %>
+
+<%= render "accounts/form", account: account, url: url do |form| %>
+
+
+
+ <%= form.fields_for :accountable do |vehicle_form| %>
+
+ <%= vehicle_form.text_field :make,
+ label: t("vehicles.form.make"),
+ placeholder: t("vehicles.form.make_placeholder") %>
+ <%= vehicle_form.text_field :model,
+ label: t("vehicles.form.model"),
+ placeholder: t("vehicles.form.model_placeholder") %>
+
+
+
+ <%= vehicle_form.number_field :year,
+ label: t("vehicles.form.year"),
+ placeholder: t("vehicles.form.year_placeholder"),
+ min: 1900,
+ max: Time.current.year + 1 %>
+ <%= vehicle_form.number_field :mileage_value,
+ label: t("vehicles.form.mileage"),
+ placeholder: t("vehicles.form.mileage_placeholder"),
+ min: 0 %>
+ <%= vehicle_form.select :mileage_unit,
+ [["Miles", "mi"], ["Kilometers", "km"]],
+ { label: t("vehicles.form.mileage_unit") } %>
+
+ <% end %>
+
+<% end %>
diff --git a/app/views/accounts/accountables/vehicle/_overview.html.erb b/app/views/vehicles/_overview.html.erb
similarity index 94%
rename from app/views/accounts/accountables/vehicle/_overview.html.erb
rename to app/views/vehicles/_overview.html.erb
index 7d72431e..ab08ae21 100644
--- a/app/views/accounts/accountables/vehicle/_overview.html.erb
+++ b/app/views/vehicles/_overview.html.erb
@@ -33,5 +33,5 @@
- <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
+ <%= link_to "Edit account details", edit_vehicle_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
diff --git a/app/views/vehicles/edit.html.erb b/app/views/vehicles/edit.html.erb
new file mode 100644
index 00000000..a64715f1
--- /dev/null
+++ b/app/views/vehicles/edit.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".edit", account: @account.name) do %>
+ <%= render "form", account: @account, url: vehicle_path(@account) %>
+<% end %>
diff --git a/app/views/vehicles/new.html.erb b/app/views/vehicles/new.html.erb
new file mode 100644
index 00000000..b5002e30
--- /dev/null
+++ b/app/views/vehicles/new.html.erb
@@ -0,0 +1,3 @@
+<%= modal_form_wrapper title: t(".title") do %>
+ <%= render "vehicles/form", account: @account, url: vehicles_path(return_to: params[:return_to]) %>
+<% end %>
diff --git a/app/views/vehicles/show.html.erb b/app/views/vehicles/show.html.erb
new file mode 100644
index 00000000..097a9bab
--- /dev/null
+++ b/app/views/vehicles/show.html.erb
@@ -0,0 +1,6 @@
+<%= render "accounts/show/template",
+ account: @account,
+ tabs: render("accounts/show/tabs", account: @account, tabs: [
+ { key: "overview", contents: render("vehicles/overview", account: @account) },
+ { key: "activity", contents: render("accounts/show/activity", account: @account) }
+ ]) %>
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index e3134b17..b854cf00 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -23,74 +23,6 @@
],
"note": ""
},
- {
- "warning_type": "Dynamic Render Path",
- "warning_code": 15,
- "fingerprint": "42595161ffdc9ce9a10c4ba2a75fd2bb668e273bc4e683880b0ea906d0bd28f8",
- "check_name": "Render",
- "message": "Render path contains parameter value",
- "file": "app/views/accounts/show.html.erb",
- "line": 8,
- "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
- "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"header\"), { :account => Current.family.accounts.find(params[:id]) })",
- "render_path": [
- {
- "type": "controller",
- "class": "AccountsController",
- "method": "show",
- "line": 36,
- "file": "app/controllers/accounts_controller.rb",
- "rendered": {
- "name": "accounts/show",
- "file": "app/views/accounts/show.html.erb"
- }
- }
- ],
- "location": {
- "type": "template",
- "template": "accounts/show"
- },
- "user_input": "params[:id]",
- "confidence": "Weak",
- "cwe_id": [
- 22
- ],
- "note": ""
- },
- {
- "warning_type": "Dynamic Render Path",
- "warning_code": 15,
- "fingerprint": "a35b18785608dbdf35607501363573576ed8c304039f8387997acd1408ca1025",
- "check_name": "Render",
- "message": "Render path contains parameter value",
- "file": "app/views/accounts/show.html.erb",
- "line": 35,
- "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
- "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"tooltip\"), { :account => Current.family.accounts.find(params[:id]) })",
- "render_path": [
- {
- "type": "controller",
- "class": "AccountsController",
- "method": "show",
- "line": 36,
- "file": "app/controllers/accounts_controller.rb",
- "rendered": {
- "name": "accounts/show",
- "file": "app/views/accounts/show.html.erb"
- }
- }
- ],
- "location": {
- "type": "template",
- "template": "accounts/show"
- },
- "user_input": "params[:id]",
- "confidence": "Weak",
- "cwe_id": [
- 22
- ],
- "note": ""
- },
{
"warning_type": "Mass Assignment",
"warning_code": 105,
@@ -98,7 +30,7 @@
"check_name": "PermitAttributes",
"message": "Potentially dangerous key allowed for mass assignment",
"file": "app/controllers/invitations_controller.rb",
- "line": 34,
+ "line": 40,
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
"code": "params.require(:invitation).permit(:email, :role)",
"render_path": null,
@@ -129,7 +61,7 @@
"type": "controller",
"class": "PagesController",
"method": "changelog",
- "line": 36,
+ "line": 35,
"file": "app/controllers/pages_controller.rb",
"rendered": {
"name": "pages/changelog",
@@ -148,40 +80,6 @@
],
"note": ""
},
- {
- "warning_type": "Dynamic Render Path",
- "warning_code": 15,
- "fingerprint": "c5c512a13c34c9696024bd4e2367a657a5c140b5b6a0f5c352e9b69965f63e1b",
- "check_name": "Render",
- "message": "Render path contains parameter value",
- "file": "app/views/accounts/show.html.erb",
- "line": 63,
- "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
- "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"tabs\"), { :account => Current.family.accounts.find(params[:id]), :selected_tab => params[:tab] })",
- "render_path": [
- {
- "type": "controller",
- "class": "AccountsController",
- "method": "show",
- "line": 36,
- "file": "app/controllers/accounts_controller.rb",
- "rendered": {
- "name": "accounts/show",
- "file": "app/views/accounts/show.html.erb"
- }
- }
- ],
- "location": {
- "type": "template",
- "template": "accounts/show"
- },
- "user_input": "params[:id]",
- "confidence": "Weak",
- "cwe_id": [
- 22
- ],
- "note": ""
- },
{
"warning_type": "Dynamic Render Path",
"warning_code": 15,
@@ -217,6 +115,6 @@
"note": ""
}
],
- "updated": "2024-11-01 09:36:40 -0500",
+ "updated": "2024-11-02 15:02:28 -0400",
"brakeman_version": "6.2.2"
}
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index f7627158..458a8c08 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -12,6 +12,7 @@ search:
- app/
relative_roots:
- app/controllers
+ - app/controllers/concerns
- app/helpers
- app/mailers
- app/presenters
@@ -25,6 +26,8 @@ search:
- app/assets/fonts
- app/assets/videos
- app/assets/builds
+ignore_missing:
+ - 'accountable_resource.{create,update,destroy}.success'
ignore_unused:
- 'activerecord.attributes.*' # i18n-tasks does not detect these on forms, forms validations (https://github.com/glebm/i18n-tasks/blob/0b4b483c82664f26c5696fb0f6aa1297356e4683/templates/config/i18n-tasks.yml#L146)
- 'activerecord.models.*' # i18n-tasks does not detect use in dynamic model names (e.g. object.model_name.human)
@@ -40,4 +43,5 @@ ignore_unused:
- 'number.*'
- 'errors.*'
- 'helpers.*'
- - 'support.*'
\ No newline at end of file
+ - 'support.*'
+ - '{credit_cards,cryptos,depositories,other_assets,other_liabilities,loans,vehicles,properties,investments}.{create,update,destroy}.success'
\ No newline at end of file
diff --git a/config/locales/views/account/entries/en.yml b/config/locales/views/account/entries/en.yml
index ca0d6e8c..b4b4cb93 100644
--- a/config/locales/views/account/entries/en.yml
+++ b/config/locales/views/account/entries/en.yml
@@ -7,6 +7,17 @@ en:
empty:
description: Try adding an entry, editing filters or refining your search
title: No entries found
+ index:
+ amount: Amount
+ balance: Balance
+ date: Date
+ entries: entries
+ entry: entry
+ new: New
+ new_balance: New balance
+ new_transaction: New transaction
+ no_entries: No entries yet
+ title: Activity
loading:
loading: Loading entries...
update:
diff --git a/config/locales/views/account/transactions/en.yml b/config/locales/views/account/transactions/en.yml
index 561c5858..81a88210 100644
--- a/config/locales/views/account/transactions/en.yml
+++ b/config/locales/views/account/transactions/en.yml
@@ -35,9 +35,5 @@ en:
overview: Overview
settings: Settings
tags_label: Tags
- transaction:
- remove_transfer: Remove transfer
- remove_transfer_body: This will remove the transfer from this transaction
- remove_transfer_confirm: Confirm
update:
success: Transaction updated successfully.
diff --git a/config/locales/views/account/transfers/en.yml b/config/locales/views/account/transfers/en.yml
index 8939c446..7728d764 100644
--- a/config/locales/views/account/transfers/en.yml
+++ b/config/locales/views/account/transfers/en.yml
@@ -18,8 +18,21 @@ en:
transfer: Transfer
new:
title: New transfer
- transfer:
- remove_body: This will NOT delete the underlying transactions. It will just
- remove the transfer.
- remove_confirm: Confirm
- remove_title: Remove transfer?
+ show:
+ delete: Delete
+ delete_subtitle: This permanently deletes both of the transactions related
+ to the transfer. This cannot be undone.
+ delete_title: Delete transfer?
+ details: Details
+ exclude_subtitle: This excludes the transfer from any in-app features or analytics.
+ exclude_title: Exclude transfer
+ note_label: Notes
+ note_placeholder: Add a note to this transfer
+ overview: Overview
+ settings: Settings
+ transfer_toggle:
+ remove_transfer: Remove transfer
+ remove_transfer_body: This will remove the transfer from this transaction
+ remove_transfer_confirm: Confirm
+ update:
+ success: Transfer updated
diff --git a/config/locales/views/account/valuations/en.yml b/config/locales/views/account/valuations/en.yml
index 4f8ed28b..5c542802 100644
--- a/config/locales/views/account/valuations/en.yml
+++ b/config/locales/views/account/valuations/en.yml
@@ -5,7 +5,8 @@ en:
create:
success: Valuation created successfully.
form:
- cancel: Cancel
+ amount: Amount
+ submit: Add balance update
index:
change: change
date: date
@@ -13,15 +14,19 @@ en:
no_valuations: No valuations for this account yet
valuations: Value
value: value
- valuation:
- confirm_accept: Delete entry
- confirm_body_html: "Deleting this entry will remove it from the account’s
- history which will impact different parts of your account. This includes
- the net worth and account graphs.
The only way you’ll be able
- to add this entry back is by re-entering it manually via a new entry.
"
- confirm_title: Delete Entry?
- delete_entry: Delete entry
- edit_entry: Edit entry
- no_change: No change
- start_balance: Starting balance
- value_update: Value update
+ new:
+ title: New balance
+ show:
+ amount: Amount
+ balance: Balance
+ date_label: Date
+ delete: Delete
+ delete_subtitle: This action cannot be undone
+ delete_title: Delete Entry
+ details: Details
+ name_label: Name
+ name_placeholder: Enter a name for this entry
+ note_label: Notes
+ note_placeholder: Add any additional details about this entry
+ overview: Overview
+ settings: Settings
diff --git a/config/locales/views/accounts/en.yml b/config/locales/views/accounts/en.yml
index f7885d6c..060aaf38 100644
--- a/config/locales/views/accounts/en.yml
+++ b/config/locales/views/accounts/en.yml
@@ -6,165 +6,64 @@ en:
troubleshoot: Troubleshoot
account_list:
new_account: New %{type}
- accountables:
- credit_card:
- form:
- annual_fee: Annual fee
- annual_fee_placeholder: '99'
- apr: APR
- apr_placeholder: '15.99'
- available_credit: Available credit
- available_credit_placeholder: '10000'
- expiration_date: Expiration date
- minimum_payment: Minimum payment
- minimum_payment_placeholder: '100'
- overview:
- amount_owed: Amount Owed
- annual_fee: Annual Fee
- apr: APR
- available_credit: Available Credit
- expiration_date: Expiration Date
- minimum_payment: Minimum Payment
- unknown: Unknown
- depository:
- form:
- none: None
- prompt: Select a subtype
- investment:
- form:
- none: None
- prompt: Select a subtype
- tooltip:
- cash: Cash
- holdings: Holdings
- total_value_tooltip: The total value is the sum of cash balance and your
- holdings value, minus margin loans.
- loan:
- form:
- interest_rate: Interest rate
- interest_rate_placeholder: '5.25'
- rate_type: Rate type
- term_months: Term (months)
- term_months_placeholder: '360'
- overview:
- interest_rate: Interest Rate
- monthly_payment: Monthly Payment
- not_applicable: N/A
- original_principal: Original Principal
- remaining_principal: Remaining Principal
- term: Term
- type: Type
- unknown: Unknown
- property:
- form:
- additional_info: Additional info
- area_unit: Area unit
- area_value: Area value
- city: City
- country: Country
- line1: Address line 1
- line2: Address line 2
- optional: optional
- postal_code: Postal code
- state: State
- year_built: Year built
- overview:
- living_area: Living Area
- market_value: Market Value
- purchase_price: Purchase Price
- trend: Trend
- unknown: Unknown
- year_built: Year Built
- vehicle:
- form:
- make: Make
- make_placeholder: Toyota
- mileage: Mileage
- mileage_placeholder: '15000'
- mileage_unit: Unit
- model: Model
- model_placeholder: Camry
- year: Year
- year_placeholder: '2023'
- overview:
- current_price: Current Price
- make_model: Make & Model
- mileage: Mileage
- purchase_price: Purchase Price
- trend: Trend
- unknown: Unknown
- year: Year
- create:
- success: New account created successfully
- destroy:
- success: Account deleted successfully
- edit:
- edit: Edit %{account}
empty:
empty_message: Add an account either via connection, importing or entering manually.
new_account: New account
no_accounts: No accounts yet
form:
- accountable_type: Account type
- balance: Today's balance
+ balance: Current balance
institution: Financial institution
- mode: Value tracking mode
- mode_prompt: Select a mode
name_label: Account name
name_placeholder: Example account name
- type_prompt: Select a type
ungrouped: "(none)"
- header:
- accounts: Accounts
- manage: Manage accounts
- new: New account
index:
accounts: Accounts
add_institution: Add institution
+ institution_accounts:
+ add_account_to_institution: Add new account
+ confirm_accept: Delete institution
+ confirm_body: Don't worry, none of the accounts within this institution will
+ be affected by this deletion. Accounts will be ungrouped and all historical
+ data will remain intact.
+ confirm_title: Delete financial institution?
+ delete: Delete institution
+ edit: Edit institution
+ has_issues: Issue detected, see accounts
+ new_account: Add account
+ status: Last synced %{last_synced_at} ago
+ status_never: Requires data sync
+ syncing: Syncing...
+ institutionless_accounts:
+ other_accounts: Other accounts
new_account: New account
- institution_accounts:
- add_account_to_institution: Add new account
- confirm_accept: Delete institution
- confirm_body: Don't worry, none of the accounts within this institution will
- be affected by this deletion. Accounts will be ungrouped and all historical
- data will remain intact.
- confirm_title: Delete financial institution?
- delete: Delete institution
- edit: Edit institution
- has_issues: Issue detected, see accounts
- new_account: Add account
- status: Last synced %{last_synced_at} ago
- status_never: Requires data sync
- syncing: Syncing...
- institutionless_accounts:
- other_accounts: Other accounts
- menu:
- confirm_accept: Delete "%{name}"
- confirm_body_html: "By deleting this account, you will erase its value history,
- affecting various aspects of your overall account. This action will have a
- direct impact on your net worth calculations and the account graphs.
After deletion, there is no way you'll be able to restore the account
- information because you'll need to add it as a new account.
"
- confirm_title: Delete account?
- edit: Edit
- import: Import transactions
- new:
- connected_entry: Securely link account with Plaid (coming soon)
- csv_entry: Import accounts CSV
- manual_entry: Enter account manually
- title: Add an account
+ new:
+ method_selector:
+ connected_entry: Link account (coming soon)
+ manual_entry: Enter account balance
+ title: How would you like to add it?
+ import_accounts: Import accounts
+ title: What would you like to add?
show:
- cash: Cash
- holdings: Holdings
- no_change: No change
- overview: Overview
- total_owed: Total Owed
- total_value: Total Value
- trades: Transactions
- transactions: Transactions
- value: Value
+ chart:
+ balance: Balance
+ no_change: no change
+ owed: Amount owed
+ menu:
+ confirm_accept: Delete "%{name}"
+ confirm_body_html: "By deleting this account, you will erase its value
+ history, affecting various aspects of your overall account. This action
+ will have a direct impact on your net worth calculations and the account
+ graphs.
After deletion, there is no way you'll be able to restore
+ the account information because you'll need to add it as a new account.
"
+ confirm_title: Delete account?
+ edit: Edit
+ import: Import transactions
summary:
- new: New
+ header:
+ accounts: Accounts
+ manage: Manage accounts
+ new: New account
+ new: New account
no_assets: No assets found
no_assets_description: Add an asset either via connection, importing or entering
manually.
@@ -174,16 +73,4 @@ en:
sync_all:
success: Successfully queued accounts for syncing.
sync_all_button:
- sync: Sync all
- update:
- success: Account updated
- credit_cards:
- create:
- success: Credit card created successfully
- update:
- success: Credit card updated successfully
- loans:
- create:
- success: Loan created successfully
- update:
- success: Loan updated successfully
+ sync: Sync all
\ No newline at end of file
diff --git a/config/locales/views/credit_cards/en.yml b/config/locales/views/credit_cards/en.yml
new file mode 100644
index 00000000..41fa8253
--- /dev/null
+++ b/config/locales/views/credit_cards/en.yml
@@ -0,0 +1,31 @@
+---
+en:
+ credit_cards:
+ create:
+ success: Credit card account created
+ edit:
+ edit: Edit %{account}
+ destroy:
+ success: Credit card account deleted
+ form:
+ annual_fee: Annual fee
+ annual_fee_placeholder: '99'
+ apr: APR
+ apr_placeholder: '15.99'
+ available_credit: Available credit
+ available_credit_placeholder: '10000'
+ expiration_date: Expiration date
+ minimum_payment: Minimum payment
+ minimum_payment_placeholder: '100'
+ new:
+ title: Enter credit card details
+ overview:
+ amount_owed: Amount Owed
+ annual_fee: Annual Fee
+ apr: APR
+ available_credit: Available Credit
+ expiration_date: Expiration Date
+ minimum_payment: Minimum Payment
+ unknown: Unknown
+ update:
+ success: Credit card account updated
diff --git a/config/locales/views/cryptos/en.yml b/config/locales/views/cryptos/en.yml
new file mode 100644
index 00000000..738c2380
--- /dev/null
+++ b/config/locales/views/cryptos/en.yml
@@ -0,0 +1,13 @@
+---
+en:
+ cryptos:
+ create:
+ success: Crypto account created
+ edit:
+ edit: Edit %{account}
+ new:
+ title: Enter account balance
+ update:
+ success: Crypto account updated
+ destroy:
+ success: Crypto account deleted
diff --git a/config/locales/views/depositories/en.yml b/config/locales/views/depositories/en.yml
new file mode 100644
index 00000000..0e4622d1
--- /dev/null
+++ b/config/locales/views/depositories/en.yml
@@ -0,0 +1,16 @@
+---
+en:
+ depositories:
+ create:
+ success: Depository account created
+ edit:
+ edit: Edit %{account}
+ form:
+ none: None
+ subtype_prompt: Select account type
+ new:
+ title: Enter account balance
+ update:
+ success: Depository account updated
+ destroy:
+ success: Depository account deleted
diff --git a/config/locales/views/investments/en.yml b/config/locales/views/investments/en.yml
new file mode 100644
index 00000000..5a712493
--- /dev/null
+++ b/config/locales/views/investments/en.yml
@@ -0,0 +1,23 @@
+---
+en:
+ investments:
+ create:
+ success: Investment account created
+ edit:
+ edit: Edit %{account}
+ form:
+ none: None
+ subtype_prompt: Select investment type
+ new:
+ title: Enter account balance
+ show:
+ chart_title: Total value
+ update:
+ success: Investment account updated
+ destroy:
+ success: Investment account deleted
+ value_tooltip:
+ cash: Cash
+ holdings: Holdings
+ total_value_tooltip: The total value is the sum of cash balance and your holdings
+ value, minus margin loans.
diff --git a/config/locales/views/loans/en.yml b/config/locales/views/loans/en.yml
new file mode 100644
index 00000000..6fbd43cb
--- /dev/null
+++ b/config/locales/views/loans/en.yml
@@ -0,0 +1,28 @@
+---
+en:
+ loans:
+ create:
+ success: Loan account created
+ edit:
+ edit: Edit %{account}
+ destroy:
+ success: Loan account deleted
+ form:
+ interest_rate: Interest rate
+ interest_rate_placeholder: '5.25'
+ rate_type: Rate type
+ term_months: Term (months)
+ term_months_placeholder: '360'
+ new:
+ title: Enter loan details
+ overview:
+ interest_rate: Interest Rate
+ monthly_payment: Monthly Payment
+ not_applicable: N/A
+ original_principal: Original Principal
+ remaining_principal: Remaining Principal
+ term: Term
+ type: Type
+ unknown: Unknown
+ update:
+ success: Loan account updated
diff --git a/config/locales/views/other_assets/en.yml b/config/locales/views/other_assets/en.yml
new file mode 100644
index 00000000..db567fcf
--- /dev/null
+++ b/config/locales/views/other_assets/en.yml
@@ -0,0 +1,13 @@
+---
+en:
+ other_assets:
+ create:
+ success: Other asset account created
+ edit:
+ edit: Edit %{account}
+ destroy:
+ success: Other asset account deleted
+ new:
+ title: Enter asset details
+ update:
+ success: Other asset account updated
diff --git a/config/locales/views/other_liabilities/en.yml b/config/locales/views/other_liabilities/en.yml
new file mode 100644
index 00000000..a90eb4c0
--- /dev/null
+++ b/config/locales/views/other_liabilities/en.yml
@@ -0,0 +1,13 @@
+---
+en:
+ other_liabilities:
+ create:
+ success: Other liability account created
+ edit:
+ edit: Edit %{account}
+ destroy:
+ success: Other liability account deleted
+ new:
+ title: Enter liability details
+ update:
+ success: Other liability account updated
diff --git a/config/locales/views/properties/en.yml b/config/locales/views/properties/en.yml
index a59f2f24..ab5a8697 100644
--- a/config/locales/views/properties/en.yml
+++ b/config/locales/views/properties/en.yml
@@ -2,6 +2,37 @@
en:
properties:
create:
- success: Property created successfully
+ success: Property account created
+ edit:
+ edit: Edit %{account}
+ destroy:
+ success: Property account deleted
+ form:
+ address_line1: Street address
+ address_line1_placeholder: 123 Main St
+ area: Living area
+ area_placeholder: '2000'
+ area_unit: Unit of measurement
+ country: Country
+ country_placeholder: US
+ locality: City
+ locality_placeholder: San Francisco
+ none: None
+ postal_code: ZIP/Postal code
+ postal_code_placeholder: '94105'
+ region: State/Province
+ region_placeholder: CA
+ subtype_prompt: Select property type
+ year_built: Year built
+ year_built_placeholder: '2000'
+ new:
+ title: Enter property details
+ overview:
+ living_area: Living Area
+ market_value: Market Value
+ purchase_price: Purchase Price
+ trend: Trend
+ unknown: Unknown
+ year_built: Year Built
update:
- success: Property updated successfully
+ success: Property account updated
diff --git a/config/locales/views/transactions/en.yml b/config/locales/views/transactions/en.yml
index 7b7189e8..c0533bb4 100644
--- a/config/locales/views/transactions/en.yml
+++ b/config/locales/views/transactions/en.yml
@@ -40,6 +40,7 @@ en:
import: Import
index:
transaction: transaction
+ transactions: transactions
mark_transfers:
success: Marked as transfer
new:
diff --git a/config/locales/views/vehicles/en.yml b/config/locales/views/vehicles/en.yml
index 73e0b8e5..308d96a6 100644
--- a/config/locales/views/vehicles/en.yml
+++ b/config/locales/views/vehicles/en.yml
@@ -2,6 +2,30 @@
en:
vehicles:
create:
- success: Vehicle created successfully
+ success: Vehicle account created
+ edit:
+ edit: Edit %{account}
+ destroy:
+ success: Vehicle account deleted
+ form:
+ make: Make
+ make_placeholder: Toyota
+ mileage: Mileage
+ mileage_placeholder: '15000'
+ mileage_unit: Unit
+ model: Model
+ model_placeholder: Camry
+ year: Year
+ year_placeholder: '2023'
+ new:
+ title: Enter vehicle details
+ overview:
+ current_price: Current Price
+ make_model: Make & Model
+ mileage: Mileage
+ purchase_price: Purchase Price
+ trend: Trend
+ unknown: Unknown
+ year: Year
update:
- success: Vehicle updated successfully
+ success: Vehicle account updated
diff --git a/config/routes.rb b/config/routes.rb
index 83c150b8..4f72e198 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -45,7 +45,7 @@ Rails.application.routes.draw do
resources :merchants, only: %i[index new create edit update destroy]
namespace :account do
- resources :transfers, only: %i[new create destroy]
+ resources :transfers, only: %i[new create destroy show update]
end
resources :imports, only: %i[index new show create destroy] do
@@ -60,7 +60,7 @@ Rails.application.routes.draw do
resources :mappings, only: :update, module: :import
end
- resources :accounts do
+ resources :accounts, only: %i[index new] do
collection do
get :summary
get :list
@@ -81,14 +81,28 @@ Rails.application.routes.draw do
get :securities, on: :collection
end
- resources :entries, only: %i[edit update show destroy]
+ resources :entries, only: %i[index edit update show destroy]
end
end
- resources :properties, only: %i[create update]
- resources :vehicles, only: %i[create update]
- resources :credit_cards, only: %i[create update]
- resources :loans, only: %i[create update]
+ # Convenience routes for polymorphic paths
+ # Example: account_path(Account.new(accountable: Depository.new)) => /depositories/123
+ direct :account do |model, options|
+ route_for model.accountable_name, model, options
+ end
+ direct :edit_account do |model, options|
+ route_for "edit_#{model.accountable_name}", model, options
+ end
+
+ resources :depositories, except: :index
+ resources :investments, except: :index
+ resources :properties, except: :index
+ resources :vehicles, except: :index
+ resources :credit_cards, except: :index
+ resources :loans, except: :index
+ resources :cryptos, except: :index
+ resources :other_assets, except: :index
+ resources :other_liabilities, except: :index
resources :transactions, only: %i[index new create] do
collection do
diff --git a/db/migrate/20241030151105_remove_account_mode.rb b/db/migrate/20241030151105_remove_account_mode.rb
new file mode 100644
index 00000000..7c01db1d
--- /dev/null
+++ b/db/migrate/20241030151105_remove_account_mode.rb
@@ -0,0 +1,5 @@
+class RemoveAccountMode < ActiveRecord::Migration[7.2]
+ def change
+ remove_column :accounts, :mode
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4db6b145..a774087f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -119,9 +119,8 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_30_222235) do
t.boolean "is_active", default: true, null: false
t.date "last_sync_date"
t.uuid "institution_id"
- 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.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.uuid "import_id"
- t.string "mode"
t.index ["accountable_id", "accountable_type"], name: "index_accounts_on_accountable_id_and_accountable_type"
t.index ["accountable_type"], name: "index_accounts_on_accountable_type"
t.index ["family_id", "accountable_type"], name: "index_accounts_on_family_id_and_accountable_type"
diff --git a/test/controllers/account/trades_controller_test.rb b/test/controllers/account/trades_controller_test.rb
index b9c72852..f45e1b4b 100644
--- a/test/controllers/account/trades_controller_test.rb
+++ b/test/controllers/account/trades_controller_test.rb
@@ -32,7 +32,7 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
}
end
- assert_redirected_to account_path(@entry.account)
+ assert_redirected_to @entry.account
end
test "creates withdrawal entry" do
@@ -51,7 +51,7 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
}
end
- assert_redirected_to account_path(@entry.account)
+ assert_redirected_to @entry.account
end
test "deposit and withdrawal has optional transfer account" do
@@ -71,7 +71,7 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
assert created_entry.amount.positive?
assert created_entry.marked_as_transfer
- assert_redirected_to account_path(@entry.account)
+ assert_redirected_to @entry.account
end
test "creates interest entry" do
@@ -88,7 +88,7 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
created_entry = Account::Entry.order(created_at: :desc).first
assert created_entry.amount.negative?
- assert_redirected_to account_path(@entry.account)
+ assert_redirected_to @entry.account
end
test "creates trade buy entry" do
@@ -110,7 +110,7 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
assert created_entry.account_trade.qty.positive?
assert_equal "Transaction created successfully.", flash[:notice]
assert_enqueued_with job: AccountSyncJob
- assert_redirected_to account_path(@entry.account)
+ assert_redirected_to @entry.account
end
test "creates trade sell entry" do
@@ -133,6 +133,6 @@ class Account::TradesControllerTest < ActionDispatch::IntegrationTest
assert created_entry.account_trade.qty.negative?
assert_equal "Transaction created successfully.", flash[:notice]
assert_enqueued_with job: AccountSyncJob
- assert_redirected_to account_path(@entry.account)
+ assert_redirected_to @entry.account
end
end
diff --git a/test/controllers/account/transfers_controller_test.rb b/test/controllers/account/transfers_controller_test.rb
index 06289fd5..671147e0 100644
--- a/test/controllers/account/transfers_controller_test.rb
+++ b/test/controllers/account/transfers_controller_test.rb
@@ -26,7 +26,7 @@ class Account::TransfersControllerTest < ActionDispatch::IntegrationTest
end
test "can destroy transfer" do
- assert_difference -> { Account::Transfer.count } => -1, -> { Account::Transaction.count } => 0 do
+ assert_difference -> { Account::Transfer.count } => -1, -> { Account::Transaction.count } => -2 do
delete account_transfer_url(account_transfers(:one))
end
end
diff --git a/test/controllers/account/valuations_controller_test.rb b/test/controllers/account/valuations_controller_test.rb
index 0523a19e..eed2a33f 100644
--- a/test/controllers/account/valuations_controller_test.rb
+++ b/test/controllers/account/valuations_controller_test.rb
@@ -45,6 +45,6 @@ class Account::ValuationsControllerTest < ActionDispatch::IntegrationTest
end
assert_equal "Date has already been taken", flash[:alert]
- assert_redirected_to account_path(@entry.account)
+ assert_redirected_to @entry.account
end
end
diff --git a/test/controllers/accounts_controller_test.rb b/test/controllers/accounts_controller_test.rb
index 09e64c09..18ff5c7d 100644
--- a/test/controllers/accounts_controller_test.rb
+++ b/test/controllers/accounts_controller_test.rb
@@ -20,11 +20,6 @@ class AccountsControllerTest < ActionDispatch::IntegrationTest
assert_response :ok
end
- test "show" do
- get account_path(@account)
- assert_response :ok
- end
-
test "can sync an account" do
post sync_account_path(@account)
assert_response :no_content
@@ -35,86 +30,4 @@ class AccountsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to accounts_url
assert_equal "Successfully queued accounts for syncing.", flash[:notice]
end
-
- test "should update account" do
- patch account_url(@account), params: {
- account: {
- name: "Updated name",
- is_active: "0",
- institution_id: institutions(:chase).id
- }
- }
-
- assert_redirected_to account_url(@account)
- assert_enqueued_with job: AccountSyncJob
- assert_equal "Account updated", flash[:notice]
- end
-
- test "updates account balance by creating new valuation" do
- assert_difference [ "Account::Entry.count", "Account::Valuation.count" ], 1 do
- patch account_url(@account), params: {
- account: {
- balance: 10000
- }
- }
- end
-
- assert_redirected_to account_url(@account)
- assert_enqueued_with job: AccountSyncJob
- assert_equal "Account updated", flash[:notice]
- end
-
- test "updates account balance by editing existing valuation for today" do
- @account.entries.create! date: Date.current, amount: 6000, currency: "USD", entryable: Account::Valuation.new
-
- assert_no_difference [ "Account::Entry.count", "Account::Valuation.count" ] do
- patch account_url(@account), params: {
- account: {
- balance: 10000
- }
- }
- end
-
- assert_redirected_to account_url(@account)
- assert_enqueued_with job: AccountSyncJob
- assert_equal "Account updated", flash[:notice]
- end
-
- test "should create an account" do
- assert_difference [ "Account.count", "Account::Valuation.count", "Account::Entry.count" ], 1 do
- post accounts_path, params: {
- account: {
- name: "Test",
- accountable_type: "Depository",
- balance: 200,
- currency: "USD",
- subtype: "checking",
- institution_id: institutions(:chase).id
- }
- }
-
- assert_equal "New account created successfully", flash[:notice]
- assert_redirected_to account_url(Account.order(:created_at).last)
- end
- end
-
- test "can add optional start date and balance to an account on create" do
- assert_difference -> { Account.count } => 1, -> { Account::Valuation.count } => 2 do
- post accounts_path, params: {
- account: {
- name: "Test",
- accountable_type: "Depository",
- balance: 200,
- currency: "USD",
- subtype: "checking",
- institution_id: institutions(:chase).id,
- start_balance: 100,
- start_date: 10.days.ago
- }
- }
-
- assert_equal "New account created successfully", flash[:notice]
- assert_redirected_to account_url(Account.order(:created_at).last)
- end
- end
end
diff --git a/test/controllers/credit_cards_controller_test.rb b/test/controllers/credit_cards_controller_test.rb
index d8f6772c..dfd1b5b6 100644
--- a/test/controllers/credit_cards_controller_test.rb
+++ b/test/controllers/credit_cards_controller_test.rb
@@ -1,12 +1,14 @@
require "test_helper"
class CreditCardsControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
setup do
sign_in @user = users(:family_admin)
@account = accounts(:credit_card)
end
- test "creates credit card" do
+ test "creates with credit card details" do
assert_difference -> { Account.count } => 1,
-> { CreditCard.count } => 1,
-> { Account::Valuation.count } => 2,
@@ -17,8 +19,6 @@ class CreditCardsControllerTest < ActionDispatch::IntegrationTest
balance: 1000,
currency: "USD",
accountable_type: "CreditCard",
- start_date: 1.month.ago.to_date,
- start_balance: 0,
accountable_attributes: {
available_credit: 5000,
minimum_payment: 25,
@@ -35,20 +35,20 @@ class CreditCardsControllerTest < ActionDispatch::IntegrationTest
assert_equal "New Credit Card", created_account.name
assert_equal 1000, created_account.balance
assert_equal "USD", created_account.currency
- assert_equal 5000, created_account.credit_card.available_credit
- assert_equal 25, created_account.credit_card.minimum_payment
- assert_equal 15.99, created_account.credit_card.apr
- assert_equal 2.years.from_now.to_date, created_account.credit_card.expiration_date
- assert_equal 99, created_account.credit_card.annual_fee
+ assert_equal 5000, created_account.accountable.available_credit
+ assert_equal 25, created_account.accountable.minimum_payment
+ assert_equal 15.99, created_account.accountable.apr
+ assert_equal 2.years.from_now.to_date, created_account.accountable.expiration_date
+ assert_equal 99, created_account.accountable.annual_fee
- assert_redirected_to account_path(created_account)
- assert_equal "Credit card created successfully", flash[:notice]
+ assert_redirected_to created_account
+ assert_equal "Credit card account created", flash[:notice]
assert_enqueued_with(job: AccountSyncJob)
end
- test "updates credit card" do
+ test "updates with credit card details" do
assert_no_difference [ "Account.count", "CreditCard.count" ] do
- patch credit_card_path(@account), params: {
+ patch account_path(@account), params: {
account: {
name: "Updated Credit Card",
balance: 2000,
@@ -70,14 +70,14 @@ class CreditCardsControllerTest < ActionDispatch::IntegrationTest
assert_equal "Updated Credit Card", @account.name
assert_equal 2000, @account.balance
- assert_equal 6000, @account.credit_card.available_credit
- assert_equal 50, @account.credit_card.minimum_payment
- assert_equal 14.99, @account.credit_card.apr
- assert_equal 3.years.from_now.to_date, @account.credit_card.expiration_date
- assert_equal 0, @account.credit_card.annual_fee
+ assert_equal 6000, @account.accountable.available_credit
+ assert_equal 50, @account.accountable.minimum_payment
+ assert_equal 14.99, @account.accountable.apr
+ assert_equal 3.years.from_now.to_date, @account.accountable.expiration_date
+ assert_equal 0, @account.accountable.annual_fee
- assert_redirected_to account_path(@account)
- assert_equal "Credit card updated successfully", flash[:notice]
+ assert_redirected_to @account
+ assert_equal "Credit card account updated", flash[:notice]
assert_enqueued_with(job: AccountSyncJob)
end
end
diff --git a/test/controllers/cryptos_controller_test.rb b/test/controllers/cryptos_controller_test.rb
new file mode 100644
index 00000000..c0a9d1b2
--- /dev/null
+++ b/test/controllers/cryptos_controller_test.rb
@@ -0,0 +1,10 @@
+require "test_helper"
+
+class CryptosControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
+ setup do
+ sign_in @user = users(:family_admin)
+ @account = accounts(:crypto)
+ end
+end
diff --git a/test/controllers/depositories_controller_test.rb b/test/controllers/depositories_controller_test.rb
new file mode 100644
index 00000000..3c6e443a
--- /dev/null
+++ b/test/controllers/depositories_controller_test.rb
@@ -0,0 +1,10 @@
+require "test_helper"
+
+class DepositoriesControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
+ setup do
+ sign_in @user = users(:family_admin)
+ @account = accounts(:depository)
+ end
+end
diff --git a/test/controllers/investments_controller_test.rb b/test/controllers/investments_controller_test.rb
new file mode 100644
index 00000000..e9caf260
--- /dev/null
+++ b/test/controllers/investments_controller_test.rb
@@ -0,0 +1,10 @@
+require "test_helper"
+
+class InvestmentsControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
+ setup do
+ sign_in @user = users(:family_admin)
+ @account = accounts(:investment)
+ end
+end
diff --git a/test/controllers/invitations_controller_test.rb b/test/controllers/invitations_controller_test.rb
index d6bdcacb..b6b7edbe 100644
--- a/test/controllers/invitations_controller_test.rb
+++ b/test/controllers/invitations_controller_test.rb
@@ -57,7 +57,7 @@ class InvitationsControllerTest < ActionDispatch::IntegrationTest
}
end
- invitation = Invitation.last
+ invitation = Invitation.order(created_at: :desc).first
assert_equal "admin", invitation.role
assert_equal @user.family, invitation.family
assert_equal @user, invitation.inviter
diff --git a/test/controllers/issue/exchange_rate_provider_missings_controller_test.rb b/test/controllers/issue/exchange_rate_provider_missings_controller_test.rb
index 10cb6f1a..01d48ce3 100644
--- a/test/controllers/issue/exchange_rate_provider_missings_controller_test.rb
+++ b/test/controllers/issue/exchange_rate_provider_missings_controller_test.rb
@@ -14,6 +14,6 @@ class Issue::ExchangeRateProviderMissingsControllerTest < ActionDispatch::Integr
}
assert_enqueued_with job: AccountSyncJob
- assert_redirected_to account_url(@issue.issuable)
+ assert_redirected_to @issue.issuable
end
end
diff --git a/test/controllers/loans_controller_test.rb b/test/controllers/loans_controller_test.rb
index 3ec9efc0..7df26b81 100644
--- a/test/controllers/loans_controller_test.rb
+++ b/test/controllers/loans_controller_test.rb
@@ -1,12 +1,14 @@
require "test_helper"
class LoansControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
setup do
sign_in @user = users(:family_admin)
@account = accounts(:loan)
end
- test "creates loan" do
+ test "creates with loan details" do
assert_difference -> { Account.count } => 1,
-> { Loan.count } => 1,
-> { Account::Valuation.count } => 2,
@@ -17,8 +19,6 @@ class LoansControllerTest < ActionDispatch::IntegrationTest
balance: 50000,
currency: "USD",
accountable_type: "Loan",
- start_date: 1.month.ago.to_date,
- start_balance: 50000,
accountable_attributes: {
interest_rate: 5.5,
term_months: 60,
@@ -33,18 +33,18 @@ class LoansControllerTest < ActionDispatch::IntegrationTest
assert_equal "New Loan", created_account.name
assert_equal 50000, created_account.balance
assert_equal "USD", created_account.currency
- assert_equal 5.5, created_account.loan.interest_rate
- assert_equal 60, created_account.loan.term_months
- assert_equal "fixed", created_account.loan.rate_type
+ assert_equal 5.5, created_account.accountable.interest_rate
+ assert_equal 60, created_account.accountable.term_months
+ assert_equal "fixed", created_account.accountable.rate_type
- assert_redirected_to account_path(created_account)
- assert_equal "Loan created successfully", flash[:notice]
+ assert_redirected_to created_account
+ assert_equal "Loan account created", flash[:notice]
assert_enqueued_with(job: AccountSyncJob)
end
- test "updates loan" do
+ test "updates with loan details" do
assert_no_difference [ "Account.count", "Loan.count" ] do
- patch loan_path(@account), params: {
+ patch account_path(@account), params: {
account: {
name: "Updated Loan",
balance: 45000,
@@ -64,12 +64,12 @@ class LoansControllerTest < ActionDispatch::IntegrationTest
assert_equal "Updated Loan", @account.name
assert_equal 45000, @account.balance
- assert_equal 4.5, @account.loan.interest_rate
- assert_equal 48, @account.loan.term_months
- assert_equal "fixed", @account.loan.rate_type
+ assert_equal 4.5, @account.accountable.interest_rate
+ assert_equal 48, @account.accountable.term_months
+ assert_equal "fixed", @account.accountable.rate_type
- assert_redirected_to account_path(@account)
- assert_equal "Loan updated successfully", flash[:notice]
+ assert_redirected_to @account
+ assert_equal "Loan account updated", flash[:notice]
assert_enqueued_with(job: AccountSyncJob)
end
end
diff --git a/test/controllers/other_assets_controller_test.rb b/test/controllers/other_assets_controller_test.rb
new file mode 100644
index 00000000..3a10fda0
--- /dev/null
+++ b/test/controllers/other_assets_controller_test.rb
@@ -0,0 +1,10 @@
+require "test_helper"
+
+class OtherAssetsControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
+ setup do
+ sign_in @user = users(:family_admin)
+ @account = accounts(:other_asset)
+ end
+end
diff --git a/test/controllers/other_liabilities_controller_test.rb b/test/controllers/other_liabilities_controller_test.rb
new file mode 100644
index 00000000..ef6ac646
--- /dev/null
+++ b/test/controllers/other_liabilities_controller_test.rb
@@ -0,0 +1,10 @@
+require "test_helper"
+
+class OtherLiabilitiesControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
+ setup do
+ sign_in @user = users(:family_admin)
+ @account = accounts(:other_liability)
+ end
+end
diff --git a/test/controllers/properties_controller_test.rb b/test/controllers/properties_controller_test.rb
index 1e017eed..46412060 100644
--- a/test/controllers/properties_controller_test.rb
+++ b/test/controllers/properties_controller_test.rb
@@ -1,12 +1,14 @@
require "test_helper"
class PropertiesControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
setup do
sign_in @user = users(:family_admin)
@account = accounts(:property)
end
- test "creates property" do
+ test "creates with property details" do
assert_difference -> { Account.count } => 1,
-> { Property.count } => 1,
-> { Account::Valuation.count } => 2,
@@ -17,8 +19,6 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
balance: 500000,
currency: "USD",
accountable_type: "Property",
- start_date: 3.years.ago.to_date,
- start_balance: 450000,
accountable_attributes: {
year_built: 2002,
area_value: 1000,
@@ -38,17 +38,17 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
created_account = Account.order(:created_at).last
- assert created_account.property.year_built.present?
- assert created_account.property.address.line1.present?
+ assert created_account.accountable.year_built.present?
+ assert created_account.accountable.address.line1.present?
- assert_redirected_to account_path(created_account)
- assert_equal "Property created successfully", flash[:notice]
+ assert_redirected_to created_account
+ assert_equal "Property account created", flash[:notice]
assert_enqueued_with(job: AccountSyncJob)
end
- test "updates property" do
+ test "updates with property details" do
assert_no_difference [ "Account.count", "Property.count" ] do
- patch property_path(@account), params: {
+ patch account_path(@account), params: {
account: {
name: "Updated Property",
balance: 500000,
@@ -72,8 +72,8 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
}
end
- assert_redirected_to account_path(@account)
- assert_equal "Property updated successfully", flash[:notice]
+ assert_redirected_to @account
+ assert_equal "Property account updated", flash[:notice]
assert_enqueued_with(job: AccountSyncJob)
end
end
diff --git a/test/controllers/vehicles_controller_test.rb b/test/controllers/vehicles_controller_test.rb
index 6908f969..00df8f36 100644
--- a/test/controllers/vehicles_controller_test.rb
+++ b/test/controllers/vehicles_controller_test.rb
@@ -1,12 +1,14 @@
require "test_helper"
class VehiclesControllerTest < ActionDispatch::IntegrationTest
+ include AccountableResourceInterfaceTest
+
setup do
sign_in @user = users(:family_admin)
@account = accounts(:vehicle)
end
- test "creates vehicle" do
+ test "creates with vehicle details" do
assert_difference -> { Account.count } => 1,
-> { Vehicle.count } => 1,
-> { Account::Valuation.count } => 2,
@@ -17,8 +19,6 @@ class VehiclesControllerTest < ActionDispatch::IntegrationTest
balance: 30000,
currency: "USD",
accountable_type: "Vehicle",
- start_date: 1.year.ago.to_date,
- start_balance: 35000,
accountable_attributes: {
make: "Toyota",
model: "Camry",
@@ -32,20 +32,20 @@ class VehiclesControllerTest < ActionDispatch::IntegrationTest
created_account = Account.order(:created_at).last
- assert_equal "Toyota", created_account.vehicle.make
- assert_equal "Camry", created_account.vehicle.model
- assert_equal 2020, created_account.vehicle.year
- assert_equal 15000, created_account.vehicle.mileage_value
- assert_equal "mi", created_account.vehicle.mileage_unit
+ assert_equal "Toyota", created_account.accountable.make
+ assert_equal "Camry", created_account.accountable.model
+ assert_equal 2020, created_account.accountable.year
+ assert_equal 15000, created_account.accountable.mileage_value
+ assert_equal "mi", created_account.accountable.mileage_unit
- assert_redirected_to account_path(created_account)
- assert_equal "Vehicle created successfully", flash[:notice]
+ assert_redirected_to created_account
+ assert_equal "Vehicle account created", flash[:notice]
assert_enqueued_with(job: AccountSyncJob)
end
- test "updates vehicle" do
+ test "updates with vehicle details" do
assert_no_difference [ "Account.count", "Vehicle.count" ] do
- patch vehicle_path(@account), params: {
+ patch account_path(@account), params: {
account: {
name: "Updated Vehicle",
balance: 28000,
@@ -64,8 +64,8 @@ class VehiclesControllerTest < ActionDispatch::IntegrationTest
}
end
- assert_redirected_to account_path(@account)
- assert_equal "Vehicle updated successfully", flash[:notice]
+ assert_redirected_to @account
+ assert_equal "Vehicle account updated", flash[:notice]
assert_enqueued_with(job: AccountSyncJob)
end
end
diff --git a/test/fixtures/accounts.yml b/test/fixtures/accounts.yml
index 9afbc8a1..5c33a1ef 100644
--- a/test/fixtures/accounts.yml
+++ b/test/fixtures/accounts.yml
@@ -5,7 +5,6 @@ other_asset:
currency: USD
accountable_type: OtherAsset
accountable: one
- mode: balance
other_liability:
family: dylan_family
@@ -14,7 +13,6 @@ other_liability:
currency: USD
accountable_type: OtherLiability
accountable: one
- mode: balance
depository:
family: dylan_family
@@ -24,7 +22,6 @@ depository:
accountable_type: Depository
accountable: one
institution: chase
- mode: transactions
credit_card:
family: dylan_family
@@ -34,7 +31,6 @@ credit_card:
accountable_type: CreditCard
accountable: one
institution: chase
- mode: transactions
investment:
family: dylan_family
@@ -43,7 +39,6 @@ investment:
currency: USD
accountable_type: Investment
accountable: one
- mode: transactions
loan:
family: dylan_family
@@ -52,7 +47,6 @@ loan:
currency: USD
accountable_type: Loan
accountable: one
- mode: transactions
property:
family: dylan_family
@@ -61,7 +55,6 @@ property:
currency: USD
accountable_type: Property
accountable: one
- mode: transactions
vehicle:
family: dylan_family
@@ -70,4 +63,11 @@ vehicle:
currency: USD
accountable_type: Vehicle
accountable: one
- mode: transactions
+
+crypto:
+ family: dylan_family
+ name: Bitcoin
+ balance: 10000
+ currency: USD
+ accountable_type: Crypto
+ accountable: one
diff --git a/test/interfaces/accountable_resource_interface_test.rb b/test/interfaces/accountable_resource_interface_test.rb
new file mode 100644
index 00000000..ce0a6871
--- /dev/null
+++ b/test/interfaces/accountable_resource_interface_test.rb
@@ -0,0 +1,90 @@
+require "test_helper"
+
+module AccountableResourceInterfaceTest
+ extend ActiveSupport::Testing::Declarative
+
+ test "shows new form" do
+ get new_polymorphic_url(@account.accountable)
+ assert_response :success
+ end
+
+ test "shows edit form" do
+ get edit_account_url(@account)
+ assert_response :success
+ end
+
+ test "renders accountable page" do
+ get account_url(@account)
+ assert_response :success
+ end
+
+ test "destroys account" do
+ delete account_url(@account)
+ assert_redirected_to accounts_path
+ assert_equal "#{@account.accountable_name.humanize} account deleted", flash[:notice]
+ end
+
+ test "updates basic account balances" do
+ assert_no_difference [ "Account.count", "@account.accountable_class.count" ] do
+ patch account_url(@account), params: {
+ account: {
+ institution_id: institutions(:chase).id,
+ name: "Updated name",
+ balance: 10000,
+ currency: "USD"
+ }
+ }
+ end
+
+ assert_redirected_to @account
+ assert_equal "#{@account.accountable_name.humanize} account updated", flash[:notice]
+ end
+
+ test "creates with basic attributes" do
+ assert_difference [ "Account.count", "@account.accountable_class.count" ], 1 do
+ post "/#{@account.accountable_name.pluralize}", params: {
+ account: {
+ accountable_type: @account.accountable_class,
+ institution_id: institutions(:chase).id,
+ name: "New accountable",
+ balance: 10000,
+ currency: "USD",
+ subtype: "checking"
+ }
+ }
+ end
+
+ assert_redirected_to Account.order(:created_at).last
+ assert_equal "#{@account.accountable_name.humanize} account created", flash[:notice]
+ end
+
+ test "updates account balance by creating new valuation" do
+ assert_difference [ "Account::Entry.count", "Account::Valuation.count" ], 1 do
+ patch account_url(@account), params: {
+ account: {
+ balance: 10000
+ }
+ }
+ end
+
+ assert_redirected_to @account
+ assert_enqueued_with job: AccountSyncJob
+ assert_equal "#{@account.accountable_name.humanize} account updated", flash[:notice]
+ end
+
+ test "updates account balance by editing existing valuation for today" do
+ @account.entries.create! date: Date.current, amount: 6000, currency: "USD", entryable: Account::Valuation.new
+
+ assert_no_difference [ "Account::Entry.count", "Account::Valuation.count" ] do
+ patch account_url(@account), params: {
+ account: {
+ balance: 10000
+ }
+ }
+ end
+
+ assert_redirected_to @account
+ assert_enqueued_with job: AccountSyncJob
+ assert_equal "#{@account.accountable_name.humanize} account updated", flash[:notice]
+ end
+end
diff --git a/test/system/accounts_test.rb b/test/system/accounts_test.rb
index b7b6d18e..87907323 100644
--- a/test/system/accounts_test.rb
+++ b/test/system/accounts_test.rb
@@ -23,12 +23,11 @@ class AccountsTest < ApplicationSystemTestCase
test "can create property account" do
assert_account_created "Property" do
fill_in "Year built", with: 2005
- fill_in "Area value", with: 2250
- fill_in "Address line 1", with: "123 Main St"
- fill_in "Address line 2", with: "Apt 4B"
+ fill_in "Living area", with: 2250
+ fill_in "Street address", with: "123 Main St"
fill_in "City", with: "San Francisco"
- fill_in "State", with: "CA"
- fill_in "Postal code", with: "94101"
+ fill_in "State/Province", with: "CA"
+ fill_in "ZIP/Postal code", with: "94101"
fill_in "Country", with: "US"
end
end
@@ -80,14 +79,16 @@ class AccountsTest < ApplicationSystemTestCase
end
def assert_account_created(accountable_type, &block)
- click_link "Enter account manually"
+ click_link humanized_accountable(accountable_type)
+ click_link "Enter account balance" if accountable_type.in?(%w[Depository Investment Crypto Loan CreditCard])
account_name = "[system test] #{accountable_type} Account"
- select accountable_type.titleize, from: "Account type"
- fill_in "Account name", with: account_name
+ fill_in "Account name*", with: account_name
fill_in "account[balance]", with: 100.99
+ yield if block_given?
+
click_button "Create Account"
find("details", text: humanized_accountable(accountable_type)).click
@@ -97,7 +98,6 @@ class AccountsTest < ApplicationSystemTestCase
assert_text account_name
created_account = Account.order(:created_at).last
- created_account.update!(mode: "transactions")
visit account_url(created_account)
@@ -106,8 +106,6 @@ class AccountsTest < ApplicationSystemTestCase
click_on "Edit"
end
- yield if block_given?
-
fill_in "Account name", with: "Updated account name"
click_button "Update Account"
assert_selector "h2", text: "Updated account name"
diff --git a/test/system/trades_test.rb b/test/system/trades_test.rb
index c9539276..87855e0d 100644
--- a/test/system/trades_test.rb
+++ b/test/system/trades_test.rb
@@ -66,15 +66,18 @@ class TradesTest < ApplicationSystemTestCase
private
def open_new_trade_modal
- click_link "new_trade_account_#{@account.id}"
+ within "[data-testid='activity-menu']" do
+ click_on "New"
+ click_on "New transaction"
+ end
end
def within_trades(&block)
- within "#" + dom_id(@account, "trades"), &block
+ within "#" + dom_id(@account, "entries"), &block
end
def visit_account_trades
- visit account_url(@account, tab: "transactions")
+ visit account_path(@account)
end
def select_combobox_option(text)
diff --git a/test/system/transactions_test.rb b/test/system/transactions_test.rb
index 622f0b81..e51c8183 100644
--- a/test/system/transactions_test.rb
+++ b/test/system/transactions_test.rb
@@ -182,15 +182,13 @@ class TransactionsTest < ApplicationSystemTestCase
investment_account = accounts(:investment)
investment_account.entries.create!(name: "Investment account", date: Date.current, amount: 1000, currency: "USD", entryable: Account::Transaction.new)
transfer_date = Date.current
- visit account_path(investment_account)
+ visit account_url(investment_account)
+ click_on "New"
click_on "New transaction"
select "Deposit", from: "Type"
fill_in "Date", with: transfer_date
fill_in "account_entry[amount]", with: 175.25
click_button "Add transaction"
- within "#account_" + investment_account.id do
- click_on "Transactions"
- end
within "#entry-group-" + transfer_date.to_s do
assert_text "175.25"
end
diff --git a/test/system/transfers_test.rb b/test/system/transfers_test.rb
index 1fc72fa5..601a50f8 100644
--- a/test/system/transfers_test.rb
+++ b/test/system/transfers_test.rb
@@ -55,7 +55,8 @@ class TransfersTest < ApplicationSystemTestCase
click_on "Mark as transfers"
within "#entry-group-" + transfer_date.to_s do
- assert_text "Transfer from"
+ assert_text "Outflow"
+ assert_text "Inflow"
end
end