diff --git a/app/controllers/account/entries_controller.rb b/app/controllers/account/entries_controller.rb
index b12ae099..22bbe34f 100644
--- a/app/controllers/account/entries_controller.rb
+++ b/app/controllers/account/entries_controller.rb
@@ -12,6 +12,10 @@ class Account::EntriesController < ApplicationController
@valuation_entries = @account.entries.account_valuations.reverse_chronological
end
+ def trades
+ @trades = @account.entries.account_trades.reverse_chronological
+ end
+
def new
@entry = @account.entries.build.tap do |entry|
if params[:entryable_type]
diff --git a/app/controllers/account/holdings_controller.rb b/app/controllers/account/holdings_controller.rb
new file mode 100644
index 00000000..136040ae
--- /dev/null
+++ b/app/controllers/account/holdings_controller.rb
@@ -0,0 +1,23 @@
+class Account::HoldingsController < ApplicationController
+ layout "with_sidebar"
+
+ before_action :set_account
+ before_action :set_holding, only: :show
+
+ def index
+ @holdings = @account.holdings.current
+ end
+
+ def show
+ end
+
+ private
+
+ def set_account
+ @account = Current.family.accounts.find(params[:account_id])
+ end
+
+ def set_holding
+ @holding = @account.holdings.current.find(params[:id])
+ end
+end
diff --git a/app/helpers/account/entries_helper.rb b/app/helpers/account/entries_helper.rb
index 17634b89..86b07a80 100644
--- a/app/helpers/account/entries_helper.rb
+++ b/app/helpers/account/entries_helper.rb
@@ -33,7 +33,7 @@ module Account::EntriesHelper
private
def permitted_entryable_key(entry)
- permitted_entryable_paths = %w[transaction valuation]
+ permitted_entryable_paths = %w[transaction valuation trade]
entry.entryable_name_short.presence_in(permitted_entryable_paths)
end
end
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
index 54ec09fa..6817d7fb 100644
--- a/app/helpers/accounts_helper.rb
+++ b/app/helpers/accounts_helper.rb
@@ -23,18 +23,37 @@ module AccountsHelper
class_mapping(accountable_type)[:hex]
end
+ def account_tabs(account)
+ holdings_tab = { key: "holdings", label: t("accounts.show.holdings"), path: account_path(account, tab: "holdings"), content_path: account_holdings_path(account) }
+ value_tab = { key: "valuations", label: t("accounts.show.value"), path: account_path(account, tab: "valuations"), content_path: valuation_account_entries_path(account) }
+ transactions_tab = { key: "transactions", label: t("accounts.show.transactions"), path: account_path(account, tab: "transactions"), content_path: transaction_account_entries_path(account) }
+ trades_tab = { key: "trades", label: t("accounts.show.trades"), path: account_path(account, tab: "trades"), content_path: trade_account_entries_path(account) }
+
+ return [ holdings_tab, trades_tab ] if account.investment?
+
+ [ value_tab, transactions_tab ]
+ end
+
+ def selected_account_tab(account)
+ available_tabs = account_tabs(account)
+
+ tab = available_tabs.find { |tab| tab[:key] == params[:tab] }
+
+ tab || available_tabs.first
+ end
+
private
- def class_mapping(accountable_type)
- {
- "CreditCard" => { text: "text-red-500", bg: "bg-red-500", bg_transparent: "bg-red-500/10", fill: "fill-red-500", hex: "#F13636" },
- "Loan" => { text: "text-fuchsia-500", bg: "bg-fuchsia-500", bg_transparent: "bg-fuchsia-500/10", fill: "fill-fuchsia-500", hex: "#D444F1" },
- "OtherLiability" => { text: "text-gray-500", bg: "bg-gray-500", bg_transparent: "bg-gray-500/10", fill: "fill-gray-500", hex: "#737373" },
- "Depository" => { text: "text-violet-500", bg: "bg-violet-500", bg_transparent: "bg-violet-500/10", fill: "fill-violet-500", hex: "#875BF7" },
- "Investment" => { text: "text-blue-600", bg: "bg-blue-600", bg_transparent: "bg-blue-600/10", fill: "fill-blue-600", hex: "#1570EF" },
- "OtherAsset" => { text: "text-green-500", bg: "bg-green-500", bg_transparent: "bg-green-500/10", fill: "fill-green-500", hex: "#12B76A" },
- "Property" => { text: "text-cyan-500", bg: "bg-cyan-500", bg_transparent: "bg-cyan-500/10", fill: "fill-cyan-500", hex: "#06AED4" },
- "Vehicle" => { text: "text-pink-500", bg: "bg-pink-500", bg_transparent: "bg-pink-500/10", fill: "fill-pink-500", hex: "#F23E94" }
- }.fetch(accountable_type, { text: "text-gray-500", bg: "bg-gray-500", bg_transparent: "bg-gray-500/10", fill: "fill-gray-500", hex: "#737373" })
- end
+ def class_mapping(accountable_type)
+ {
+ "CreditCard" => { text: "text-red-500", bg: "bg-red-500", bg_transparent: "bg-red-500/10", fill: "fill-red-500", hex: "#F13636" },
+ "Loan" => { text: "text-fuchsia-500", bg: "bg-fuchsia-500", bg_transparent: "bg-fuchsia-500/10", fill: "fill-fuchsia-500", hex: "#D444F1" },
+ "OtherLiability" => { text: "text-gray-500", bg: "bg-gray-500", bg_transparent: "bg-gray-500/10", fill: "fill-gray-500", hex: "#737373" },
+ "Depository" => { text: "text-violet-500", bg: "bg-violet-500", bg_transparent: "bg-violet-500/10", fill: "fill-violet-500", hex: "#875BF7" },
+ "Investment" => { text: "text-blue-600", bg: "bg-blue-600", bg_transparent: "bg-blue-600/10", fill: "fill-blue-600", hex: "#1570EF" },
+ "OtherAsset" => { text: "text-green-500", bg: "bg-green-500", bg_transparent: "bg-green-500/10", fill: "fill-green-500", hex: "#12B76A" },
+ "Property" => { text: "text-cyan-500", bg: "bg-cyan-500", bg_transparent: "bg-cyan-500/10", fill: "fill-cyan-500", hex: "#06AED4" },
+ "Vehicle" => { text: "text-pink-500", bg: "bg-pink-500", bg_transparent: "bg-pink-500/10", fill: "fill-pink-500", hex: "#F23E94" }
+ }.fetch(accountable_type, { text: "text-gray-500", bg: "bg-gray-500", bg_transparent: "bg-gray-500/10", fill: "fill-gray-500", hex: "#737373" })
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 141db192..b834f54b 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -17,6 +17,10 @@ module ApplicationHelper
turbo_stream_from [ Current.family, :notifications ] if Current.family
end
+ def family_stream
+ turbo_stream_from Current.family if Current.family
+ end
+
def render_flash_notifications
notifications = flash.flat_map do |type, message_or_messages|
Array(message_or_messages).map do |message|
diff --git a/app/javascript/controllers/bulk_select_controller.js b/app/javascript/controllers/bulk_select_controller.js
index ed989aee..5a713f0c 100644
--- a/app/javascript/controllers/bulk_select_controller.js
+++ b/app/javascript/controllers/bulk_select_controller.js
@@ -59,6 +59,7 @@ export default class extends Controller {
deselectAll() {
this.selectedIdsValue = []
+ this.element.querySelectorAll('input[type="checkbox"]').forEach(el => el.checked = false)
}
selectedIdsValueChanged() {
diff --git a/app/models/account/holding.rb b/app/models/account/holding.rb
index b5a63248..775a3b7c 100644
--- a/app/models/account/holding.rb
+++ b/app/models/account/holding.rb
@@ -1,6 +1,46 @@
class Account::Holding < ApplicationRecord
+ include Monetizable
+
+ monetize :amount
+
belongs_to :account
belongs_to :security
+ validates :qty, :currency, presence: true
+
scope :chronological, -> { order(:date) }
+ scope :current, -> { where(date: Date.current).order(amount: :desc) }
+ scope :for, ->(security) { where(security_id: security).order(:date) }
+
+ delegate :name, to: :security
+ delegate :symbol, to: :security
+
+ def weight
+ return nil unless amount
+
+ portfolio_value = account.holdings.current.where.not(amount: nil).sum(&:amount)
+ portfolio_value.zero? ? 1 : amount / portfolio_value * 100
+ end
+
+ # Basic approximation of cost-basis
+ def avg_cost
+ avg_cost = account.holdings.for(security).where("date <= ?", date).average(:price)
+ Money.new(avg_cost, currency)
+ end
+
+ def trend
+ @trend ||= calculate_trend
+ end
+
+ private
+
+ def calculate_trend
+ return nil unless amount_money
+
+ start_amount = qty * avg_cost
+
+ TimeSeries::Trend.new \
+ current: amount_money,
+ previous: start_amount
+ end
end
diff --git a/app/models/account/holding/syncer.rb b/app/models/account/holding/syncer.rb
index 3f2af7a7..1494c8e0 100644
--- a/app/models/account/holding/syncer.rb
+++ b/app/models/account/holding/syncer.rb
@@ -38,14 +38,18 @@ class Account::Holding::Syncer
@portfolio = generate_next_portfolio(@portfolio, trades)
@portfolio.map do |isin, holding|
- price = Security::Price.find_by!(date: date, isin: isin).price
+ trade = trades.find { |trade| trade.account_trade.security_id == holding[:security_id] }
+ trade_price = trade&.account_trade&.price
+
+ price = Security::Price.find_by(date: date, isin: isin)&.price || trade_price
account.holdings.build \
date: date,
security_id: holding[:security_id],
qty: holding[:qty],
price: price,
- amount: price * holding[:qty]
+ amount: price ? (price * holding[:qty]) : nil,
+ currency: holding[:currency]
end
end
@@ -61,6 +65,7 @@ class Account::Holding::Syncer
qty: new_qty,
price: price,
amount: new_qty * price,
+ currency: entry.currency,
security_id: trade.security_id
}
end
@@ -85,6 +90,7 @@ class Account::Holding::Syncer
qty: holding.qty,
price: holding.price,
amount: holding.amount,
+ currency: holding.currency,
security_id: holding.security_id
}
end
diff --git a/app/models/account/sync.rb b/app/models/account/sync.rb
index 1442ec0b..721399b3 100644
--- a/app/models/account/sync.rb
+++ b/app/models/account/sync.rb
@@ -78,6 +78,6 @@ class Account::Sync < ApplicationRecord
partial: "shared/notification",
locals: { type: type, message: message }
)
- broadcast_refresh_to account
+ account.family.broadcast_refresh
end
end
diff --git a/app/models/demo/generator.rb b/app/models/demo/generator.rb
index 2fb27157..b1ea2f12 100644
--- a/app/models/demo/generator.rb
+++ b/app/models/demo/generator.rb
@@ -165,6 +165,9 @@ class Demo::Generator
end
def load_securities!
+ # Create an unknown security to simulate edge cases
+ Security.create! isin: "unknown", symbol: "UNKNOWN", name: "Unknown Demo Stock"
+
securities = [
{ isin: "US0378331005", symbol: "AAPL", name: "Apple Inc.", reference_price: 210 },
{ isin: "JP3633400001", symbol: "TM", name: "Toyota Motor Corporation", reference_price: 202 },
@@ -200,6 +203,10 @@ class Demo::Generator
aapl = Security.find_by(symbol: "AAPL")
tm = Security.find_by(symbol: "TM")
msft = Security.find_by(symbol: "MSFT")
+ unknown = Security.find_by(symbol: "UNKNOWN")
+
+ # Buy 20 shares of the unknown stock to simulate a stock where we can't fetch security prices
+ account.entries.create! date: 10.days.ago.to_date, amount: 100, currency: "USD", name: "Buy unknown stock", entryable: Account::Trade.new(qty: 20, price: 5, security: unknown)
trades = [
{ security: aapl, qty: 20 }, { security: msft, qty: 10 }, { security: aapl, qty: -5 },
@@ -212,7 +219,7 @@ class Demo::Generator
date = Faker::Number.positive(to: 730).days.ago.to_date
security = trade[:security]
qty = trade[:qty]
- price = Security::Price.find_by!(isin: security.isin, date: date).price
+ price = Security::Price.find_by(isin: security.isin, date: date)&.price || 1
name_prefix = qty < 0 ? "Sell " : "Buy "
account.entries.create! \
diff --git a/app/models/time_series/trend.rb b/app/models/time_series/trend.rb
index 88fdd3a1..4b989e9f 100644
--- a/app/models/time_series/trend.rb
+++ b/app/models/time_series/trend.rb
@@ -44,7 +44,7 @@ class TimeSeries::Trend
end
def percent
- if previous.nil?
+ if previous.nil? || (previous.zero? && current.zero?)
0.0
elsif previous.zero?
Float::INFINITY
diff --git a/app/views/account/entries/entryables/trade/_selection_bar.html.erb b/app/views/account/entries/entryables/trade/_selection_bar.html.erb
new file mode 100644
index 00000000..f4f7208e
--- /dev/null
+++ b/app/views/account/entries/entryables/trade/_selection_bar.html.erb
@@ -0,0 +1,15 @@
+
+
+ <%= check_box_tag "entry_selection", 1, true, class: "maybe-checkbox maybe-checkbox--dark", data: { action: "bulk-select#deselectAll" } %>
+
+
+
+
+
+ <%= form_with url: bulk_delete_transactions_path, data: { turbo_confirm: true, turbo_frame: "_top" } do %>
+
+ <% end %>
+
+
diff --git a/app/views/account/entries/entryables/trade/_show.html.erb b/app/views/account/entries/entryables/trade/_show.html.erb
new file mode 100644
index 00000000..ccd64727
--- /dev/null
+++ b/app/views/account/entries/entryables/trade/_show.html.erb
@@ -0,0 +1,31 @@
+<%# locals: (entry:) %>
+
+<% trade, account = entry.account_trade, entry.account %>
+
+<%= drawer do %>
+
+
+
+
+
+
+ <%= t(".overview") %>
+ <%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
+
+
+
+
Details coming soon...
+
+
+
+
+<% end %>
diff --git a/app/views/account/entries/entryables/trade/_trade.html.erb b/app/views/account/entries/entryables/trade/_trade.html.erb
new file mode 100644
index 00000000..5c01d83c
--- /dev/null
+++ b/app/views/account/entries/entryables/trade/_trade.html.erb
@@ -0,0 +1,41 @@
+<%# locals: (entry:, selectable: true, **opts) %>
+
+<% trade, account = entry.account_trade, entry.account %>
+
+
+
+ <% if selectable %>
+ <%= check_box_tag dom_id(entry, "selection"),
+ class: "maybe-checkbox maybe-checkbox--light",
+ data: { id: entry.id, "bulk-select-target": "row", action: "bulk-select#toggleRowSelection" } %>
+ <% end %>
+
+
+ <%= tag.div class: ["flex items-center gap-2"] do %>
+
+ <%= entry.name[0].upcase %>
+
+
+
+ <% if entry.new_record? %>
+ <%= content_tag :p, entry.name %>
+ <% else %>
+ <%= link_to entry.name,
+ account_entry_path(account, entry),
+ data: { turbo_frame: "drawer", turbo_prefetch: false },
+ class: "hover:underline hover:text-gray-800" %>
+ <% end %>
+
+ <% end %>
+
+
+
+
+ <%= tag.p trade.buy? ? t(".buy") : t(".sell") %>
+
+
+
+ <%= tag.p format_money(entry.amount_money * -1), class: { "text-green-500": trade.sell? } %>
+
+
+
diff --git a/app/views/account/entries/_selection_bar.html.erb b/app/views/account/entries/entryables/transaction/_selection_bar.html.erb
similarity index 100%
rename from app/views/account/entries/_selection_bar.html.erb
rename to app/views/account/entries/entryables/transaction/_selection_bar.html.erb
diff --git a/app/views/account/entries/trades.html.erb b/app/views/account/entries/trades.html.erb
new file mode 100644
index 00000000..2c02dd91
--- /dev/null
+++ b/app/views/account/entries/trades.html.erb
@@ -0,0 +1,42 @@
+<%= turbo_frame_tag dom_id(@account, "trades") do %>
+ " class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
+
+
<%= t(".trades") %>
+ <%= link_to new_account_entry_path(@account),
+ disabled: true,
+ class: "cursor-not-allowed flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg",
+ data: { turbo_frame: :modal } do %>
+ <%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
+ <%= t(".new") %>
+ <% end %>
+
+
+
+
+ <%= check_box_tag "selection_entry",
+ class: "maybe-checkbox maybe-checkbox--light",
+ data: { action: "bulk-select#togglePageSelection" } %>
+ <%= tag.p t(".trade") %>
+
+
+ <%= tag.p t(".type"), class: "col-span-3 justify-self-end" %>
+ <%= tag.p t(".amount"), class: "col-span-3 justify-self-end" %>
+
+
+
+
+ <%= render "account/entries/entryables/trade/selection_bar" %>
+
+
+ <% if @trades.empty? %>
+
<%= t(".no_trades") %>
+ <% else %>
+
+ <% @trades.group_by(&:date).each do |date, entries| %>
+ <%= render "entry_group", date:, entries: entries %>
+ <% end %>
+
+ <% end %>
+
+
+<% end %>
diff --git a/app/views/account/entries/transactions.html.erb b/app/views/account/entries/transactions.html.erb
index ea824d3e..623ad9c0 100644
--- a/app/views/account/entries/transactions.html.erb
+++ b/app/views/account/entries/transactions.html.erb
@@ -12,7 +12,7 @@
">
- <%= render "selection_bar" %>
+ <%= render "account/entries/entryables/transaction/selection_bar" %>
<% if @transaction_entries.empty? %>
diff --git a/app/views/account/holdings/_holding.html.erb b/app/views/account/holdings/_holding.html.erb
new file mode 100644
index 00000000..62878810
--- /dev/null
+++ b/app/views/account/holdings/_holding.html.erb
@@ -0,0 +1,45 @@
+<%# locals: (holding:) %>
+
+<%= turbo_frame_tag dom_id(holding) do %>
+
+
+ <%= render "shared/circle_logo", name: holding.name %>
+
+ <%= link_to holding.name, account_holding_path(holding.account, holding), data: { turbo_frame: :drawer }, class: "hover:underline" %>
+ <%= tag.p holding.symbol, class: "text-gray-500 text-xs uppercase" %>
+
+
+
+
+ <% if holding.weight %>
+ <%= render "shared/progress_circle", progress: holding.weight, text_class: "text-blue-500" %>
+ <%= tag.p number_to_percentage(holding.weight, precision: 1) %>
+ <% else %>
+ <%= tag.p "?", class: "text-gray-500" %>
+ <% end %>
+
+
+
+ <%= tag.p format_money holding.avg_cost %>
+ <%= tag.p t(".per_share"), class: "font-normal text-gray-500" %>
+
+
+
+ <% if holding.amount_money %>
+ <%= tag.p format_money holding.amount_money %>
+ <% else %>
+ <%= tag.p "?", class: "text-gray-500" %>
+ <% end %>
+ <%= tag.p t(".shares", qty: number_with_precision(holding.qty, precision: 1)), class: "font-normal text-gray-500" %>
+
+
+
+ <% if holding.trend %>
+ <%= tag.p format_money(holding.trend.value), style: "color: #{holding.trend.color};" %>
+ <%= tag.p "(#{number_to_percentage(holding.trend.percent, precision: 1)})", style: "color: #{holding.trend.color};" %>
+ <% else %>
+ <%= tag.p "?", class: "text-gray-500" %>
+ <% end %>
+
+
+<% end %>
diff --git a/app/views/account/holdings/_ruler.html.erb b/app/views/account/holdings/_ruler.html.erb
new file mode 100644
index 00000000..31c6ee6c
--- /dev/null
+++ b/app/views/account/holdings/_ruler.html.erb
@@ -0,0 +1 @@
+
diff --git a/app/views/account/holdings/index.html.erb b/app/views/account/holdings/index.html.erb
new file mode 100644
index 00000000..e58a0626
--- /dev/null
+++ b/app/views/account/holdings/index.html.erb
@@ -0,0 +1,37 @@
+<%= turbo_frame_tag dom_id(@account, "holdings") do %>
+
+
+ <%= tag.h2 t(".holdings"), class: "font-medium text-lg" %>
+ <%= link_to new_account_holding_path(@account),
+ disabled: true,
+ data: { turbo_frame: :modal },
+ class: "cursor-not-allowed flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg" do %>
+ <%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
+ <%= tag.span t(".new_holding"), class: "text-sm" %>
+ <% end %>
+
+
+
+
+ <%= tag.p t(".name"), class: "col-span-4" %>
+ <%= tag.p t(".weight"), class: "col-span-2 justify-self-end" %>
+ <%= tag.p t(".cost"), class: "col-span-2 justify-self-end" %>
+ <%= tag.p t(".holdings"), class: "col-span-2 justify-self-end" %>
+ <%= tag.p t(".return"), class: "col-span-2 justify-self-end" %>
+
+
+
+ <% if @holdings.any? %>
+ <%= render partial: "account/holdings/holding", collection: @holdings, spacer_template: "ruler" %>
+ <% elsif @account.needs_sync? || true %>
+
+
<%= t(".needs_sync") %>
+ <%= button_to "Sync holding prices", sync_account_path(@account), class: "bg-gray-900 text-white text-sm rounded-lg px-3 py-2" %>
+
+ <% else %>
+
<%= t(".no_holdings") %>
+ <% end %>
+
+
+
+<% end %>
diff --git a/app/views/account/holdings/new.html.erb b/app/views/account/holdings/new.html.erb
new file mode 100644
index 00000000..91dd849b
--- /dev/null
+++ b/app/views/account/holdings/new.html.erb
@@ -0,0 +1 @@
+
Coming soon...
diff --git a/app/views/account/holdings/show.html.erb b/app/views/account/holdings/show.html.erb
new file mode 100644
index 00000000..07ae6798
--- /dev/null
+++ b/app/views/account/holdings/show.html.erb
@@ -0,0 +1,45 @@
+<%= drawer do %>
+
+
+
+ <%= tag.h3 @holding.name, class: "text-2xl font-medium text-gray-900" %>
+ <%= tag.p @holding.symbol.upcase, class: "text-sm text-gray-500" %>
+
+
+ <%= render "shared/circle_logo", name: @holding.name %>
+
+
+
+
+ <%= t(".overview") %>
+ <%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
+
+
+
+
+
+
+
+ <%= t(".history") %>
+ <%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
+
+
+
+
+
+
+
+ <%= t(".settings") %>
+ <%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
+
+
+
+
+
+<% end %>
diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb
index b67c5869..478a6ff3 100644
--- a/app/views/accounts/show.html.erb
+++ b/app/views/accounts/show.html.erb
@@ -1,6 +1,6 @@
<%= turbo_stream_from @account %>
-
+<%= tag.div id: dom_id(@account), class: "space-y-4" do %>
<%= image_tag account_logo_url(@account), class: "w-8 h-8" %>
@@ -74,22 +74,17 @@
- <% selected_tab = params[:tab] || "value" %>
+ <% selected_tab_key, selected_tab_content_path = selected_account_tab(@account).values_at(:key, :content_path) %>
- <%= link_to t(".value"), account_path(tab: "value"), class: ["px-2 py-1.5 rounded-md border border-transparent", "bg-white shadow-xs border-alpha-black-50": selected_tab == "value"] %>
- <%= link_to t(".transactions"), account_path(tab: "transactions"), class: ["px-2 py-1.5 rounded-md border border-transparent", "bg-white shadow-xs border-alpha-black-50": selected_tab == "transactions"] %>
+ <% account_tabs(@account).each do |tab| %>
+ <%= link_to tab[:label], tab[:path], class: ["px-2 py-1.5 rounded-md border border-transparent", "bg-white shadow-xs border-alpha-black-50": selected_tab_key == tab[:key]] %>
+ <% end %>
- <% if selected_tab == "transactions" %>
- <%= turbo_frame_tag dom_id(@account, "transactions"), src: transaction_account_entries_path(@account) do %>
- <%= render "account/entries/loading" %>
- <% end %>
- <% else %>
- <%= turbo_frame_tag dom_id(@account, "valuations"), src: valuation_account_entries_path(@account) do %>
- <%= render "account/entries/loading" %>
- <% end %>
+ <%= turbo_frame_tag dom_id(@account, selected_tab_key), src: selected_tab_content_path do %>
+ <%= render "account/entries/loading" %>
<% end %>
-
+<% end %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index ab57b5d3..6f4851c8 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -32,6 +32,7 @@
<%= family_notifications_stream %>
+ <%= family_stream %>
<%= content_for?(:content) ? yield(:content) : yield %>
diff --git a/app/views/shared/_drawer.html.erb b/app/views/shared/_drawer.html.erb
index 4bce63fc..ebe70cf6 100644
--- a/app/views/shared/_drawer.html.erb
+++ b/app/views/shared/_drawer.html.erb
@@ -1,8 +1,8 @@
<%= turbo_frame_tag "drawer" do %>