mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-07 14:35:23 +02:00
New Design System + Codebase Refresh (#1823)
Since the very first 0.1.0-alpha.1 release, we've been moving quickly to add new features to the Maybe app. In doing so, some parts of the codebase have become outdated, unnecessary, or overly-complex as a natural result of this feature prioritization. Now that "core" Maybe is complete, we're moving into a second phase of development where we'll be working hard to improve the accuracy of existing features and build additional features on top of "core". This PR is a quick overhaul of the existing codebase aimed to: - Establish the brand new and simplified dashboard view (pictured above) - Establish and move towards the conventions introduced in Cursor rules and project design overview #1788 - Consolidate layouts and improve the performance of layout queries - Organize the core models of the Maybe domain (i.e. Account::Entry, Account::Transaction, etc.) and break out specific traits of each model into dedicated concerns for better readability - Remove stale / dead code from codebase - Remove overly complex code paths in favor of simpler ones
This commit is contained in:
parent
8539ac7dec
commit
d75be2282b
278 changed files with 3428 additions and 4354 deletions
|
@ -1,48 +0,0 @@
|
|||
<%# locals: (accountable_group:) %>
|
||||
<% text_class = accountable_text_class(accountable_group.name) %>
|
||||
<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-secondary 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_group.name) %>"></div>
|
||||
<p class="text-primary ml-2"><%= to_accountable_title(Accountable.from_type(accountable_group.name)) %></p>
|
||||
<span class="mx-1">·</span>
|
||||
<div><%= accountable_group.children.count %></div>
|
||||
<div class="ml-auto text-right flex items-center gap-10 text-sm font-medium text-primary">
|
||||
<div class="flex items-center justify-end gap-2 w-24">
|
||||
<%= render partial: "shared/progress_circle", locals: { progress: accountable_group.percent_of_total, text_class: text_class } %>
|
||||
<p><%= accountable_group.percent_of_total.round(1) %>%</p>
|
||||
</div>
|
||||
<div class="w-24">
|
||||
<p><%= format_money accountable_group.sum %></p>
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<%= render partial: "shared/trend_change", locals: { trend: accountable_group.series.trend } %>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="px-4 py-3 space-y-4">
|
||||
<% accountable_group.children.map do |account_value_node| %>
|
||||
<div class="flex items-center justify-between text-sm font-medium text-primary">
|
||||
<div class="flex items-center gap-4 overflow-hidden">
|
||||
<%= render "accounts/logo", account: account_value_node.original, size: "sm" %>
|
||||
<div class="truncate">
|
||||
<p><%= account_value_node.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_value_node.percent_of_total, text_class: text_class } %>
|
||||
<p><%= account_value_node.percent_of_total %>%</p>
|
||||
</div>
|
||||
<div class="w-24">
|
||||
<p><%= format_money account_value_node.original.balance_money %></p>
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<%= render partial: "shared/trend_change", locals: { trend: account_value_node.original.series.trend } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</details>
|
|
@ -1,17 +0,0 @@
|
|||
<%# locals: (account_groups:) %>
|
||||
<div class="space-y-4">
|
||||
<div class="flex gap-1">
|
||||
<% account_groups.sort_by(&:percent_of_total).reverse.each do |group| %>
|
||||
<div class="h-1.5 rounded-sm w-12 <%= accountable_bg_class(group.name) %>" style="width: <%= group.percent_of_total %>%;"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<% account_groups.sort_by(&:percent_of_total).reverse.each do |group| %>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<div class="h-2.5 w-2.5 rounded-full <%= accountable_bg_class(group.name) %>"></div>
|
||||
<p class="text-secondary"><%= to_accountable_title(Accountable.from_type(group.name)) %></p>
|
||||
<p class="text-black"><%= group.percent_of_total.round(1) %>%</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,20 +0,0 @@
|
|||
<%# 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-secondary">
|
||||
<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: "pages/account_group_disclosure", collection: account_groups.sort_by(&:name), as: :accountable_group %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,30 +1,21 @@
|
|||
<% content_for :sidebar do %>
|
||||
<%= render "settings/nav" %>
|
||||
<% end %>
|
||||
<%= content_for :page_title, t(".title") %>
|
||||
|
||||
<div class="space-y-4 flex flex-col h-full">
|
||||
<h1 class="text-primary text-xl font-medium mb-4"><%= t(".title") %></h1>
|
||||
<div class="bg-white shadow-xs border border-alpha-black-25 rounded-xl p-4 grow overflow-y-auto">
|
||||
<div class="flex justify-between gap-4 mb-12 last:mb-0">
|
||||
<div class="w-1/3">
|
||||
<div class="px-3 flex items-center gap-3">
|
||||
<div class="text-white shrink-0 w-9 h-9">
|
||||
<%= image_tag @release_notes[:avatar], class: "rounded-full w-full h-full object-cover" %>
|
||||
</div>
|
||||
<div>
|
||||
<a class="text-primary font-medium text-sm" href="https://github.com/<%= @release_notes[:username] %>"><%= "@#{@release_notes[:username]}" %></a>
|
||||
<div class="text-secondary text-sm"><%= @release_notes[:published_at].strftime("%B %d, %Y") %></div>
|
||||
</div>
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4 grow overflow-y-auto">
|
||||
<div class="flex justify-between gap-4 mb-12 last:mb-0">
|
||||
<div class="w-1/3">
|
||||
<div class="px-3 flex items-center gap-3">
|
||||
<div class="text-white shrink-0 w-9 h-9">
|
||||
<%= image_tag @release_notes[:avatar], class: "rounded-full w-full h-full object-cover" %>
|
||||
</div>
|
||||
<div>
|
||||
<a class="text-primary font-medium text-sm" href="https://github.com/<%= @release_notes[:username] %>"><%= "@#{@release_notes[:username]}" %></a>
|
||||
<div class="text-secondary text-sm"><%= @release_notes[:published_at].strftime("%B %d, %Y") %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-2/3 text-secondary text-sm prose prose--github-release-notes">
|
||||
<h2 class="mb-5 text-xl text-primary"><%= @release_notes[:name] %></h2>
|
||||
<%= @release_notes[:body].html_safe %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-2/3 text-secondary text-sm prose prose--github-release-notes">
|
||||
<h2 class="mb-5 text-xl text-primary"><%= @release_notes[:name] %></h2>
|
||||
<%= @release_notes[:body].html_safe %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto">
|
||||
<%= settings_nav_footer %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,203 +1,28 @@
|
|||
<div class="space-y-4">
|
||||
<% if self_hosted? %>
|
||||
<% if Current.family&.synth_overage? %>
|
||||
<div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded relative" role="alert">
|
||||
Your Synth API credit limit has been exceeded. Please visit your <a href="https://dashboard.synthfinance.com/settings" class="font-medium underline hover:text-yellow-900">Synth billing settings</a> to upgrade your plan or wait for your credits to reset.
|
||||
</div>
|
||||
<% elsif !Current.family&.synth_valid? %>
|
||||
<div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded relative" role="alert">
|
||||
Your Synth API Key is invalid. Please visit your <a href="https://dashboard.synthfinance.com/dashboard" class="font-medium underline hover:text-yellow-900">Synth dashboard</a> and verify that your API key is correct.
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<header class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="sr-only"><%= t(".title") %></h1>
|
||||
<p class="text-xl font-medium text-primary mb-1">
|
||||
<%= Current.user.first_name.present? ? t(".greeting", name: Current.user.first_name ) : t(".fallback_greeting") %>
|
||||
</p>
|
||||
<% unless @accounts.blank? %>
|
||||
<p class="text-secondary text-sm"><%= t(".subtitle") %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="w-full space-y-6 pb-24">
|
||||
<header class="space-y-6">
|
||||
<nav class="flex items-center gap-2">
|
||||
<button data-action="sidebar#toggle" class="w-9 h-9 inline-flex rounded-lg items-center justify-center hover:bg-gray-100 cursor-pointer">
|
||||
<%= icon("panel-left", color: "gray") %>
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= contextual_menu_modal_action_item t(".import"), new_import_path, icon: "hard-drive-upload" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<span class="text-sm text-gray-500 font-medium">Home</span>
|
||||
|
||||
<%= link_to new_account_path, class: "flex items-center gap-1 btn btn--primary", data: { turbo_frame: "modal" } do %>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||
<span><%= t(".new") %></span>
|
||||
<% end %>
|
||||
<%= icon("chevron-right", color: "gray", size: "sm") %>
|
||||
|
||||
<span class="text-gray-900 font-medium text-sm">Dashboard</span>
|
||||
</nav>
|
||||
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-3xl font-medium text-gray-900">Welcome back, <%= Current.user.first_name %></h1>
|
||||
<p class="text-gray-500">Here's what's happening with your money this week</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<% if @accounts.empty? %>
|
||||
<%= render "shared/no_account_empty_state" %>
|
||||
<% else %>
|
||||
<section class="flex gap-4">
|
||||
<div class="bg-white border border-alpha-black-25 shadow-xs rounded-xl w-3/4 min-h-48 flex flex-col">
|
||||
<div class="flex justify-between p-4">
|
||||
<div>
|
||||
<%= render partial: "shared/value_heading", locals: {
|
||||
label: t(".net_worth"),
|
||||
period: @period,
|
||||
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 |form| %>
|
||||
<%= period_select form: form, selected: @period.name %>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render partial: "pages/dashboard/net_worth_chart", locals: { series: @net_worth_series } %>
|
||||
</div>
|
||||
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl w-1/4">
|
||||
<%= render partial: "pages/dashboard/allocation_chart", locals: { account_groups: @account_groups } %>
|
||||
</div>
|
||||
</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 flex-col gap-4 h-full">
|
||||
<div class="flex gap-4">
|
||||
<div class="grow">
|
||||
<%= render partial: "shared/value_heading", locals: {
|
||||
label: t(".income"),
|
||||
period: Period.last_30_days,
|
||||
value: @income_series.last&.value,
|
||||
trend: @income_series.trend
|
||||
} %>
|
||||
</div>
|
||||
<div
|
||||
id="incomeChart"
|
||||
class="h-full w-2/5"
|
||||
data-controller="time-series-chart"
|
||||
data-time-series-chart-data-value="<%= @income_series.to_json %>"
|
||||
data-time-series-chart-use-labels-value="false"
|
||||
data-time-series-chart-use-tooltip-value="false"></div>
|
||||
</div>
|
||||
<div class="flex gap-1.5 mt-auto">
|
||||
<% @top_earners.first(3).each do |account| %>
|
||||
<%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-primary font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %>
|
||||
<%= render "accounts/logo", account: account, size: "sm" %>
|
||||
<span>+<%= Money.new(account.income, account.currency) %></span>
|
||||
<%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @top_earners.count > 3 %>
|
||||
<div class="bg-gray-25 rounded-full flex h-full aspect-1 items-center justify-center text-xs font-medium text-secondary">+<%= @top_earners.count - 3 %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl">
|
||||
<div class="flex flex-col gap-4 h-full">
|
||||
<div class="flex gap-4">
|
||||
<div class="grow">
|
||||
<%= render partial: "shared/value_heading", locals: {
|
||||
label: t(".spending"),
|
||||
period: Period.last_30_days,
|
||||
value: @spending_series.last&.value,
|
||||
trend: @spending_series.trend
|
||||
} %>
|
||||
</div>
|
||||
<div
|
||||
id="spendingChart"
|
||||
class="h-full w-2/5"
|
||||
data-controller="time-series-chart"
|
||||
data-time-series-chart-data-value="<%= @spending_series.to_json %>"
|
||||
data-time-series-chart-use-labels-value="false"
|
||||
data-time-series-chart-use-tooltip-value="false"></div>
|
||||
</div>
|
||||
<div class="mt-auto flex gap-1.5">
|
||||
<% @top_spenders.first(3).each do |account| %>
|
||||
<%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-primary font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %>
|
||||
<%= render "accounts/logo", account: account, size: "sm" %>
|
||||
-<%= Money.new(account.spending, account.currency) %>
|
||||
<%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @top_spenders.count > 3 %>
|
||||
<div class="bg-gray-25 rounded-full flex h-full aspect-1 items-center justify-center text-xs font-medium text-secondary">+<%= @top_spenders.count - 3 %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white p-4 border border-alpha-black-25 shadow-xs rounded-xl">
|
||||
<div class="flex flex-col gap-4 h-full">
|
||||
<div class="flex gap-4">
|
||||
<div class="grow">
|
||||
<%= render partial: "shared/value_heading", locals: {
|
||||
label: t(".savings_rate"),
|
||||
period: Period.last_30_days,
|
||||
value: (@savings_rate_series.last&.value)*100,
|
||||
trend: @savings_rate_series.trend,
|
||||
is_percentage: true
|
||||
} %>
|
||||
</div>
|
||||
<div
|
||||
id="savingsRateChart"
|
||||
class="h-full w-2/5"
|
||||
data-controller="time-series-chart"
|
||||
data-time-series-chart-data-value="<%= @savings_rate_series.to_json %>"
|
||||
data-time-series-chart-use-labels-value="false"
|
||||
data-time-series-chart-use-tooltip-value="false"></div>
|
||||
</div>
|
||||
<div class="flex gap-1.5">
|
||||
<% @top_savers.first(3).each do |account| %>
|
||||
<% unless account.savings_rate.infinite? %>
|
||||
<%= link_to account, class: "border border-alpha-black-25 rounded-full p-1 pr-2 flex items-center gap-1 text-xs text-primary font-medium hover:bg-gray-25", data: { controller: "tooltip" } do %>
|
||||
<%= render "accounts/logo", account: account, size: "sm" %>
|
||||
<span><%= account.savings_rate > 0 ? "+" : "-" %><%= number_to_percentage(account.savings_rate.abs * 100, precision: 2) %></span>
|
||||
<%= render partial: "shared/text_tooltip", locals: { tooltip_text: account.name } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @top_savers.count > 3 %>
|
||||
<div class="bg-gray-25 rounded-full flex h-full aspect-1 items-center justify-center text-xs font-medium text-secondary">+<%= @top_savers.count - 3 %></div>
|
||||
<% end %>
|
||||
</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
|
||||
id="investingChart"
|
||||
class="h-full w-2/5"
|
||||
data-controller="time-series-chart"
|
||||
data-time-series-chart-data-value="<%= @investing_series.to_json %>"
|
||||
data-time-series-chart-use-labels-value="false"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="w-full">
|
||||
<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-primary"><%= t(".transactions") %></h2>
|
||||
<% if @transaction_entries.empty? %>
|
||||
<div class="text-secondary flex items-center justify-center py-12">
|
||||
<p><%= t(".no_transactions") %></p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-secondary p-1 space-y-1 bg-gray-25 rounded-xl">
|
||||
<%= entries_by_date(@transaction_entries, selectable: false) do |entries, _transfers| %>
|
||||
<%= render entries, selectable: false %>
|
||||
<% end %>
|
||||
<section class="bg-white py-4 rounded-xl shadow-border-xs">
|
||||
<%= render partial: "pages/dashboard/net_worth_chart", locals: { series: @balance_sheet.net_worth_series(period: @period), period: @period } %>
|
||||
</section>
|
||||
|
||||
<p class="py-2 text-sm text-center"><%= link_to t(".view_all"), transactions_path %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
<section>
|
||||
<%= render "pages/dashboard/balance_sheet", balance_sheet: @balance_sheet %>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<%# locals: (account_groups:) -%>
|
||||
<div data-controller="tabs" data-tabs-active-class="bg-white border-alpha-black-25 shadow-xs text-primary" data-tabs-default-tab-value="asset-tab">
|
||||
<div class="bg-gray-25 rounded-lg p-1 flex gap-1 text-sm text-secondary 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-secondary flex items-center justify-center py-6">
|
||||
<div
|
||||
data-controller="pie-chart"
|
||||
class="w-full aspect-1"
|
||||
data-pie-chart-data-value="<%= value_group_pie_data(account_groups[:assets]) %>"
|
||||
data-pie-chart-total-value="<%= format_money(account_groups[:assets].sum, precision: 0) %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-tabs-target="tab" id="liability-tab" class="space-y-6 hidden">
|
||||
<div class="text-secondary flex items-center justify-center py-6">
|
||||
<div
|
||||
data-controller="pie-chart"
|
||||
class="w-full aspect-1"
|
||||
data-pie-chart-data-value="<%= value_group_pie_data(account_groups[:liabilities]) %>"
|
||||
data-pie-chart-total-value="<%= format_money(account_groups[:liabilities].sum, precision: 0) %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
103
app/views/pages/dashboard/_balance_sheet.html.erb
Normal file
103
app/views/pages/dashboard/_balance_sheet.html.erb
Normal file
|
@ -0,0 +1,103 @@
|
|||
<%# locals: (balance_sheet:) %>
|
||||
|
||||
<div class="space-y-4">
|
||||
<% balance_sheet.classification_groups.each do |classification_group| %>
|
||||
<div class="bg-white shadow-border-xs rounded-xl space-y-4 p-4">
|
||||
<h2 class="text-lg font-medium"><%= classification_group.display_name %></h2>
|
||||
|
||||
<% if classification_group.account_groups.any? %>
|
||||
<div class="space-y-4">
|
||||
<div class="flex gap-1">
|
||||
<% classification_group.account_groups.each do |account_group| %>
|
||||
<div class="h-1.5 rounded-sm" style="width: <%= account_group.weight %>%; background-color: <%= account_group.color %>;"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<% classification_group.account_groups.each do |account_group| %>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<div class="h-2.5 w-2.5 rounded-full" style="background-color: <%= account_group.color %>;"></div>
|
||||
<p class="text-secondary"><%= account_group.name %></p>
|
||||
<p class="text-black"><%= number_to_percentage(account_group.weight, precision: 0) %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface rounded-xl p-1 space-y-1">
|
||||
<div class="px-4 py-2 flex items-center uppercase text-xs font-medium text-secondary">
|
||||
<div>Name</div>
|
||||
<div class="ml-auto text-right flex items-center gap-6">
|
||||
<div class="w-24">
|
||||
<p>Weight</p>
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<p>Value</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shadow-border-xs rounded-lg bg-white">
|
||||
<% classification_group.account_groups.each do |account_group| %>
|
||||
<details class="group rounded-lg open:bg-surface font-medium text-sm">
|
||||
<summary class="cursor-pointer p-4 group-open:bg-surface bg-white rounded-lg flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<%= lucide_icon("chevron-right", class: "group-open:rotate-90 text-secondary w-5 h-5") %>
|
||||
|
||||
<p><%= account_group.name %></p>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center text-right gap-6">
|
||||
<div class="w-24 flex items-center justify-end gap-2">
|
||||
<%= render partial: "shared/progress_circle", locals: { progress: account_group.weight, color: account_group.color } %>
|
||||
<p><%= number_to_percentage(account_group.weight, precision: 0) %></p>
|
||||
</div>
|
||||
|
||||
<div class="w-40">
|
||||
<p><%= format_money(account_group.total_money) %></p>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
|
||||
<div>
|
||||
<% account_group.accounts.each_with_index do |account, idx| %>
|
||||
<div class="pl-12 pr-4 py-3 flex items-center justify-between text-sm font-medium">
|
||||
<div class="flex items-center gap-3">
|
||||
<%= render "accounts/logo", account: account, size: "sm", color: account_group.color %>
|
||||
<%= link_to account.name, account_path(account) %>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center text-right gap-6">
|
||||
<div class="w-24 flex items-center justify-end gap-2">
|
||||
<%= render partial: "shared/progress_circle", locals: { progress: account.weight, color: account_group.color } %>
|
||||
<p><%= number_to_percentage(account.weight, precision: 0) %></p>
|
||||
</div>
|
||||
|
||||
<div class="w-40">
|
||||
<p><%= format_money(account.balance_money) %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if idx < account_group.accounts.size - 1 %>
|
||||
|
||||
<div class="pl-[84px] pr-40">
|
||||
<div class="w-full border-subdued border-b"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</details>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% else %>
|
||||
<div class="py-20 flex flex-col items-center">
|
||||
<%= lucide_icon classification_group.icon, class: "w-6 h-6 shrink-0 text-secondary" %>
|
||||
<p class="text-primary text-sm font-medium mb-1 mt-4">No <%= classification_group.display_name %></p>
|
||||
<p class="text-secondary text-sm max-w-xs text-center"><%= "You have no #{classification_group.display_name}" %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
|
@ -1,16 +1,37 @@
|
|||
<%# locals: (series:) %>
|
||||
<% if series.has_current_day_value? %>
|
||||
<%# locals: (series:, period:) %>
|
||||
|
||||
<div class="flex justify-between p-4">
|
||||
<div class="space-y-2">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-secondary font-medium">Net Worth</p>
|
||||
<p class="text-primary -space-x-0.5 text-xl font-medium">
|
||||
<%= series.current.format %>
|
||||
</p>
|
||||
<% if series.trend.nil? %>
|
||||
<p class="text-sm text-secondary">Data not available for the selected period</p>
|
||||
<% elsif series.trend.direction.flat? %>
|
||||
<p class="text-sm text-secondary">No change vs. prior period</p>
|
||||
<% else %>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= render partial: "shared/trend_change", locals: { trend: series.trend } %>
|
||||
<span class="text-sm text-secondary"><%= period.comparison_label %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= form_with url: root_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do |form| %>
|
||||
<%= period_select form: form, selected: period %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if series.any? %>
|
||||
<div
|
||||
id="netWorthChart"
|
||||
class="w-full flex-1 min-h-52"
|
||||
data-controller="time-series-chart"
|
||||
data-time-series-chart-data-value="<%= series.to_json %>"></div>
|
||||
<% elsif series.empty? %>
|
||||
<% else %>
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<p class="text-secondary text-sm">No data available for the selected period.</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<p class="text-secondary text-sm animate-pulse">Calculating latest balance data...</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
15
app/views/pages/dashboard/_no_account_empty_state.html.erb
Normal file
15
app/views/pages/dashboard/_no_account_empty_state.html.erb
Normal file
|
@ -0,0 +1,15 @@
|
|||
<div class="flex justify-center items-center h-[800px]">
|
||||
<div class="text-center flex flex-col gap-4 items-center max-w-[300px]">
|
||||
<%= lucide_icon "layers", class: "w-6 h-6 text-secondary" %>
|
||||
|
||||
<div class="space-y-1 text-sm">
|
||||
<p class="text-primary font-medium"><%= t(".no_account_title") %></p>
|
||||
<p class="text-secondary"><%= t(".no_account_subtitle") %></p>
|
||||
</div>
|
||||
|
||||
<%= link_to new_account_path, class: "btn btn--primary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||
<span><%= t(".new_account") %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,35 +1,28 @@
|
|||
<% content_for :sidebar do %>
|
||||
<%= render "settings/nav" %>
|
||||
<% end %>
|
||||
<%= content_for :page_title, "Feedback" %>
|
||||
|
||||
<div class="space-y-4">
|
||||
<h1 class="text-primary text-xl font-medium mb-4">Feedback</h1>
|
||||
<div class="bg-white shadow-xs border border-alpha-black-25 rounded-xl p-4">
|
||||
<h2 class="text-lg font-medium text-primary mb-1">Leave feedback</h2>
|
||||
<p class="text-sm text-secondary mb-4">Let us know if you have any specific feedback. Feel free to include links to videos or screenshots.</p>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "https://github.com/maybe-finance/maybe/discussions/categories/feature-requests", target: "_blank", rel: "noopener noreferrer", class: "w-1/3 flex flex-col items-center p-4 border border-alpha-black-25 rounded-xl hover:bg-gray-50" do %>
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4">
|
||||
<h2 class="text-lg font-medium text-primary mb-1">Leave feedback</h2>
|
||||
<p class="text-sm text-secondary mb-4">Let us know if you have any specific feedback. Feel free to include links to videos or screenshots.</p>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "https://github.com/maybe-finance/maybe/discussions/categories/feature-requests", target: "_blank", rel: "noopener noreferrer", class: "w-1/3 flex flex-col items-center p-4 border border-alpha-black-25 rounded-xl hover:bg-gray-50" do %>
|
||||
<%= image_tag "github-icon.svg", class: "w-8 h-8 mb-2" %>
|
||||
<span class="text-sm font-medium text-primary">Write a feature request</span>
|
||||
<% end %>
|
||||
<% if self_hosted? %>
|
||||
<%= link_to "https://github.com/maybe-finance/maybe/issues/new?assignees=&labels=bug&template=bug_report.md&title=", target: "_blank", rel: "noopener noreferrer", class: "w-1/3 flex flex-col items-center p-4 border border-alpha-black-25 rounded-xl hover:bg-gray-50" do %>
|
||||
<%= image_tag "github-icon.svg", class: "w-8 h-8 mb-2" %>
|
||||
<span class="text-sm font-medium text-primary">Write a feature request</span>
|
||||
<span class="text-sm font-medium text-primary">File a bug report</span>
|
||||
<% end %>
|
||||
<% if self_hosted? %>
|
||||
<%= link_to "https://github.com/maybe-finance/maybe/issues/new?assignees=&labels=bug&template=bug_report.md&title=", target: "_blank", rel: "noopener noreferrer", class: "w-1/3 flex flex-col items-center p-4 border border-alpha-black-25 rounded-xl hover:bg-gray-50" do %>
|
||||
<%= image_tag "github-icon.svg", class: "w-8 h-8 mb-2" %>
|
||||
<span class="text-sm font-medium text-primary">File a bug report</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to "mailto:hello@maybefinance.com", class: "w-1/3 flex flex-col items-center p-4 border border-alpha-black-25 rounded-xl hover:bg-gray-50", onclick: "Intercom('showNewMessage'); return false;" do %>
|
||||
<%= lucide_icon "bug", class: "w-8 h-8 mb-2" %>
|
||||
<span class="text-sm font-medium text-primary">File a bug report</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to "mailto:hello@maybefinance.com", class: "w-1/3 flex flex-col items-center p-4 border border-alpha-black-25 rounded-xl hover:bg-gray-50", onclick: "Intercom('showNewMessage'); return false;" do %>
|
||||
<%= lucide_icon "bug", class: "w-8 h-8 mb-2" %>
|
||||
<span class="text-sm font-medium text-primary">File a bug report</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to "https://link.maybe.co/discord", target: "_blank", rel: "noopener noreferrer", class: "w-1/3 flex flex-col items-center p-4 border border-alpha-black-25 rounded-xl hover:bg-gray-50" do %>
|
||||
<%= image_tag "discord-icon.svg", class: "w-8 h-8 mb-2" %>
|
||||
<span class="text-sm font-medium text-primary">Discuss Maybe with others</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= link_to "https://link.maybe.co/discord", target: "_blank", rel: "noopener noreferrer", class: "w-1/3 flex flex-col items-center p-4 border border-alpha-black-25 rounded-xl hover:bg-gray-50" do %>
|
||||
<%= image_tag "discord-icon.svg", class: "w-8 h-8 mb-2" %>
|
||||
<span class="text-sm font-medium text-primary">Discuss Maybe with others</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= settings_nav_footer %>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue