diff --git a/app/controllers/account/logos_controller.rb b/app/controllers/account/logos_controller.rb deleted file mode 100644 index dd56e665..00000000 --- a/app/controllers/account/logos_controller.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Account::LogosController < ApplicationController - def show - @account = Current.family.accounts.find(params[:account_id]) - render_placeholder - end - - def render_placeholder - render formats: :svg - end -end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 4dcbbb27..033ed3b8 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -23,11 +23,8 @@ class AccountsController < ApplicationController end def new - @account = Account.new( - accountable: Accountable.from_type(params[:type])&.new, - currency: Current.family.currency - ) - + @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] @@ -36,8 +33,6 @@ class AccountsController < ApplicationController end def show - @series = @account.series(period: @period) - @trend = @series.trend end def edit @@ -57,6 +52,7 @@ class AccountsController < ApplicationController 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 diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index 542c80ef..b65033c5 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -1,4 +1,14 @@ 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 + + raise "Unpermitted accountable partial: #{name}" unless permitted_names.include?(name) + + "accounts/accountables/#{folder}/#{name}" + end + def summary_card(title:, &block) content = capture(&block) render "accounts/summary_card", title: title, content: content diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8cbbcf21..19aa187e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -9,10 +9,6 @@ module ApplicationHelper content_for(:header_title) { page_title } end - def permitted_accountable_partial(name) - name.underscore - end - def family_notifications_stream turbo_stream_from [ Current.family, :notifications ] if Current.family end @@ -80,8 +76,8 @@ module ApplicationHelper color = hex || "#1570EF" # blue-600 <<-STYLE.strip - background-color: color-mix(in srgb, #{color} 5%, white); - border-color: color-mix(in srgb, #{color} 10%, white); + background-color: color-mix(in srgb, #{color} 10%, white); + border-color: color-mix(in srgb, #{color} 30%, white); color: #{color}; STYLE end diff --git a/app/javascript/controllers/time_series_chart_controller.js b/app/javascript/controllers/time_series_chart_controller.js index ce27460f..7660f3f4 100644 --- a/app/javascript/controllers/time_series_chart_controller.js +++ b/app/javascript/controllers/time_series_chart_controller.js @@ -51,12 +51,17 @@ export default class extends Controller { _normalizeDataPoints() { this._normalDataPoints = (this.dataValue.values || []).map((d) => ({ ...d, - date: new Date(d.date), + date: this._parseDate(d.date), value: d.value.amount ? +d.value.amount : +d.value, currency: d.value.currency, })); } + _parseDate(dateString) { + const [year, month, day] = dateString.split("-").map(Number); + return new Date(year, month - 1, day); + } + _rememberInitialContainerSize() { this._d3InitialContainerWidth = this._d3Container.node().clientWidth; this._d3InitialContainerHeight = this._d3Container.node().clientHeight; diff --git a/app/models/account.rb b/app/models/account.rb index 61d473d8..9dd49d60 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -27,6 +27,8 @@ class Account < ApplicationRecord scope :alphabetically, -> { order(:name) } scope :ungrouped, -> { where(institution_id: nil) } + has_one_attached :logo + delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy accepts_nested_attributes_for :accountable diff --git a/app/models/account/balance/calculator.rb b/app/models/account/balance/calculator.rb new file mode 100644 index 00000000..68d85853 --- /dev/null +++ b/app/models/account/balance/calculator.rb @@ -0,0 +1,57 @@ +class Account::Balance::Calculator + def initialize(account, sync_start_date) + @account = account + @sync_start_date = sync_start_date + end + + def calculate(is_partial_sync: false) + cached_entries = account.entries.where("date >= ?", sync_start_date).to_a + sync_starting_balance = is_partial_sync ? find_start_balance_for_partial_sync : find_start_balance_for_full_sync(cached_entries) + + prior_balance = sync_starting_balance + + (sync_start_date..Date.current).map do |date| + current_balance = calculate_balance_for_date(date, entries: cached_entries, prior_balance:) + + prior_balance = current_balance + + build_balance(date, current_balance) + end + end + + private + attr_reader :account, :sync_start_date + + def find_start_balance_for_partial_sync + account.balances.find_by(currency: account.currency, date: sync_start_date - 1.day).balance + end + + def find_start_balance_for_full_sync(cached_entries) + account.balance + net_entry_flows(cached_entries) + end + + def calculate_balance_for_date(date, entries:, prior_balance:) + valuation = entries.find { |e| e.date == date && e.account_valuation? } + + return valuation.amount if valuation + + entries = entries.select { |e| e.date == date } + + prior_balance - net_entry_flows(entries) + end + + def net_entry_flows(entries, target_currency = account.currency) + converted_entry_amounts = entries.map { |t| t.amount_money.exchange_to(target_currency, date: t.date) } + + flows = converted_entry_amounts.sum(&:amount) + + account.liability? ? flows * -1 : flows + end + + def build_balance(date, balance, currency = nil) + account.balances.build \ + date: date, + balance: balance, + currency: currency || account.currency + end +end diff --git a/app/models/account/balance/converter.rb b/app/models/account/balance/converter.rb new file mode 100644 index 00000000..f5e55749 --- /dev/null +++ b/app/models/account/balance/converter.rb @@ -0,0 +1,46 @@ +class Account::Balance::Converter + def initialize(account, sync_start_date) + @account = account + @sync_start_date = sync_start_date + end + + def convert(balances) + calculate_converted_balances(balances) + end + + private + attr_reader :account, :sync_start_date + + def calculate_converted_balances(balances) + from_currency = account.currency + to_currency = account.family.currency + + if ExchangeRate.exchange_rates_provider.nil? + account.observe_missing_exchange_rate_provider + return [] + end + + exchange_rates = ExchangeRate.find_rates from: from_currency, + to: to_currency, + start_date: sync_start_date + + missing_exchange_rates = balances.map(&:date) - exchange_rates.map(&:date) + + if missing_exchange_rates.any? + account.observe_missing_exchange_rates(from: from_currency, to: to_currency, dates: missing_exchange_rates) + return [] + end + + balances.map do |balance| + exchange_rate = exchange_rates.find { |er| er.date == balance.date } + build_balance(balance.date, exchange_rate.rate * balance.balance, to_currency) + end + end + + def build_balance(date, balance, currency = nil) + account.balances.build \ + date: date, + balance: balance, + currency: currency || account.currency + end +end diff --git a/app/models/account/balance/loader.rb b/app/models/account/balance/loader.rb new file mode 100644 index 00000000..cb6ba0bd --- /dev/null +++ b/app/models/account/balance/loader.rb @@ -0,0 +1,37 @@ +class Account::Balance::Loader + def initialize(account) + @account = account + end + + def load(balances, start_date) + Account::Balance.transaction do + upsert_balances!(balances) + purge_stale_balances!(start_date) + + account.reload + + update_account_balance!(balances) + end + end + + private + attr_reader :account + + def update_account_balance!(balances) + last_balance = balances.select { |db| db.currency == account.currency }.last&.balance + account.update! balance: last_balance if last_balance.present? + end + + def upsert_balances!(balances) + current_time = Time.now + balances_to_upsert = balances.map do |balance| + balance.attributes.slice("date", "balance", "currency").merge("updated_at" => current_time) + end + + account.balances.upsert_all(balances_to_upsert, unique_by: %i[account_id date currency]) + end + + def purge_stale_balances!(start_date) + account.balances.delete_by("date < ?", start_date) + end +end diff --git a/app/models/account/balance/syncer.rb b/app/models/account/balance/syncer.rb index a937955d..d0e4546e 100644 --- a/app/models/account/balance/syncer.rb +++ b/app/models/account/balance/syncer.rb @@ -1,133 +1,51 @@ class Account::Balance::Syncer def initialize(account, start_date: nil) @account = account + @provided_start_date = start_date @sync_start_date = calculate_sync_start_date(start_date) + @loader = Account::Balance::Loader.new(account) + @converter = Account::Balance::Converter.new(account, sync_start_date) + @calculator = Account::Balance::Calculator.new(account, sync_start_date) end def run - daily_balances = calculate_daily_balances - daily_balances += calculate_converted_balances(daily_balances) if account.currency != account.family.currency + daily_balances = calculator.calculate(is_partial_sync: is_partial_sync?) + daily_balances += converter.convert(daily_balances) if account.currency != account.family.currency - Account::Balance.transaction do - upsert_balances!(daily_balances) - purge_stale_balances! - - if daily_balances.any? - account.reload - last_balance = daily_balances.select { |db| db.currency == account.currency }.last&.balance - account.update! balance: last_balance - end - end + loader.load(daily_balances, account_start_date) rescue Money::ConversionError => e account.observe_missing_exchange_rates(from: e.from_currency, to: e.to_currency, dates: [ e.date ]) end private - attr_reader :sync_start_date, :account - - def upsert_balances!(balances) - current_time = Time.now - balances_to_upsert = balances.map do |balance| - balance.attributes.slice("date", "balance", "currency").merge("updated_at" => current_time) - end - - account.balances.upsert_all(balances_to_upsert, unique_by: %i[account_id date currency]) - end - - def purge_stale_balances! - account.balances.delete_by("date < ?", account_start_date) - end - - def calculate_balance_for_date(date, entries:, prior_balance:) - valuation = entries.find { |e| e.date == date && e.account_valuation? } - - return valuation.amount if valuation - return derived_sync_start_balance(entries) unless prior_balance - - entries = entries.select { |e| e.date == date } - - prior_balance - net_entry_flows(entries) - end - - def calculate_daily_balances - entries = account.entries.where("date >= ?", sync_start_date).to_a - prior_balance = find_prior_balance - - (sync_start_date..Date.current).map do |date| - current_balance = calculate_balance_for_date(date, entries:, prior_balance:) - - prior_balance = current_balance - - build_balance(date, current_balance) - end - end - - def calculate_converted_balances(balances) - from_currency = account.currency - to_currency = account.family.currency - - if ExchangeRate.exchange_rates_provider.nil? - account.observe_missing_exchange_rate_provider - return [] - end - - exchange_rates = ExchangeRate.find_rates from: from_currency, - to: to_currency, - start_date: sync_start_date - - missing_exchange_rates = balances.map(&:date) - exchange_rates.map(&:date) - - if missing_exchange_rates.any? - account.observe_missing_exchange_rates(from: from_currency, to: to_currency, dates: missing_exchange_rates) - return [] - end - - balances.map do |balance| - exchange_rate = exchange_rates.find { |er| er.date == balance.date } - build_balance(balance.date, exchange_rate.rate * balance.balance, to_currency) - end - end - - def build_balance(date, balance, currency = nil) - account.balances.build \ - date: date, - balance: balance, - currency: currency || account.currency - end - - def derived_sync_start_balance(entries) - transactions_and_trades = entries.reject { |e| e.account_valuation? }.select { |e| e.date > sync_start_date } - - account.balance + net_entry_flows(transactions_and_trades) - end - - def find_prior_balance - account.balances.where(currency: account.currency).where("date < ?", sync_start_date).order(date: :desc).first&.balance - end - - def net_entry_flows(entries, target_currency = account.currency) - converted_entry_amounts = entries.map { |t| t.amount_money.exchange_to(target_currency, date: t.date) } - - flows = converted_entry_amounts.sum(&:amount) - - account.liability? ? flows * -1 : flows - end + attr_reader :sync_start_date, :provided_start_date, :account, :loader, :converter, :calculator def account_start_date @account_start_date ||= begin - oldest_entry_date = account.entries.chronological.first.try(:date) + oldest_entry = account.entries.chronological.first - return Date.current unless oldest_entry_date + return Date.current unless oldest_entry.present? - oldest_entry_is_valuation = account.entries.account_valuations.where(date: oldest_entry_date).exists? - - oldest_entry_date -= 1 unless oldest_entry_is_valuation - oldest_entry_date + if oldest_entry.account_valuation? + oldest_entry.date + else + oldest_entry.date - 1.day + end end end def calculate_sync_start_date(provided_start_date) - [ provided_start_date, account_start_date ].compact.max + return provided_start_date if provided_start_date.present? && prior_balance_available?(provided_start_date) + + account_start_date + end + + def prior_balance_available?(date) + account.balances.find_by(currency: account.currency, date: date - 1.day).present? + end + + def is_partial_sync? + sync_start_date == provided_start_date && sync_start_date < Date.current end end diff --git a/app/models/credit_card.rb b/app/models/credit_card.rb index 5c8bb0ae..bdde91c8 100644 --- a/app/models/credit_card.rb +++ b/app/models/credit_card.rb @@ -12,4 +12,8 @@ class CreditCard < ApplicationRecord def annual_fee_money annual_fee ? Money.new(annual_fee, account.currency) : nil end + + def color + "#F13636" + end end diff --git a/app/models/crypto.rb b/app/models/crypto.rb index 5aec6157..e4f81ae4 100644 --- a/app/models/crypto.rb +++ b/app/models/crypto.rb @@ -1,3 +1,7 @@ class Crypto < ApplicationRecord include Accountable + + def color + "#737373" + end end diff --git a/app/models/depository.rb b/app/models/depository.rb index 8ae1924d..90abe087 100644 --- a/app/models/depository.rb +++ b/app/models/depository.rb @@ -1,3 +1,7 @@ class Depository < ApplicationRecord include Accountable + + def color + "#875BF7" + end end diff --git a/app/models/investment.rb b/app/models/investment.rb index d5c583fe..1912899f 100644 --- a/app/models/investment.rb +++ b/app/models/investment.rb @@ -46,4 +46,8 @@ class Investment < ApplicationRecord rescue Money::ConversionError TimeSeries.new([]) end + + def color + "#1570EF" + end end diff --git a/app/models/loan.rb b/app/models/loan.rb index be8e8c2b..5051b69b 100644 --- a/app/models/loan.rb +++ b/app/models/loan.rb @@ -16,4 +16,8 @@ class Loan < ApplicationRecord Money.new(payment.round, account.currency) end + + def color + "#D444F1" + end end diff --git a/app/models/other_asset.rb b/app/models/other_asset.rb index d9434f6b..90ca9e32 100644 --- a/app/models/other_asset.rb +++ b/app/models/other_asset.rb @@ -1,3 +1,7 @@ class OtherAsset < ApplicationRecord include Accountable + + def color + "#12B76A" + end end diff --git a/app/models/other_liability.rb b/app/models/other_liability.rb index 83be97f5..04a3737b 100644 --- a/app/models/other_liability.rb +++ b/app/models/other_liability.rb @@ -1,3 +1,7 @@ class OtherLiability < ApplicationRecord include Accountable + + def color + "#737373" + end end diff --git a/app/models/property.rb b/app/models/property.rb index a23519ed..304c4d78 100644 --- a/app/models/property.rb +++ b/app/models/property.rb @@ -19,6 +19,10 @@ class Property < ApplicationRecord TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount) end + def color + "#06AED4" + end + private def first_valuation_amount account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money diff --git a/app/models/vehicle.rb b/app/models/vehicle.rb index 2ab34d7b..741070c2 100644 --- a/app/models/vehicle.rb +++ b/app/models/vehicle.rb @@ -15,6 +15,10 @@ class Vehicle < ApplicationRecord TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount) end + def color + "#F23E94" + end + private def first_valuation_amount account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money diff --git a/app/views/account/logos/show.svg.erb b/app/views/account/logos/show.svg.erb deleted file mode 100644 index 19186e26..00000000 --- a/app/views/account/logos/show.svg.erb +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/app/views/accounts/_account_list.html.erb b/app/views/accounts/_account_list.html.erb index b42df152..46e8004b 100644 --- a/app/views/accounts/_account_list.html.erb +++ b/app/views/accounts/_account_list.html.erb @@ -30,7 +30,7 @@ <% 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 %> - <%= image_tag account_logo_url(account), class: "w-6 h-6" %> + <%= render "accounts/logo", account: account, size: "sm" %>

<%= account_value_node.name %>

<% if account.subtype %> @@ -63,7 +63,7 @@ <% 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 %> <%= lucide_icon("plus", class: "w-5 h-5") %> -

New <%= type.model_name.human.downcase %>

+ <%= t(".new_account", type: type.model_name.human.downcase) %> <% end %> <% end %> diff --git a/app/views/accounts/_account_type.html.erb b/app/views/accounts/_account_type.html.erb index d8b400c7..d4b69527 100644 --- a/app/views/accounts/_account_type.html.erb +++ b/app/views/accounts/_account_type.html.erb @@ -1,5 +1,4 @@ <%= link_to new_account_path( - step: "method", type: type.class.name.demodulize, institution_id: params[:institution_id] ), diff --git a/app/views/accounts/_empty.html.erb b/app/views/accounts/_empty.html.erb index e938c863..68c64316 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, 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(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 %> <%= 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 index ea73af73..1e9b6971 100644 --- a/app/views/accounts/_entry_method.html.erb +++ b/app/views/accounts/_entry_method.html.erb @@ -1,12 +1,14 @@ -<% if local_assigns[:disabled] && disabled %> - +<%# 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(type: type.class.name.demodulize, 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 %> + <%= 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") %> diff --git a/app/views/accounts/_form.html.erb b/app/views/accounts/_form.html.erb index 4fa0cce1..e85448e0 100644 --- a/app/views/accounts/_form.html.erb +++ b/app/views/accounts/_form.html.erb @@ -2,8 +2,8 @@ <%= styled_form_with model: account, url: url, scope: :account, class: "flex flex-col gap-4 justify-between grow", data: { turbo: false } do |f| %>
- <%= f.hidden_field :accountable_type %> - <%= f.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label"), autofocus: true %> + <%= 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") %> <% if account.new_record? %> <%= f.hidden_field :institution_id %> @@ -13,15 +13,10 @@ <%= f.money_field :balance, label: t(".balance"), required: true, default_currency: Current.family.currency %> - <% if account.new_record? %> -
-
<%= f.date_field :start_date, label: t(".start_date"), max: Date.yesterday, min: Account::Entry.min_supported_date %>
-
<%= f.money_field :start_balance, label: t(".start_balance"), placeholder: 90, hide_currency: true, default_currency: Current.family.currency %>
-
+ <% if account.accountable %> + <%= render permitted_accountable_partial(account, "form"), f: f %> <% end %> - - <%= render "accounts/accountables/#{permitted_accountable_partial(account.accountable_type)}", f: f %>
- <%= f.submit "#{account.new_record? ? "Add" : "Update"} #{account.accountable.model_name.human.downcase}" %> + <%= f.submit %> <% end %> diff --git a/app/views/accounts/_header.html.erb b/app/views/accounts/_header.html.erb index f8c8eb3e..348fc9b9 100644 --- a/app/views/accounts/_header.html.erb +++ b/app/views/accounts/_header.html.erb @@ -13,7 +13,7 @@ <% end %> - <%= 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 %> + <%= 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 %> <%= lucide_icon("plus", class: "w-5 h-5") %>

<%= t(".new") %>

<% end %> diff --git a/app/views/accounts/_logo.html.erb b/app/views/accounts/_logo.html.erb new file mode 100644 index 00000000..9036ad18 --- /dev/null +++ b/app/views/accounts/_logo.html.erb @@ -0,0 +1,14 @@ +<%# locals: (account:, size: "md") %> + +<% size_classes = { + "sm" => "w-6 h-6", + "md" => "w-9 h-9", + "lg" => "w-10 h-10", + "full" => "w-full h-full" +} %> + +<% if account.logo.attached? %> + <%= image_tag account.logo, class: size_classes[size] %> +<% else %> + <%= circle_logo(account.name, hex: account.accountable.color, size: size) %> +<% end %> diff --git a/app/views/accounts/_menu.html.erb b/app/views/accounts/_menu.html.erb new file mode 100644 index 00000000..8ebb53f1 --- /dev/null +++ b/app/views/accounts/_menu.html.erb @@ -0,0 +1,33 @@ +<%# locals: (account:) %> + +<%= contextual_menu do %> +
+ <%= link_to edit_account_path(account), + 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 "pencil-line", class: "w-5 h-5 text-gray-500" %> + + <%= t(".edit") %> + <% end %> + + <%= link_to new_import_path, + 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" %> + + <%= t(".import") %> + <% end %> + + <%= button_to account_path(account), + 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_confirm: { + title: t(".confirm_title"), + body: t(".confirm_body_html"), + accept: t(".confirm_accept", name: account.name) + } + } do %> + <%= lucide_icon("trash-2", class: "w-5 h-5 mr-2") %> Delete account + <% end %> +
+<% end %> diff --git a/app/views/accounts/_new_account_setup_bar.html.erb b/app/views/accounts/_new_account_setup_bar.html.erb new file mode 100644 index 00000000..16b6e430 --- /dev/null +++ b/app/views/accounts/_new_account_setup_bar.html.erb @@ -0,0 +1,8 @@ +
+

Setup your new account

+ +
+ <%= link_to "Track balances only", new_account_valuation_path(@account), class: "btn btn--ghost", data: { turbo_frame: dom_id(@account.entries.account_valuations.new) } %> + <%= link_to "Add your first transaction", new_transaction_path(account_id: @account.id), class: "btn btn--primary", data: { turbo_frame: :modal } %> +
+
diff --git a/app/views/accounts/_sync_all_button.html.erb b/app/views/accounts/_sync_all_button.html.erb index 0cf16bbd..8300df19 100644 --- a/app/views/accounts/_sync_all_button.html.erb +++ b/app/views/accounts/_sync_all_button.html.erb @@ -1,4 +1,4 @@ -<%= button_to sync_all_accounts_path, class: "btn btn--outline flex items-center gap-2", title: "Sync All" do %> +<%= button_to sync_all_accounts_path, class: "btn btn--outline flex items-center gap-2", title: t(".sync") do %> <%= lucide_icon "refresh-cw", class: "w-5 h-5" %> - <%= t("accounts.sync_all.button_text") %> + <%= t(".sync") %> <% end %> diff --git a/app/views/accounts/accountables/_default_header.html.erb b/app/views/accounts/accountables/_default_header.html.erb new file mode 100644 index 00000000..5feae417 --- /dev/null +++ b/app/views/accounts/accountables/_default_header.html.erb @@ -0,0 +1,7 @@ +
+ <%= 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 new file mode 100644 index 00000000..0a2f355a --- /dev/null +++ b/app/views/accounts/accountables/_default_tabs.html.erb @@ -0,0 +1,7 @@ +<%# locals: (account:) %> + +<% if account.transactions.any? %> + <%= render "accounts/accountables/transactions", account: account %> +<% else %> + <%= render "accounts/accountables/valuations", account: account %> +<% end %> diff --git a/app/views/accounts/accountables/_tab.html.erb b/app/views/accounts/accountables/_tab.html.erb new file mode 100644 index 00000000..4ddd0e6e --- /dev/null +++ b/app/views/accounts/accountables/_tab.html.erb @@ -0,0 +1,8 @@ +<%# locals: (account:, key:, is_selected:) %> + +<%= link_to key.titleize, + account_path(account, tab: key), + class: [ + "px-2 py-1.5 rounded-md border border-transparent", + "bg-white shadow-xs border-alpha-black-50": is_selected + ] %> diff --git a/app/views/accounts/accountables/_transactions.html.erb b/app/views/accounts/accountables/_transactions.html.erb new file mode 100644 index 00000000..d6943303 --- /dev/null +++ b/app/views/accounts/accountables/_transactions.html.erb @@ -0,0 +1,5 @@ +<%# 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 new file mode 100644 index 00000000..b4bf1fc3 --- /dev/null +++ b/app/views/accounts/accountables/_valuations.html.erb @@ -0,0 +1,5 @@ +<%# 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/_credit_card.html.erb b/app/views/accounts/accountables/credit_card/_form.html.erb similarity index 100% rename from app/views/accounts/accountables/_credit_card.html.erb rename to app/views/accounts/accountables/credit_card/_form.html.erb diff --git a/app/views/accounts/accountables/credit_card/_header.html.erb b/app/views/accounts/accountables/credit_card/_header.html.erb new file mode 100644 index 00000000..51889135 --- /dev/null +++ b/app/views/accounts/accountables/credit_card/_header.html.erb @@ -0,0 +1 @@ +<%= render "accounts/accountables/default_header", account: account %> diff --git a/app/views/accounts/accountables/credit_card/_overview.html.erb b/app/views/accounts/accountables/credit_card/_overview.html.erb index 3121528b..668326b5 100644 --- a/app/views/accounts/accountables/credit_card/_overview.html.erb +++ b/app/views/accounts/accountables/credit_card/_overview.html.erb @@ -25,3 +25,7 @@ <%= format_money(account.credit_card.annual_fee_money || Money.new(0, account.currency)) %> <% end %>
+ +
+ <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %> +
\ No newline at end of file diff --git a/app/views/accounts/accountables/credit_card/_tabs.html.erb b/app/views/accounts/accountables/credit_card/_tabs.html.erb new file mode 100644 index 00000000..781cc6d0 --- /dev/null +++ b/app/views/accounts/accountables/credit_card/_tabs.html.erb @@ -0,0 +1,15 @@ +<%# 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: "transactions", is_selected: selected_tab == "transactions" %> +
+ +
+ <% case selected_tab %> + <% when nil, "overview" %> + <%= render "accounts/accountables/credit_card/overview", account: account %> + <% when "transactions" %> + <%= render "accounts/accountables/transactions", account: account %> + <% end %> +
diff --git a/app/views/accounts/accountables/_crypto.html.erb b/app/views/accounts/accountables/crypto/_form.html.erb similarity index 100% rename from app/views/accounts/accountables/_crypto.html.erb rename to app/views/accounts/accountables/crypto/_form.html.erb diff --git a/app/views/accounts/accountables/crypto/_header.html.erb b/app/views/accounts/accountables/crypto/_header.html.erb new file mode 100644 index 00000000..51889135 --- /dev/null +++ b/app/views/accounts/accountables/crypto/_header.html.erb @@ -0,0 +1 @@ +<%= 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 new file mode 100644 index 00000000..381f7273 --- /dev/null +++ b/app/views/accounts/accountables/crypto/_tabs.html.erb @@ -0,0 +1 @@ +<%= render "accounts/accountables/default_tabs", account: account %> diff --git a/app/views/accounts/accountables/_depository.html.erb b/app/views/accounts/accountables/depository/_form.html.erb similarity index 100% rename from app/views/accounts/accountables/_depository.html.erb rename to app/views/accounts/accountables/depository/_form.html.erb diff --git a/app/views/accounts/accountables/depository/_header.html.erb b/app/views/accounts/accountables/depository/_header.html.erb new file mode 100644 index 00000000..51889135 --- /dev/null +++ b/app/views/accounts/accountables/depository/_header.html.erb @@ -0,0 +1 @@ +<%= 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 new file mode 100644 index 00000000..381f7273 --- /dev/null +++ b/app/views/accounts/accountables/depository/_tabs.html.erb @@ -0,0 +1 @@ +<%= render "accounts/accountables/default_tabs", account: account %> diff --git a/app/views/accounts/accountables/_investment.html.erb b/app/views/accounts/accountables/investment/_form.html.erb similarity index 100% rename from app/views/accounts/accountables/_investment.html.erb rename to app/views/accounts/accountables/investment/_form.html.erb diff --git a/app/views/accounts/accountables/investment/_header.html.erb b/app/views/accounts/accountables/investment/_header.html.erb new file mode 100644 index 00000000..51889135 --- /dev/null +++ b/app/views/accounts/accountables/investment/_header.html.erb @@ -0,0 +1 @@ +<%= 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 new file mode 100644 index 00000000..8da4905d --- /dev/null +++ b/app/views/accounts/accountables/investment/_tabs.html.erb @@ -0,0 +1,28 @@ +<%# locals: (account:, selected_tab:) %> + +<% if account.entries.account_trades.any? || account.entries.account_transactions.any? %> +
+ <%= 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" %> +
+ +
+ <% 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 %> diff --git a/app/views/accounts/_tooltip.html.erb b/app/views/accounts/accountables/investment/_tooltip.html.erb similarity index 99% rename from app/views/accounts/_tooltip.html.erb rename to app/views/accounts/accountables/investment/_tooltip.html.erb index 952d2ad1..83432ebc 100644 --- a/app/views/accounts/_tooltip.html.erb +++ b/app/views/accounts/accountables/investment/_tooltip.html.erb @@ -1,4 +1,5 @@ <%# locals: (account:) -%> +
<%= lucide_icon("info", class: "w-4 h-4 shrink-0 text-gray-500") %> + +
+ <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %> +
\ No newline at end of file diff --git a/app/views/accounts/accountables/loan/_tabs.html.erb b/app/views/accounts/accountables/loan/_tabs.html.erb new file mode 100644 index 00000000..3ed1efcf --- /dev/null +++ b/app/views/accounts/accountables/loan/_tabs.html.erb @@ -0,0 +1,15 @@ +<%# 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/loan/overview", account: account %> + <% when "value" %> + <%= render "accounts/accountables/valuations", account: account %> + <% end %> +
diff --git a/app/views/accounts/accountables/_other_asset.html.erb b/app/views/accounts/accountables/other_asset/_form.html.erb similarity index 100% rename from app/views/accounts/accountables/_other_asset.html.erb rename to app/views/accounts/accountables/other_asset/_form.html.erb diff --git a/app/views/accounts/accountables/other_asset/_header.html.erb b/app/views/accounts/accountables/other_asset/_header.html.erb new file mode 100644 index 00000000..51889135 --- /dev/null +++ b/app/views/accounts/accountables/other_asset/_header.html.erb @@ -0,0 +1 @@ +<%= 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 new file mode 100644 index 00000000..480136bf --- /dev/null +++ b/app/views/accounts/accountables/other_asset/_tabs.html.erb @@ -0,0 +1 @@ +<%= render "accounts/accountables/valuations", account: account %> diff --git a/app/views/accounts/accountables/_other_liability.html.erb b/app/views/accounts/accountables/other_liability/_form.html.erb similarity index 100% rename from app/views/accounts/accountables/_other_liability.html.erb rename to app/views/accounts/accountables/other_liability/_form.html.erb diff --git a/app/views/accounts/accountables/other_liability/_header.html.erb b/app/views/accounts/accountables/other_liability/_header.html.erb new file mode 100644 index 00000000..51889135 --- /dev/null +++ b/app/views/accounts/accountables/other_liability/_header.html.erb @@ -0,0 +1 @@ +<%= 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 new file mode 100644 index 00000000..480136bf --- /dev/null +++ b/app/views/accounts/accountables/other_liability/_tabs.html.erb @@ -0,0 +1 @@ +<%= render "accounts/accountables/valuations", account: account %> diff --git a/app/views/accounts/accountables/_property.html.erb b/app/views/accounts/accountables/property/_form.html.erb similarity index 100% rename from app/views/accounts/accountables/_property.html.erb rename to app/views/accounts/accountables/property/_form.html.erb diff --git a/app/views/accounts/accountables/property/_header.html.erb b/app/views/accounts/accountables/property/_header.html.erb new file mode 100644 index 00000000..27701588 --- /dev/null +++ b/app/views/accounts/accountables/property/_header.html.erb @@ -0,0 +1,11 @@ +
+ <%= 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/_overview.html.erb b/app/views/accounts/accountables/property/_overview.html.erb index f7ede76a..c0fde9f9 100644 --- a/app/views/accounts/accountables/property/_overview.html.erb +++ b/app/views/accounts/accountables/property/_overview.html.erb @@ -27,3 +27,7 @@ <%= account.property.area || t(".unknown") %> <% end %>
+ +
+ <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %> +
diff --git a/app/views/accounts/accountables/property/_tabs.html.erb b/app/views/accounts/accountables/property/_tabs.html.erb new file mode 100644 index 00000000..07ff76a6 --- /dev/null +++ b/app/views/accounts/accountables/property/_tabs.html.erb @@ -0,0 +1,15 @@ +<%# 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.html.erb b/app/views/accounts/accountables/vehicle/_form.html.erb similarity index 100% rename from app/views/accounts/accountables/_vehicle.html.erb rename to app/views/accounts/accountables/vehicle/_form.html.erb diff --git a/app/views/accounts/accountables/vehicle/_header.html.erb b/app/views/accounts/accountables/vehicle/_header.html.erb new file mode 100644 index 00000000..51889135 --- /dev/null +++ b/app/views/accounts/accountables/vehicle/_header.html.erb @@ -0,0 +1 @@ +<%= render "accounts/accountables/default_header", account: account %> diff --git a/app/views/accounts/accountables/vehicle/_overview.html.erb b/app/views/accounts/accountables/vehicle/_overview.html.erb index 2455c67b..7d72431e 100644 --- a/app/views/accounts/accountables/vehicle/_overview.html.erb +++ b/app/views/accounts/accountables/vehicle/_overview.html.erb @@ -31,3 +31,7 @@ <% end %> + +
+ <%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %> +
diff --git a/app/views/accounts/accountables/vehicle/_tabs.html.erb b/app/views/accounts/accountables/vehicle/_tabs.html.erb new file mode 100644 index 00000000..a79c89c2 --- /dev/null +++ b/app/views/accounts/accountables/vehicle/_tabs.html.erb @@ -0,0 +1,15 @@ +<%# 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 adf33f50..fedf238e 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, + <%= link_to new_account_path(step: "method"), data: { turbo_frame: "modal" }, class: "btn btn--primary flex items-center gap-1" do %> <%= lucide_icon("plus", class: "w-5 h-5") %> diff --git a/app/views/accounts/new.html.erb b/app/views/accounts/new.html.erb index bb544e85..9e8a562f 100644 --- a/app/views/accounts/new.html.erb +++ b/app/views/accounts/new.html.erb @@ -1,58 +1,24 @@

<%= t(".title") %>

<%= modal do %>
- <% if @account.accountable.blank? %> -
- <%= t ".select_accountable_type" %> -
-
- - - <%= render "account_type", type: Depository.new, bg_color: "bg-blue-500/5", text_color: "text-blue-500", icon: "landmark" %> - <%= render "account_type", type: Investment.new, bg_color: "bg-green-500/5", text_color: "text-green-500", icon: "line-chart" %> - <%= render "account_type", type: Crypto.new, bg_color: "bg-orange-500/5", text_color: "text-orange-500", icon: "bitcoin" %> - <%= render "account_type", type: Property.new, bg_color: "bg-pink-500/5", text_color: "text-pink-500", icon: "home" %> - <%= render "account_type", type: Vehicle.new, bg_color: "bg-cyan-500/5", text_color: "text-cyan-500", icon: "car-front" %> - <%= render "account_type", type: CreditCard.new, bg_color: "bg-violet-500/5", text_color: "text-violet-500", icon: "credit-card" %> - <%= render "account_type", type: Loan.new, bg_color: "bg-yellow-500/5", text_color: "text-yellow-500", icon: "hand-coins" %> - <%= render "account_type", type: OtherAsset.new, bg_color: "bg-green-500/5", text_color: "text-green-500", icon: "plus" %> - <%= render "account_type", type: OtherLiability.new, bg_color: "bg-red-500/5", text_color: "text-red-500", icon: "minus" %> -
-
-
-
- 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 -
-
- <% elsif params[:step] == 'method' && @account.accountable.present? %> + <% if params[:step] == 'method' %>
- <%= link_to new_account_path, class: "flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 back focus:outline-gray-300 focus:outline" do %> - <%= lucide_icon("arrow-left", class: "text-gray-500 w-5 h-5") %> - <% end %> How would you like to add it?
- <%= render "entry_method", type: @account.accountable, text: "Enter account balance manually", icon: "keyboard" %> - <%= link_to new_import_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 %> + + <%= 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") %> - Upload CSV + <%= t(".csv_entry") %> <% end %> - <%= render "entry_method", type: @account.accountable, text: "Securely link bank account with data provider (coming soon)", icon: "link-2", disabled: true %> + + <%= render "entry_method", text: t(".connected_entry"), icon: "link-2", disabled: true %>
@@ -73,13 +39,13 @@
<% else %>
- <%= link_to new_account_path(step: "method", type: params[:type]), 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 %> + <%= 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.accountable.model_name.human.downcase %> + Add account
-
+
<%= render "form", account: @account, url: new_account_form_url(@account) %>
<% end %> diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index d9041766..c49b64e2 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -1,56 +1,25 @@ <%= turbo_stream_from @account %> -<%= tag.div id: dom_id(@account), class: "space-y-4" do %> -
-
- <%= image_tag account_logo_url(@account), class: "w-8 h-8" %> -
-

<%= @account.name %>

+<% series = @account.series(period: @period) %> +<% trend = series.trend %> - <% if @account.property? && @account.property.address&.line1.present? %> -

<%= @account.property.address %>

- <% end %> -
-
-
+<%= 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 %> - <%= contextual_menu do %> -
- <%= link_to edit_account_path(@account), - 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 "pencil-line", class: "w-5 h-5 text-gray-500" %> - - <%= t(".edit") %> - <% end %> - - <%= link_to new_import_path, - 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" %> - - <%= t(".import") %> - <% end %> - - <%= button_to account_path(@account), - 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_confirm: { - title: t(".confirm_title"), - body: t(".confirm_body_html"), - accept: t(".confirm_accept", name: @account.name) - } - } do %> - <%= lucide_icon("trash-2", class: "w-5 h-5 mr-2") %> Delete account - <% end %> -
- <% end %> + <%= render "menu", account: @account %>
+ <% if @account.entries.empty? && @account.depository? %> + <%= render "accounts/new_account_setup_bar", account: @account %> + <% end %> + <% if @account.highest_priority_issue %> <%= render partial: "issues/issue", locals: { issue: @account.highest_priority_issue } %> <% end %> @@ -66,47 +35,35 @@ <%= tag.p t(".total_owed"), class: "text-sm font-medium text-gray-500" %> <% end %>
- <%= render "tooltip", account: @account if @account.investment? %> + + <%= 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 @series.trend.direction.flat? %> + <% if trend.direction.flat? %> <%= tag.span t(".no_change"), class: "text-gray-500" %> <% else %> - <%= tag.span format_money(@series.trend.value), style: "color: #{@trend.color}" %> - <%= tag.span "(#{@trend.percent}%)", style: "color: #{@trend.color}" %> + <%= 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 partial: "shared/line_chart", locals: { series: @series } %> + <%= render "shared/line_chart", series: @account.series(period: @period) %>
- <% selected_tab = selected_account_tab(@account) %> - <% selected_tab_key = selected_tab[:key] %> - <% selected_tab_partial_path = selected_tab[:partial_path] %> - <% selected_tab_route = selected_tab[:route] %> - -
- <% account_tabs(@account).each do |tab| %> - <%= link_to tab[:label], tab[:path], class: ["px-2 py-1.5 rounded-md border border-transparent", "bg-white shadow-xs border-alpha-black-50": selected_tab_key == tab[:key]] %> - <% end %> -
-
- <% if selected_tab_route.present? %> - <%= turbo_frame_tag dom_id(@account, selected_tab_key), src: selected_tab_route do %> - <%= render "account/entries/loading" %> - <% end %> - <% else %> - <%= render selected_tab_partial_path, account: @account %> - <% end %> + <%= render permitted_accountable_partial(@account, "tabs"), account: @account, selected_tab: params[:tab] %>
<% end %> diff --git a/app/views/accounts/summary.html.erb b/app/views/accounts/summary.html.erb index feffec64..db9ab322 100644 --- a/app/views/accounts/summary.html.erb +++ b/app/views/accounts/summary.html.erb @@ -41,7 +41,7 @@

Assets

- <%= link_to new_account_path, class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %> + <%= link_to new_account_path(step: "method"), 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 %> @@ -66,7 +66,7 @@

Liabilities

- <%= link_to new_account_path, class: "btn btn--secondary flex items-center gap-1", data: { turbo_frame: "modal" } do %> + <%= link_to new_account_path(step: "method"), 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 %> diff --git a/app/views/imports/new.html.erb b/app/views/imports/new.html.erb index b0005dc8..7cbcf685 100644 --- a/app/views/imports/new.html.erb +++ b/app/views/imports/new.html.erb @@ -15,14 +15,14 @@

<%= t(".sources") %>

  • - <% if @pending_import.present? %> + <% if @pending_import.present? && (params[:type].nil? || params[:type] == @pending_import.type) %> <%= link_to import_path(@pending_import), class: "flex items-center justify-between p-4 group cursor-pointer", data: { turbo: false } do %>
    <%= lucide_icon("loader", class: "w-5 h-5 text-orange-500") %>
    - <%= t(".resume") %> + <%= t(".resume", type: @pending_import.type.titleize) %>
    <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> @@ -33,75 +33,84 @@
<% end %> -
  • - <%= button_to imports_path(import: { type: "TransactionImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> -
    -
    - <%= lucide_icon("file-spreadsheet", class: "w-5 h-5 text-indigo-500") %> + + <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "TransactionImport") %> +
  • + <%= button_to imports_path(import: { type: "TransactionImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> +
    +
    + <%= lucide_icon("file-spreadsheet", class: "w-5 h-5 text-indigo-500") %> +
    + + <%= t(".import_transactions") %> +
    - - <%= t(".import_transactions") %> - + <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> + <% end %> + +
    +
    - <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> - <% end %> +
  • + <% end %> -
    -
    -
    - - -
  • - <%= button_to imports_path(import: { type: "TradeImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> -
    -
    - <%= lucide_icon("square-percent", class: "w-5 h-5 text-yellow-500") %> + <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "TradeImport") %> +
  • + <%= button_to imports_path(import: { type: "TradeImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> +
    +
    + <%= lucide_icon("square-percent", class: "w-5 h-5 text-yellow-500") %> +
    + + <%= t(".import_portfolio") %> +
    - - <%= t(".import_portfolio") %> - + <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> + <% end %> + +
    +
    - <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> - <% end %> +
  • + <% end %> -
    -
    -
    - - -
  • - <%= button_to imports_path(import: { type: "AccountImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> -
    -
    - <%= lucide_icon("building", class: "w-5 h-5 text-violet-500") %> + <% if params[:type].nil? || params[:type] == "AccountImport" %> +
  • + <%= button_to imports_path(import: { type: "AccountImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> +
    +
    + <%= lucide_icon("building", class: "w-5 h-5 text-violet-500") %> +
    + + <%= t(".import_accounts") %> +
    - - <%= t(".import_accounts") %> - + <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> + <% end %> + +
    +
    - <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> - <% end %> +
  • + <% end %> -
    -
    -
    - + <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "MintImport" || params[:type] == "TransactionImport") %> +
  • + <%= button_to imports_path(import: { type: "MintImport" }), class: "flex items-center justify-between p-4 group w-full", data: { turbo: false } do %> +
    + <%= image_tag("mint-logo.jpeg", alt: "Mint logo", class: "w-8 h-8 rounded-md") %> + + <%= t(".import_mint") %> + +
    + <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> + <% end %> -
  • - <%= button_to imports_path(import: { type: "MintImport" }), class: "flex items-center justify-between p-4 group w-full", data: { turbo: false } do %> -
    - <%= image_tag("mint-logo.jpeg", alt: "Mint logo", class: "w-8 h-8 rounded-md") %> - - <%= t(".import_mint") %> - +
    +
    - <%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %> - <% end %> - -
    -
    -
    -
  • + + <% end %>
    diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb index e62a104a..e038247e 100644 --- a/app/views/layouts/_sidebar.html.erb +++ b/app/views/layouts/_sidebar.html.erb @@ -103,7 +103,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, id: "sidebar-new-account", class: "block hover:bg-gray-100 font-semibold text-gray-900 flex items-center rounded", title: t(".new_account"), data: { turbo_frame: "modal" } do %> + <%= 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 %> <%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %> <% end %> @@ -114,7 +114,7 @@ <%= render "accounts/account_list", group: group %> <% end %> <% else %> - <%= 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 %> + <%= 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 %> <%= lucide_icon("plus", class: "w-5 h-5") %> <%= tag.p t(".new_account") %> <% end %> diff --git a/app/views/pages/_account_group_disclosure.erb b/app/views/pages/_account_group_disclosure.erb index 6fc6f7e9..e3e1128d 100644 --- a/app/views/pages/_account_group_disclosure.erb +++ b/app/views/pages/_account_group_disclosure.erb @@ -25,7 +25,7 @@ <% accountable_group.children.map do |account_value_node| %>
    - <%= image_tag account_logo_url(account_value_node.original), class: "w-8 h-8" %> + <%= render "accounts/logo", account: account_value_node.original, size: "sm" %>

    <%= account_value_node.name %>

    diff --git a/app/views/pages/dashboard.html.erb b/app/views/pages/dashboard.html.erb index 9a70d493..b25e8497 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, class: "flex items-center gap-1 btn btn--primary", data: { turbo_frame: "modal" } do %> + <%= link_to new_account_path(step: "method"), 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 %> @@ -70,10 +70,10 @@ data-time-series-chart-use-labels-value="false" data-time-series-chart-use-tooltip-value="false">
    -
    +
    <% @top_earners.first(3).each do |account| %> <%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-gray-900 font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %> - <%= image_tag account_logo_url(account), class: "w-5 h-5" %> + <%= render "accounts/logo", account: account, size: "sm" %> +<%= Money.new(account.income, account.currency) %> <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %> <% end %> @@ -103,12 +103,12 @@ data-time-series-chart-use-labels-value="false" data-time-series-chart-use-tooltip-value="false">
    -
    +
    <% @top_spenders.first(3).each do |account| %> <%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-gray-900 font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %> - <%= image_tag account_logo_url(account), class: "w-5 h-5" %> + <%= render "accounts/logo", account: account, size: "sm" %> -<%= Money.new(account.spending, account.currency) %> - <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %> + <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %> <% end %> <% end %> <% if @top_spenders.count > 3 %> @@ -141,9 +141,9 @@ <% @top_savers.first(3).each do |account| %> <% unless account.savings_rate.infinite? %> <%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-gray-900 font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %> - <%= image_tag account_logo_url(account), class: "w-5 h-5" %> + <%= render "accounts/logo", account: account, size: "sm" %> <%= account.savings_rate > 0 ? "+" : "-" %><%= number_to_percentage(account.savings_rate.abs * 100, precision: 2) %> - <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %> + <%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %> <% end %> <% end %> <% end %> diff --git a/app/views/shared/_circle_logo.html.erb b/app/views/shared/_circle_logo.html.erb index 328cb476..17f1948a 100644 --- a/app/views/shared/_circle_logo.html.erb +++ b/app/views/shared/_circle_logo.html.erb @@ -2,7 +2,7 @@ <% size_classes = { "sm" => "w-6 h-6", - "md" => "w-8 h-8", + "md" => "w-9 h-9", "lg" => "w-10 h-10", "full" => "w-full h-full" } %> diff --git a/app/views/shared/_no_account_empty_state.html.erb b/app/views/shared/_no_account_empty_state.html.erb index cfb79003..b4eda597 100644 --- a/app/views/shared/_no_account_empty_state.html.erb +++ b/app/views/shared/_no_account_empty_state.html.erb @@ -7,7 +7,7 @@

    <%= t(".no_account_subtitle") %>

    - <%= link_to new_account_path, class: "btn btn--primary flex items-center gap-1", data: { turbo_frame: "modal" } do %> + <%= link_to new_account_path(step: "method"), class: "btn btn--primary flex items-center gap-1", data: { turbo_frame: "modal" } do %> <%= lucide_icon("plus", class: "w-5 h-5") %> <%= t(".new_account") %> <% end %> diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 71a47b67..78648c48 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -23,6 +23,74 @@ ], "note": "" }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "42595161ffdc9ce9a10c4ba2a75fd2bb668e273bc4e683880b0ea906d0bd28f8", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/accounts/show.html.erb", + "line": 8, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"header\"), { :account => Current.family.accounts.find(params[:id]) })", + "render_path": [ + { + "type": "controller", + "class": "AccountsController", + "method": "show", + "line": 39, + "file": "app/controllers/accounts_controller.rb", + "rendered": { + "name": "accounts/show", + "file": "app/views/accounts/show.html.erb" + } + } + ], + "location": { + "type": "template", + "template": "accounts/show" + }, + "user_input": "params[:id]", + "confidence": "Weak", + "cwe_id": [ + 22 + ], + "note": "" + }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "a35b18785608dbdf35607501363573576ed8c304039f8387997acd1408ca1025", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/accounts/show.html.erb", + "line": 35, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"tooltip\"), { :account => Current.family.accounts.find(params[:id]) })", + "render_path": [ + { + "type": "controller", + "class": "AccountsController", + "method": "show", + "line": 39, + "file": "app/controllers/accounts_controller.rb", + "rendered": { + "name": "accounts/show", + "file": "app/views/accounts/show.html.erb" + } + } + ], + "location": { + "type": "template", + "template": "accounts/show" + }, + "user_input": "params[:id]", + "confidence": "Weak", + "cwe_id": [ + 22 + ], + "note": "" + }, { "warning_type": "Cross-Site Scripting", "warning_code": 2, @@ -38,7 +106,7 @@ "type": "controller", "class": "PagesController", "method": "changelog", - "line": 35, + "line": 36, "file": "app/controllers/pages_controller.rb", "rendered": { "name": "pages/changelog", @@ -60,19 +128,19 @@ { "warning_type": "Dynamic Render Path", "warning_code": 15, - "fingerprint": "b7a59d6dd91f4d30873b271659636c7975e25b47f436b4f03900a08809af2e92", + "fingerprint": "c5c512a13c34c9696024bd4e2367a657a5c140b5b6a0f5c352e9b69965f63e1b", "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/accounts/show.html.erb", - "line": 105, + "line": 63, "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", - "code": "render(action => selected_account_tab(Current.family.accounts.find(params[:id]))[:partial_path], { :account => Current.family.accounts.find(params[:id]) })", + "code": "render(action => permitted_accountable_partial(Current.family.accounts.find(params[:id]), \"tabs\"), { :account => Current.family.accounts.find(params[:id]), :selected_tab => params[:tab] })", "render_path": [ { "type": "controller", "class": "AccountsController", "method": "show", - "line": 38, + "line": 39, "file": "app/controllers/accounts_controller.rb", "rendered": { "name": "accounts/show", @@ -98,7 +166,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/import/configurations/show.html.erb", - "line": 13, + "line": 15, "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(partial => permitted_import_configuration_path(Current.family.imports.find(params[:import_id])), { :locals => ({ :import => Current.family.imports.find(params[:import_id]) }) })", "render_path": [ @@ -126,6 +194,6 @@ "note": "" } ], - "updated": "2024-09-28 13:27:09 -0400", + "updated": "2024-10-17 11:30:15 -0400", "brakeman_version": "6.2.1" } diff --git a/config/locales/views/accounts/en.yml b/config/locales/views/accounts/en.yml index cb6668ef..0e12a0f5 100644 --- a/config/locales/views/accounts/en.yml +++ b/config/locales/views/accounts/en.yml @@ -1,37 +1,88 @@ --- en: accounts: + sync_all_button: + sync: Sync all account: has_issues: Issue detected. troubleshoot: Troubleshoot + account_list: + new_account: "New %{type}" + empty: + no_accounts: No accounts yet + empty_message: Add an account either via connection, importing or entering manually. + new_account: New account + form: + name_label: Account name + name_placeholder: Example account name + institution: Financial institution + ungrouped: "(none)" + balance: Today's balance + accountable_type: Account type + type_prompt: Select a type + header: + accounts: Accounts + manage: Manage accounts + new: New account + institution_accounts: + add_account_to_institution: Add new account + has_issues: Issue detected, see accounts + syncing: Syncing... + status: "Last synced %{last_synced_at} ago" + status_never: Requires data sync + edit: Edit institution + delete: Delete institution + confirm_title: Delete financial institution? + confirm_body: Don't worry, none of the accounts within this institution will be affected by this deletion. Accounts will be ungrouped and all historical data will remain intact. + confirm_accept: Delete institution + new_account: Add account + institutionless_accounts: + other_accounts: Other accounts + menu: + edit: Edit + import: Import transactions + confirm_title: Delete account? + confirm_body_html: "

    By deleting this account, you will erase its value history, affecting various aspects of your overall account. This action will have a direct impact on your net worth calculations and the account graphs.


    After deletion, there is no way you'll be able to restore the account information because you'll need to add it as a new account.

    " + confirm_accept: 'Delete "%{name}"' accountables: - investment: - prompt: Select a subtype - none: None credit_card: - annual_fee: Annual fee - annual_fee_placeholder: '99' - apr: APR - apr_placeholder: '15.99' - available_credit: Available credit - available_credit_placeholder: '10000' - expiration_date: Expiration date - minimum_payment: Minimum payment - minimum_payment_placeholder: '100' + form: + available_credit: Available credit + available_credit_placeholder: '10000' + minimum_payment: Minimum payment + minimum_payment_placeholder: '100' + apr: APR + apr_placeholder: '15.99' + expiration_date: Expiration date + annual_fee: Annual fee + annual_fee_placeholder: '99' overview: amount_owed: Amount Owed - annual_fee: Annual Fee - apr: APR available_credit: Available Credit - expiration_date: Expiration Date minimum_payment: Minimum Payment + apr: APR + expiration_date: Expiration Date + annual_fee: Annual Fee unknown: Unknown depository: - prompt: Select a subtype - none: None + form: + none: None + prompt: Select a subtype + investment: + form: + none: None + prompt: Select a subtype + tooltip: + cash: Cash + holdings: Holdings + total_value_tooltip: The total value is the sum of cash balance and your holdings value, minus margin loans. loan: - interest_rate: Interest rate - interest_rate_placeholder: '5.25' + form: + interest_rate: Interest rate + interest_rate_placeholder: '5.25' + rate_type: Rate type + term_months: Term (months) + term_months_placeholder: '360' overview: interest_rate: Interest Rate monthly_payment: Monthly Payment @@ -41,18 +92,19 @@ en: term: Term type: Type unknown: Unknown - rate_type: Rate type - term_months: Term (months) - term_months_placeholder: '360' property: - additional_info: Additional info - area_unit: Area unit - area_value: Area value - city: City - country: Country - line1: Address line 1 - line2: Address line 2 - optional: optional + form: + additional_info: Additional info + area_unit: Area unit + area_value: Area value + city: City + country: Country + line1: Address line 1 + line2: Address line 2 + optional: optional + postal_code: Postal code + state: State + year_built: Year built overview: living_area: Living Area market_value: Market Value @@ -60,17 +112,17 @@ en: trend: Trend unknown: Unknown year_built: Year Built - postal_code: Postal code - state: State - year_built: Year built vehicle: - make: Make - make_placeholder: Toyota - mileage: Mileage - mileage_placeholder: '15000' - mileage_unit: Unit - model: Model - model_placeholder: Camry + form: + make: Make + make_placeholder: Toyota + mileage: Mileage + mileage_placeholder: '15000' + mileage_unit: Unit + model: Model + model_placeholder: Camry + year: Year + year_placeholder: '2023' overview: current_price: Current Price make_model: Make & Model @@ -79,70 +131,22 @@ en: trend: Trend unknown: Unknown year: Year - year: Year - year_placeholder: '2023' - create: - success: New account created successfully - destroy: - success: Account deleted successfully edit: - edit: Edit %{account} - empty: - empty_message: Add an account either via connection, importing or entering manually. - new_account: New account - no_accounts: No accounts yet - form: - institution: Financial institution - ungrouped: "(none)" - balance: Current balance - name_label: Account name - name_placeholder: Example account name - start_balance: Start balance (optional) - start_date: Start date (optional) - header: - accounts: Accounts - manage: Manage accounts - new: New account + edit: "Edit %{account}" index: accounts: Accounts add_institution: Add institution new_account: New account - institution_accounts: - add_account_to_institution: Add new account - confirm_accept: Delete institution - confirm_body: Don't worry, none of the accounts within this institution will - be affected by this deletion. Accounts will be ungrouped and all historical - data will remain intact. - confirm_title: Delete financial institution? - delete: Delete institution - edit: Edit institution - has_issues: Issue detected, see accounts - new_account: Add account - status: Last synced %{last_synced_at} ago - status_never: Requires data sync - syncing: Syncing... - institutionless_accounts: - other_accounts: Other accounts new: - select_accountable_type: What would you like to add? title: Add an account + manual_entry: Enter account manually + csv_entry: Import accounts CSV + connected_entry: Securely link account with Plaid (coming soon) show: cash: Cash - confirm_accept: Delete "%{name}" - confirm_body_html: "

    By deleting this account, you will erase its value history, - affecting various aspects of your overall account. This action will have a - direct impact on your net worth calculations and the account graphs.


    After deletion, there is no way you'll be able to restore the account - information because you'll need to add it as a new account.

    " - confirm_title: Delete account? - edit: Edit holdings: Holdings - import: Import transactions no_change: No change overview: Overview - sync_message_missing_rates: Since exchange rates haven't been synced, balance - graphs may not reflect accurate values. - sync_message_unknown_error: An error has occurred during the sync. total_owed: Total Owed total_value: Total Value trades: Transactions @@ -151,21 +155,17 @@ en: summary: new: New no_assets: No assets found - no_assets_description: Add an asset either via connection, importing or entering - manually. + no_assets_description: Add an asset either via connection, importing or entering manually. no_liabilities: No liabilities found - no_liabilities_description: Add a liability either via connection, importing - or entering manually. - sync_all: - button_text: Sync all - success: Successfully queued accounts for syncing. - tooltip: - cash: Cash - holdings: Holdings - total_value_tooltip: The total value is the sum of cash balance and your holdings - value, minus margin loans. + no_liabilities_description: Add a liability either via connection, importing or entering manually. + create: + success: New account created successfully + destroy: + success: Account deleted successfully update: success: Account updated + sync_all: + success: Successfully queued accounts for syncing. credit_cards: create: success: Credit card created successfully diff --git a/config/locales/views/imports/en.yml b/config/locales/views/imports/en.yml index 7227a9f2..e5fd7920 100644 --- a/config/locales/views/imports/en.yml +++ b/config/locales/views/imports/en.yml @@ -69,7 +69,7 @@ en: import_mint: Import from Mint import_portfolio: Import investments import_transactions: Import transactions - resume: Resume latest import + resume: Resume %{type} sources: Sources title: New CSV Import ready: diff --git a/config/routes.rb b/config/routes.rb index 2cfa2b2d..079943a7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -61,8 +61,6 @@ Rails.application.routes.draw do end scope module: :account do - resource :logo, only: :show - resources :holdings, only: %i[index new show destroy] resources :cashes, only: :index diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb index d4c1be45..e4c3bf36 100644 --- a/test/helpers/application_helper_test.rb +++ b/test/helpers/application_helper_test.rb @@ -11,12 +11,6 @@ class ApplicationHelperTest < ActionView::TestCase assert_equal "Test Header Title", content_for(:header_title) end - test "#permitted_accountable_partial(accountable_type)" do - assert_equal "account", permitted_accountable_partial("Account") - assert_equal "user", permitted_accountable_partial("User") - assert_equal "admin_user", permitted_accountable_partial("AdminUser") - end - def setup @account1 = Account.new(currency: "USD", balance: 1) @account2 = Account.new(currency: "USD", balance: 2) diff --git a/test/models/account/balance/syncer_test.rb b/test/models/account/balance/syncer_test.rb index 9bfeffcb..748b3e6a 100644 --- a/test/models/account/balance/syncer_test.rb +++ b/test/models/account/balance/syncer_test.rb @@ -35,15 +35,6 @@ class Account::Balance::SyncerTest < ActiveSupport::TestCase assert_equal [ 19600, 19500, 19500, 20000, 20000, 20000 ], @account.balances.chronological.map(&:balance) end - test "syncs account with trades only" do - aapl = securities(:aapl) - create_trade(aapl, account: @investment_account, date: 1.day.ago.to_date, qty: 10) - - run_sync_for @investment_account - - assert_equal [ 52140, 50000, 50000 ], @investment_account.balances.chronological.map(&:balance) - end - test "syncs account with valuations and transactions" do create_valuation(account: @account, date: 5.days.ago.to_date, amount: 20000) create_transaction(account: @account, date: 3.days.ago.to_date, amount: -500) diff --git a/test/system/.keep b/test/system/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/test/system/accounts_test.rb b/test/system/accounts_test.rb index 1b7248c0..97f3fd91 100644 --- a/test/system/accounts_test.rb +++ b/test/system/accounts_test.rb @@ -80,19 +80,15 @@ class AccountsTest < ApplicationSystemTestCase end def assert_account_created(accountable_type, &block) - click_link humanized_accountable(accountable_type) - click_link "Enter account balance manually" + click_link "Enter account manually" account_name = "[system test] #{accountable_type} Account" + select accountable_type.titleize, from: "Account type" fill_in "Account name", with: account_name fill_in "account[balance]", with: 100.99 - fill_in "Start date (optional)", with: 10.days.ago.to_date - fill_in "account[start_balance]", with: 95.25 - yield if block_given? - - click_button "Add #{humanized_accountable(accountable_type).downcase}" + click_button "Create Account" find("details", text: humanized_accountable(accountable_type)).click assert_text account_name @@ -107,8 +103,10 @@ class AccountsTest < ApplicationSystemTestCase click_on "Edit" end + yield if block_given? + fill_in "Account name", with: "Updated account name" - click_button "Update #{humanized_accountable(accountable_type).downcase}" + click_button "Update Account" assert_selector "h2", text: "Updated account name" end diff --git a/test/system/tooltips_test.rb b/test/system/tooltips_test.rb deleted file mode 100644 index 7666269c..00000000 --- a/test/system/tooltips_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "application_system_test_case" - -class TooltipsTest < ApplicationSystemTestCase - include ActionView::Helpers::NumberHelper - include ApplicationHelper - - setup do - sign_in @user = users(:family_admin) - @account = accounts(:investment) - end - - test "can see account information tooltip" do - visit account_path(@account) - tooltip_element = find('[data-controller="tooltip"]') - tooltip_element.hover - tooltip_contents = find('[data-tooltip-target="tooltip"]') - assert tooltip_contents.visible? - within tooltip_contents do - assert_text I18n.t("accounts.tooltip.total_value_tooltip") - assert_text I18n.t("accounts.tooltip.holdings") - assert_text format_money(@account.investment.holdings_value, precision: 0) - assert_text I18n.t("accounts.tooltip.cash") - assert_text format_money(@account.balance_money, precision: 0) - end - find("body").click - assert find('[data-tooltip-target="tooltip"]', visible: false) - end -end diff --git a/test/system/trades_test.rb b/test/system/trades_test.rb index 541a445c..c9f7835b 100644 --- a/test/system/trades_test.rb +++ b/test/system/trades_test.rb @@ -62,6 +62,6 @@ class TradesTest < ApplicationSystemTestCase end def visit_account_trades - visit account_url(@account, tab: "trades") + visit account_url(@account, tab: "transactions") end end diff --git a/test/system/transactions_test.rb b/test/system/transactions_test.rb index 4544c6c4..956e800b 100644 --- a/test/system/transactions_test.rb +++ b/test/system/transactions_test.rb @@ -156,6 +156,7 @@ class TransactionsTest < ApplicationSystemTestCase test "can create deposit transaction for investment account" do investment_account = accounts(:investment) + investment_account.entries.create!(name: "Investment account", date: Date.current, amount: 1000, currency: "USD", entryable: Account::Transaction.new) transfer_date = Date.current visit account_path(investment_account) click_on "New transaction"