mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 13:19:39 +02:00
Finalize transaction drawer, simplify money form helpers (#1191)
* Finalize transaction drawer, simplify money form helpers * Fix money form errors * Reusable disclosure helper, fix styles * Final style tweaks
This commit is contained in:
parent
730e58d763
commit
5942ce7e3c
16 changed files with 275 additions and 214 deletions
|
@ -20,14 +20,14 @@
|
|||
}
|
||||
|
||||
.form-field__label {
|
||||
@apply block text-xs text-gray-500;
|
||||
@apply block text-xs text-gray-500 peer-disabled:text-gray-400;
|
||||
}
|
||||
|
||||
.form-field__input {
|
||||
@apply border-none bg-transparent text-sm opacity-100 w-full p-0;
|
||||
@apply focus:opacity-100 focus:outline-none focus:ring-0;
|
||||
@apply placeholder-shown:opacity-50;
|
||||
@apply disabled:opacity-50;
|
||||
@apply disabled:text-gray-400;
|
||||
}
|
||||
|
||||
.form-field__radio {
|
||||
|
@ -63,12 +63,16 @@
|
|||
}
|
||||
|
||||
select[multiple="multiple"] option {
|
||||
@apply p-2 rounded-md;
|
||||
@apply py-2 rounded-md;
|
||||
}
|
||||
|
||||
select[multiple="multiple"] option:checked {
|
||||
@apply bg-gray-50;
|
||||
@apply after:content-['\2713'] after:float-right after:text-gray-500;
|
||||
@apply after:content-['\2713'] bg-white after:text-gray-500 after:ml-2;
|
||||
}
|
||||
|
||||
select[multiple="multiple"] option:active,
|
||||
select[multiple="multiple"] option:focus {
|
||||
@apply bg-white;
|
||||
}
|
||||
|
||||
.maybe-switch {
|
||||
|
|
|
@ -17,6 +17,9 @@ class TransactionsController < ApplicationController
|
|||
@entry = Current.family.entries.new(entryable: Account::Transaction.new).tap do |e|
|
||||
if params[:account_id]
|
||||
e.account = Current.family.accounts.find(params[:account_id])
|
||||
e.currency = e.account.currency
|
||||
else
|
||||
e.currency = Current.family.currency
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,6 +57,11 @@ module ApplicationHelper
|
|||
render partial: "shared/drawer", locals: { content: content }
|
||||
end
|
||||
|
||||
def disclosure(title, &block)
|
||||
content = capture &block
|
||||
render partial: "shared/disclosure", locals: { title: title, content: content }
|
||||
end
|
||||
|
||||
def sidebar_link_to(name, path, options = {})
|
||||
is_current = current_page?(path) || (request.path.start_with?(path) && path != "/")
|
||||
|
||||
|
|
|
@ -10,11 +10,6 @@ module FormsHelper
|
|||
render partial: "shared/modal_form", locals: { title:, subtitle:, content: }
|
||||
end
|
||||
|
||||
def form_field_tag(options = {}, &block)
|
||||
options[:class] = [ "form-field", options[:class] ].compact.join(" ")
|
||||
tag.div(**options, &block)
|
||||
end
|
||||
|
||||
def radio_tab_tag(form:, name:, value:, label:, icon:, checked: false, disabled: false)
|
||||
form.label name, for: form.field_id(name, value), class: "group has-[:disabled]:cursor-not-allowed" do
|
||||
concat radio_tab_contents(label:, icon:)
|
||||
|
@ -27,56 +22,11 @@ module FormsHelper
|
|||
form.select(:period, periods_for_select, { selected: selected }, class: classes, data: { "auto-submit-form-target": "auto" })
|
||||
end
|
||||
|
||||
def money_with_currency_field(form, money_method, options = {})
|
||||
render partial: "shared/money_field", locals: {
|
||||
form: form,
|
||||
money_method: money_method,
|
||||
default_currency: options[:default_currency] || "USD",
|
||||
disable_currency: options[:disable_currency] || false,
|
||||
hide_currency: options[:hide_currency] || false,
|
||||
label: options[:label] || "Amount",
|
||||
required: options[:required] || false
|
||||
}
|
||||
end
|
||||
|
||||
def money_field(form, method, options = {})
|
||||
value = form.object ? form.object.send(method) : nil
|
||||
|
||||
currency = value&.currency || Money::Currency.new(options[:default_currency] || "USD")
|
||||
|
||||
# See "Monetizable" concern
|
||||
money_amount_method = method.to_s.chomp("_money").to_sym
|
||||
|
||||
money_options = {
|
||||
value: value&.amount,
|
||||
placeholder: "100",
|
||||
min: -99999999999999,
|
||||
max: 99999999999999,
|
||||
step: currency.step
|
||||
}
|
||||
|
||||
merged_options = options.merge(money_options)
|
||||
|
||||
form.number_field money_amount_method, merged_options
|
||||
end
|
||||
|
||||
def currency_select_full(form, method, options = {}, html_options = {}, &block)
|
||||
choices = currencies_for_select.map { |currency| [ "#{currency.name} (#{currency.iso_code})", currency.iso_code ] }
|
||||
form.select method, choices, options, html_options, &block
|
||||
end
|
||||
|
||||
def currency_select(form, method, options = {}, html_options = {}, &block)
|
||||
choices = currencies_for_select.map(&:iso_code)
|
||||
form.select method, choices, options, html_options, &block
|
||||
def currencies_for_select
|
||||
Money::Currency.all_instances.sort_by(&:priority)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def currencies_for_select
|
||||
Money::Currency.all_instances
|
||||
.sort_by(&:priority)
|
||||
end
|
||||
|
||||
def radio_tab_contents(label:, icon:)
|
||||
tag.div(class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-gray-400 group-has-[:checked]:bg-white group-has-[:checked]:text-gray-800 group-has-[:checked]:shadow-sm") do
|
||||
concat lucide_icon(icon, class: "w-5 h-5")
|
||||
|
|
|
@ -6,53 +6,69 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
|
|||
text_field_helpers.each do |selector|
|
||||
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def #{selector}(method, options = {})
|
||||
input_html = label_html(method, options) + super(method, merged_options(options))
|
||||
input_html = apply_form_field_wrapper(input_html) unless options[:inline]
|
||||
input_html
|
||||
merged_options = { class: "form-field__input" }.merge(options)
|
||||
label = build_label(method, options)
|
||||
field = super(method, merged_options)
|
||||
|
||||
build_styled_field(label, field, merged_options)
|
||||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
|
||||
def radio_button(method, tag_value, options = {})
|
||||
super(method, tag_value, merged_options(options, "form-field__radio"))
|
||||
merged_options = { class: "form-field__radio" }.merge(options)
|
||||
|
||||
super(method, tag_value, merged_options)
|
||||
end
|
||||
|
||||
def select(method, choices, options = {}, html_options = {})
|
||||
input_html = label_html(method, options) + super(method, choices, options, merged_options(html_options))
|
||||
input_html = apply_form_field_wrapper(input_html, class: "pr-0") unless options[:inline]
|
||||
input_html
|
||||
merged_html_options = { class: "form-field__input" }.merge(html_options)
|
||||
|
||||
label = build_label(method, options)
|
||||
field = super(method, choices, options, merged_html_options)
|
||||
|
||||
build_styled_field(label, field, options, remove_padding_right: true)
|
||||
end
|
||||
|
||||
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
||||
input_html = label_html(method, options) + super(method, collection, value_method, text_method, options, merged_options(html_options))
|
||||
input_html = apply_form_field_wrapper(input_html, class: "pr-0") unless options[:inline]
|
||||
input_html
|
||||
merged_html_options = { class: "form-field__input" }.merge(html_options)
|
||||
|
||||
label = build_label(method, options)
|
||||
field = super(method, collection, value_method, text_method, options, merged_html_options)
|
||||
|
||||
build_styled_field(label, field, options, remove_padding_right: true)
|
||||
end
|
||||
|
||||
def money_field(amount_method, currency_method, options = {})
|
||||
@template.render partial: "shared/money_field", locals: {
|
||||
form: self,
|
||||
amount_method:,
|
||||
currency_method:,
|
||||
**options
|
||||
}
|
||||
end
|
||||
|
||||
def submit(value = nil, options = {})
|
||||
merged_options = { class: "form-field__submit" }.merge(options)
|
||||
value, options = nil, value if value.is_a?(Hash)
|
||||
super(value, merged_options(options, "form-field__submit"))
|
||||
super(value, merged_options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apply_form_field_wrapper(input_html, **options)
|
||||
@template.form_field_tag(**options) do
|
||||
input_html
|
||||
def build_styled_field(label, field, options, remove_padding_right: false)
|
||||
if options[:inline]
|
||||
label + field
|
||||
else
|
||||
@template.tag.div class: [ "form-field", options[:container_class], ("pr-0" if remove_padding_right) ] do
|
||||
label + field
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merged_options(options, default_class = "form-field__input")
|
||||
combined_classes = options.fetch(:class, "") + " #{default_class}"
|
||||
style_options = { class: combined_classes }
|
||||
non_custom_options = options.except(:class, :label, :inline)
|
||||
style_options.merge(non_custom_options)
|
||||
end
|
||||
|
||||
def label_html(method, options)
|
||||
return label(method, class: "form-field__label") if options[:label] == true
|
||||
def build_label(method, options)
|
||||
return "".html_safe unless options[:label]
|
||||
|
||||
return label(method, class: "form-field__label") if options[:label] == true
|
||||
label(method, options[:label], class: "form-field__label")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,6 @@ export default class extends Controller {
|
|||
|
||||
updateAmount(currency) {
|
||||
(new CurrenciesService).get(currency).then((currency) => {
|
||||
console.log(currency)
|
||||
this.amountTarget.step = currency.step;
|
||||
|
||||
if (isFinite(this.amountTarget.value)) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<%= styled_form_with data: { turbo_frame: "_top", controller: "trade-form" },
|
||||
scope: :account_entry,
|
||||
url: entry.new_record? ? account_trades_path(entry.account) : account_entry_path(entry.account, entry) do |form| %>
|
||||
url: account_trades_path(entry.account) do |form| %>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<%= form.select :type, options_for_select([%w[Buy buy], %w[Sell sell], %w[Deposit transfer_in], %w[Withdrawal transfer_out], %w[Interest interest]], "buy"), { label: t(".type") }, { data: { "trade-form-target": "typeInput" } } %>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<%= form.date_field :date, label: true %>
|
||||
|
||||
<div data-trade-form-target="amountInput" hidden>
|
||||
<%= money_with_currency_field form, :amount_money, label: t(".amount"), disable_currency: true %>
|
||||
<%= form.money_field :amount, :currency, label: t(".amount"), disable_currency: true %>
|
||||
</div>
|
||||
|
||||
<div data-trade-form-target="transferAccountInput" hidden>
|
||||
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
|
||||
<div data-trade-form-target="priceInput">
|
||||
<%= money_with_currency_field form, :price_money, label: t(".price"), disable_currency: true %>
|
||||
<%= form.money_field :price, :currency, label: t(".price"), disable_currency: true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,119 +1,162 @@
|
|||
<% entry, transaction, account = @entry, @entry.account_transaction, @entry.account %>
|
||||
|
||||
<%= drawer do %>
|
||||
<div>
|
||||
<header class="mb-4 space-y-1">
|
||||
<div class="flex items-center gap-4">
|
||||
<h3 class="font-medium">
|
||||
<span class="text-2xl"><%= format_money -entry.amount_money %></span>
|
||||
<span class="text-lg text-gray-500"><%= entry.currency %></span>
|
||||
</h3>
|
||||
<header class="mb-4 space-y-1">
|
||||
<div class="flex items-center gap-4">
|
||||
<h3 class="font-medium">
|
||||
<span class="text-2xl">
|
||||
<%= format_money -entry.amount_money %>
|
||||
</span>
|
||||
|
||||
<% if entry.marked_as_transfer? %>
|
||||
<%= lucide_icon "arrow-left-right", class: "text-gray-500 mt-1 w-5 h-5" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<span class="text-lg text-gray-500">
|
||||
<%= entry.currency %>
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<span class="text-sm text-gray-500"><%= entry.date.strftime("%A %d %B") %></span>
|
||||
</header>
|
||||
<% if entry.marked_as_transfer? %>
|
||||
<%= lucide_icon "arrow-left-right", class: "text-gray-500 mt-1 w-5 h-5" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<details class="group space-y-2" open>
|
||||
<summary class="flex list-none items-center justify-between rounded-xl px-3 py-2 text-xs font-medium uppercase text-gray-500 bg-gray-25 focus-visible:outline-none">
|
||||
<h4><%= t(".overview") %></h4>
|
||||
<%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
|
||||
</summary>
|
||||
<span class="text-sm text-gray-500">
|
||||
<%= entry.date.strftime("%A %d %B") %>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div class="pb-6">
|
||||
<%= styled_form_with model: [account, entry], url: account_transaction_path(account, entry), class: "space-y-2", data: { controller: "auto-submit-form" } do |f| %>
|
||||
<%= f.text_field :name, label: t(".name_label"), "data-auto-submit-form-target": "auto" %>
|
||||
<% unless entry.marked_as_transfer? %>
|
||||
<div class="flex space-x-2">
|
||||
<div>
|
||||
<%= f.select :nature, [["Expense", "expense"], ["Income", "income"]], { label: t(".nature"), selected: entry.amount.negative? ? "income" : "expense" }, "data-auto-submit-form-target": "auto" %>
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<%= f.number_field :amount, value: entry.amount.abs, label: t(".amount"), step: "0.01", "data-auto-submit-form-target": "auto", "data-autosubmit-trigger-event": "change" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= f.date_field :date, label: t(".date_label"), max: Date.current, "data-auto-submit-form-target": "auto" %>
|
||||
<div class="space-y-2">
|
||||
<!-- Overview Section -->
|
||||
<%= disclosure t(".overview") do %>
|
||||
<div class="pb-4">
|
||||
<%= styled_form_with model: [account, entry],
|
||||
url: account_transaction_path(account, entry),
|
||||
class: "space-y-2",
|
||||
data: { controller: "auto-submit-form" } do |f| %>
|
||||
<%= f.text_field :name,
|
||||
label: t(".name_label"),
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
|
||||
<%= f.fields_for :entryable do |ef| %>
|
||||
<% unless entry.marked_as_transfer? %>
|
||||
<%= ef.collection_select :category_id, Current.family.categories.alphabetically, :id, :name, { prompt: t(".category_placeholder"), label: t(".category_label"), class: "text-gray-400" }, "data-auto-submit-form-target": "auto" %>
|
||||
<%= ef.collection_select :merchant_id, Current.family.merchants.alphabetically, :id, :name, { prompt: t(".merchant_placeholder"), label: t(".merchant_label"), class: "text-gray-400" }, "data-auto-submit-form-target": "auto" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= f.collection_select :account_id, Current.family.accounts.alphabetically, :id, :name, { prompt: t(".account_placeholder"), label: t(".account_label"), class: "text-gray-500" }, { class: "form-field__input cursor-not-allowed text-gray-400", disabled: "disabled" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="group space-y-2" open>
|
||||
<summary class="flex list-none items-center justify-between rounded-xl px-3 py-2 text-xs font-medium uppercase text-gray-500 bg-gray-25 focus-visible:outline-none">
|
||||
<h4><%= t(".additional") %></h4>
|
||||
<%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
|
||||
</summary>
|
||||
|
||||
<div class="pb-6">
|
||||
<%= styled_form_with model: [account, entry], url: account_transaction_path(account, entry), class: "space-y-2", data: { controller: "auto-submit-form" } do |f| %>
|
||||
|
||||
<%= f.fields_for :entryable do |ef| %>
|
||||
<%= ef.select :tag_ids,
|
||||
options_for_select(Current.family.tags.alphabetically.pluck(:name, :id), transaction.tag_ids),
|
||||
{
|
||||
multiple: true,
|
||||
label: t(".tags_label"),
|
||||
class: "placeholder:text-gray-500"
|
||||
},
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
<%= ef.text_area :notes, label: t(".note_label"), placeholder: t(".note_placeholder"), "data-auto-submit-form-target": "auto" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="group space-y-2" open>
|
||||
<summary class="flex list-none items-center justify-between rounded-xl px-3 py-2 text-xs font-medium uppercase text-gray-500 bg-gray-25 focus-visible:outline-none">
|
||||
<h4><%= t(".settings") %></h4>
|
||||
<%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
|
||||
</summary>
|
||||
|
||||
<div class="pb-6">
|
||||
<%= styled_form_with model: [account, entry], url: account_transaction_path(account, entry), class: "p-3 space-y-3", data: { controller: "auto-submit-form" } do |f| %>
|
||||
<%= f.fields_for :entryable do |ef| %>
|
||||
<div class="flex cursor-pointer items-center gap-2 justify-between">
|
||||
<div class="text-sm space-y-1">
|
||||
<h4 class="text-gray-900"><%= t(".exclude_title") %></h4>
|
||||
<p class="text-gray-500"><%= t(".exclude_subtitle") %></p>
|
||||
</div>
|
||||
|
||||
<div class="relative inline-block select-none">
|
||||
<%= ef.check_box :excluded, class: "sr-only peer", "data-auto-submit-form-target": "auto" %>
|
||||
<label for="account_entry_entryable_attributes_excluded" class="maybe-switch"></label>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= f.date_field :date,
|
||||
label: t(".date_label"),
|
||||
max: Date.current,
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
|
||||
<% unless entry.marked_as_transfer? %>
|
||||
<div class="flex items-center justify-between gap-2 p-3">
|
||||
<div class="text-sm space-y-1">
|
||||
<h4 class="text-gray-900"><%= t(".delete_title") %></h4>
|
||||
<p class="text-gray-500"><%= t(".delete_subtitle") %></p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= f.select :nature,
|
||||
[["Expense", "expense"], ["Income", "income"]],
|
||||
{ container_class: "w-1/3", label: t(".nature"), selected: entry.amount.negative? ? "income" : "expense" },
|
||||
{ data: { "auto-submit-form-target": "auto" } } %>
|
||||
|
||||
<%= button_to t(".delete"),
|
||||
account_entry_path(account, entry),
|
||||
method: :delete,
|
||||
class: "rounded-lg px-3 py-2 text-red-500 text-sm font-medium border border-alpha-black-200",
|
||||
data: { turbo_confirm: true, turbo_frame: "_top" } %>
|
||||
<%= f.money_field :amount, :currency, label: t(".amount"),
|
||||
container_class: "w-2/3",
|
||||
auto_submit: true,
|
||||
min: 0,
|
||||
value: entry.amount.abs %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<%= f.select :account,
|
||||
options_for_select(
|
||||
Current.family.accounts.alphabetically.pluck(:name, :id),
|
||||
entry.account_id
|
||||
),
|
||||
{ label: t(".account_label") },
|
||||
{ disabled: true } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Details Section -->
|
||||
<%= disclosure t(".details") do %>
|
||||
<div class="pb-4">
|
||||
<%= styled_form_with model: [account, entry],
|
||||
url: account_transaction_path(account, entry),
|
||||
class: "space-y-2",
|
||||
data: { controller: "auto-submit-form" } do |f| %>
|
||||
<%= f.fields_for :entryable do |ef| %>
|
||||
<% unless entry.marked_as_transfer? %>
|
||||
<%= ef.collection_select :category_id,
|
||||
Current.family.categories.alphabetically,
|
||||
:id, :name,
|
||||
{ prompt: t(".category_placeholder"),
|
||||
label: t(".category_label"),
|
||||
class: "text-gray-400" },
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
|
||||
<%= ef.collection_select :merchant_id,
|
||||
Current.family.merchants.alphabetically,
|
||||
:id, :name,
|
||||
{ prompt: t(".merchant_placeholder"),
|
||||
label: t(".merchant_label"),
|
||||
class: "text-gray-400" },
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
<% end %>
|
||||
|
||||
<%= ef.select :tag_ids,
|
||||
options_for_select(
|
||||
Current.family.tags.alphabetically.pluck(:name, :id),
|
||||
transaction.tag_ids
|
||||
),
|
||||
{
|
||||
multiple: true,
|
||||
label: t(".tags_label"),
|
||||
container_class: "h-40"
|
||||
},
|
||||
{ "data-auto-submit-form-target": "auto" } %>
|
||||
|
||||
<%= ef.text_area :notes,
|
||||
label: t(".note_label"),
|
||||
placeholder: t(".note_placeholder"),
|
||||
rows: 5,
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Settings Section -->
|
||||
<%= disclosure t(".settings") do %>
|
||||
<div class="pb-4">
|
||||
<!-- Exclude Transaction Form -->
|
||||
<%= styled_form_with model: [account, entry],
|
||||
url: account_transaction_path(account, entry),
|
||||
class: "p-3",
|
||||
data: { controller: "auto-submit-form" } do |f| %>
|
||||
<%= f.fields_for :entryable do |ef| %>
|
||||
<div class="flex cursor-pointer items-center gap-2 justify-between">
|
||||
<div class="text-sm space-y-1">
|
||||
<h4 class="text-gray-900"><%= t(".exclude_title") %></h4>
|
||||
<p class="text-gray-500"><%= t(".exclude_subtitle") %></p>
|
||||
</div>
|
||||
|
||||
<div class="relative inline-block select-none">
|
||||
<%= ef.check_box :excluded,
|
||||
class: "sr-only peer",
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
<label for="account_entry_entryable_attributes_excluded"
|
||||
class="maybe-switch"></label>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<!-- Delete Transaction Form -->
|
||||
<% unless entry.marked_as_transfer? %>
|
||||
<div class="flex items-center justify-between gap-2 p-3">
|
||||
<div class="text-sm space-y-1">
|
||||
<h4 class="text-gray-900"><%= t(".delete_title") %></h4>
|
||||
<p class="text-gray-500"><%= t(".delete_subtitle") %></p>
|
||||
</div>
|
||||
|
||||
<%= button_to t(".delete"),
|
||||
account_entry_path(account, entry),
|
||||
method: :delete,
|
||||
class: "rounded-lg px-3 py-2 text-red-500 text-sm
|
||||
font-medium border border-alpha-black-200",
|
||||
data: { turbo_confirm: true, turbo_frame: "_top" } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<%= f.text_field :name, value: transfer.name, label: t(".description"), placeholder: t(".description_placeholder"), required: true %>
|
||||
<%= f.collection_select :from_account_id, Current.family.accounts.alphabetically, :id, :name, { prompt: t(".select_account"), label: t(".from") }, required: true %>
|
||||
<%= f.collection_select :to_account_id, Current.family.accounts.alphabetically, :id, :name, { prompt: t(".select_account"), label: t(".to") }, required: true %>
|
||||
<%= money_field f, :amount_money, label: t(".amount"), required: true %>
|
||||
<%= f.money_field :amount, :currency, label: t(".amount"), required: true %>
|
||||
<%= f.date_field :date, value: transfer.date, label: t(".date"), required: true, max: Date.current %>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<%= f.hidden_field :accountable_type %>
|
||||
<%= f.text_field :name, placeholder: t(".name_placeholder"), required: "required", label: t(".name_label"), autofocus: true %>
|
||||
<%= f.collection_select :institution_id, Current.family.institutions.alphabetically, :id, :name, { include_blank: t(".ungrouped"), label: t(".institution") } %>
|
||||
<%= money_with_currency_field f, :balance_money, label: t(".balance"), required: true, default_currency: Current.family.currency %>
|
||||
<%= f.money_field :balance, :currency, label: t(".balance"), required: true, default_currency: Current.family.currency %>
|
||||
|
||||
<% if account.new_record? %>
|
||||
<div class="flex items-center gap-2 mt-3 mb-6">
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
<div>
|
||||
<%= styled_form_with model: Current.user, url: settings_preferences_path, class: "space-y-4", data: { controller: "auto-submit-form" } do |form| %>
|
||||
<%= form.fields_for :family_attributes do |family_form| %>
|
||||
<%= currency_select_full family_form, :currency, { label: "Currency", selected: Current.family.currency }, { data: { auto_submit_form_target: "auto" } } %>
|
||||
<%= family_form.select :currency,
|
||||
currencies_for_select.map { |currency| [ "#{currency.name} (#{currency.iso_code})", currency.iso_code ] },
|
||||
{ label: "Currency", selected: Current.family.currency },
|
||||
{ data: { auto_submit_form_target: "auto" } } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
12
app/views/shared/_disclosure.html.erb
Normal file
12
app/views/shared/_disclosure.html.erb
Normal file
|
@ -0,0 +1,12 @@
|
|||
<%# locals: (title:, content:, open: true) %>
|
||||
|
||||
<details class="group space-y-2" <%= "open" if open %>>
|
||||
<summary class="flex list-none items-center justify-between rounded-xl px-3 py-2 text-xs font-medium
|
||||
uppercase text-gray-500 bg-gray-25 focus-visible:outline-none">
|
||||
<h3><%= title %></h3>
|
||||
<%= lucide_icon "chevron-down",
|
||||
class: "group-open:transform group-open:rotate-180 text-gray-500 w-5 h-5" %>
|
||||
</summary>
|
||||
|
||||
<%= content %>
|
||||
</details>
|
|
@ -1,26 +1,53 @@
|
|||
<%# locals: (form:, money_method:, default_currency:, disable_currency: false, hide_currency: false, label: nil, required: false) %>
|
||||
<%# locals: (form:, amount_method:, currency_method:, **options) %>
|
||||
|
||||
<% fallback_label = t(".money-label") %>
|
||||
<% currency = form.object ? (form.object.send(money_method)&.currency || Money::Currency.new(default_currency)) : Money::Currency.new(default_currency) %>
|
||||
<% currency_value = if form.object && form.object.respond_to?(currency_method)
|
||||
form.object.public_send(currency_method)
|
||||
end
|
||||
currency = Money::Currency.new(currency_value || options[:default_currency] || "USD") %>
|
||||
|
||||
<div class="form-field pr-0" data-controller="money-field">
|
||||
<%= form.label label || fallback_label, { class: "form-field__label" } %>
|
||||
<div class="form-field pr-0 <%= options[:container_class] %>" data-controller="money-field">
|
||||
<%= form.label options[:label] || t(".label"), class: "form-field__label" %>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex items-center grow gap-1">
|
||||
<span class="text-gray-500 text-sm" data-money-field-target="symbol"><%= currency.symbol %></span>
|
||||
<%= money_field form, money_method, { inline: true, "data-money-field-target" => "amount", default_currency: currency, required: required } %>
|
||||
<span class="text-gray-500 text-sm" data-money-field-target="symbol">
|
||||
<%= currency.symbol %>
|
||||
</span>
|
||||
|
||||
<%= form.number_field amount_method,
|
||||
class: "form-field__input",
|
||||
inline: true,
|
||||
placeholder: "100",
|
||||
value: if options[:value]
|
||||
sprintf("%.#{currency.default_precision}f", options[:value])
|
||||
elsif form.object && form.object.respond_to?(amount_method)
|
||||
form.object.public_send(amount_method)
|
||||
end,
|
||||
min: options[:min] || -99999999999999,
|
||||
max: options[:max] || 99999999999999,
|
||||
step: currency.step,
|
||||
data: {
|
||||
"money-field-target": "amount",
|
||||
action: "change->money-field#handleAmountChange",
|
||||
"auto-submit-form-target": ("auto" if options[:auto_submit])
|
||||
}.compact,
|
||||
required: options[:required] %>
|
||||
</div>
|
||||
<% unless hide_currency %>
|
||||
|
||||
<% unless options[:hide_currency] %>
|
||||
<div>
|
||||
<%= currency_select form, :currency, { inline: true, selected: currency.iso_code }, {
|
||||
class: "form-field__input text-right pr-8 disabled:text-gray-500",
|
||||
disabled: disable_currency,
|
||||
data: {
|
||||
"money-field-target" => "currency",
|
||||
action: "money-field#handleCurrencyChange"
|
||||
}
|
||||
} %>
|
||||
<%= form.select currency_method,
|
||||
currencies_for_select.map(&:iso_code),
|
||||
{ inline: true },
|
||||
{
|
||||
class: "w-fit pr-5 disabled:text-gray-400 form-field__input",
|
||||
disabled: options[:disable_currency],
|
||||
data: {
|
||||
"money-field-target": "currency",
|
||||
action: "change->money-field#handleCurrencyChange",
|
||||
"auto-submit-form-target": ("auto" if options[:auto_submit])
|
||||
}.compact
|
||||
} %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<section class="space-y-2">
|
||||
<%= f.text_field :name, label: t(".description"), placeholder: t(".description_placeholder"), required: true %>
|
||||
<%= f.collection_select :account_id, Current.family.accounts.alphabetically, :id, :name, { prompt: t(".account_prompt"), label: t(".account") }, required: true %>
|
||||
<%= money_with_currency_field f, :amount_money, label: t(".amount"), required: true, default_currency: @entry.account&.currency || Current.family.currency %>
|
||||
<%= f.money_field :amount, :currency, label: t(".amount"), required: true %>
|
||||
<%= f.hidden_field :entryable_type, value: "Account::Transaction" %>
|
||||
<%= f.fields_for :entryable do |ef| %>
|
||||
<%= ef.collection_select :category_id, Current.family.categories.alphabetically, :id, :name, { prompt: t(".category_prompt"), label: t(".category") } %>
|
||||
|
|
|
@ -14,8 +14,6 @@ en:
|
|||
longer be included in income or spending calculations.
|
||||
show:
|
||||
account_label: Account
|
||||
account_placeholder: Select an account
|
||||
additional: Additional
|
||||
amount: Amount
|
||||
category_label: Category
|
||||
category_placeholder: Select a category
|
||||
|
@ -24,18 +22,19 @@ en:
|
|||
delete_subtitle: This permanently deletes the transaction, affects your historical
|
||||
balances, and cannot be undone.
|
||||
delete_title: Delete transaction
|
||||
details: Details
|
||||
exclude_subtitle: This excludes the transaction from any in-app features or
|
||||
analytics.
|
||||
exclude_title: Exclude transaction
|
||||
merchant_label: Merchant
|
||||
merchant_placeholder: Select a merchant
|
||||
name_label: Name
|
||||
nature: Transaction type
|
||||
nature: Type
|
||||
note_label: Notes
|
||||
note_placeholder: Enter a note
|
||||
overview: Overview
|
||||
settings: Settings
|
||||
tags_label: Select one or more tags
|
||||
tags_label: Tags
|
||||
transaction:
|
||||
remove_transfer: Remove transfer
|
||||
remove_transfer_body: This will remove the transfer from this transaction
|
||||
|
|
|
@ -7,7 +7,7 @@ en:
|
|||
cancel: Cancel
|
||||
title: Are you sure?
|
||||
money_field:
|
||||
money-label: Amount
|
||||
label: Amount
|
||||
no_account_empty_state:
|
||||
new_account: New account
|
||||
no_account_subtitle: Since no accounts have been added, there's no data to display.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue