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

Multi-currency support: Money + Currency class improvements (#553)

* Money improvements

* Replace all old money usage
This commit is contained in:
Zach Gollwitzer 2024-03-18 11:21:00 -04:00 committed by GitHub
parent e5750d1a13
commit fe2fa0eac1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 2982 additions and 196 deletions

View file

@ -10,7 +10,7 @@
</div>
<div class="flex items-center gap-8">
<p class="text-sm font-medium <%= account.is_active ? "text-gray-900" : "text-gray-400" %>">
<%= format_currency account.balance %>
<%= format_money account.balance_money %>
</p>
<%= form_with model: account, method: :patch, html: { class: "flex items-center", data: { turbo_frame: "_top" } } do |form| %>
<div class="relative inline-block select-none">

View file

@ -1,31 +1,27 @@
<%# locals: (type:) -%>
<% accounts = Current.family.accounts.where(accountable_type: type.name) %>
<% if accounts.sum(&:converted_balance) > 0 %>
<details class="mb-1 text-sm group" data-controller="account-collapse" data-account-collapse-type-value="<%= type %>">
<summary class="flex gap-4 px-2 py-3 items-center w-full rounded-[10px] font-medium hover:bg-gray-100">
<%= lucide_icon("chevron-down", class: "hidden group-open:block text-gray-500 w-5 h-5") %>
<%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
<div class="text-left"><%= type.model_name.human %></div>
<div class="ml-auto"><%= format_currency accounts.sum(&:converted_balance) %></div>
</summary>
<% accounts.each do |account| %>
<%= link_to account_path(account), class: "flex items-center w-full gap-3 px-2 py-3 mb-1 hover:bg-gray-100 rounded-[10px]" do %>
<div>
<p class="font-medium"><%= account.name %></p>
<% if account.subtype %>
<p class="text-xs text-gray-500"><%= account.subtype&.humanize %></p>
<% end %>
</div>
<p class="ml-auto font-medium"><%= format_currency account.converted_balance %></p>
<details class="mb-1 text-sm group" data-controller="account-collapse" data-account-collapse-type-value="<%= type %>">
<summary class="flex gap-4 px-2 py-3 items-center w-full rounded-[10px] font-medium hover:bg-gray-100">
<%= lucide_icon("chevron-down", class: "hidden group-open:block text-gray-500 w-5 h-5") %>
<%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
<div class="text-left"><%= type.model_name.human %></div>
<div class="ml-auto"><%= format_money accounts.sum(&:converted_balance) %></div>
</summary>
<% accounts.each do |account| %>
<%= link_to account_path(account), class: "flex items-center w-full gap-3 px-2 py-3 mb-1 hover:bg-gray-100 rounded-[10px]" do %>
<div>
<p class="font-medium"><%= account.name %></p>
<% if account.subtype %>
<p class="text-xs text-gray-500"><%= account.subtype&.humanize %></p>
<% end %>
</div>
<p class="ml-auto font-medium"><%= format_money account.converted_balance %></p>
<% 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 %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<p>New <%= type.model_name.human.downcase %></p>
<% end %>
</details>
<%= 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 %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<p>New <%= type.model_name.human.downcase %></p>
<% end %>
</details>
<% end %>

View file

@ -15,13 +15,13 @@
<%# TODO: Add descriptive name of valuation %>
<p class="text-gray-500">Manually entered</p>
</div>
<div class="flex text-sm font-medium text-right"><%= format_currency(valuation.value) %></div>
<div class="flex text-sm font-medium text-right"><%= format_money valuation.value_money %></div>
</div>
<div class="flex w-56 justify-end text-right text-sm font-medium">
<% if trend.amount == 0 %>
<span class="text-gray-500">No change</span>
<% else %>
<span class="<%= valuation_styles[:text_class] %>"><%= valuation_styles[:symbol] %><%= format_currency(trend.amount.abs) %></span>
<span class="<%= valuation_styles[:text_class] %>"><%= valuation_styles[:symbol] %><%= format_money trend.amount.abs %></span>
<span class="<%= valuation_styles[:text_class] %>">(<%= lucide_icon(valuation_styles[:icon], class: "w-4 h-4 align-text-bottom inline") %> <%= trend.percent %>%)</span>
<% end %>
</div>

View file

@ -40,7 +40,7 @@
<p><%= to_accountable_title(Accountable.from_type(group)) %></p>
<span class="text-gray-400 mx-2">&middot;</span>
<p><%= accounts.count %></p>
<p class="ml-auto"><%= format_currency accounts.sum(&:balance) %></p>
<p class="ml-auto"><%= format_money accounts.sum(&:balance_money) %></p>
</div>
<div class="bg-white">
<% accounts.each do |account| %>

View file

@ -69,11 +69,7 @@
<%= f.hidden_field :accountable_type %>
<%= f.text_field :name, placeholder: t('accounts.new.name.placeholder'), required: 'required', label: t('accounts.new.name.label'), autofocus: true %>
<%= render "accounts/#{permitted_accountable_partial(@account.accountable_type)}", f: f %>
<%= form_field_tag do %>
<%= f.label :balance, class: "form-field__label" %>
<%= f.number_field :balance, step: :any, placeholder: number_to_currency(0), in: 0.00..100000000.00, required: 'required', class: 'form-field__input max-w-[80%]' %>
<%= currency_dropdown(f: f, options: Currency.all.order(:iso_code).pluck(:iso_code)) if Currency.count > 1 %>
<% end %>
<%= f.money_field :balance_money, label: "Balance", required: 'required' %>
</div>
<%= f.submit "Add #{@account.accountable.model_name.human.downcase}" %>
<% end %>

View file

@ -1,5 +1,5 @@
<%= turbo_stream_from @account %>
<% balance = Money.from_amount(@account.balance, @account.currency) %>
<% balance = Money.new(@account.balance, @account.currency) %>
<div class="space-y-4">
<div class="flex justify-between items-center">
<div class="flex items-center gap-3">
@ -11,7 +11,7 @@
<div class="flex items-center gap-3">
<div class="relative cursor-not-allowed">
<div class="flex items-center gap-2 px-3 py-2">
<span class="text-gray-900"><%= balance.currency %> <%= balance.symbol %></span>
<span class="text-gray-900"><%= balance.currency.iso_code %> <%= balance.currency.symbol %></span>
<%= lucide_icon("chevron-down", class: "w-5 h-5 text-gray-500") %>
</div>
</div>
@ -29,7 +29,7 @@
<%= render partial: "shared/balance_heading", locals: {
label: "Total Value",
period: @period,
balance: Money.from_amount(@account.balance, @account.currency),
balance: @account.balance_money,
trend: @balance_series.trend
}
%>

View file

@ -15,7 +15,7 @@
<p><%= account_details[:allocation] %>%</p>
</div>
<div class="w-24">
<p><%= format_currency account_details[:end_balance] %></p>
<p><%= format_money account_details[:end_balance] %></p>
</div>
<div class="w-40">
<%= render partial: "shared/trend_change", locals: { trend: account_details[:trend] } %>
@ -39,7 +39,7 @@
<p><%= account[:allocation] %>%</p>
</div>
<div class="w-24">
<p><%= format_currency account[:end_balance] %></p>
<p><%= format_money account[:end_balance] %></p>
</div>
<div class="w-40">
<%= render partial: "shared/trend_change", locals: { trend: account[:trend] } %>

View file

@ -10,7 +10,7 @@
<%= render partial: "shared/balance_heading", locals: {
label: "Net Worth",
period: @period,
balance: Money.from_amount(Current.family.net_worth, Current.family.currency),
balance: Current.family.net_worth_money,
trend: @net_worth_series.trend
}
%>
@ -26,7 +26,7 @@
<%= render partial: "shared/balance_heading", locals: {
label: "Assets",
period: @period,
balance: Money.from_amount(Current.family.assets, Current.family.currency),
balance: Current.family.assets_money,
trend: @asset_series.trend
} %>
</div>
@ -44,7 +44,7 @@
label: "Liabilities",
period: @period,
size: "md",
balance: Money.from_amount(Current.family.liabilities, Current.family.currency),
balance: Current.family.liabilities_money,
trend: @liability_series.trend
} %>
</div>

View file

@ -2,11 +2,11 @@
<div class="space-y-2">
<p class="text-sm text-gray-500"><%= label %></p>
<p class="text-gray-900 -space-x-0.5">
<span class="text-gray-500"><%= balance.symbol %></span>
<span class="<%= size == "lg" ? "text-xl" : "text-lg" %> font-medium"><%= format_currency(balance.amount, precision: 0, unit: '') %></span>
<%- if balance.precision.positive? -%>
<span class="text-gray-500"><%= balance.currency.symbol %></span>
<span class="<%= size == "lg" ? "text-xl" : "text-lg" %> font-medium"><%= format_money_without_symbol balance, precision: 0 %></span>
<%- if balance.currency.default_precision.positive? -%>
<span class="text-gray-500">
<%= balance.separator %><%= balance.cents %>
<%= balance.currency.separator %><%= balance.cents_str %>
</span>
<% end %>
</p>

View file

@ -4,7 +4,7 @@
<% if trend.direction == "flat" %>
<span>No change</span>
<% else %>
<span><%= styles[:symbol] %><%= format_currency(trend.amount.abs) %></span>
<span><%= styles[:symbol] %><%= format_money trend.amount.abs %></span>
<span>(<%= lucide_icon(styles[:icon], class: "w-4 h-4 align-text-bottom inline") %><%= trend.percent %>%)</span>
<% end %>
</p>

View file

@ -1,7 +1,7 @@
<%= form_with model: @transaction do |f| %>
<%= f.collection_select :account_id, Current.family.accounts, :id, :name, { prompt: "Select an Account", label: "Account" } %>
<%= f.date_field :date, label: "Date" %>
<%= f.text_field :name, label: "Name" %>
<%= f.number_field :amount, label: "Amount", step: :any, placeholder: number_to_currency(0), in: 0.00..100000000.00 %>
<%= f.text_field :name, label: "Name", placeholder: "Groceries" %>
<%= f.money_field :amount_money, label: "Amount" %>
<%= f.submit %>
<% end %>

View file

@ -12,6 +12,6 @@
<p><%= transaction.account.name %></p>
</div>
<div class="ml-auto">
<p class="<%= transaction.amount < 0 ? "text-green-600" : "" %>"><%= number_to_currency(-transaction.amount, { precision: 2 }) %></p>
<p class="<%= transaction.amount < 0 ? "text-green-600" : "" %>"><%= format_money -transaction.amount_money %></p>
</div>
<% end %>

View file

@ -2,7 +2,7 @@
<div class="bg-gray-25 rounded-xl p-1">
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-gray-500">
<h4><%= date.strftime('%b %d, %Y') %> &middot; <%= transactions.size %></h4>
<span><%= number_to_currency(-transactions.sum(&:amount)) %></span>
<span><%= format_money -transactions.sum(&:amount_money) %></span>
</div>
<div class="bg-white shadow-xs rounded-md border border-alpha-black-25 divide-y divide-alpha-black-50">
<%= render partial: "transactions/transaction", collection: transactions %>

View file

@ -23,13 +23,13 @@
<div class="p-4 space-y-2">
<p class="text-sm text-gray-500">Income</p>
<p class="text-gray-900 font-medium text-xl">
<%= number_to_currency(@transactions.select { |t| t.amount < 0 }.sum(&:amount).abs, precision: 2) %>
<%= format_money @transactions.inflows.sum(&:amount_money).abs %>
</p>
</div>
<div class="p-4 space-y-2">
<p class="text-sm text-gray-500">Expenses</p>
<p class="text-gray-900 font-medium text-xl">
<%= number_to_currency(@transactions.select { |t| t.amount >= 0 }.sum(&:amount), precision: 2) %>
<%= format_money @transactions.outflows.sum(&:amount_money) %>
</p>
</div>
</div>
@ -50,7 +50,6 @@
</div>
<div>
<%= form.select :date, options_for_select([['All', 'all'], ['7D', 'last_7_days'], ['1M', 'last_30_days'], ["1Y", "last_365_days"]], selected: params.dig(:q, :date)), {}, { class: "block h-full w-full border border-gray-200 rounded-lg text-sm py-2 pr-8 pl-2", "data-transactions-search-target": "date" } %>
<!-- Hidden fields for date range -->
<%= form.hidden_field :date_gteq, value: '', "data-transactions-search-target": "dateGteq" %>
<%= form.hidden_field :date_lteq, value: '', "data-transactions-search-target": "dateLteq" %>
@ -76,14 +75,12 @@
<p>amount</p>
</div>
</div>
<div class="space-y-6">
<% @transactions.group_by { |transaction| transaction.date }.each do |date, grouped_transactions| %>
<%= render partial: "transactions/transaction_group", locals: { date: date, transactions: grouped_transactions } %>
<% end %>
</div>
<% end %>
<% if @pagy.pages > 1 %>
<nav class="flex items-center justify-center px-4 mt-4 sm:px-0">
<%= render partial: "transactions/pagination", locals: { pagy: @pagy } %>

View file

@ -1,10 +1,9 @@
<%= sidebar_modal do %>
<h3 class="font-medium mb-1">
<span class="text-2xl"><%=format_currency @transaction.amount %></span>
<span class="text-2xl"><%=format_money @transaction.amount_money %></span>
<span class="text-lg text-gray-500"><%= @transaction.currency %></span>
</h3>
<span class="text-sm text-gray-500"><%= @transaction.date.strftime("%A %d %B") %></span>
<%= form_with model: @transaction, html: {data: {controller: "auto-submit-form"}} do |f| %>
<details class="group" open>
<summary class="list-none bg-gray-25 rounded-xl py-1 mt-4 group-open:mb-2">
@ -14,12 +13,10 @@
<%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
</div>
</summary>
<%= f.date_field :date, label: "Date" %>
<div class="h-2"></div>
<%= f.collection_select :account_id, Current.family.accounts, :id, :name, { prompt: "Select an Account", label: "Account", class: "text-gray-400" }, {class: "form-field__input cursor-not-allowed text-gray-400", disabled: "disabled"} %>
</details>
<details class="group" open>
<summary class="list-none bg-gray-25 rounded-xl py-1 mt-6 group-open:mb-2">
<div class="py-2 px-[11px] flex items-center justify-between font-medium text-xs text-gray-500">
@ -28,10 +25,8 @@
<%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
</div>
</summary>
<%= f.text_field :name, label: "Name" %>
</details>
<details class="group" open>
<summary class="list-none bg-gray-25 rounded-xl py-1 mt-6 group-open:mb-2">
<div class="py-2 px-[11px] flex items-center justify-between font-medium text-xs text-gray-500">
@ -40,7 +35,6 @@
<%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
</div>
</summary>
<label class="flex items-center cursor-pointer justify-between mx-3">
<%= f.check_box :excluded, class: "sr-only peer" %>
<div class="flex flex-col justify-center text-sm w-[340px] py-3">
@ -50,7 +44,6 @@
<div class="relative w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-100 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
</label>
</details>
<details class="group" open>
<summary class="list-none bg-gray-25 rounded-xl py-1 mt-6 mb-2">
<div class="py-2 px-[11px] flex items-center justify-between font-medium text-xs text-gray-500">
@ -59,10 +52,7 @@
<%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
</div>
</summary>
<%= f.text_area :notes, label: "Notes", placeholder: "Enter a note" %>
</details>
<% end %>
<% end %>

View file

@ -7,7 +7,7 @@
</div>
<div class="flex items-center justify-between grow">
<%= f.date_field :date, required: 'required', class: "border border-alpha-black-200 bg-white rounded-lg shadow-xs min-w-[200px] px-3 py-1.5 text-gray-900 text-sm" %>
<%= 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 text-gray-900 text-sm px-3 py-1.5 text-right" %>
<%= f.number_field :value, required: 'required', placeholder: "0.00", class: "bg-white border border-alpha-black-200 rounded-lg shadow-xs text-gray-900 text-sm px-3 py-1.5 text-right" %>
</div>
<div class="w-[296px] flex gap-2 justify-end items-center">
<%= link_to "Cancel", account_path(@valuation.account), class: "text-sm text-gray-900 hover:text-gray-800 font-medium px-3 py-1.5" %>