1
0
Fork 0
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:
Josh Brown 2024-04-29 14:56:38 +01:00 committed by GitHub
parent 93953499a6
commit 7f491f5064
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 87 additions and 28 deletions

View file

@ -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,

View file

@ -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

View file

@ -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() {

View file

@ -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

View file

@ -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 %>

View 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>

View file

@ -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">&bull;</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>

View file

@ -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 %>

View file

@ -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 %>

View file

@ -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>

View file

@ -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

View file

@ -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