Skip to content

Commit

Permalink
Add placeholders for dashboard features (#642)
Browse files Browse the repository at this point in the history
* Add placeholders for new dashboard

* Fix tests and lint errors
  • Loading branch information
zachgoll committed Apr 18, 2024
1 parent 4708e85 commit f5f6248
Show file tree
Hide file tree
Showing 14 changed files with 193 additions and 106 deletions.
9 changes: 9 additions & 0 deletions app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ def dashboard
@asset_series = snapshot[:asset_series]
@liability_series = snapshot[:liability_series]
@account_groups = Current.family.accounts.by_group(period: @period, currency: Current.family.currency)

# TODO: Placeholders for trendlines
placeholder_series_data = 10.times.map do |i|
{ date: Date.current - i.days, value: Money.new(0) }
end
@income_series = TimeSeries.new(placeholder_series_data)
@spending_series = TimeSeries.new(placeholder_series_data)
@savings_rate_series = TimeSeries.new(10.times.map { |i| { date: Date.current - i.days, value: 0 } })
@investing_series = TimeSeries.new(placeholder_series_data)
end

def changelog
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/controllers/trendline_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class extends Controller {
prepareData(series) {
return series.values.map((d) => ({
date: new Date(d.date + "T00:00:00"),
value: +d.value.amount,
value: d.value.amount ? +d.value.amount : +d.value,
}));
}

Expand Down
2 changes: 1 addition & 1 deletion app/models/time_series/value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class TimeSeries::Value
attr_reader :value, :date, :original

def initialize(obj)
@original = obj[:original] || obj
@original = obj.fetch(:original, obj)

if obj.is_a?(Hash)
@date = obj[:date]
Expand Down
4 changes: 2 additions & 2 deletions app/views/accounts/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@
<div class="bg-white shadow-xs rounded-xl border border-alpha-black-25 rounded-lg">
<div class="p-4 flex justify-between">
<div class="space-y-2">
<%= render partial: "shared/balance_heading", locals: {
<%= render partial: "shared/value_heading", locals: {
label: "Total Value",
period: @period,
balance: @account.balance_money,
value: @account.balance_money,
trend: @balance_series.trend
} %>
</div>
Expand Down
8 changes: 4 additions & 4 deletions app/views/accounts/summary.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
<div class="bg-white rounded-xl shadow-xs border border-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: {
<%= render partial: "shared/value_heading", locals: {
label: "Assets",
period: @period,
balance: Current.family.assets,
value: Current.family.assets,
trend: @asset_series.trend
} %>
</div>
Expand All @@ -25,11 +25,11 @@
</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: {
<%= render partial: "shared/value_heading", locals: {
label: "Liabilities",
period: @period,
size: "md",
balance: Current.family.liabilities,
value: Current.family.liabilities,
trend: @liability_series.trend
} %>
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
<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) }) %>
<div class="flex h-full">
<div class="p-6 w-80 shrink-0 h-full overflow-y-auto">
<div class="p-6 pb-20 w-80 shrink-0 h-full overflow-y-auto">
<% if content_for?(:sidebar) %>
<%= yield :sidebar %>
<% else %>
<%= render "layouts/sidebar" %>
<% end %>
</div>
<main class="grow px-20 py-6 h-full overflow-y-auto">
<main class="grow px-20 pt-6 pb-32 h-full overflow-y-auto">
<%= yield %>
</main>
</div>
Expand Down
174 changes: 105 additions & 69 deletions app/views/pages/dashboard.html.erb
Original file line number Diff line number Diff line change
@@ -1,91 +1,127 @@
<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",
<header class="flex items-center justify-between">
<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"><%= t(".subtitle") %></p>
</div>
<%= link_to new_account_path, class: "flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<span><%= t(".new") %></span>
<% end %>
</header>
<section class="flex gap-4">
<div class="bg-white border border-alpha-black-25 shadow-xs rounded-xl w-3/4 min-h-48">
<div class="flex justify-between p-4">
<div>
<%= render partial: "shared/value_heading", locals: {
label: t(".net_worth"),
period: @period,
balance: Current.family.net_worth,
value: Current.family.net_worth,
trend: @net_worth_series.trend
} %>
} %>
</div>
<%= form_with url: root_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %>
<%= render partial: "shared/period_select", locals: { value: @period.name } %>
<% end %>
</div>
<%= form_with url: root_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %>
<%= render partial: "shared/period_select", locals: { value: @period.name } %>
<% end %>
<%= render partial: "pages/dashboard/net_worth_chart", locals: { series: @net_worth_series } %>
</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 class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl w-1/4">
<%= render partial: "pages/dashboard/allocation_chart" %>
</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: Current.family.assets,
trend: @asset_series.trend
} %>
</section>
<section class="grid grid-cols-2 gap-4">
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl">
<div class="flex gap-4 h-full">
<div class="grow">
<%= render partial: "shared/value_heading", locals: {
label: t(".income"),
period: @period,
value: @income_series.last.value,
trend: @income_series.trend
} %>
</div>
<div
data-controller="trendline"
id="assetsTrendline"
class="h-full w-2/5"
data-trendline-series-value="<%= @asset_series.to_json %>"
data-trendline-classification-value="asset"></div>
data-controller="trendline"
class="h-full w-2/5"
data-trendline-series-value="<%= @income_series.to_json %>"
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: Current.family.liabilities,
trend: @liability_series.trend
} %>
</div>
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl">
<div class="flex gap-4 h-full">
<div class="grow">
<%= render partial: "shared/value_heading", locals: {
label: t(".spending"),
period: @period,
value: @spending_series.last.value,
trend: @spending_series.trend
} %>
</div>
<div
data-controller="trendline"
id="liabilitiesTrendline"
class="h-full w-2/5"
data-trendline-series-value="<%= @liability_series.to_json %>"
data-trendline-classification-value="liability"></div>
data-controller="trendline"
class="h-full w-2/5"
data-trendline-series-value="<%= @spending_series.to_json %>"
data-trendline-classification-value="asset"></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 class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl">
<div class="flex gap-4 h-full">
<div class="grow">
<%= render partial: "shared/value_heading", locals: {
label: t(".savings_rate"),
period: @period,
value: @savings_rate_series.last.value,
trend: @savings_rate_series.trend,
is_percentage: true
} %>
</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 %>
<%= form_with url: root_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %>
<%= render partial: "shared/period_select", locals: { value: @period.name } %>
<% end %>
<div
data-controller="trendline"
class="h-full w-2/5"
data-trendline-series-value="<%= @savings_rate_series.to_json %>"
data-trendline-classification-value="asset"></div>
</div>
</div>
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl">
<div class="flex gap-4 h-full">
<div class="grow">
<%= render partial: "shared/value_heading", locals: {
label: t(".investing"),
period: @period,
value: @investing_series.last.value,
trend: @investing_series.trend
} %>
</div>
<div
data-controller="trendline"
class="h-full w-2/5"
data-trendline-series-value="<%= @investing_series.to_json %>"
data-trendline-classification-value="asset"></div>
</div>
</div>
</section>
<section class="flex items-start gap-4">
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl w-1/2 space-y-4">
<h2 class="text-lg font-medium text-gray-900"><%= t(".transactions") %></h2>
<div class="text-gray-500 flex items-center justify-center py-12">
<p>Coming soon...</p>
</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[:assets].children } %>
<%= render partial: "account_percentages_table", locals: { account_groups: @account_groups[:assets].children } %>
</div>
<div class="w-1/2 space-y-4">
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl space-y-4">
<h2 class="text-lg font-medium text-gray-900"><%= t(".recurring") %></h2>
<div class="text-gray-500 flex items-center justify-center py-12">
<p>Coming soon...</p>
</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[:liabilities].children } %>
<%= render partial: "account_percentages_table", locals: { account_groups: @account_groups[:liabilities].children } %>
</div>
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl space-y-4">
<h2 class="text-lg font-medium text-gray-900"><%= t(".categories") %></h2>
<div class="text-gray-500 flex items-center justify-center py-12">
<p>Coming soon...</p>
</div>
</div>
</div>
<div>
</div>
</section>
</div>
18 changes: 18 additions & 0 deletions app/views/pages/dashboard/_allocation_chart.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div data-controller="tabs" data-tabs-active-class="bg-white border-alpha-black-25 shadow-xs text-gray-900" data-tabs-default-tab-value="asset-tab">
<div class="bg-gray-25 rounded-lg p-1 flex gap-1 text-sm text-gray-500 font-medium">
<button data-id="asset-tab" class="w-1/2 px-2 py-1 rounded-md border border-transparent" data-tabs-target="btn" data-action="tabs#select"><%= t(".assets") %></button>
<button data-id="liability-tab" class="w-1/2 px-2 py-1 rounded-md border border-transparent" data-tabs-target="btn" data-action="tabs#select"><%= t(".debts") %></button>
</div>
<div>
<div data-tabs-target="tab" id="asset-tab" class="space-y-6">
<div class="text-gray-500 flex items-center justify-center py-12">
<p>Coming soon...</p>
</div>
</div>
<div data-tabs-target="tab" id="liability-tab" class="space-y-6 hidden">
<div class="text-gray-500 flex items-center justify-center py-12">
<p>Coming soon...</p>
</div>
</div>
</div>
</div>
8 changes: 8 additions & 0 deletions app/views/pages/dashboard/_net_worth_chart.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<%# locals: (series:) %>
<% if series %>
<div data-controller="line-chart" id="lineChart" class="w-full h-full" data-line-chart-series-value="<%= series.to_json %>"></div>
<% else %>
<div class="w-full h-full flex items-center justify-center">
<p class="text-gray-500">No data available for the selected period.</p>
</div>
<% end %>
23 changes: 0 additions & 23 deletions app/views/shared/_balance_heading.html.erb

This file was deleted.

2 changes: 1 addition & 1 deletion app/views/shared/_modal.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<%# locals: (content:) -%>
<%= turbo_frame_tag "modal" do %>
<dialog class="bg-white border border-alpha-black-25 rounded-2xl max-h-[648px] max-w-[580px] w-full shadow-xs h-full" data-controller="modal" data-action="click->modal#clickOutside">
<dialog class="bg-white border border-alpha-black-25 rounded-2xl max-h-[648px] max-w-[580px] w-full shadow-xs h-fit" data-controller="modal" data-action="click->modal#clickOutside">
<div class="flex flex-col h-full">
<%= content %>
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/views/shared/_trend_change.html.erb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<%# locals: (trend:) %>
<%# locals: { trend: } %>
<% styles = trend_styles(trend) %>
<p class="text-sm <%= styles[:text_class] %>">
<% if trend.direction == "flat" %>
<span>No change</span>
<% else %>
<span><%= styles[:symbol] %><%= format_money trend.value.abs %></span>
<span><%= styles[:symbol] %><%= trend.value.is_a?(Money) ? format_money(trend.value.abs) : trend.value.abs %></span>
<span>(<%= lucide_icon(styles[:icon], class: "w-4 h-4 align-text-bottom inline") %><%= trend.percent %>%)</span>
<% end %>
</p>
27 changes: 27 additions & 0 deletions app/views/shared/_value_heading.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<%# locals: (label:, period:, value:, trend:, size: "lg", is_percentage: false)%>
<div class="space-y-2">
<p class="text-sm text-gray-500 font-medium"><%= label %></p>
<p class="text-gray-900 -space-x-0.5">
<% if value.is_a?(Money) %>
<span class="text-gray-500"><%= value.currency.symbol %></span>
<span class="<%= size == "lg" ? "text-xl" : "text-lg" %> font-medium"><%= format_money_without_symbol value, precision: 0 %></span>
<%- if value.currency.default_precision.positive? -%>
<span class="text-gray-500">
<%= value.currency.separator %><%= value.cents_str %>
</span>
<% end %>
<% else %>
<span class="<%= size == "lg" ? "text-xl" : "text-lg" %> font-medium"><%= is_percentage ? number_to_percentage(value, precision: 2) : value %></span>
<% end %>
</p>
<% if trend.nil? %>
<p class="text-sm text-gray-500">Data not available for the selected period</p>
<% elsif trend.direction == "flat" %>
<p class="text-sm text-gray-500">No change vs. prior period</p>
<% else %>
<div class="flex items-center gap-2">
<%= render partial: "shared/trend_change", locals: { trend: trend } %>
<span class="text-sm text-gray-500"><%= period_label(period) %></span>
</div>
<% end %>
</div>
Loading

0 comments on commit f5f6248

Please sign in to comment.