1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-08 23:15:24 +02:00

Simplify balance trends in activity view

This commit is contained in:
Zach Gollwitzer 2025-05-08 12:20:53 -04:00
parent d72e026aa2
commit 993c147aa9
4 changed files with 27 additions and 95 deletions

View file

@ -5,90 +5,26 @@
class Balance::TrendCalculator class Balance::TrendCalculator
BalanceTrend = Struct.new(:trend, :cash, keyword_init: true) BalanceTrend = Struct.new(:trend, :cash, keyword_init: true)
class << self def initialize(balances)
def for(entries)
return nil if entries.blank?
account = entries.first.account
date_range = entries.minmax_by(&:date)
min_entry_date, max_entry_date = date_range.map(&:date)
# In case view is filtered and there are entry gaps, refetch all entries in range
all_entries = account.entries.where(date: min_entry_date..max_entry_date).chronological.to_a
balances = account.balances.where(date: (min_entry_date - 1.day)..max_entry_date).chronological.to_a
holdings = account.holdings.where(date: (min_entry_date - 1.day)..max_entry_date).to_a
new(all_entries, balances, holdings)
end
end
def initialize(entries, balances, holdings)
@entries = entries
@balances = balances @balances = balances
@holdings = holdings
end end
def trend_for(entry) def trend_for(date)
intraday_balance = nil balance = @balances.find { |b| b.date == date }
intraday_cash_balance = nil prior_balance = @balances.find { |b| b.date == date - 1.day }
start_of_day_balance = balances.find { |b| b.date == entry.date - 1.day && b.currency == entry.currency } return BalanceTrend.new(trend: nil) unless balance.present?
end_of_day_balance = balances.find { |b| b.date == entry.date && b.currency == entry.currency }
return BalanceTrend.new(trend: nil) if start_of_day_balance.blank? || end_of_day_balance.blank?
todays_holdings_value = holdings.select { |h| h.date == entry.date }.sum(&:amount)
prior_balance = start_of_day_balance.balance
prior_cash_balance = start_of_day_balance.cash_balance
current_balance = nil
current_cash_balance = nil
todays_entries = entries.select { |e| e.date == entry.date }
todays_entries.each_with_index do |e, idx|
if e.valuation?
current_balance = e.amount
current_cash_balance = e.amount
else
multiplier = e.account.liability? ? 1 : -1
balance_change = e.trade? ? 0 : multiplier * e.amount
cash_change = multiplier * e.amount
current_balance = prior_balance + balance_change
current_cash_balance = prior_cash_balance + cash_change
end
if e.id == entry.id
# Final entry should always match the end-of-day balances
if idx == todays_entries.size - 1
intraday_balance = end_of_day_balance.balance
intraday_cash_balance = end_of_day_balance.cash_balance
else
intraday_balance = current_balance
intraday_cash_balance = current_cash_balance
end
break
else
prior_balance = current_balance
prior_cash_balance = current_cash_balance
end
end
return BalanceTrend.new(trend: nil) unless intraday_balance.present?
BalanceTrend.new( BalanceTrend.new(
trend: Trend.new( trend: Trend.new(
current: Money.new(intraday_balance, entry.currency), current: Money.new(balance.balance, balance.currency),
previous: Money.new(prior_balance, entry.currency), previous: Money.new(prior_balance.balance, balance.currency),
favorable_direction: entry.account.favorable_direction favorable_direction: balance.account.favorable_direction
), ),
cash: Money.new(intraday_cash_balance, entry.currency), cash: Money.new(balance.cash_balance, balance.currency),
) )
end end
private private
attr_reader :entries, :balances, :holdings attr_reader :balances
end end

View file

@ -77,10 +77,11 @@
<div> <div>
<div class="rounded-tl-lg rounded-tr-lg bg-container border-alpha-black-25 shadow-xs"> <div class="rounded-tl-lg rounded-tr-lg bg-container border-alpha-black-25 shadow-xs">
<div class="space-y-4"> <div class="space-y-4">
<% calculator = Balance::TrendCalculator.for(@entries) %> <% calculator = Balance::TrendCalculator.new(@account.balances) %>
<%= entries_by_date(@entries) do |entries| %> <%= entries_by_date(@entries) do |entries| %>
<% entries.each do |entry| %> <% entries.each_with_index do |entry, index| %>
<%= render entry, balance_trend: calculator&.trend_for(entry), view_ctx: "account" %> <%= render entry, balance_trend: index == 0 ? calculator.trend_for(entry.date) : nil, view_ctx: "account" %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

View file

@ -4,12 +4,11 @@
<%= turbo_frame_tag dom_id(entry) do %> <%= turbo_frame_tag dom_id(entry) do %>
<%= turbo_frame_tag dom_id(transaction) do %> <%= turbo_frame_tag dom_id(transaction) do %>
<div class="grid grid-cols-12 items-center text-primary text-sm font-medium p-4 md:p-4 <div class="grid grid-cols-12 items-center text-primary text-sm font-medium p-4 lg:p-4
<%= @focused_record == entry || @focused_record == transaction ? <%= @focused_record == entry || @focused_record == transaction ?
"border border-gray-900 rounded-lg" : "" %>"> "border border-gray-900 rounded-lg" : "" %>">
<div class="pr-4 md:pr-10 flex items-center gap-3 md:gap-4 <div class="pr-4 lg:pr-10 flex items-center gap-3 lg:gap-4 col-span-8 lg:col-span-6">
<%= balance_trend ? "col-span-8 md:col-span-6" : "col-span-8" %>">
<%= check_box_tag dom_id(entry, "selection"), <%= check_box_tag dom_id(entry, "selection"),
disabled: transaction.transfer?, disabled: transaction.transfer?,
class: "checkbox checkbox--light", class: "checkbox checkbox--light",
@ -58,7 +57,7 @@
<% end %> <% end %>
</div> </div>
<div class="text-secondary text-xs font-normal hidden md:block"> <div class="text-secondary text-xs font-normal hidden lg:block">
<% if transaction.transfer? %> <% if transaction.transfer? %>
<%= render "transfers/account_links", <%= render "transfers/account_links",
transfer: transaction.transfer, transfer: transaction.transfer,
@ -76,26 +75,24 @@
</div> </div>
</div> </div>
<div class="hidden md:flex items-center gap-1 col-span-2"> <div class="hidden lg:flex items-center gap-1 col-span-2">
<%= render "transactions/transaction_category", transaction: transaction %> <%= render "transactions/transaction_category", transaction: transaction %>
</div> </div>
<div class="col-span-4 md:col-span-2 ml-auto text-right"> <div class="col-span-4 lg:col-span-2 ml-auto text-right">
<%= content_tag :p, <%= content_tag :p,
transaction.transfer? && view_ctx == "global" ? "+/- #{format_money(entry.amount_money.abs)}" : format_money(-entry.amount_money), transaction.transfer? && view_ctx == "global" ? "+/- #{format_money(entry.amount_money.abs)}" : format_money(-entry.amount_money),
class: ["text-green-600": entry.amount.negative?] %> class: ["text-green-600": entry.amount.negative?] %>
</div> </div>
<% if balance_trend %> <div class="col-span-2 justify-self-end hidden lg:block">
<div class="col-span-2 justify-self-end hidden md:block"> <% if balance_trend&.trend %>
<% if balance_trend.trend %> <%= tag.p format_money(balance_trend.trend.current),
<%= tag.p format_money(balance_trend.trend.current),
class: "font-medium text-sm text-primary" %> class: "font-medium text-sm text-primary" %>
<% else %> <% else %>
<%= tag.p "--", class: "font-medium text-sm text-gray-400" %> <%= tag.p "--", class: "font-medium text-sm text-gray-400" %>
<% end %> <% end %>
</div> </div>
<% end %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>

View file

@ -14,9 +14,7 @@
data: { id: entry.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %> data: { id: entry.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<%= tag.div class: "w-6 h-6 rounded-full p-1.5 flex items-center justify-center", style: "color: #{color}" do %> <%= render FilledIconComponent.new(icon: icon, size: "sm", hex_color: color, rounded: true) %>
<%= icon(icon, size: "sm", color: "current") %>
<% end %>
<div class="truncate text-primary"> <div class="truncate text-primary">
<%= link_to entry.name, <%= link_to entry.name,