From 65db49273c0dd275ff4ae10e4e7bf30dbf2cec68 Mon Sep 17 00:00:00 2001 From: Zach Gollwitzer Date: Mon, 4 Nov 2024 20:27:31 -0500 Subject: [PATCH] Account Activity View + Account Forms (#1406) * Remove balance mode, sketch out refactor * Activity view checkpoint * Entry partials, checkpoint * Finish txn partial * Give entries context when editing for different turbo responses * Calculate change of balance for each entry * Account tabs consolidation * Translations, linting, brakeman updates * Account actions concern * Finalize forms, get account system tests passing * Get tests passing * Lint, rubocop, schema updates * Improve routing and stream responses * Fix broken routes * Add import option for adding accounts * Fix system test * Fix test specificity * Fix sparklines * Improve account redirects --- app/controllers/account/entries_controller.rb | 17 +- app/controllers/account/trades_controller.rb | 4 +- .../account/transactions_controller.rb | 25 ++- .../account/transfers_controller.rb | 26 ++- .../account/valuations_controller.rb | 2 +- app/controllers/accounts_controller.rb | 48 +---- .../concerns/accountable_resource.rb | 60 ++++++ app/controllers/concerns/filterable.rb | 23 -- app/controllers/credit_cards_controller.rb | 47 +--- app/controllers/cryptos_controller.rb | 3 + app/controllers/depositories_controller.rb | 3 + app/controllers/investments_controller.rb | 3 + ...hange_rate_provider_missings_controller.rb | 5 +- app/controllers/loans_controller.rb | 40 +--- app/controllers/other_assets_controller.rb | 3 + .../other_liabilities_controller.rb | 3 + app/controllers/pages_controller.rb | 3 +- app/controllers/properties_controller.rb | 48 ++--- app/controllers/transactions_controller.rb | 2 +- app/controllers/vehicles_controller.rb | 42 +--- app/helpers/account/entries_helper.rb | 43 +--- app/helpers/accounts_helper.rb | 81 ++----- app/helpers/application_helper.rb | 23 -- .../controllers/bulk_select_controller.js | 11 +- .../time_series_chart_controller.js | 2 +- app/models/account.rb | 38 ++-- app/models/account/entry.rb | 47 ++-- app/models/account/trade.rb | 6 + app/models/account/transaction.rb | 20 +- app/models/account/transfer.rb | 22 +- app/models/account/valuation.rb | 40 ++++ app/models/concerns/accountable.rb | 4 - app/models/credit_card.rb | 4 + app/models/crypto.rb | 4 + app/models/demo/generator.rb | 17 +- app/models/depository.rb | 9 + app/models/investment.rb | 4 + app/models/loan.rb | 4 + app/models/other_asset.rb | 4 +- app/models/other_liability.rb | 4 +- app/models/period.rb | 16 +- app/models/property.rb | 12 +- app/models/time_series/trend.rb | 12 +- app/models/vehicle.rb | 4 +- app/views/account/entries/_entry.html.erb | 4 +- .../account/entries/_entry_group.html.erb | 6 +- .../account/entries/_selection_bar.html.erb | 15 ++ app/views/account/entries/index.html.erb | 88 ++++++++ .../account/trades/_security.turbo_stream.erb | 2 +- app/views/account/trades/_trade.html.erb | 8 +- .../trades/securities.turbo_stream.erb | 2 +- .../transactions/_transaction.html.erb | 74 +++---- app/views/account/transactions/show.html.erb | 24 ++- .../account/transfers/_account_logos.html.erb | 25 +++ .../account/transfers/_transfer.html.erb | 49 ----- .../transfers/_transfer_toggle.html.erb | 16 ++ app/views/account/transfers/show.html.erb | 121 +++++++++++ app/views/account/valuations/_form.html.erb | 28 +-- .../account/valuations/_valuation.html.erb | 71 +++---- app/views/account/valuations/new.html.erb | 5 +- app/views/account/valuations/show.html.erb | 84 +++++++- app/views/accounts/_account.html.erb | 4 +- app/views/accounts/_account_list.html.erb | 72 +++---- app/views/accounts/_account_type.html.erb | 13 +- app/views/accounts/_empty.html.erb | 2 +- app/views/accounts/_entry_method.html.erb | 17 -- app/views/accounts/_form.html.erb | 25 +-- app/views/accounts/_overview.html.erb | 3 - .../accountables/_default_header.html.erb | 7 - .../accountables/_default_tabs.html.erb | 13 -- .../accountables/_transactions.html.erb | 5 - .../accountables/_valuations.html.erb | 5 - .../accountables/_value_onboarding.html.erb | 16 -- .../accountables/credit_card/_form.html.erb | 21 -- .../accountables/credit_card/_header.html.erb | 1 - .../accountables/credit_card/_tabs.html.erb | 26 --- .../accountables/crypto/_form.html.erb | 0 .../accountables/crypto/_header.html.erb | 1 - .../accountables/crypto/_tabs.html.erb | 1 - .../accountables/depository/_form.html.erb | 1 - .../accountables/depository/_header.html.erb | 1 - .../accountables/depository/_tabs.html.erb | 1 - .../accountables/investment/_form.html.erb | 1 - .../accountables/investment/_header.html.erb | 1 - .../accountables/investment/_tabs.html.erb | 34 --- .../accounts/accountables/loan/_form.html.erb | 16 -- .../accountables/loan/_header.html.erb | 1 - .../accounts/accountables/loan/_tabs.html.erb | 25 --- .../accountables/other_asset/_form.html.erb | 0 .../accountables/other_asset/_header.html.erb | 1 - .../accountables/other_asset/_tabs.html.erb | 1 - .../other_liability/_form.html.erb | 0 .../other_liability/_header.html.erb | 1 - .../other_liability/_tabs.html.erb | 1 - .../accountables/property/_form.html.erb | 36 ---- .../accountables/property/_header.html.erb | 11 - .../accountables/property/_tabs.html.erb | 15 -- .../accountables/vehicle/_form.html.erb | 23 -- .../accountables/vehicle/_header.html.erb | 1 - .../accountables/vehicle/_tabs.html.erb | 15 -- app/views/accounts/index.html.erb | 6 +- .../_institution_accounts.html.erb | 4 +- .../_institutionless_accounts.html.erb | 0 app/views/accounts/new.html.erb | 69 ++---- app/views/accounts/new/_container.html.erb | 40 ++++ .../accounts/new/_method_selector.html.erb | 19 ++ app/views/accounts/show.html.erb | 65 ------ app/views/accounts/show/_activity.html.erb | 5 + app/views/accounts/show/_chart.html.erb | 40 ++++ app/views/accounts/show/_header.html.erb | 34 +++ app/views/accounts/show/_loading.html.erb | 3 + app/views/accounts/{ => show}/_menu.html.erb | 2 + .../{accountables => show}/_tab.html.erb | 0 app/views/accounts/show/_tabs.html.erb | 11 + app/views/accounts/show/_template.html.erb | 27 +++ app/views/accounts/summary.html.erb | 16 +- .../accounts/{ => summary}/_header.html.erb | 2 +- app/views/categories/_menu.html.erb | 4 +- app/views/category/dropdowns/_row.html.erb | 4 +- app/views/category/dropdowns/show.html.erb | 2 +- app/views/credit_cards/_form.html.erb | 37 ++++ .../_overview.html.erb | 2 +- .../{accounts => credit_cards}/edit.html.erb | 2 +- app/views/credit_cards/new.html.erb | 7 + app/views/credit_cards/show.html.erb | 6 + app/views/cryptos/_form.html.erb | 3 + app/views/cryptos/edit.html.erb | 3 + app/views/cryptos/new.html.erb | 7 + app/views/cryptos/show.html.erb | 1 + app/views/depositories/_form.html.erb | 7 + app/views/depositories/edit.html.erb | 3 + app/views/depositories/new.html.erb | 7 + app/views/depositories/show.html.erb | 1 + app/views/import/confirms/_mappings.html.erb | 4 +- app/views/investments/_cash_tab.html.erb | 5 + app/views/investments/_chart.html.erb | 1 + app/views/investments/_form.html.erb | 7 + app/views/investments/_holdings_tab.html.erb | 5 + .../_value_tooltip.html.erb} | 6 +- app/views/investments/edit.html.erb | 3 + app/views/investments/new.html.erb | 7 + app/views/investments/show.html.erb | 24 +++ .../invitation_mailer/invite_email.html.erb | 4 +- app/views/invitations/new.html.erb | 4 +- app/views/layouts/_sidebar.html.erb | 4 +- app/views/loans/_form.html.erb | 26 +++ .../loan => loans}/_overview.html.erb | 2 +- app/views/loans/edit.html.erb | 3 + app/views/loans/new.html.erb | 7 + app/views/loans/show.html.erb | 6 + app/views/other_assets/_form.html.erb | 3 + app/views/other_assets/edit.html.erb | 3 + app/views/other_assets/new.html.erb | 3 + app/views/other_assets/show.html.erb | 1 + app/views/other_liabilities/_form.html.erb | 3 + app/views/other_liabilities/edit.html.erb | 3 + app/views/other_liabilities/new.html.erb | 3 + app/views/other_liabilities/show.html.erb | 1 + app/views/pages/dashboard.html.erb | 4 +- app/views/properties/_form.html.erb | 54 +++++ .../_overview.html.erb | 2 +- app/views/properties/edit.html.erb | 3 + app/views/properties/new.html.erb | 3 + app/views/properties/show.html.erb | 7 + app/views/registrations/new.html.erb | 6 +- app/views/settings/profiles/show.html.erb | 7 +- app/views/shared/_modal_form.html.erb | 6 +- .../shared/_no_account_empty_state.html.erb | 2 +- app/views/shared/_sparkline.html.erb | 13 ++ app/views/transactions/index.html.erb | 14 +- app/views/vehicles/_form.html.erb | 33 +++ .../vehicle => vehicles}/_overview.html.erb | 2 +- app/views/vehicles/edit.html.erb | 3 + app/views/vehicles/new.html.erb | 3 + app/views/vehicles/show.html.erb | 6 + config/brakeman.ignore | 108 +--------- config/i18n-tasks.yml | 6 +- config/locales/views/account/entries/en.yml | 11 + .../locales/views/account/transactions/en.yml | 4 - config/locales/views/account/transfers/en.yml | 23 +- .../locales/views/account/valuations/en.yml | 31 +-- config/locales/views/accounts/en.yml | 201 ++++-------------- config/locales/views/credit_cards/en.yml | 31 +++ config/locales/views/cryptos/en.yml | 13 ++ config/locales/views/depositories/en.yml | 16 ++ config/locales/views/investments/en.yml | 23 ++ config/locales/views/loans/en.yml | 28 +++ config/locales/views/other_assets/en.yml | 13 ++ config/locales/views/other_liabilities/en.yml | 13 ++ config/locales/views/properties/en.yml | 35 ++- config/locales/views/transactions/en.yml | 1 + config/locales/views/vehicles/en.yml | 28 ++- config/routes.rb | 28 ++- .../20241030151105_remove_account_mode.rb | 5 + db/schema.rb | 3 +- .../account/trades_controller_test.rb | 12 +- .../account/transfers_controller_test.rb | 2 +- .../account/valuations_controller_test.rb | 2 +- test/controllers/accounts_controller_test.rb | 87 -------- .../credit_cards_controller_test.rb | 38 ++-- test/controllers/cryptos_controller_test.rb | 10 + .../depositories_controller_test.rb | 10 + .../investments_controller_test.rb | 10 + .../invitations_controller_test.rb | 2 +- ..._rate_provider_missings_controller_test.rb | 2 +- test/controllers/loans_controller_test.rb | 30 +-- .../other_assets_controller_test.rb | 10 + .../other_liabilities_controller_test.rb | 10 + .../controllers/properties_controller_test.rb | 22 +- test/controllers/vehicles_controller_test.rb | 28 +-- test/fixtures/accounts.yml | 16 +- .../accountable_resource_interface_test.rb | 90 ++++++++ test/system/accounts_test.rb | 20 +- test/system/trades_test.rb | 9 +- test/system/transactions_test.rb | 6 +- test/system/transfers_test.rb | 3 +- 216 files changed, 2043 insertions(+), 1620 deletions(-) create mode 100644 app/controllers/concerns/accountable_resource.rb delete mode 100644 app/controllers/concerns/filterable.rb create mode 100644 app/controllers/cryptos_controller.rb create mode 100644 app/controllers/depositories_controller.rb create mode 100644 app/controllers/investments_controller.rb create mode 100644 app/controllers/other_assets_controller.rb create mode 100644 app/controllers/other_liabilities_controller.rb create mode 100644 app/views/account/entries/_selection_bar.html.erb create mode 100644 app/views/account/entries/index.html.erb create mode 100644 app/views/account/transfers/_account_logos.html.erb delete mode 100644 app/views/account/transfers/_transfer.html.erb create mode 100644 app/views/account/transfers/_transfer_toggle.html.erb create mode 100644 app/views/account/transfers/show.html.erb delete mode 100644 app/views/accounts/_entry_method.html.erb delete mode 100644 app/views/accounts/_overview.html.erb delete mode 100644 app/views/accounts/accountables/_default_header.html.erb delete mode 100644 app/views/accounts/accountables/_default_tabs.html.erb delete mode 100644 app/views/accounts/accountables/_transactions.html.erb delete mode 100644 app/views/accounts/accountables/_valuations.html.erb delete mode 100644 app/views/accounts/accountables/_value_onboarding.html.erb delete mode 100644 app/views/accounts/accountables/credit_card/_form.html.erb delete mode 100644 app/views/accounts/accountables/credit_card/_header.html.erb delete mode 100644 app/views/accounts/accountables/credit_card/_tabs.html.erb delete mode 100644 app/views/accounts/accountables/crypto/_form.html.erb delete mode 100644 app/views/accounts/accountables/crypto/_header.html.erb delete mode 100644 app/views/accounts/accountables/crypto/_tabs.html.erb delete mode 100644 app/views/accounts/accountables/depository/_form.html.erb delete mode 100644 app/views/accounts/accountables/depository/_header.html.erb delete mode 100644 app/views/accounts/accountables/depository/_tabs.html.erb delete mode 100644 app/views/accounts/accountables/investment/_form.html.erb delete mode 100644 app/views/accounts/accountables/investment/_header.html.erb delete mode 100644 app/views/accounts/accountables/investment/_tabs.html.erb delete mode 100644 app/views/accounts/accountables/loan/_form.html.erb delete mode 100644 app/views/accounts/accountables/loan/_header.html.erb delete mode 100644 app/views/accounts/accountables/loan/_tabs.html.erb delete mode 100644 app/views/accounts/accountables/other_asset/_form.html.erb delete mode 100644 app/views/accounts/accountables/other_asset/_header.html.erb delete mode 100644 app/views/accounts/accountables/other_asset/_tabs.html.erb delete mode 100644 app/views/accounts/accountables/other_liability/_form.html.erb delete mode 100644 app/views/accounts/accountables/other_liability/_header.html.erb delete mode 100644 app/views/accounts/accountables/other_liability/_tabs.html.erb delete mode 100644 app/views/accounts/accountables/property/_form.html.erb delete mode 100644 app/views/accounts/accountables/property/_header.html.erb delete mode 100644 app/views/accounts/accountables/property/_tabs.html.erb delete mode 100644 app/views/accounts/accountables/vehicle/_form.html.erb delete mode 100644 app/views/accounts/accountables/vehicle/_header.html.erb delete mode 100644 app/views/accounts/accountables/vehicle/_tabs.html.erb rename app/views/accounts/{ => index}/_institution_accounts.html.erb (95%) rename app/views/accounts/{ => index}/_institutionless_accounts.html.erb (100%) create mode 100644 app/views/accounts/new/_container.html.erb create mode 100644 app/views/accounts/new/_method_selector.html.erb delete mode 100644 app/views/accounts/show.html.erb create mode 100644 app/views/accounts/show/_activity.html.erb create mode 100644 app/views/accounts/show/_chart.html.erb create mode 100644 app/views/accounts/show/_header.html.erb create mode 100644 app/views/accounts/show/_loading.html.erb rename app/views/accounts/{ => show}/_menu.html.erb (93%) rename app/views/accounts/{accountables => show}/_tab.html.erb (100%) create mode 100644 app/views/accounts/show/_tabs.html.erb create mode 100644 app/views/accounts/show/_template.html.erb rename app/views/accounts/{ => summary}/_header.html.erb (81%) create mode 100644 app/views/credit_cards/_form.html.erb rename app/views/{accounts/accountables/credit_card => credit_cards}/_overview.html.erb (89%) rename app/views/{accounts => credit_cards}/edit.html.erb (50%) create mode 100644 app/views/credit_cards/new.html.erb create mode 100644 app/views/credit_cards/show.html.erb create mode 100644 app/views/cryptos/_form.html.erb create mode 100644 app/views/cryptos/edit.html.erb create mode 100644 app/views/cryptos/new.html.erb create mode 100644 app/views/cryptos/show.html.erb create mode 100644 app/views/depositories/_form.html.erb create mode 100644 app/views/depositories/edit.html.erb create mode 100644 app/views/depositories/new.html.erb create mode 100644 app/views/depositories/show.html.erb create mode 100644 app/views/investments/_cash_tab.html.erb create mode 100644 app/views/investments/_chart.html.erb create mode 100644 app/views/investments/_form.html.erb create mode 100644 app/views/investments/_holdings_tab.html.erb rename app/views/{accounts/accountables/investment/_tooltip.html.erb => investments/_value_tooltip.html.erb} (80%) create mode 100644 app/views/investments/edit.html.erb create mode 100644 app/views/investments/new.html.erb create mode 100644 app/views/investments/show.html.erb create mode 100644 app/views/loans/_form.html.erb rename app/views/{accounts/accountables/loan => loans}/_overview.html.erb (92%) create mode 100644 app/views/loans/edit.html.erb create mode 100644 app/views/loans/new.html.erb create mode 100644 app/views/loans/show.html.erb create mode 100644 app/views/other_assets/_form.html.erb create mode 100644 app/views/other_assets/edit.html.erb create mode 100644 app/views/other_assets/new.html.erb create mode 100644 app/views/other_assets/show.html.erb create mode 100644 app/views/other_liabilities/_form.html.erb create mode 100644 app/views/other_liabilities/edit.html.erb create mode 100644 app/views/other_liabilities/new.html.erb create mode 100644 app/views/other_liabilities/show.html.erb create mode 100644 app/views/properties/_form.html.erb rename app/views/{accounts/accountables/property => properties}/_overview.html.erb (88%) create mode 100644 app/views/properties/edit.html.erb create mode 100644 app/views/properties/new.html.erb create mode 100644 app/views/properties/show.html.erb create mode 100644 app/views/shared/_sparkline.html.erb create mode 100644 app/views/vehicles/_form.html.erb rename app/views/{accounts/accountables/vehicle => vehicles}/_overview.html.erb (94%) create mode 100644 app/views/vehicles/edit.html.erb create mode 100644 app/views/vehicles/new.html.erb create mode 100644 app/views/vehicles/show.html.erb create mode 100644 config/locales/views/credit_cards/en.yml create mode 100644 config/locales/views/cryptos/en.yml create mode 100644 config/locales/views/depositories/en.yml create mode 100644 config/locales/views/investments/en.yml create mode 100644 config/locales/views/loans/en.yml create mode 100644 config/locales/views/other_assets/en.yml create mode 100644 config/locales/views/other_liabilities/en.yml create mode 100644 db/migrate/20241030151105_remove_account_mode.rb create mode 100644 test/controllers/cryptos_controller_test.rb create mode 100644 test/controllers/depositories_controller_test.rb create mode 100644 test/controllers/investments_controller_test.rb create mode 100644 test/controllers/other_assets_controller_test.rb create mode 100644 test/controllers/other_liabilities_controller_test.rb create mode 100644 test/interfaces/accountable_resource_interface_test.rb 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 %> + + <% 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" %> +
+ + +
+
+ + <% 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 %> + + +
+
+ <%= 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 %> +
+
+

+ + <%= format_money @transfer.amount_money %> + + + + <%= @transfer.amount_money.currency.iso_code %> + +

+ + <%= lucide_icon "arrow-left-right", class: "text-gray-500 mt-1 w-5 h-5" %> +
+ + + <%= @transfer.name %> + +
+ +
+ + <%= 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 %> +
+ + <%= t(".balance") %> + + +
+

+ + <%= format_money entry.amount_money %> + +

+
+ + + <%= I18n.l(entry.date, format: :long) %> + +
+ +
+ + <%= 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? -
-
- - +<%= 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") %> -
-
-
- - 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 %> +
+ +
+ + + + <%= 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") %> +
+
+
+ + 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 @@ +
+

Loading account...

+
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) %>

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") %>

-