mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-21 06:09:38 +02:00
Rework account views and addition flow (#1324)
* Move accountable partials * Split accountables into separate view partials * Fix test * Add form to permitted partials * Fix failing system tests * Update new account modal views * New sync algorithm implementation * Update account system test assertions to match new behavior * Fix off by 1 date error * Revert new balance sync algorithm * Add missing account overviews
This commit is contained in:
parent
c7c281073f
commit
e8e100e1d8
88 changed files with 763 additions and 526 deletions
|
@ -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
|
|
|
@ -23,11 +23,8 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@account = Account.new(
|
@account = Account.new(currency: Current.family.currency)
|
||||||
accountable: Accountable.from_type(params[:type])&.new,
|
@account.accountable = Accountable.from_type(params[:type])&.new if params[:type].present?
|
||||||
currency: Current.family.currency
|
|
||||||
)
|
|
||||||
|
|
||||||
@account.accountable.address = Address.new if @account.accountable.is_a?(Property)
|
@account.accountable.address = Address.new if @account.accountable.is_a?(Property)
|
||||||
|
|
||||||
if params[:institution_id]
|
if params[:institution_id]
|
||||||
|
@ -36,8 +33,6 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@series = @account.series(period: @period)
|
|
||||||
@trend = @series.trend
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
@ -57,6 +52,7 @@ class AccountsController < ApplicationController
|
||||||
start_date: account_params[:start_date],
|
start_date: account_params[:start_date],
|
||||||
start_balance: account_params[:start_balance]
|
start_balance: account_params[:start_balance]
|
||||||
@account.sync_later
|
@account.sync_later
|
||||||
|
|
||||||
redirect_back_or_to account_path(@account), notice: t(".success")
|
redirect_back_or_to account_path(@account), notice: t(".success")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
module AccountsHelper
|
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)
|
def summary_card(title:, &block)
|
||||||
content = capture(&block)
|
content = capture(&block)
|
||||||
render "accounts/summary_card", title: title, content: content
|
render "accounts/summary_card", title: title, content: content
|
||||||
|
|
|
@ -9,10 +9,6 @@ module ApplicationHelper
|
||||||
content_for(:header_title) { page_title }
|
content_for(:header_title) { page_title }
|
||||||
end
|
end
|
||||||
|
|
||||||
def permitted_accountable_partial(name)
|
|
||||||
name.underscore
|
|
||||||
end
|
|
||||||
|
|
||||||
def family_notifications_stream
|
def family_notifications_stream
|
||||||
turbo_stream_from [ Current.family, :notifications ] if Current.family
|
turbo_stream_from [ Current.family, :notifications ] if Current.family
|
||||||
end
|
end
|
||||||
|
@ -80,8 +76,8 @@ module ApplicationHelper
|
||||||
color = hex || "#1570EF" # blue-600
|
color = hex || "#1570EF" # blue-600
|
||||||
|
|
||||||
<<-STYLE.strip
|
<<-STYLE.strip
|
||||||
background-color: color-mix(in srgb, #{color} 5%, white);
|
background-color: color-mix(in srgb, #{color} 10%, white);
|
||||||
border-color: color-mix(in srgb, #{color} 10%, white);
|
border-color: color-mix(in srgb, #{color} 30%, white);
|
||||||
color: #{color};
|
color: #{color};
|
||||||
STYLE
|
STYLE
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,12 +51,17 @@ export default class extends Controller {
|
||||||
_normalizeDataPoints() {
|
_normalizeDataPoints() {
|
||||||
this._normalDataPoints = (this.dataValue.values || []).map((d) => ({
|
this._normalDataPoints = (this.dataValue.values || []).map((d) => ({
|
||||||
...d,
|
...d,
|
||||||
date: new Date(d.date),
|
date: this._parseDate(d.date),
|
||||||
value: d.value.amount ? +d.value.amount : +d.value,
|
value: d.value.amount ? +d.value.amount : +d.value,
|
||||||
currency: d.value.currency,
|
currency: d.value.currency,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_parseDate(dateString) {
|
||||||
|
const [year, month, day] = dateString.split("-").map(Number);
|
||||||
|
return new Date(year, month - 1, day);
|
||||||
|
}
|
||||||
|
|
||||||
_rememberInitialContainerSize() {
|
_rememberInitialContainerSize() {
|
||||||
this._d3InitialContainerWidth = this._d3Container.node().clientWidth;
|
this._d3InitialContainerWidth = this._d3Container.node().clientWidth;
|
||||||
this._d3InitialContainerHeight = this._d3Container.node().clientHeight;
|
this._d3InitialContainerHeight = this._d3Container.node().clientHeight;
|
||||||
|
|
|
@ -27,6 +27,8 @@ class Account < ApplicationRecord
|
||||||
scope :alphabetically, -> { order(:name) }
|
scope :alphabetically, -> { order(:name) }
|
||||||
scope :ungrouped, -> { where(institution_id: nil) }
|
scope :ungrouped, -> { where(institution_id: nil) }
|
||||||
|
|
||||||
|
has_one_attached :logo
|
||||||
|
|
||||||
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
|
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
|
||||||
|
|
||||||
accepts_nested_attributes_for :accountable
|
accepts_nested_attributes_for :accountable
|
||||||
|
|
57
app/models/account/balance/calculator.rb
Normal file
57
app/models/account/balance/calculator.rb
Normal file
|
@ -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
|
46
app/models/account/balance/converter.rb
Normal file
46
app/models/account/balance/converter.rb
Normal file
|
@ -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
|
37
app/models/account/balance/loader.rb
Normal file
37
app/models/account/balance/loader.rb
Normal file
|
@ -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
|
|
@ -1,133 +1,51 @@
|
||||||
class Account::Balance::Syncer
|
class Account::Balance::Syncer
|
||||||
def initialize(account, start_date: nil)
|
def initialize(account, start_date: nil)
|
||||||
@account = account
|
@account = account
|
||||||
|
@provided_start_date = start_date
|
||||||
@sync_start_date = calculate_sync_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
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
daily_balances = calculate_daily_balances
|
daily_balances = calculator.calculate(is_partial_sync: is_partial_sync?)
|
||||||
daily_balances += calculate_converted_balances(daily_balances) if account.currency != account.family.currency
|
daily_balances += converter.convert(daily_balances) if account.currency != account.family.currency
|
||||||
|
|
||||||
Account::Balance.transaction do
|
loader.load(daily_balances, account_start_date)
|
||||||
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
|
|
||||||
rescue Money::ConversionError => e
|
rescue Money::ConversionError => e
|
||||||
account.observe_missing_exchange_rates(from: e.from_currency, to: e.to_currency, dates: [ e.date ])
|
account.observe_missing_exchange_rates(from: e.from_currency, to: e.to_currency, dates: [ e.date ])
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :sync_start_date, :account
|
attr_reader :sync_start_date, :provided_start_date, :account, :loader, :converter, :calculator
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def account_start_date
|
def account_start_date
|
||||||
@account_start_date ||= begin
|
@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?
|
if oldest_entry.account_valuation?
|
||||||
|
oldest_entry.date
|
||||||
oldest_entry_date -= 1 unless oldest_entry_is_valuation
|
else
|
||||||
oldest_entry_date
|
oldest_entry.date - 1.day
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_sync_start_date(provided_start_date)
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,4 +12,8 @@ class CreditCard < ApplicationRecord
|
||||||
def annual_fee_money
|
def annual_fee_money
|
||||||
annual_fee ? Money.new(annual_fee, account.currency) : nil
|
annual_fee ? Money.new(annual_fee, account.currency) : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#F13636"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
class Crypto < ApplicationRecord
|
class Crypto < ApplicationRecord
|
||||||
include Accountable
|
include Accountable
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#737373"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
class Depository < ApplicationRecord
|
class Depository < ApplicationRecord
|
||||||
include Accountable
|
include Accountable
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#875BF7"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -46,4 +46,8 @@ class Investment < ApplicationRecord
|
||||||
rescue Money::ConversionError
|
rescue Money::ConversionError
|
||||||
TimeSeries.new([])
|
TimeSeries.new([])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#1570EF"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,4 +16,8 @@ class Loan < ApplicationRecord
|
||||||
|
|
||||||
Money.new(payment.round, account.currency)
|
Money.new(payment.round, account.currency)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#D444F1"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
class OtherAsset < ApplicationRecord
|
class OtherAsset < ApplicationRecord
|
||||||
include Accountable
|
include Accountable
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#12B76A"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
class OtherLiability < ApplicationRecord
|
class OtherLiability < ApplicationRecord
|
||||||
include Accountable
|
include Accountable
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#737373"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,10 @@ class Property < ApplicationRecord
|
||||||
TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount)
|
TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#06AED4"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def first_valuation_amount
|
def first_valuation_amount
|
||||||
account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money
|
account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money
|
||||||
|
|
|
@ -15,6 +15,10 @@ class Vehicle < ApplicationRecord
|
||||||
TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount)
|
TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def color
|
||||||
|
"#F23E94"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def first_valuation_amount
|
def first_valuation_amount
|
||||||
account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money
|
account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
viewBox="0 0 32 32"
|
|
||||||
aria-hidden="true">
|
|
||||||
<g>
|
|
||||||
<rect width="100%" height="100%" rx="50%" fill="<%= accountable_color(@account.accountable_type) %>" opacity="0.1" />
|
|
||||||
<text
|
|
||||||
x="50%"
|
|
||||||
y="50%"
|
|
||||||
fill="<%= accountable_color(@account.accountable_type) %>"
|
|
||||||
text-anchor="middle"
|
|
||||||
dy="0.35em"
|
|
||||||
font-family="ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji"
|
|
||||||
font-size="16"
|
|
||||||
font-weight="400">
|
|
||||||
<%= @account.name[0].upcase %>
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 653 B |
|
@ -30,7 +30,7 @@
|
||||||
<% group.children.sort_by(&:name).each do |account_value_node| %>
|
<% group.children.sort_by(&:name).each do |account_value_node| %>
|
||||||
<% account = account_value_node.original %>
|
<% 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 %>
|
<%= 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" %>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium"><%= account_value_node.name %></p>
|
<p class="font-medium"><%= account_value_node.name %></p>
|
||||||
<% if account.subtype %>
|
<% if account.subtype %>
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
<% 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_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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||||
<p>New <%= type.model_name.human.downcase %></p>
|
<%= t(".new_account", type: type.model_name.human.downcase) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</details>
|
</details>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<%= link_to new_account_path(
|
<%= link_to new_account_path(
|
||||||
step: "method",
|
|
||||||
type: type.class.name.demodulize,
|
type: type.class.name.demodulize,
|
||||||
institution_id: params[:institution_id]
|
institution_id: params[:institution_id]
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<%= tag.p t(".no_accounts"), class: "text-gray-900 mb-1 font-medium" %>
|
<%= tag.p t(".no_accounts"), class: "text-gray-900 mb-1 font-medium" %>
|
||||||
<%= tag.p t(".empty_message"), class: "text-gray-500 mb-4" %>
|
<%= 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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||||
<span><%= t(".new_account") %></span>
|
<span><%= t(".new_account") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<% if local_assigns[:disabled] && disabled %>
|
<%# locals: (text:, icon:, disabled: false) %>
|
||||||
<span class="flex items-center w-full gap-4 p-2 px-2 text-center border border-transparent rounded-lg cursor-not-allowed focus:outline-none focus:bg-gray-50 focus:border focus:border-gray-200 hover:bg-gray-50">
|
|
||||||
|
<% if disabled %>
|
||||||
|
<span class="flex items-center w-full gap-4 p-2 px-2 text-center border border-transparent rounded-lg cursor-not-allowed focus:outline-none focus:bg-gray-50 focus:border focus:border-gray-200 text-gray-400">
|
||||||
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
||||||
<%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
|
<%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
|
||||||
</span>
|
</span>
|
||||||
<%= text %>
|
<%= text %>
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% 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 %>
|
||||||
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
||||||
<%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
|
<%= lucide_icon(icon, class: "text-gray-500 w-5 h-5") %>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -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| %>
|
<%= styled_form_with model: account, url: url, scope: :account, class: "flex flex-col gap-4 justify-between grow", data: { turbo: false } do |f| %>
|
||||||
<div class="grow space-y-2">
|
<div class="grow space-y-2">
|
||||||
<%= f.hidden_field :accountable_type %>
|
<%= 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"), autofocus: true %>
|
<%= f.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label") %>
|
||||||
|
|
||||||
<% if account.new_record? %>
|
<% if account.new_record? %>
|
||||||
<%= f.hidden_field :institution_id %>
|
<%= f.hidden_field :institution_id %>
|
||||||
|
@ -13,15 +13,10 @@
|
||||||
|
|
||||||
<%= f.money_field :balance, label: t(".balance"), required: true, default_currency: Current.family.currency %>
|
<%= f.money_field :balance, label: t(".balance"), required: true, default_currency: Current.family.currency %>
|
||||||
|
|
||||||
<% if account.new_record? %>
|
<% if account.accountable %>
|
||||||
<div class="flex items-center gap-2 mt-3 mb-6">
|
<%= render permitted_accountable_partial(account, "form"), f: f %>
|
||||||
<div class="w-1/2"><%= f.date_field :start_date, label: t(".start_date"), max: Date.yesterday, min: Account::Entry.min_supported_date %></div>
|
|
||||||
<div class="w-1/2"><%= f.money_field :start_balance, label: t(".start_balance"), placeholder: 90, hide_currency: true, default_currency: Current.family.currency %></div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= render "accounts/accountables/#{permitted_accountable_partial(account.accountable_type)}", f: f %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= f.submit "#{account.new_record? ? "Add" : "Update"} #{account.accountable.model_name.human.downcase}" %>
|
<%= f.submit %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
<% end %>
|
<% 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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||||
<p class="text-sm font-medium"><%= t(".new") %></p>
|
<p class="text-sm font-medium"><%= t(".new") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
14
app/views/accounts/_logo.html.erb
Normal file
14
app/views/accounts/_logo.html.erb
Normal file
|
@ -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 %>
|
33
app/views/accounts/_menu.html.erb
Normal file
33
app/views/accounts/_menu.html.erb
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
|
<%= contextual_menu do %>
|
||||||
|
<div class="w-48 p-1 text-sm leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||||
|
<%= 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" %>
|
||||||
|
|
||||||
|
<span><%= t(".edit") %></span>
|
||||||
|
<% 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" %>
|
||||||
|
|
||||||
|
<span><%= t(".import") %></span>
|
||||||
|
<% 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 %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
8
app/views/accounts/_new_account_setup_bar.html.erb
Normal file
8
app/views/accounts/_new_account_setup_bar.html.erb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="flex items-center gap-2 rounded-xl justify-between shadow-xs bg-white p-4 border border-alpha-black-25">
|
||||||
|
<p class="text-lg font-medium text-gray-900">Setup your new account</p>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<%= 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 } %>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -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" %>
|
<%= lucide_icon "refresh-cw", class: "w-5 h-5" %>
|
||||||
<span><%= t("accounts.sync_all.button_text") %></span>
|
<span><%= t(".sync") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
7
app/views/accounts/accountables/_default_header.html.erb
Normal file
7
app/views/accounts/accountables/_default_header.html.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<%= render "accounts/logo", account: account %>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="font-medium text-xl"><%= account.name %></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
7
app/views/accounts/accountables/_default_tabs.html.erb
Normal file
7
app/views/accounts/accountables/_default_tabs.html.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
|
<% if account.transactions.any? %>
|
||||||
|
<%= render "accounts/accountables/transactions", account: account %>
|
||||||
|
<% else %>
|
||||||
|
<%= render "accounts/accountables/valuations", account: account %>
|
||||||
|
<% end %>
|
8
app/views/accounts/accountables/_tab.html.erb
Normal file
8
app/views/accounts/accountables/_tab.html.erb
Normal file
|
@ -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
|
||||||
|
] %>
|
5
app/views/accounts/accountables/_transactions.html.erb
Normal file
5
app/views/accounts/accountables/_transactions.html.erb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
|
<%= turbo_frame_tag dom_id(account, :transactions), src: account_transactions_path(account) do %>
|
||||||
|
<%= render "account/entries/loading" %>
|
||||||
|
<% end %>
|
5
app/views/accounts/accountables/_valuations.html.erb
Normal file
5
app/views/accounts/accountables/_valuations.html.erb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
|
<%= turbo_frame_tag dom_id(account, :valuations), src: account_valuations_path(account) do %>
|
||||||
|
<%= render "account/entries/loading" %>
|
||||||
|
<% end %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_header", account: account %>
|
|
@ -25,3 +25,7 @@
|
||||||
<%= format_money(account.credit_card.annual_fee_money || Money.new(0, account.currency)) %>
|
<%= format_money(account.credit_card.annual_fee_money || Money.new(0, account.currency)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center py-8">
|
||||||
|
<%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
|
||||||
|
</div>
|
15
app/views/accounts/accountables/credit_card/_tabs.html.erb
Normal file
15
app/views/accounts/accountables/credit_card/_tabs.html.erb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<%# locals: (account:, selected_tab:) %>
|
||||||
|
|
||||||
|
<div class="flex gap-2 text-sm text-gray-900 font-medium mb-4">
|
||||||
|
<%= 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" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-h-[800px]">
|
||||||
|
<% case selected_tab %>
|
||||||
|
<% when nil, "overview" %>
|
||||||
|
<%= render "accounts/accountables/credit_card/overview", account: account %>
|
||||||
|
<% when "transactions" %>
|
||||||
|
<%= render "accounts/accountables/transactions", account: account %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
1
app/views/accounts/accountables/crypto/_header.html.erb
Normal file
1
app/views/accounts/accountables/crypto/_header.html.erb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_header", account: account %>
|
1
app/views/accounts/accountables/crypto/_tabs.html.erb
Normal file
1
app/views/accounts/accountables/crypto/_tabs.html.erb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_tabs", account: account %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_header", account: account %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_tabs", account: account %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_header", account: account %>
|
28
app/views/accounts/accountables/investment/_tabs.html.erb
Normal file
28
app/views/accounts/accountables/investment/_tabs.html.erb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<%# locals: (account:, selected_tab:) %>
|
||||||
|
|
||||||
|
<% if account.entries.account_trades.any? || account.entries.account_transactions.any? %>
|
||||||
|
<div class="flex gap-2 text-sm text-gray-900 font-medium mb-4">
|
||||||
|
<%= 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" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-h-[800px]">
|
||||||
|
<% 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 %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<%= render "accounts/accountables/valuations", account: account %>
|
||||||
|
<% end %>
|
|
@ -1,4 +1,5 @@
|
||||||
<%# locals: (account:) -%>
|
<%# locals: (account:) -%>
|
||||||
|
|
||||||
<div data-controller="tooltip" data-tooltip-placement-value="right" data-tooltip-offset-value=10 data-tooltip-cross-axis-value=50>
|
<div data-controller="tooltip" data-tooltip-placement-value="right" data-tooltip-offset-value=10 data-tooltip-cross-axis-value=50>
|
||||||
<%= lucide_icon("info", class: "w-4 h-4 shrink-0 text-gray-500") %>
|
<%= lucide_icon("info", class: "w-4 h-4 shrink-0 text-gray-500") %>
|
||||||
<div role="tooltip" data-tooltip-target="tooltip" class="tooltip bg-gray-700 text-sm p-2 rounded w-64">
|
<div role="tooltip" data-tooltip-target="tooltip" class="tooltip bg-gray-700 text-sm p-2 rounded w-64">
|
1
app/views/accounts/accountables/loan/_header.html.erb
Normal file
1
app/views/accounts/accountables/loan/_header.html.erb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_header", account: account %>
|
|
@ -43,3 +43,7 @@
|
||||||
<%= account.loan.rate_type&.titleize || t(".unknown") %>
|
<%= account.loan.rate_type&.titleize || t(".unknown") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center py-8">
|
||||||
|
<%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
|
||||||
|
</div>
|
15
app/views/accounts/accountables/loan/_tabs.html.erb
Normal file
15
app/views/accounts/accountables/loan/_tabs.html.erb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<%# locals: (account:, selected_tab:) %>
|
||||||
|
|
||||||
|
<div class="flex gap-2 text-sm text-gray-900 font-medium mb-4">
|
||||||
|
<%= 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" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-h-[800px]">
|
||||||
|
<% case selected_tab %>
|
||||||
|
<% when nil, "overview" %>
|
||||||
|
<%= render "accounts/accountables/loan/overview", account: account %>
|
||||||
|
<% when "value" %>
|
||||||
|
<%= render "accounts/accountables/valuations", account: account %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_header", account: account %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/valuations", account: account %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_header", account: account %>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/valuations", account: account %>
|
11
app/views/accounts/accountables/property/_header.html.erb
Normal file
11
app/views/accounts/accountables/property/_header.html.erb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<%= render "accounts/logo", account: account %>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="font-medium text-xl"><%= account.name %></h2>
|
||||||
|
|
||||||
|
<% if account.property.address&.line1.present? %>
|
||||||
|
<p class="text-gray-500"><%= account.property.address %></p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -27,3 +27,7 @@
|
||||||
<%= account.property.area || t(".unknown") %>
|
<%= account.property.area || t(".unknown") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center py-8">
|
||||||
|
<%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
|
||||||
|
</div>
|
||||||
|
|
15
app/views/accounts/accountables/property/_tabs.html.erb
Normal file
15
app/views/accounts/accountables/property/_tabs.html.erb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<%# locals: (account:, selected_tab:) %>
|
||||||
|
|
||||||
|
<div class="flex gap-2 text-sm text-gray-900 font-medium mb-4">
|
||||||
|
<%= 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" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-h-[800px]">
|
||||||
|
<% case selected_tab %>
|
||||||
|
<% when nil, "overview" %>
|
||||||
|
<%= render "accounts/accountables/property/overview", account: account %>
|
||||||
|
<% when "value" %>
|
||||||
|
<%= render "accounts/accountables/valuations", account: account %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
1
app/views/accounts/accountables/vehicle/_header.html.erb
Normal file
1
app/views/accounts/accountables/vehicle/_header.html.erb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<%= render "accounts/accountables/default_header", account: account %>
|
|
@ -31,3 +31,7 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center py-8">
|
||||||
|
<%= link_to "Edit account details", edit_account_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
|
||||||
|
</div>
|
||||||
|
|
15
app/views/accounts/accountables/vehicle/_tabs.html.erb
Normal file
15
app/views/accounts/accountables/vehicle/_tabs.html.erb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<%# locals: (account:, selected_tab:) %>
|
||||||
|
|
||||||
|
<div class="flex gap-2 text-sm text-gray-900 font-medium mb-4">
|
||||||
|
<%= 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" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-h-[800px]">
|
||||||
|
<% case selected_tab %>
|
||||||
|
<% when nil, "overview" %>
|
||||||
|
<%= render "accounts/accountables/vehicle/overview", account: account %>
|
||||||
|
<% when "value" %>
|
||||||
|
<%= render "accounts/accountables/valuations", account: account %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<%= render "sync_all_button" %>
|
<%= render "sync_all_button" %>
|
||||||
|
|
||||||
<%= link_to new_account_path,
|
<%= link_to new_account_path(step: "method"),
|
||||||
data: { turbo_frame: "modal" },
|
data: { turbo_frame: "modal" },
|
||||||
class: "btn btn--primary flex items-center gap-1" do %>
|
class: "btn btn--primary flex items-center gap-1" do %>
|
||||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||||
|
|
|
@ -1,58 +1,24 @@
|
||||||
<h1 class="text-3xl font-semibold font-display"><%= t(".title") %></h1>
|
<h1 class="text-3xl font-semibold font-display"><%= t(".title") %></h1>
|
||||||
<%= modal do %>
|
<%= modal do %>
|
||||||
<div class="flex flex-col w-screen max-w-xl" data-controller="list-keyboard-navigation">
|
<div class="flex flex-col w-screen max-w-xl" data-controller="list-keyboard-navigation">
|
||||||
<% if @account.accountable.blank? %>
|
<% if params[:step] == 'method' %>
|
||||||
<div class="border-b border-alpha-black-25 p-4 text-gray-400">
|
|
||||||
<%= t ".select_accountable_type" %>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col p-2 text-sm grow">
|
|
||||||
<button hidden data-controller="hotkey" data-hotkey="k,K,ArrowUp,ArrowLeft" data-action="list-keyboard-navigation#focusPrevious">Previous</button>
|
|
||||||
<button hidden data-controller="hotkey" data-hotkey="j,J,ArrowDown,ArrowRight" data-action="list-keyboard-navigation#focusNext">Next</button>
|
|
||||||
<%= 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" %>
|
|
||||||
</div>
|
|
||||||
<div class="border-t border-alpha-black-25 p-4 text-gray-500 text-sm flex justify-between">
|
|
||||||
<div class="flex space-x-5">
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<span>Select</span>
|
|
||||||
<kbd class="bg-alpha-black-50 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.1)] p-1 rounded-md flex w-5 h-5 shrink-0 grow-0 items-center justify-center"><%= lucide_icon("corner-down-left", class: "inline w-3 h-3") %></kbd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<span>Navigate</span>
|
|
||||||
<kbd class="bg-alpha-black-50 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.1)] p-1 rounded-md flex w-5 h-5 shrink-0 grow-0 items-center justify-center"><%= lucide_icon("arrow-up", class: "inline w-3 h-3") %></kbd>
|
|
||||||
<kbd class="bg-alpha-black-50 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.1)] p-1 rounded-md flex w-5 h-5 shrink-0 grow-0 items-center justify-center"><%= lucide_icon("arrow-down", class: "inline w-3 h-3") %></kbd>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<button data-action="modal#close">Close</button>
|
|
||||||
<kbd class="bg-alpha-black-50 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.1)] p-1 rounded-md flex w-8 h-5 shrink-0 grow-0 items-center justify-center text-xs">ESC</kbd>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% elsif params[:step] == 'method' && @account.accountable.present? %>
|
|
||||||
<div class="border-b border-alpha-black-25 p-4 text-gray-400 flex items-center space-x-3">
|
<div class="border-b border-alpha-black-25 p-4 text-gray-400 flex items-center space-x-3">
|
||||||
<%= 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 %>
|
|
||||||
<span>How would you like to add it?</span>
|
<span>How would you like to add it?</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col p-2 text-sm grow">
|
<div class="flex flex-col p-2 text-sm grow">
|
||||||
<button hidden data-controller="hotkey" data-hotkey="k,K,ArrowUp,ArrowLeft" data-action="list-keyboard-navigation#focusPrevious">Previous</button>
|
<button hidden data-controller="hotkey" data-hotkey="k,K,ArrowUp,ArrowLeft" data-action="list-keyboard-navigation#focusPrevious">Previous</button>
|
||||||
<button hidden data-controller="hotkey" data-hotkey="j,J,ArrowDown,ArrowRight" data-action="list-keyboard-navigation#focusNext">Next</button>
|
<button hidden data-controller="hotkey" data-hotkey="j,J,ArrowDown,ArrowRight" data-action="list-keyboard-navigation#focusNext">Next</button>
|
||||||
<%= 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 %>
|
||||||
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
||||||
<%= lucide_icon("sheet", class: "text-gray-500 w-5 h-5") %>
|
<%= lucide_icon("sheet", class: "text-gray-500 w-5 h-5") %>
|
||||||
</span>
|
</span>
|
||||||
Upload CSV
|
<%= t(".csv_entry") %>
|
||||||
<% end %>
|
<% 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 %>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-alpha-black-25 p-4 text-gray-500 text-sm flex justify-between">
|
<div class="border-t border-alpha-black-25 p-4 text-gray-500 text-sm flex justify-between">
|
||||||
<div class="flex space-x-5">
|
<div class="flex space-x-5">
|
||||||
|
@ -73,13 +39,13 @@
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="border-b border-alpha-black-25 p-4 text-gray-800 flex items-center space-x-3">
|
<div class="border-b border-alpha-black-25 p-4 text-gray-800 flex items-center space-x-3">
|
||||||
<%= 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") %>
|
<%= lucide_icon("arrow-left", class: "text-gray-500 w-5 h-5") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span>Add <%= @account.accountable.model_name.human.downcase %></span>
|
<span>Add account</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-4 pt-1">
|
<div class="p-4">
|
||||||
<%= render "form", account: @account, url: new_account_form_url(@account) %>
|
<%= render "form", account: @account, url: new_account_form_url(@account) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,56 +1,25 @@
|
||||||
<%= turbo_stream_from @account %>
|
<%= turbo_stream_from @account %>
|
||||||
|
|
||||||
<%= tag.div id: dom_id(@account), class: "space-y-4" do %>
|
<% series = @account.series(period: @period) %>
|
||||||
<header class="flex justify-between items-center">
|
<% trend = series.trend %>
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<%= image_tag account_logo_url(@account), class: "w-8 h-8" %>
|
|
||||||
<div>
|
|
||||||
<h2 class="font-medium text-xl"><%= @account.name %></h2>
|
|
||||||
|
|
||||||
<% if @account.property? && @account.property.address&.line1.present? %>
|
<%= tag.div id: dom_id(@account), class: "space-y-4" do %>
|
||||||
<p class="text-gray-500"><%= @account.property.address %></p>
|
<header class="flex items-center gap-4">
|
||||||
<% end %>
|
<%= render permitted_accountable_partial(@account, "header"), account: @account %>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="flex items-center gap-3 ml-auto">
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<%= button_to sync_account_path(@account), method: :post, class: "flex items-center gap-2", title: "Sync Account" do %>
|
<%= 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" %>
|
<%= lucide_icon "refresh-cw", class: "w-4 h-4 text-gray-500 hover:text-gray-400" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= contextual_menu do %>
|
<%= render "menu", account: @account %>
|
||||||
<div class="w-48 p-1 text-sm leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
|
||||||
<%= 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" %>
|
|
||||||
|
|
||||||
<span><%= t(".edit") %></span>
|
|
||||||
<% 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" %>
|
|
||||||
|
|
||||||
<span><%= t(".import") %></span>
|
|
||||||
<% 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 %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<% if @account.entries.empty? && @account.depository? %>
|
||||||
|
<%= render "accounts/new_account_setup_bar", account: @account %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if @account.highest_priority_issue %>
|
<% if @account.highest_priority_issue %>
|
||||||
<%= render partial: "issues/issue", locals: { issue: @account.highest_priority_issue } %>
|
<%= render partial: "issues/issue", locals: { issue: @account.highest_priority_issue } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -66,47 +35,35 @@
|
||||||
<%= tag.p t(".total_owed"), class: "text-sm font-medium text-gray-500" %>
|
<%= tag.p t(".total_owed"), class: "text-sm font-medium text-gray-500" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= render "tooltip", account: @account if @account.investment? %>
|
|
||||||
|
<%= render permitted_accountable_partial(@account, "tooltip"), account: @account if @account.investment? %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= tag.p format_money(@account.value), class: "text-gray-900 text-3xl font-medium" %>
|
<%= tag.p format_money(@account.value), class: "text-gray-900 text-3xl font-medium" %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<% if @series.trend.direction.flat? %>
|
<% if trend.direction.flat? %>
|
||||||
<%= tag.span t(".no_change"), class: "text-gray-500" %>
|
<%= tag.span t(".no_change"), class: "text-gray-500" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= tag.span format_money(@series.trend.value), style: "color: #{@trend.color}" %>
|
<%= tag.span format_money(trend.value), style: "color: #{trend.color}" %>
|
||||||
<%= tag.span "(#{@trend.percent}%)", style: "color: #{@trend.color}" %>
|
<%= tag.span "(#{trend.percent}%)", style: "color: #{trend.color}" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= tag.span period_label(@period), class: "text-gray-500" %>
|
<%= tag.span period_label(@period), class: "text-gray-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= form_with url: account_path(@account), method: :get, data: { controller: "auto-submit-form" } do |form| %>
|
<%= form_with url: account_path(@account), method: :get, data: { controller: "auto-submit-form" } do |form| %>
|
||||||
<%= period_select form: form, selected: @period.name %>
|
<%= period_select form: form, selected: @period.name %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="h-96 flex items-center justify-center text-2xl font-bold">
|
<div class="h-96 flex items-center justify-center text-2xl font-bold">
|
||||||
<%= render partial: "shared/line_chart", locals: { series: @series } %>
|
<%= render "shared/line_chart", series: @account.series(period: @period) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% 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] %>
|
|
||||||
|
|
||||||
<div class="flex gap-2 text-sm text-gray-900 font-medium mb-4">
|
|
||||||
<% 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 %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="min-h-[800px]">
|
<div class="min-h-[800px]">
|
||||||
<% if selected_tab_route.present? %>
|
<%= render permitted_accountable_partial(@account, "tabs"), account: @account, selected_tab: params[:tab] %>
|
||||||
<%= 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 %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<div class="flex justify-between items-center mb-5">
|
<div class="flex justify-between items-center mb-5">
|
||||||
<h2 class="text-lg font-medium text-gray-900">Assets</h2>
|
<h2 class="text-lg font-medium text-gray-900">Assets</h2>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<%= 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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
|
||||||
<p><%= t(".new") %></p>
|
<p><%= t(".new") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
<div class="flex justify-between items-center mb-5">
|
<div class="flex justify-between items-center mb-5">
|
||||||
<h2 class="text-lg font-medium text-gray-900">Liabilities</h2>
|
<h2 class="text-lg font-medium text-gray-900">Liabilities</h2>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<%= 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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
|
||||||
<p><%= t(".new") %></p>
|
<p><%= t(".new") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -15,14 +15,14 @@
|
||||||
<h3 class="uppercase text-gray-500 text-xs font-medium px-3 py-1.5"><%= t(".sources") %></h3>
|
<h3 class="uppercase text-gray-500 text-xs font-medium px-3 py-1.5"><%= t(".sources") %></h3>
|
||||||
<ul class="bg-white border border-alpha-black-25 rounded-lg shadow-xs">
|
<ul class="bg-white border border-alpha-black-25 rounded-lg shadow-xs">
|
||||||
<li>
|
<li>
|
||||||
<% 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 %>
|
<%= link_to import_path(@pending_import), class: "flex items-center justify-between p-4 group cursor-pointer", data: { turbo: false } do %>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="bg-orange-500/5 rounded-md w-8 h-8 flex items-center justify-center">
|
<div class="bg-orange-500/5 rounded-md w-8 h-8 flex items-center justify-center">
|
||||||
<%= lucide_icon("loader", class: "w-5 h-5 text-orange-500") %>
|
<%= lucide_icon("loader", class: "w-5 h-5 text-orange-500") %>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-gray-900 group-hover:text-gray-700">
|
<span class="text-sm text-gray-900 group-hover:text-gray-700">
|
||||||
<%= t(".resume") %>
|
<%= t(".resume", type: @pending_import.type.titleize) %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
||||||
|
@ -33,75 +33,84 @@
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<li>
|
|
||||||
<%= button_to imports_path(import: { type: "TransactionImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
|
<% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "TransactionImport") %>
|
||||||
<div class="flex items-center gap-2">
|
<li>
|
||||||
<div class="bg-indigo-500/5 rounded-md w-8 h-8 flex items-center justify-center">
|
<%= 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") %>
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="bg-indigo-500/5 rounded-md w-8 h-8 flex items-center justify-center">
|
||||||
|
<%= lucide_icon("file-spreadsheet", class: "w-5 h-5 text-indigo-500") %>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-gray-900 group-hover:text-gray-700">
|
||||||
|
<%= t(".import_transactions") %>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-gray-900 group-hover:text-gray-700">
|
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
||||||
<%= t(".import_transactions") %>
|
<% end %>
|
||||||
</span>
|
|
||||||
|
<div class="pl-14 pr-3">
|
||||||
|
<div class="h-px bg-alpha-black-50"></div>
|
||||||
</div>
|
</div>
|
||||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pl-14 pr-3">
|
<% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "TradeImport") %>
|
||||||
<div class="h-px bg-alpha-black-50"></div>
|
<li>
|
||||||
</div>
|
<%= button_to imports_path(import: { type: "TradeImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
|
||||||
</li>
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="bg-yellow-500/5 rounded-md w-8 h-8 flex items-center justify-center">
|
||||||
<li>
|
<%= lucide_icon("square-percent", class: "w-5 h-5 text-yellow-500") %>
|
||||||
<%= button_to imports_path(import: { type: "TradeImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<span class="text-sm text-gray-900 group-hover:text-gray-700">
|
||||||
<div class="bg-yellow-500/5 rounded-md w-8 h-8 flex items-center justify-center">
|
<%= t(".import_portfolio") %>
|
||||||
<%= lucide_icon("square-percent", class: "w-5 h-5 text-yellow-500") %>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-gray-900 group-hover:text-gray-700">
|
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
||||||
<%= t(".import_portfolio") %>
|
<% end %>
|
||||||
</span>
|
|
||||||
|
<div class="pl-14 pr-3">
|
||||||
|
<div class="h-px bg-alpha-black-50"></div>
|
||||||
</div>
|
</div>
|
||||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pl-14 pr-3">
|
<% if params[:type].nil? || params[:type] == "AccountImport" %>
|
||||||
<div class="h-px bg-alpha-black-50"></div>
|
<li>
|
||||||
</div>
|
<%= button_to imports_path(import: { type: "AccountImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
|
||||||
</li>
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="bg-violet-500/5 rounded-md w-8 h-8 flex items-center justify-center">
|
||||||
<li>
|
<%= lucide_icon("building", class: "w-5 h-5 text-violet-500") %>
|
||||||
<%= button_to imports_path(import: { type: "AccountImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<span class="text-sm text-gray-900 group-hover:text-gray-700">
|
||||||
<div class="bg-violet-500/5 rounded-md w-8 h-8 flex items-center justify-center">
|
<%= t(".import_accounts") %>
|
||||||
<%= lucide_icon("building", class: "w-5 h-5 text-violet-500") %>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-gray-900 group-hover:text-gray-700">
|
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
||||||
<%= t(".import_accounts") %>
|
<% end %>
|
||||||
</span>
|
|
||||||
|
<div class="pl-14 pr-3">
|
||||||
|
<div class="h-px bg-alpha-black-50"></div>
|
||||||
</div>
|
</div>
|
||||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pl-14 pr-3">
|
<% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "MintImport" || params[:type] == "TransactionImport") %>
|
||||||
<div class="h-px bg-alpha-black-50"></div>
|
<li>
|
||||||
</div>
|
<%= button_to imports_path(import: { type: "MintImport" }), class: "flex items-center justify-between p-4 group w-full", data: { turbo: false } do %>
|
||||||
</li>
|
<div class="flex items-center gap-2">
|
||||||
|
<%= image_tag("mint-logo.jpeg", alt: "Mint logo", class: "w-8 h-8 rounded-md") %>
|
||||||
|
<span class="text-sm text-gray-900">
|
||||||
|
<%= t(".import_mint") %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<li>
|
<div class="pl-14 pr-3">
|
||||||
<%= button_to imports_path(import: { type: "MintImport" }), class: "flex items-center justify-between p-4 group w-full", data: { turbo: false } do %>
|
<div class="h-px bg-alpha-black-50"></div>
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<%= image_tag("mint-logo.jpeg", alt: "Mint logo", class: "w-8 h-8 rounded-md") %>
|
|
||||||
<span class="text-sm text-gray-900">
|
|
||||||
<%= t(".import_mint") %>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pl-14 pr-3">
|
|
||||||
<div class="h-px bg-alpha-black-50"></div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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" %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= 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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
<%= render "accounts/account_list", group: group %>
|
<%= render "accounts/account_list", group: group %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% 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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||||
<%= tag.p t(".new_account") %>
|
<%= tag.p t(".new_account") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<% accountable_group.children.map do |account_value_node| %>
|
<% accountable_group.children.map do |account_value_node| %>
|
||||||
<div class="flex items-center justify-between text-sm font-medium text-gray-900">
|
<div class="flex items-center justify-between text-sm font-medium text-gray-900">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<%= image_tag account_logo_url(account_value_node.original), class: "w-8 h-8" %>
|
<%= render "accounts/logo", account: account_value_node.original, size: "sm" %>
|
||||||
<div>
|
<div>
|
||||||
<p><%= account_value_node.name %></p>
|
<p><%= account_value_node.name %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% 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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||||
<span><%= t(".new") %></span>
|
<span><%= t(".new") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -70,10 +70,10 @@
|
||||||
data-time-series-chart-use-labels-value="false"
|
data-time-series-chart-use-labels-value="false"
|
||||||
data-time-series-chart-use-tooltip-value="false"></div>
|
data-time-series-chart-use-tooltip-value="false"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-1.5">
|
<div class="flex gap-1.5 mt-auto">
|
||||||
<% @top_earners.first(3).each do |account| %>
|
<% @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 %>
|
<%= 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" %>
|
||||||
<span>+<%= Money.new(account.income, account.currency) %></span>
|
<span>+<%= Money.new(account.income, account.currency) %></span>
|
||||||
<%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
|
<%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -103,12 +103,12 @@
|
||||||
data-time-series-chart-use-labels-value="false"
|
data-time-series-chart-use-labels-value="false"
|
||||||
data-time-series-chart-use-tooltip-value="false"></div>
|
data-time-series-chart-use-tooltip-value="false"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-1.5">
|
<div class="mt-auto flex gap-1.5">
|
||||||
<% @top_spenders.first(3).each do |account| %>
|
<% @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 %>
|
<%= 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) %>
|
-<%= 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 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @top_spenders.count > 3 %>
|
<% if @top_spenders.count > 3 %>
|
||||||
|
@ -141,9 +141,9 @@
|
||||||
<% @top_savers.first(3).each do |account| %>
|
<% @top_savers.first(3).each do |account| %>
|
||||||
<% unless account.savings_rate.infinite? %>
|
<% 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 %>
|
<%= 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" %>
|
||||||
<span><%= account.savings_rate > 0 ? "+" : "-" %><%= number_to_percentage(account.savings_rate.abs * 100, precision: 2) %></span>
|
<span><%= account.savings_rate > 0 ? "+" : "-" %><%= number_to_percentage(account.savings_rate.abs * 100, precision: 2) %></span>
|
||||||
<%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
|
<%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<% size_classes = {
|
<% size_classes = {
|
||||||
"sm" => "w-6 h-6",
|
"sm" => "w-6 h-6",
|
||||||
"md" => "w-8 h-8",
|
"md" => "w-9 h-9",
|
||||||
"lg" => "w-10 h-10",
|
"lg" => "w-10 h-10",
|
||||||
"full" => "w-full h-full"
|
"full" => "w-full h-full"
|
||||||
} %>
|
} %>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<p class="text-gray-500"><%= t(".no_account_subtitle") %></p>
|
<p class="text-gray-500"><%= t(".no_account_subtitle") %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= 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") %>
|
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||||
<span><%= t(".new_account") %></span>
|
<span><%= t(".new_account") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -23,6 +23,74 @@
|
||||||
],
|
],
|
||||||
"note": ""
|
"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_type": "Cross-Site Scripting",
|
||||||
"warning_code": 2,
|
"warning_code": 2,
|
||||||
|
@ -38,7 +106,7 @@
|
||||||
"type": "controller",
|
"type": "controller",
|
||||||
"class": "PagesController",
|
"class": "PagesController",
|
||||||
"method": "changelog",
|
"method": "changelog",
|
||||||
"line": 35,
|
"line": 36,
|
||||||
"file": "app/controllers/pages_controller.rb",
|
"file": "app/controllers/pages_controller.rb",
|
||||||
"rendered": {
|
"rendered": {
|
||||||
"name": "pages/changelog",
|
"name": "pages/changelog",
|
||||||
|
@ -60,19 +128,19 @@
|
||||||
{
|
{
|
||||||
"warning_type": "Dynamic Render Path",
|
"warning_type": "Dynamic Render Path",
|
||||||
"warning_code": 15,
|
"warning_code": 15,
|
||||||
"fingerprint": "b7a59d6dd91f4d30873b271659636c7975e25b47f436b4f03900a08809af2e92",
|
"fingerprint": "c5c512a13c34c9696024bd4e2367a657a5c140b5b6a0f5c352e9b69965f63e1b",
|
||||||
"check_name": "Render",
|
"check_name": "Render",
|
||||||
"message": "Render path contains parameter value",
|
"message": "Render path contains parameter value",
|
||||||
"file": "app/views/accounts/show.html.erb",
|
"file": "app/views/accounts/show.html.erb",
|
||||||
"line": 105,
|
"line": 63,
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
"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": [
|
"render_path": [
|
||||||
{
|
{
|
||||||
"type": "controller",
|
"type": "controller",
|
||||||
"class": "AccountsController",
|
"class": "AccountsController",
|
||||||
"method": "show",
|
"method": "show",
|
||||||
"line": 38,
|
"line": 39,
|
||||||
"file": "app/controllers/accounts_controller.rb",
|
"file": "app/controllers/accounts_controller.rb",
|
||||||
"rendered": {
|
"rendered": {
|
||||||
"name": "accounts/show",
|
"name": "accounts/show",
|
||||||
|
@ -98,7 +166,7 @@
|
||||||
"check_name": "Render",
|
"check_name": "Render",
|
||||||
"message": "Render path contains parameter value",
|
"message": "Render path contains parameter value",
|
||||||
"file": "app/views/import/configurations/show.html.erb",
|
"file": "app/views/import/configurations/show.html.erb",
|
||||||
"line": 13,
|
"line": 15,
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
"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]) }) })",
|
"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": [
|
"render_path": [
|
||||||
|
@ -126,6 +194,6 @@
|
||||||
"note": ""
|
"note": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated": "2024-09-28 13:27:09 -0400",
|
"updated": "2024-10-17 11:30:15 -0400",
|
||||||
"brakeman_version": "6.2.1"
|
"brakeman_version": "6.2.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,88 @@
|
||||||
---
|
---
|
||||||
en:
|
en:
|
||||||
accounts:
|
accounts:
|
||||||
|
sync_all_button:
|
||||||
|
sync: Sync all
|
||||||
account:
|
account:
|
||||||
has_issues: Issue detected.
|
has_issues: Issue detected.
|
||||||
troubleshoot: Troubleshoot
|
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: "<p>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.</p><br /> <p>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.</p>"
|
||||||
|
confirm_accept: 'Delete "%{name}"'
|
||||||
accountables:
|
accountables:
|
||||||
investment:
|
|
||||||
prompt: Select a subtype
|
|
||||||
none: None
|
|
||||||
credit_card:
|
credit_card:
|
||||||
annual_fee: Annual fee
|
form:
|
||||||
annual_fee_placeholder: '99'
|
available_credit: Available credit
|
||||||
apr: APR
|
available_credit_placeholder: '10000'
|
||||||
apr_placeholder: '15.99'
|
minimum_payment: Minimum payment
|
||||||
available_credit: Available credit
|
minimum_payment_placeholder: '100'
|
||||||
available_credit_placeholder: '10000'
|
apr: APR
|
||||||
expiration_date: Expiration date
|
apr_placeholder: '15.99'
|
||||||
minimum_payment: Minimum payment
|
expiration_date: Expiration date
|
||||||
minimum_payment_placeholder: '100'
|
annual_fee: Annual fee
|
||||||
|
annual_fee_placeholder: '99'
|
||||||
overview:
|
overview:
|
||||||
amount_owed: Amount Owed
|
amount_owed: Amount Owed
|
||||||
annual_fee: Annual Fee
|
|
||||||
apr: APR
|
|
||||||
available_credit: Available Credit
|
available_credit: Available Credit
|
||||||
expiration_date: Expiration Date
|
|
||||||
minimum_payment: Minimum Payment
|
minimum_payment: Minimum Payment
|
||||||
|
apr: APR
|
||||||
|
expiration_date: Expiration Date
|
||||||
|
annual_fee: Annual Fee
|
||||||
unknown: Unknown
|
unknown: Unknown
|
||||||
depository:
|
depository:
|
||||||
prompt: Select a subtype
|
form:
|
||||||
none: None
|
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:
|
loan:
|
||||||
interest_rate: Interest rate
|
form:
|
||||||
interest_rate_placeholder: '5.25'
|
interest_rate: Interest rate
|
||||||
|
interest_rate_placeholder: '5.25'
|
||||||
|
rate_type: Rate type
|
||||||
|
term_months: Term (months)
|
||||||
|
term_months_placeholder: '360'
|
||||||
overview:
|
overview:
|
||||||
interest_rate: Interest Rate
|
interest_rate: Interest Rate
|
||||||
monthly_payment: Monthly Payment
|
monthly_payment: Monthly Payment
|
||||||
|
@ -41,18 +92,19 @@ en:
|
||||||
term: Term
|
term: Term
|
||||||
type: Type
|
type: Type
|
||||||
unknown: Unknown
|
unknown: Unknown
|
||||||
rate_type: Rate type
|
|
||||||
term_months: Term (months)
|
|
||||||
term_months_placeholder: '360'
|
|
||||||
property:
|
property:
|
||||||
additional_info: Additional info
|
form:
|
||||||
area_unit: Area unit
|
additional_info: Additional info
|
||||||
area_value: Area value
|
area_unit: Area unit
|
||||||
city: City
|
area_value: Area value
|
||||||
country: Country
|
city: City
|
||||||
line1: Address line 1
|
country: Country
|
||||||
line2: Address line 2
|
line1: Address line 1
|
||||||
optional: optional
|
line2: Address line 2
|
||||||
|
optional: optional
|
||||||
|
postal_code: Postal code
|
||||||
|
state: State
|
||||||
|
year_built: Year built
|
||||||
overview:
|
overview:
|
||||||
living_area: Living Area
|
living_area: Living Area
|
||||||
market_value: Market Value
|
market_value: Market Value
|
||||||
|
@ -60,17 +112,17 @@ en:
|
||||||
trend: Trend
|
trend: Trend
|
||||||
unknown: Unknown
|
unknown: Unknown
|
||||||
year_built: Year Built
|
year_built: Year Built
|
||||||
postal_code: Postal code
|
|
||||||
state: State
|
|
||||||
year_built: Year built
|
|
||||||
vehicle:
|
vehicle:
|
||||||
make: Make
|
form:
|
||||||
make_placeholder: Toyota
|
make: Make
|
||||||
mileage: Mileage
|
make_placeholder: Toyota
|
||||||
mileage_placeholder: '15000'
|
mileage: Mileage
|
||||||
mileage_unit: Unit
|
mileage_placeholder: '15000'
|
||||||
model: Model
|
mileage_unit: Unit
|
||||||
model_placeholder: Camry
|
model: Model
|
||||||
|
model_placeholder: Camry
|
||||||
|
year: Year
|
||||||
|
year_placeholder: '2023'
|
||||||
overview:
|
overview:
|
||||||
current_price: Current Price
|
current_price: Current Price
|
||||||
make_model: Make & Model
|
make_model: Make & Model
|
||||||
|
@ -79,70 +131,22 @@ en:
|
||||||
trend: Trend
|
trend: Trend
|
||||||
unknown: Unknown
|
unknown: Unknown
|
||||||
year: Year
|
year: Year
|
||||||
year: Year
|
|
||||||
year_placeholder: '2023'
|
|
||||||
create:
|
|
||||||
success: New account created successfully
|
|
||||||
destroy:
|
|
||||||
success: Account deleted successfully
|
|
||||||
edit:
|
edit:
|
||||||
edit: Edit %{account}
|
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
|
|
||||||
index:
|
index:
|
||||||
accounts: Accounts
|
accounts: Accounts
|
||||||
add_institution: Add institution
|
add_institution: Add institution
|
||||||
new_account: New account
|
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:
|
new:
|
||||||
select_accountable_type: What would you like to add?
|
|
||||||
title: Add an account
|
title: Add an account
|
||||||
|
manual_entry: Enter account manually
|
||||||
|
csv_entry: Import accounts CSV
|
||||||
|
connected_entry: Securely link account with Plaid (coming soon)
|
||||||
show:
|
show:
|
||||||
cash: Cash
|
cash: Cash
|
||||||
confirm_accept: Delete "%{name}"
|
|
||||||
confirm_body_html: "<p>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.</p><br
|
|
||||||
/> <p>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.</p>"
|
|
||||||
confirm_title: Delete account?
|
|
||||||
edit: Edit
|
|
||||||
holdings: Holdings
|
holdings: Holdings
|
||||||
import: Import transactions
|
|
||||||
no_change: No change
|
no_change: No change
|
||||||
overview: Overview
|
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_owed: Total Owed
|
||||||
total_value: Total Value
|
total_value: Total Value
|
||||||
trades: Transactions
|
trades: Transactions
|
||||||
|
@ -151,21 +155,17 @@ en:
|
||||||
summary:
|
summary:
|
||||||
new: New
|
new: New
|
||||||
no_assets: No assets found
|
no_assets: No assets found
|
||||||
no_assets_description: Add an asset either via connection, importing or entering
|
no_assets_description: Add an asset either via connection, importing or entering manually.
|
||||||
manually.
|
|
||||||
no_liabilities: No liabilities found
|
no_liabilities: No liabilities found
|
||||||
no_liabilities_description: Add a liability either via connection, importing
|
no_liabilities_description: Add a liability either via connection, importing or entering manually.
|
||||||
or entering manually.
|
create:
|
||||||
sync_all:
|
success: New account created successfully
|
||||||
button_text: Sync all
|
destroy:
|
||||||
success: Successfully queued accounts for syncing.
|
success: Account deleted successfully
|
||||||
tooltip:
|
|
||||||
cash: Cash
|
|
||||||
holdings: Holdings
|
|
||||||
total_value_tooltip: The total value is the sum of cash balance and your holdings
|
|
||||||
value, minus margin loans.
|
|
||||||
update:
|
update:
|
||||||
success: Account updated
|
success: Account updated
|
||||||
|
sync_all:
|
||||||
|
success: Successfully queued accounts for syncing.
|
||||||
credit_cards:
|
credit_cards:
|
||||||
create:
|
create:
|
||||||
success: Credit card created successfully
|
success: Credit card created successfully
|
||||||
|
|
|
@ -69,7 +69,7 @@ en:
|
||||||
import_mint: Import from Mint
|
import_mint: Import from Mint
|
||||||
import_portfolio: Import investments
|
import_portfolio: Import investments
|
||||||
import_transactions: Import transactions
|
import_transactions: Import transactions
|
||||||
resume: Resume latest import
|
resume: Resume %{type}
|
||||||
sources: Sources
|
sources: Sources
|
||||||
title: New CSV Import
|
title: New CSV Import
|
||||||
ready:
|
ready:
|
||||||
|
|
|
@ -61,8 +61,6 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope module: :account do
|
scope module: :account do
|
||||||
resource :logo, only: :show
|
|
||||||
|
|
||||||
resources :holdings, only: %i[index new show destroy]
|
resources :holdings, only: %i[index new show destroy]
|
||||||
resources :cashes, only: :index
|
resources :cashes, only: :index
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,6 @@ class ApplicationHelperTest < ActionView::TestCase
|
||||||
assert_equal "Test Header Title", content_for(:header_title)
|
assert_equal "Test Header Title", content_for(:header_title)
|
||||||
end
|
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
|
def setup
|
||||||
@account1 = Account.new(currency: "USD", balance: 1)
|
@account1 = Account.new(currency: "USD", balance: 1)
|
||||||
@account2 = Account.new(currency: "USD", balance: 2)
|
@account2 = Account.new(currency: "USD", balance: 2)
|
||||||
|
|
|
@ -35,15 +35,6 @@ class Account::Balance::SyncerTest < ActiveSupport::TestCase
|
||||||
assert_equal [ 19600, 19500, 19500, 20000, 20000, 20000 ], @account.balances.chronological.map(&:balance)
|
assert_equal [ 19600, 19500, 19500, 20000, 20000, 20000 ], @account.balances.chronological.map(&:balance)
|
||||||
end
|
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
|
test "syncs account with valuations and transactions" do
|
||||||
create_valuation(account: @account, date: 5.days.ago.to_date, amount: 20000)
|
create_valuation(account: @account, date: 5.days.ago.to_date, amount: 20000)
|
||||||
create_transaction(account: @account, date: 3.days.ago.to_date, amount: -500)
|
create_transaction(account: @account, date: 3.days.ago.to_date, amount: -500)
|
||||||
|
|
|
@ -80,19 +80,15 @@ class AccountsTest < ApplicationSystemTestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_account_created(accountable_type, &block)
|
def assert_account_created(accountable_type, &block)
|
||||||
click_link humanized_accountable(accountable_type)
|
click_link "Enter account manually"
|
||||||
click_link "Enter account balance manually"
|
|
||||||
|
|
||||||
account_name = "[system test] #{accountable_type} Account"
|
account_name = "[system test] #{accountable_type} Account"
|
||||||
|
|
||||||
|
select accountable_type.titleize, from: "Account type"
|
||||||
fill_in "Account name", with: account_name
|
fill_in "Account name", with: account_name
|
||||||
fill_in "account[balance]", with: 100.99
|
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 "Create Account"
|
||||||
|
|
||||||
click_button "Add #{humanized_accountable(accountable_type).downcase}"
|
|
||||||
|
|
||||||
find("details", text: humanized_accountable(accountable_type)).click
|
find("details", text: humanized_accountable(accountable_type)).click
|
||||||
assert_text account_name
|
assert_text account_name
|
||||||
|
@ -107,8 +103,10 @@ class AccountsTest < ApplicationSystemTestCase
|
||||||
click_on "Edit"
|
click_on "Edit"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
yield if block_given?
|
||||||
|
|
||||||
fill_in "Account name", with: "Updated account name"
|
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"
|
assert_selector "h2", text: "Updated account name"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -62,6 +62,6 @@ class TradesTest < ApplicationSystemTestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit_account_trades
|
def visit_account_trades
|
||||||
visit account_url(@account, tab: "trades")
|
visit account_url(@account, tab: "transactions")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -156,6 +156,7 @@ class TransactionsTest < ApplicationSystemTestCase
|
||||||
|
|
||||||
test "can create deposit transaction for investment account" do
|
test "can create deposit transaction for investment account" do
|
||||||
investment_account = accounts(:investment)
|
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
|
transfer_date = Date.current
|
||||||
visit account_path(investment_account)
|
visit account_path(investment_account)
|
||||||
click_on "New transaction"
|
click_on "New transaction"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue