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

Dashboard View and Calculations (#521)

* Handle Turbo updates with tabs

Fixes #491

* Add Filterable concern for controllers

* Add trendline chart

* Extract common UI to partials

* Series refactor

* Put placeholders for calculations in

* Add classification generated column to account

* Add basic net worth calculation

* Add net worth tests

* Get net worth graph working

* Fix lint errors

* Implement asset grouping query

* Make trends and series more intuitive

* Fully functional dashboard

* Remove logging
This commit is contained in:
Zach Gollwitzer 2024-03-06 09:56:59 -05:00 committed by GitHub
parent 680a91d807
commit 6f0e410684
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 594 additions and 74 deletions

View file

@ -0,0 +1,51 @@
<%# locals: (account_group:) %>
<% accountable_type, account_details = account_group%>
<% text_class = accountable_text_class(accountable_type) %>
<details class="open:bg-gray-25 group">
<summary class="flex p-4 items-center w-full rounded-lg font-medium hover:bg-gray-50 text-gray-500 text-sm font-medium cursor-pointer">
<%= lucide_icon("chevron-down", class: "hidden group-open:block w-5 h-5") %>
<%= lucide_icon("chevron-right", class: "group-open:hidden w-5 h-5") %>
<div class="ml-4 h-2.5 w-2.5 rounded-full <%= accountable_bg_class(accountable_type) %>"></div>
<p class="text-gray-900 ml-2"><%= to_accountable_title(Accountable.from_type(accountable_type)) %></p>
<span class="mx-1">&middot;</span>
<div ><%= account_details[:accounts].size %></div>
<div class="ml-auto text-right flex items-center gap-10 text-sm font-medium text-gray-900">
<div class="flex items-center justify-end gap-2 w-24">
<%= render partial: "shared/progress_circle", locals: { progress: account_details[:allocation], text_class: text_class } %>
<p><%= account_details[:allocation] %>%</p>
</div>
<div class="w-24">
<p><%= format_currency account_details[:end_balance] %></p>
</div>
<div class="w-40">
<%= render partial: "shared/trend_change", locals: { trend: account_details[:trend] } %>
</div>
</div>
</summary>
<div class="px-4 py-3 space-y-4">
<% account_details[:accounts].map do |account| %>
<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 justify-center w-8 h-8 rounded-full <%= text_class %> <%= accountable_bg_transparent_class(accountable_type) %>">
<%= account[:name][0].upcase %>
</div>
<div>
<p><%= account[:name] %></p>
</div>
</div>
<div class="flex gap-10 items-center text-right">
<div class="flex items-center justify-end gap-2 w-24">
<%= render partial: "shared/progress_circle", locals: { progress: account[:allocation], text_class: text_class } %>
<p><%= account[:allocation] %>%</p>
</div>
<div class="w-24">
<p><%= format_currency account[:end_balance] %></p>
</div>
<div class="w-40">
<%= render partial: "shared/trend_change", locals: { trend: account[:trend] } %>
</div>
</div>
</div>
<% end %>
</div>
</details>

View file

@ -0,0 +1,17 @@
<%# locals: (account_groups:) %>
<div class="space-y-4">
<div class="flex gap-1">
<% account_groups.each do |type, value| %>
<div class="h-1.5 rounded-sm w-12 <%= accountable_bg_class(type) %>" style="width: <%= value[:allocation] %>%;"></div>
<% end %>
</div>
<div class="flex gap-4">
<% account_groups.each do |type, value| %>
<div class="flex items-center gap-2 text-sm">
<div class="h-2.5 w-2.5 rounded-full <%= accountable_bg_class(type) %>"></div>
<p class="text-gray-500"><%= to_accountable_title(Accountable.from_type(type)) %></p>
<p class="text-black"><%= value[:allocation] %>%</p>
</div>
<% end %>
</div>
</div>

View file

@ -0,0 +1,20 @@
<%# locals: (account_groups:) %>
<div class="bg-gray-25 p-1 rounded-xl">
<div class="px-4 py-2 flex items-center uppercase text-xs font-medium text-gray-500">
<div>Name</div>
<div class="ml-auto text-right flex items-center gap-10">
<div class="w-24">
<p>% of total</p>
</div>
<div class="w-24">
<p>Value</p>
</div>
<div class="w-40">
<p>Change</p>
</div>
</div>
</div>
<div class="bg-white border border-alpha-black-25 shadow-xs rounded-lg divide-y divide-alpha-black-50">
<%= render partial: "account_group_disclosure", collection: account_groups, as: :account_group %>
</div>
</div>

View file

@ -1 +1,90 @@
<h1 class="text-3xl font-semibold font-display"><%= t('.title')%></h1>
<div class="space-y-4">
<div>
<h1 class="sr-only">Dashboard</h1>
<p class="text-xl font-medium text-gray-900 mb-1"><%= t('.greeting', name: Current.user.first_name )%></p>
<p class="text-gray-500 text-sm font-medium"><%= Date.current.strftime('%A, %b %d') %></p>
</div>
<section class="bg-white rounded-xl shadow-xs border border-alpha-black-25">
<div class="flex justify-between p-4">
<div>
<%= render partial: "shared/balance_heading", locals: {
label: "Net Worth",
period: @period,
balance: Money.from_amount(Current.family.net_worth, Current.family.currency),
trend: @net_worth_series.trend
}
%>
</div>
<%= render partial: "shared/period_dropdown", locals: { period: @period, path: root_path } %>
</div>
<div class="h-96 flex items-center justify-center text-2xl font-bold">
<%= render partial: "shared/line_chart", locals: { series: @net_worth_series } %>
</div>
<div class="border-t border-t-alpha-black-100 flex divide-x divide-gray-200">
<div class="w-1/2 p-4 flex items-stretch justify-between">
<div class="space-y-2 grow">
<%= render partial: "shared/balance_heading", locals: {
label: "Assets",
period: @period,
balance: Money.from_amount(Current.family.assets, Current.family.currency),
trend: @asset_series.trend
} %>
</div>
<div
data-controller="trendline"
id="assetsTrendline"
class="h-full w-2/5"
data-trendline-series-value="<%= @asset_series.serialize_for_d3_chart %>"
data-trendline-classification-value="asset"
></div>
</div>
<div class="w-1/2 p-4 flex items-stretch justify-between">
<div class="space-y-2 grow">
<%= render partial: "shared/balance_heading", locals: {
label: "Liabilities",
period: @period,
size: "md",
balance: Money.from_amount(Current.family.liabilities, Current.family.currency),
trend: @liability_series.trend
} %>
</div>
<div
data-controller="trendline"
id="liabilitiesTrendline"
class="h-full w-2/5"
data-trendline-series-value="<%= @liability_series.serialize_for_d3_chart %>"
data-trendline-classification-value="liability"
></div>
</div>
</div>
</section>
<section class="p-4 bg-white rounded-xl shadow-xs border border-alpha-black-25">
<div data-controller="tabs" data-tabs-active-class="bg-white border border-alpha-black-25 shadow-xs" data-tabs-default-tab-value="asset-summary-tab">
<div class="flex justify-between items-center mb-6">
<div class="bg-gray-50 rounded-lg p-1 flex gap-1 text-sm text-gray-900 font-medium">
<button data-id="asset-summary-tab" class="px-2 py-1 rounded-md" data-tabs-target="btn" data-action="tabs#select">Assets</button>
<button data-id="liability-summary-tab" class="px-2 py-1 rounded-md" data-tabs-target="btn" data-action="tabs#select">Liabilities</button>
</div>
<div class="flex items-center gap-2">
<%= link_to new_account_path, class: "flex items-center gap-1 p-2 text-gray-900 text-sm font-medium bg-gray-50 rounded-lg hover:bg-gray-100", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
<p><%= t('.new') %></p>
<% end %>
<%= render partial: "shared/period_dropdown", locals: { period: @period, path: root_path } %>
</div>
</div>
<div>
<div data-tabs-target="tab" id="asset-summary-tab" class="space-y-6">
<%= render partial: "account_percentages_bar", locals: { account_groups: @account_groups[:asset][:groups] } %>
<%= render partial: "account_percentages_table", locals: { account_groups: @account_groups[:asset][:groups] } %>
</div>
<div data-tabs-target="tab" id="liability-summary-tab" class="space-y-6 hidden">
<%= render partial: "account_percentages_bar", locals: { account_groups: @account_groups[:liability][:groups] } %>
<%= render partial: "account_percentages_table", locals: { account_groups: @account_groups[:liability][:groups] } %>
</div>
</div>
</div>
<div>
</div>
</section>
</div>