mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-21 14:19:39 +02:00
Add trends to sidebar account list (#697)
This commit is contained in:
parent
93953499a6
commit
7f491f5064
12 changed files with 87 additions and 28 deletions
|
@ -14,6 +14,9 @@ class AccountsController < ApplicationController
|
||||||
@account_groups = Current.family.accounts.by_group(period: @period, currency: Current.family.currency)
|
@account_groups = Current.family.accounts.by_group(period: @period, currency: Current.family.currency)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def list
|
||||||
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@account = Account.new(
|
@account = Account.new(
|
||||||
balance: nil,
|
balance: nil,
|
||||||
|
|
|
@ -27,8 +27,8 @@ module ApplicationHelper
|
||||||
render partial: "shared/modal", locals: { content: content }
|
render partial: "shared/modal", locals: { content: content }
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_groups
|
def account_groups(period: nil)
|
||||||
assets, liabilities = Current.family.accounts.by_group(currency: Current.family.currency, period: Period.last_30_days).values_at(:assets, :liabilities)
|
assets, liabilities = Current.family.accounts.by_group(currency: Current.family.currency, period: period || Period.last_30_days).values_at(:assets, :liabilities)
|
||||||
[ assets.children, liabilities.children ].flatten
|
[ assets.children, liabilities.children ].flatten
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import * as d3 from "d3"
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static values = {
|
static values = {
|
||||||
data: Object,
|
data: Object,
|
||||||
|
strokeWidth: { type: Number, default: 2 },
|
||||||
useLabels: { type: Boolean, default: true },
|
useLabels: { type: Boolean, default: true },
|
||||||
useTooltip: { type: Boolean, default: true },
|
useTooltip: { type: Boolean, default: true },
|
||||||
usePercentSign: Boolean
|
usePercentSign: Boolean
|
||||||
|
@ -128,7 +129,7 @@ export default class extends Controller {
|
||||||
.attr("d", this.#d3Line)
|
.attr("d", this.#d3Line)
|
||||||
.attr("stroke-linejoin", "round")
|
.attr("stroke-linejoin", "round")
|
||||||
.attr("stroke-linecap", "round")
|
.attr("stroke-linecap", "round")
|
||||||
.attr("stroke-width", 2)
|
.attr("stroke-width", this.strokeWidthValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
#installTrendlineSplit() {
|
#installTrendlineSplit() {
|
||||||
|
|
|
@ -39,8 +39,8 @@ class TimeSeries::Trend
|
||||||
elsif previous.zero?
|
elsif previous.zero?
|
||||||
Float::INFINITY
|
Float::INFINITY
|
||||||
else
|
else
|
||||||
change = (current_amount - previous_amount).abs
|
change = (current_amount - previous_amount)
|
||||||
base = previous_amount.abs.to_f
|
base = previous_amount.to_f
|
||||||
|
|
||||||
(change / base * 100).round(1).to_f
|
(change / base * 100).round(1).to_f
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,20 +8,58 @@
|
||||||
<div class="text-left"><%= type.model_name.human %></div>
|
<div class="text-left"><%= type.model_name.human %></div>
|
||||||
<div class="ml-auto flex flex-col items-end">
|
<div class="ml-auto flex flex-col items-end">
|
||||||
<p class="text-right"><%= format_money group.sum %></p>
|
<p class="text-right"><%= format_money group.sum %></p>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<%=
|
||||||
|
tag.div(
|
||||||
|
id: "#{group.name}_sparkline",
|
||||||
|
class: "h-3 w-8 ml-auto",
|
||||||
|
data: {
|
||||||
|
controller: "time-series-chart",
|
||||||
|
"time-series-chart-data-value": group.series.to_json,
|
||||||
|
"time-series-chart-stroke-width-value": 1,
|
||||||
|
"time-series-chart-use-labels-value": false,
|
||||||
|
"time-series-chart-use-tooltip-value": false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
%>
|
||||||
|
<% styles = trend_styles(group.series.trend) %>
|
||||||
|
<span class="text-xs <%= styles[:text_class] %>"><%= sprintf("%+.2f", group.series.trend.percent) %>%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</summary>
|
</summary>
|
||||||
<% group.children.each do |account_value_node| %>
|
<% group.children.each do |account_value_node| %>
|
||||||
<%= link_to account_path(account_value_node.original), class: "flex items-center w-full gap-3 px-2 py-3 mb-1 hover:bg-gray-100 rounded-[10px]" do %>
|
<% account = account_value_node.original %>
|
||||||
|
<%= link_to account_path(account), class: "flex items-center w-full gap-3 px-3 py-2 mb-1 hover:bg-gray-100 rounded-[10px]" do %>
|
||||||
|
<%= image_tag account_logo_url(account), class: "w-6 h-6" %>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium"><%= account_value_node.name %></p>
|
<p class="font-medium"><%= account_value_node.name %></p>
|
||||||
<% if account_value_node.original.subtype %>
|
<% if account.subtype %>
|
||||||
<p class="text-xs text-gray-500"><%= account_value_node.original.subtype&.humanize %></p>
|
<p class="text-xs text-gray-500"><%= account.subtype&.humanize %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<p class="ml-auto font-medium"><%= format_money account_value_node.original.balance_money %></p>
|
<div class="flex flex-col ml-auto font-medium text-right">
|
||||||
|
<p><%= format_money account.balance_money %></p>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<%=
|
||||||
|
tag.div(
|
||||||
|
id: dom_id(account, :list_sparkline),
|
||||||
|
class: "h-3 w-8 ml-auto",
|
||||||
|
data: {
|
||||||
|
controller: "time-series-chart",
|
||||||
|
"time-series-chart-data-value": account_value_node.series.to_json,
|
||||||
|
"time-series-chart-stroke-width-value": 1,
|
||||||
|
"time-series-chart-use-labels-value": false,
|
||||||
|
"time-series-chart-use-tooltip-value": false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
%>
|
||||||
|
<% styles = trend_styles(account_value_node.series.trend) %>
|
||||||
|
<span class="text-xs <%= styles[:text_class] %>"><%= sprintf("%+.2f", account_value_node.series.trend.percent) %>%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to new_account_path(step: "method", type: type.name.demodulize), class: "flex items-center gap-4 px-2 py-3 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>
|
<p>New <%= type.model_name.human.downcase %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
5
app/views/accounts/list.html.erb
Normal file
5
app/views/accounts/list.html.erb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<turbo-frame id="account-list" target="_top">
|
||||||
|
<% account_groups(period: @period).each do |group| %>
|
||||||
|
<%= render "accounts/account_list", group: group %>
|
||||||
|
<% end %>
|
||||||
|
</turbo-frame>
|
|
@ -72,19 +72,23 @@
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex flex-col mt-6">
|
<div class="flex flex-col mt-6">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between px-3 py-2 mb-2">
|
||||||
|
<div class="flex items-center gap-2 text-xs uppercase text-gray-500">
|
||||||
<%= link_to accounts_path, class: "text-xs uppercase text-gray-500 font-bold tracking-wide" do %>
|
<%= link_to accounts_path, class: "text-xs uppercase text-gray-500 font-bold tracking-wide" do %>
|
||||||
<%= t(".accounts") %>
|
<%= t(".portfolio") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<span class="font-bold tracking-wide">•</span>
|
||||||
|
<%= form_with url: list_accounts_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form", turbo_frame: "account-list" } do %>
|
||||||
|
<%= render partial: "shared/period_select", locals: { button_class: "flex items-center gap-1 w-full cursor-pointer font-bold tracking-wide" } %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<%= link_to new_account_path, class: "block hover:bg-gray-100 p-2 text-sm font-semibold text-gray-900 flex items-center rounded", title: t(".new_account"), data: { turbo_frame: "modal" } do %>
|
<%= link_to new_account_path, class: "block hover:bg-gray-100 p-2 text-sm font-semibold text-gray-900 flex items-center rounded", 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>
|
||||||
<%= link_to new_account_path, class: "flex items-center gap-4 px-2 py-3 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %>
|
<turbo-frame id="account-list" target="_top">
|
||||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
|
||||||
<p><%= t(".new_account") %></p>
|
|
||||||
<% end %>
|
|
||||||
<% account_groups.each do |group| %>
|
<% account_groups.each do |group| %>
|
||||||
<%= render "accounts/account_list", group: group %>
|
<%= render "accounts/account_list", group: group %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</turbo-frame>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<div id="notification-tray" class="fixed z-50 space-y-1 top-6 right-6"></div>
|
<div id="notification-tray" class="fixed z-50 space-y-1 top-6 right-6"></div>
|
||||||
<%= safe_join(flash.map { |type, message| notification(message, type: type) }) %>
|
<%= safe_join(flash.map { |type, message| notification(message, type: type) }) %>
|
||||||
<div class="flex h-full">
|
<div class="flex h-full">
|
||||||
<div class="p-6 pb-20 w-80 shrink-0 h-full overflow-y-auto">
|
<div class="p-6 pb-20 w-[368px] shrink-0 h-full overflow-y-auto">
|
||||||
<% if content_for?(:sidebar) %>
|
<% if content_for?(:sidebar) %>
|
||||||
<%= yield :sidebar %>
|
<%= yield :sidebar %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
<%# locals: (value: 'last_30_days') -%>
|
<%# locals: (value: 'last_30_days', button_class: '') -%>
|
||||||
<% options = [["7D", "last_7_days"], ["1M", "last_30_days"], ["1Y", "last_365_days"], ["All", "all"]] %>
|
<% options = [["7D", "last_7_days"], ["1M", "last_30_days"], ["1Y", "last_365_days"], ["All", "all"]] %>
|
||||||
<div data-controller="select" data-select-active-class="bg-alpha-black-50" class="relative" data-select-selected-value="<%= value %>">
|
<div data-controller="select" data-select-active-class="bg-alpha-black-50" class="relative" data-select-selected-value="<%= value %>">
|
||||||
<button type="button" data-select-target="button" class="flex items-center gap-1 w-full border border-alpha-black-100 shadow-xs rounded-lg text-sm p-2 cursor-pointer">
|
<%=
|
||||||
<span data-select-target="buttonText" class="text-gray-900 text-sm"><%= options.find { |option| option[1] == value }[0] %></span>
|
tag.button(
|
||||||
|
type: "button",
|
||||||
|
data: { "select-target": "button" },
|
||||||
|
class: button_class.presence || "flex items-center gap-1 w-full border border-alpha-black-100 shadow-xs rounded-lg text-sm p-2 cursor-pointer text-gray-900 text-sm"
|
||||||
|
) do
|
||||||
|
%>
|
||||||
|
<span data-select-target="buttonText"><%= options.find { |option| option[1] == value }[0] %></span>
|
||||||
<%= lucide_icon("chevron-down", class: "w-5 h-5 text-gray-500") %>
|
<%= lucide_icon("chevron-down", class: "w-5 h-5 text-gray-500") %>
|
||||||
</button>
|
<% end %>
|
||||||
<input type="hidden" name="period" data-select-target="input" data-auto-submit-form-target="auto">
|
<input type="hidden" name="period" data-select-target="input" data-auto-submit-form-target="auto">
|
||||||
<ul data-select-target="list" class="hidden absolute z-10 top-10 right-0 border border-alpha-black-25 bg-white rounded-lg shadow-xs">
|
<ul data-select-target="list" class="hidden absolute z-10 top-[100%] right-0 border border-alpha-black-25 bg-white rounded-lg shadow-xs">
|
||||||
<% options.each do |label, value| %>
|
<% options.each do |label, value| %>
|
||||||
<li tabindex="0" data-select-target="option" data-action="click->select#selectOption" data-value="<%= value %>" class="text-sm text-gray-900 rounded-lg cursor-pointer hover:bg-alpha-black-50 px-5 py-1">
|
<li tabindex="0" data-select-target="option" data-action="click->select#selectOption" data-value="<%= value %>" class="text-sm text-gray-900 rounded-lg cursor-pointer hover:bg-alpha-black-50 px-5 py-1">
|
||||||
<%= label %>
|
<%= label %>
|
||||||
|
|
|
@ -5,6 +5,6 @@
|
||||||
<span>No change</span>
|
<span>No change</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span><%= styles[:symbol] %><%= trend.value.is_a?(Money) ? format_money(trend.value.abs) : trend.value.abs.round(2) %></span>
|
<span><%= styles[:symbol] %><%= trend.value.is_a?(Money) ? format_money(trend.value.abs) : trend.value.abs.round(2) %></span>
|
||||||
<span>(<%= lucide_icon(styles[:icon], class: "w-4 h-4 align-text-bottom inline") %><%= trend.percent %>%)</span>
|
<span>(<%= lucide_icon(styles[:icon], class: "w-4 h-4 align-text-bottom inline") %><%= trend.percent.abs %>%)</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -12,4 +12,5 @@ en:
|
||||||
accounts: Accounts
|
accounts: Accounts
|
||||||
dashboard: Dashboard
|
dashboard: Dashboard
|
||||||
new_account: New account
|
new_account: New account
|
||||||
|
portfolio: Portfolio
|
||||||
transactions: Transactions
|
transactions: Transactions
|
||||||
|
|
|
@ -34,6 +34,7 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
resources :accounts, shallow: true do
|
resources :accounts, shallow: true do
|
||||||
get :summary, on: :collection
|
get :summary, on: :collection
|
||||||
|
get :list, on: :collection
|
||||||
post :sync, on: :member
|
post :sync, on: :member
|
||||||
resource :logo, only: %i[show], module: :accounts
|
resource :logo, only: %i[show], module: :accounts
|
||||||
resources :valuations
|
resources :valuations
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue