mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-02 20:15:22 +02:00
Add Live Data to Account Page (#464)
* Add trends, time series, seed data * Remove test data * Replace old view values with helpers * Fix tooltip bugs in D3 chart * Fix tests * Fix smoke test * Add CRUD actions for valuations * Scaffold out inline editing with Turbo
This commit is contained in:
parent
298b50a909
commit
b5b2d335fd
28 changed files with 512 additions and 167 deletions
|
@ -1,3 +1,5 @@
|
|||
<% balances = @account.balances_with_trend %>
|
||||
<% balance_styles = trend_styles(balances[:trend].direction) %>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-3">
|
||||
|
@ -22,24 +24,19 @@
|
|||
<div class="p-4 flex justify-between">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-gray-500">Total Value</p>
|
||||
<%# TODO: Will need a normalized way to split a formatted monetary value into these 3 parts %>
|
||||
<%# TODO: Will need a better way to split a formatted monetary value into these 3 parts %>
|
||||
<p class="text-gray-900">
|
||||
<span class="text-gray-500"><%= number_to_currency(@account.converted_balance)[0] %></span>
|
||||
<span class="text-xl font-medium"><%= number_with_delimiter(@account.converted_balance.round) %></span>
|
||||
<span class="text-gray-500">.<%= number_to_currency(@account.converted_balance, precision: 2)[-2, 2] %></span>
|
||||
</p>
|
||||
<% if @account.dollar_change == 0 %>
|
||||
<p class="text-sm text-gray-500">
|
||||
<span class="text-gray-500">No change vs last month</span>
|
||||
</p>
|
||||
<% if balances[:trend].amount == 0 %>
|
||||
<p class="text-sm text-gray-500">No change vs. prior period</p>
|
||||
<% else %>
|
||||
<p class="text-sm <%= @account.dollar_change > 0 ? 'text-green-600' : 'text-red-600' %>">
|
||||
<span><%= @account.dollar_change > 0 ? '+' : '-' %><%= number_to_currency(@account.dollar_change.abs, precision: 2) %></span>
|
||||
<span>
|
||||
(<%= lucide_icon(@account.dollar_change > 0 ? 'arrow-up' : 'arrow-down', class: "w-4 h-4 align-text-bottom inline") %>
|
||||
<span><%= @account.percent_change %>%</span>)
|
||||
</span>
|
||||
<span class="text-gray-500">vs last month</span>
|
||||
<p class="text-sm <%= balance_styles[:text_class] %>">
|
||||
<span><%= balance_styles[:symbol] %><%= number_to_currency(balances[:trend].amount.abs, precision: 2) %></span>
|
||||
<span>(<%= lucide_icon(balances[:trend].amount > 0 ? 'arrow-up' : 'arrow-down', class: "w-4 h-4 align-text-bottom inline") %> <%= balances[:trend].percent %>%)</span>
|
||||
<span class="text-gray-500"><%= trend_label(balances[:date_range]) %></span>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -51,64 +48,80 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="h-96 flex items-center justify-center text-2xl font-bold">
|
||||
<div data-controller="line-chart" id="lineChart" class="w-full h-full"></div>
|
||||
<div data-controller="line-chart" id="lineChart" class="w-full h-full" data-line-chart-series-value="<%= balances[:series].map { |b| b.merge(trend: { amount: b[:trend].amount, direction: b[:trend].direction, percent: b[:trend].percent }) }.to_json %>"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="font-medium text-xl">History</h3>
|
||||
<button class="cursor-not-allowed flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg">
|
||||
<h3 class="font-medium text-lg">History</h3>
|
||||
<%= link_to new_account_valuation_path(@account), data: { turbo_frame: dom_id(Valuation.new) }, class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg" do %>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
|
||||
<span>New entry</span>
|
||||
</button>
|
||||
<span class="text-sm">New entry</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="rounded-xl bg-gray-25 p-1">
|
||||
<div class="flex flex-col rounded-lg space-y-1">
|
||||
<div class="flex justify-between gap-10 text-xs font-medium text-gray-500 uppercase py-2">
|
||||
<div class="ml-4 flex-1">
|
||||
DATE
|
||||
</div>
|
||||
<div class="ml-4 flex-1">DATE</div>
|
||||
<div class="flex-1 text-right">VALUE</div>
|
||||
<div class="flex-1 text-right">CHANGE</div>
|
||||
<div class="w-12"></div>
|
||||
</div>
|
||||
<div class="rounded-lg py-2 bg-white border-alpha-black-25 shadow-xs">
|
||||
<% @account.balances.each_with_index do |balance, index| %>
|
||||
<div>
|
||||
<% series = @account.valuations_with_trend[:series].reverse_each %>
|
||||
<% series.with_index do |valuation, index| %>
|
||||
<% valuation_styles = trend_styles(valuation[:trend].direction) %>
|
||||
<%= turbo_frame_tag dom_id(valuation[:current]) do %>
|
||||
<div class="p-4 flex items-center justify-between gap-10 w-full">
|
||||
<div class="flex-1 flex items-center gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-gray-500/5 p-1.5 flex items-center justify-center">
|
||||
<%= lucide_icon(balance.icon, class: "w-4 h-4 #{balance.change > 0 ? 'text-green-500' : balance.change < 0 ? 'text-red-500' : 'text-gray-500'}") %>
|
||||
<div class="w-8 h-8 rounded-full p-1.5 flex items-center justify-center <%= valuation_styles[:bg_class] %>">
|
||||
<%= lucide_icon(valuation_styles[:icon], class: "w-4 h-4 #{valuation_styles[:text_class]}") %>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<p class=""><%= balance.date %></p>
|
||||
<p class="text-gray-500"><%= balance.description %></p>
|
||||
<p><%= valuation[:date] %></p>
|
||||
<%# TODO: Add descriptive name of valuation %>
|
||||
<p class="text-gray-500">Manually entered</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 text-sm font-medium text-right"><%= number_to_currency(balance.amount, precision: 2) %></div>
|
||||
<div class="flex-1 text-sm font-medium text-right"><%= format_currency(valuation[:value]) %></div>
|
||||
<div class="flex-1 text-right text-sm font-medium">
|
||||
<% if balance.change == 0 %>
|
||||
<% if valuation[:trend].amount == 0 %>
|
||||
<span class="text-gray-500">No change</span>
|
||||
<% else %>
|
||||
<span class="<%= balance.change > 0 ? 'text-green-500' : 'text-red-500' %>"><%= balance.change > 0 ? '+' : '' %>$<%= balance.change.abs %></span>
|
||||
<span class="<%= balance.change > 0 ? 'text-green-500' : 'text-red-500' %>">
|
||||
(<%= lucide_icon(balance.icon, class: "w-4 h-4 align-text-bottom inline") %> <%= balance.percentage_change %>%)</span>
|
||||
<span class="<%= valuation_styles[:text_class] %>"><%= valuation_styles[:symbol] %><%= format_currency(valuation[:trend].amount.abs) %></span>
|
||||
<span class="<%= valuation_styles[:text_class] %>">(<%= lucide_icon(valuation_styles[:icon], class: "w-4 h-4 align-text-bottom inline") %> <%= valuation[:trend].percent %>%)</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="w-8 cursor-not-allowed">
|
||||
<%= lucide_icon("more-horizontal", class: "w-5 h-5 text-gray-500") %>
|
||||
<div class="relative w-8" data-controller="dropdown">
|
||||
<button data-action="click->dropdown#toggleMenu" class="flex items-center justify-center hover:bg-gray-50 w-8 h-8 rounded-lg">
|
||||
<%= lucide_icon("more-horizontal", class: "w-5 h-5 text-gray-500" ) %>
|
||||
</button>
|
||||
<div class="hidden absolute min-w-[200px] z-10 top-10 right-0 bg-white p-1 rounded-sm shadow-xs border border-alpha-black-25 w-fit" data-dropdown-target="menu">
|
||||
<%= link_to edit_valuation_path(valuation[:current]), class: "flex gap-1 items-center hover:bg-gray-50 rounded-md p-2" do %>
|
||||
<%= lucide_icon("pencil-line", class: "w-5 h-5 text-gray-500 shrink-0") %>
|
||||
<span class="text-gray-900 text-sm">Edit entry</span>
|
||||
<% end %>
|
||||
<%= link_to valuation_path(valuation[:current]), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" }, class: "text-red-600 flex gap-1 items-center hover:bg-gray-50 rounded-md p-2" do %>
|
||||
<%= lucide_icon("trash-2", class: "w-5 h-5 shrink-0") %>
|
||||
<span class="text-sm">Delete entry</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if index < @account.balances.size - 1 %>
|
||||
<div class="h-px bg-alpha-black-50 mr-6 ml-16"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div>
|
||||
<% if index < series.size - 1 %>
|
||||
<div class="h-px bg-alpha-black-50 mr-6 ml-16"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= turbo_frame_tag dom_id(Valuation.new) do %>
|
||||
<% end %>
|
||||
</div>
|
||||
<button class="cursor-not-allowed hover:bg-white w-full flex items-center justify-center gap-2 text-gray-500 px-4 py-2 rounded-md">
|
||||
<%= lucide_icon("plus", class: "w-4 h-4") %> New entry
|
||||
</button>
|
||||
</div>
|
||||
<%= link_to new_account_valuation_path(@account), data: { turbo_frame: "new_valuation" }, class: "hover:bg-white w-full text-sm flex items-center justify-center gap-2 text-gray-500 px-4 py-2 rounded-md" do %>
|
||||
<%= lucide_icon("plus", class: "w-4 h-4") %> New entry
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
15
app/views/valuations/_form_row.html.erb
Normal file
15
app/views/valuations/_form_row.html.erb
Normal file
|
@ -0,0 +1,15 @@
|
|||
<%#
|
||||
Locals:
|
||||
- f: form object for valuation
|
||||
- form_icon: string representing the icon to be displayed
|
||||
- submit_button_text: string representing the text on the submit button
|
||||
%>
|
||||
<div class="p-4 flex items-center justify-between w-full">
|
||||
<div class="shrink-0 w-8 h-8 rounded-full p-1.5 flex items-center justify-center bg-gray-500/5">
|
||||
<%= lucide_icon(form_icon, class: "w-4 h-4 text-gray-500") %>
|
||||
</div>
|
||||
<%= f.date_field :date, class: "border border-alpha-black-200 bg-white rounded-lg shadow-xs min-w-[200px] px-3 py-1.5" %>
|
||||
<%= f.number_field :value, step: :any, placeholder: number_to_currency(0), in: 0.00..100000000.00, required: 'required', class: "bg-white border border-alpha-black-200 rounded-lg shadow-xs px-3 py-1.5" %>
|
||||
<%= link_to "Cancel", account_path(@valuation.account), class: "text-sm text-gray-900 hover:text-gray-800 font-medium px-3 py-1.5" %>
|
||||
<%= f.submit submit_button_text, class: "bg-gray-50 rounded-lg font-medium px-3 py-1.5 cursor-pointer hover:bg-gray-100 text-sm" %>
|
||||
</div>
|
4
app/views/valuations/create.html.erb
Normal file
4
app/views/valuations/create.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<h1 class="font-bold text-4xl">Valuations#create</h1>
|
||||
<p>Find me in app/views/valuations/create.html.erb</p>
|
||||
</div>
|
3
app/views/valuations/create.turbo_stream.erb
Normal file
3
app/views/valuations/create.turbo_stream.erb
Normal file
|
@ -0,0 +1,3 @@
|
|||
<%# TODO: We need a way to determine the order the new valuation needs to be in the array, calculate the trend, and append it to the right spot %>
|
||||
<%= turbo_stream.update Valuation.new, "" %>
|
||||
<%= turbo_stream.append "notification-tray", partial: "shared/notification", locals: { type: "success", content: "Valuation created" } %>
|
4
app/views/valuations/destroy.html.erb
Normal file
4
app/views/valuations/destroy.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<h1 class="font-bold text-4xl">Valuations#destroy</h1>
|
||||
<p>Find me in app/views/valuations/destroy.html.erb</p>
|
||||
</div>
|
2
app/views/valuations/destroy.turbo_stream.erb
Normal file
2
app/views/valuations/destroy.turbo_stream.erb
Normal file
|
@ -0,0 +1,2 @@
|
|||
<%= turbo_stream.remove @valuation %>
|
||||
<%= turbo_stream.append "notification-tray", partial: "shared/notification", locals: { type: "success", content: "Valuation deleted" } %>
|
8
app/views/valuations/edit.html.erb
Normal file
8
app/views/valuations/edit.html.erb
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div>
|
||||
<h1 class="font-bold text-4xl">Edit Valuation: <%= @valuation.type %></h1>
|
||||
<%= turbo_frame_tag dom_id(@valuation) do %>
|
||||
<%= form_with model: @valuation, url: valuation_path(@valuation), scope: :valuation do |f| %>
|
||||
<%= render 'form_row', f: f, form_icon: "pencil-line", submit_button_text: "Update" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
8
app/views/valuations/new.html.erb
Normal file
8
app/views/valuations/new.html.erb
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div>
|
||||
<h1 class="font-bold text-4xl">Add Valuation: <%= @account.name %></h1>
|
||||
<%= turbo_frame_tag dom_id(Valuation.new) do %>
|
||||
<%= form_with model: [@account, @valuation], url: account_valuations_path(@account), scope: :valuation do |f| %>
|
||||
<%= render 'form_row', f: f, form_icon: "plus", submit_button_text: "Add" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
4
app/views/valuations/show.html.erb
Normal file
4
app/views/valuations/show.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<h1 class="font-bold text-4xl">Valuation: <%= @valuation.type %></h1>
|
||||
<p>Find me in app/views/valuations/show.html.erb</p>
|
||||
</div>
|
4
app/views/valuations/update.html.erb
Normal file
4
app/views/valuations/update.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<h1 class="font-bold text-4xl">Valuations#update</h1>
|
||||
<p>Find me in app/views/valuations/update.html.erb</p>
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue