mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-02 20:15:22 +02:00
Transactions cleanup (#817)
An overhaul and cleanup of the transactions feature including: - Simplification of transactions search and filtering - Consolidation of account sync logic after transaction change - Split sidebar modal and modal into "drawer" and "modal" concepts - Refactor of transaction partials and folder organization - Cleanup turbo frames and streams for transaction updates, including new Transactions::RowsController for inline updates - Refactored and added several integration and systems tests
This commit is contained in:
parent
ee162bbef7
commit
4ebc08e5a4
61 changed files with 789 additions and 683 deletions
|
@ -7,11 +7,14 @@
|
|||
<span class="text-sm">New transaction</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if transactions.empty? %>
|
||||
<p class="text-gray-500 py-4">No transactions for this account yet.</p>
|
||||
<% else %>
|
||||
<div class="space-y-6">
|
||||
<%= render partial: "transactions/transaction_group", collection: transactions.group_by(&:date), as: :transaction_group %>
|
||||
<% transactions.group_by(&:date).each do |date, transactions| %>
|
||||
<%= transactions_group(date, transactions) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
</div>
|
||||
|
||||
<div class="mb-8 space-y-4">
|
||||
<%= render partial: "imports/transactions/transaction_group", collection: @import.dry_run.group_by(&:date) %>
|
||||
<% @import.dry_run.group_by(&:date).each do |date, draft_transactions| %>
|
||||
<%= transactions_group(date, draft_transactions, "imports/transactions/transaction") %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= button_to "Import " + @import.csv.table.size.to_s + " transactions", confirm_import_path(@import), method: :patch, class: "px-4 py-2 block w-60 text-center mx-auto rounded-lg bg-gray-900 text-white text-sm font-medium", data: { turbo: false } %>
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
<%# locals: (transaction:) %>
|
||||
<div class="text-gray-900 flex items-center gap-6 py-4 text-sm font-medium px-4">
|
||||
<%= render partial: "transactions/transaction_name", locals: { name: transaction.name } %>
|
||||
<div class="text-gray-900 grid grid-cols-8 items-center py-4 text-sm font-medium px-4">
|
||||
<div class="col-span-3">
|
||||
<%= render "transactions/name", transaction: transaction %>
|
||||
</div>
|
||||
|
||||
<div class="w-48">
|
||||
<div class="col-span-2">
|
||||
<%= render partial: "transactions/categories/badge", locals: { category: transaction.category } %>
|
||||
</div>
|
||||
|
||||
<div class="w-48 flex gap-1">
|
||||
<div class="col-span-2 flex items-center gap-1">
|
||||
<% transaction.tags.each do |tag| %>
|
||||
<%= render partial: "tags/badge", locals: { tag: tag } %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto">
|
||||
<%= content_tag :p, format_money(Money.new(-transaction.amount, @import.account.currency)), class: ["whitespace-nowrap", BigDecimal(transaction.amount).negative? ? "text-green-600" : "text-red-600"] %>
|
||||
<div class="col-span-1 justify-self-end">
|
||||
<%= render "transactions/amount", transaction: transaction %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<%# locals: (transaction_group:) %>
|
||||
<% date = transaction_group[0] %>
|
||||
<% transactions = transaction_group[1] %>
|
||||
|
||||
<div class="bg-gray-25 rounded-xl p-1 w-full">
|
||||
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-gray-500">
|
||||
<h4><%= date.strftime("%b %d, %Y") %> · <%= transactions.size %></h4>
|
||||
<span><%= format_money Money.new(-transactions.sum { |t| t.amount }, @import.account.currency) %></span>
|
||||
</div>
|
||||
<div class="bg-white shadow-xs rounded-md border border-alpha-black-25 divide-y divide-alpha-black-50">
|
||||
<%= render partial: "imports/transactions/transaction", collection: transactions %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="h-full">
|
||||
<html class="h-full" lang="en">
|
||||
<head>
|
||||
<title><%= content_for(:title) || "Maybe" %></title>
|
||||
|
||||
|
@ -13,7 +13,8 @@
|
|||
<%= hotwire_livereload_tags if Rails.env.development? %>
|
||||
<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
|
||||
|
||||
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="viewport"
|
||||
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="Maybe">
|
||||
|
@ -30,10 +31,13 @@
|
|||
<%= content_for?(:content) ? yield(:content) : yield %>
|
||||
|
||||
<%= turbo_frame_tag "modal" %>
|
||||
<%= turbo_frame_tag "drawer" %>
|
||||
|
||||
<%= render "shared/confirm_modal" %>
|
||||
|
||||
<% if self_hosted? %>
|
||||
<%= render "shared/app_version" %>
|
||||
<% end %>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -161,9 +161,12 @@
|
|||
<p><%= t(".no_transactions") %></p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-gray-500 flex items-center justify-center flex-col bg-gray-25 rounded-md">
|
||||
<%= render partial: "transactions/transaction_group", collection: @transactions.group_by(&:date), as: :transaction_group %>
|
||||
<p class="py-2 text-sm"><%= link_to t(".view_all"), transactions_path %></p>
|
||||
<div class="text-gray-500 p-1 space-y-1 bg-gray-25 rounded-xl">
|
||||
<% @transactions.group_by(&:date).each do |date, transactions| %>
|
||||
<%= transactions_group(date, transactions, "pages/dashboard/transactions/transaction") %>
|
||||
<% end %>
|
||||
|
||||
<p class="py-2 text-sm text-center"><%= link_to t(".view_all"), transactions_path %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<div class="text-gray-900 flex items-center py-4 text-sm font-medium px-4">
|
||||
<div class="grow">
|
||||
<%= render "transactions/name", transaction: transaction %>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto">
|
||||
<%= render "transactions/amount", transaction: transaction %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||
<%= turbo_frame_tag "modal" do %>
|
||||
<%= turbo_frame_tag "drawer" do %>
|
||||
<dialog class="bg-white border border-alpha-black-25 rounded-2xl max-h-[calc(100vh-32px)] max-w-[480px] w-full shadow-xs h-full mt-4 mr-4" data-controller="modal" data-action="click->modal#clickOutside">
|
||||
<div class="flex flex-col h-full p-4">
|
||||
<div class="flex justify-end items-center h-9">
|
9
app/views/shared/_list_group.html.erb
Normal file
9
app/views/shared/_list_group.html.erb
Normal file
|
@ -0,0 +1,9 @@
|
|||
<%# locals: (header:, content:) %>
|
||||
<div class="bg-gray-25 rounded-xl p-1 w-full">
|
||||
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-gray-500">
|
||||
<%= header %>
|
||||
</div>
|
||||
<div class="bg-white shadow-xs rounded-md border border-alpha-black-25 divide-y divide-alpha-black-50">
|
||||
<%= content %>
|
||||
</div>
|
||||
</div>
|
3
app/views/transactions/_amount.html.erb
Normal file
3
app/views/transactions/_amount.html.erb
Normal file
|
@ -0,0 +1,3 @@
|
|||
<%= content_tag :p,
|
||||
format_money(-transaction.amount_money),
|
||||
class: ["text-green-600": transaction.inflow?] %>
|
4
app/views/transactions/_empty.html.erb
Normal file
4
app/views/transactions/_empty.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div class="flex flex-col items-center justify-center py-40">
|
||||
<p class="text-gray-500 mb-2"><%= t(".title") %></p>
|
||||
<p class="text-gray-400 max-w-xs text-center"><%= t(".description") %></p>
|
||||
</div>
|
|
@ -1,48 +0,0 @@
|
|||
<%# locals: (filter:) %>
|
||||
<div class="flex items-center gap-1 text-sm border border-alpha-black-200 rounded-3xl p-1.5">
|
||||
<% case filter[:type] %>
|
||||
<% when "account" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-5 h-5 bg-blue-600/10 text-xs flex items-center justify-center rounded-full"><%= filter[:value].name[0].upcase %></div>
|
||||
<p><%= filter[:value].name %></p>
|
||||
</div>
|
||||
<% when "category" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-2 h-4 text-xs flex items-center justify-center rounded-full" style="background-color: <%= filter[:value].color %>"></div>
|
||||
<p><%= filter[:value].name %></p>
|
||||
</div>
|
||||
<% when "merchant" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-2 h-4 text-xs flex items-center justify-center rounded-full" style="background-color: <%= filter[:value].color %>"></div>
|
||||
<p><%= filter[:value].name %></p>
|
||||
</div>
|
||||
<% when "search" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= lucide_icon "text", class: "w-5 h-5 text-gray-500" %>
|
||||
<p><%= "\"#{filter[:value]}\"".truncate(20) %></p>
|
||||
</div>
|
||||
<% when "date_range" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= lucide_icon "calendar", class: "w-5 h-5 text-gray-500" %>
|
||||
<p>
|
||||
<% if filter[:value][:gteq] && filter[:value][:lteq] %>
|
||||
<%= filter[:value][:gteq] %> → <%= filter[:value][:lteq] %>
|
||||
<% elsif filter[:value][:gteq] %>
|
||||
on or after <%= filter[:value][:gteq] %>
|
||||
<% elsif filter[:value][:lteq] %>
|
||||
on or before <%= filter[:value][:lteq] %>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= form_with url: search_transactions_path, html: { class: "flex items-center" } do |form| %>
|
||||
<%= form.hidden_field :remove_param, value: filter[:original][:key] %>
|
||||
<% if filter[:original][:value] %>
|
||||
<%= form.hidden_field :remove_param_value, value: filter[:original][:value] %>
|
||||
<% else %>
|
||||
<% end %>
|
||||
<%= form.button type: "submit", class: "hover:text-gray-900" do %>
|
||||
<%= lucide_icon "x", class: "w-4 h-4 text-gray-500" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
<%# locals: (filters:) %>
|
||||
<div>
|
||||
<%= turbo_frame_tag "transactions_filters" do %>
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<% filters.each do |filter| %>
|
||||
<%= render partial: "transactions/filter", locals: { filter: filter } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
33
app/views/transactions/_header.html.erb
Normal file
33
app/views/transactions/_header.html.erb
Normal file
|
@ -0,0 +1,33 @@
|
|||
<header class="flex justify-between items-center text-gray-900 font-medium">
|
||||
<h1 class="text-xl">Transactions</h1>
|
||||
<div class="flex items-center gap-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= link_to transaction_categories_path,
|
||||
class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg font-normal" do %>
|
||||
<%= lucide_icon "tags", class: "w-5 h-5 text-gray-500" %>
|
||||
<span class="text-black"><%= t(".edit_categories") %></span>
|
||||
<% end %>
|
||||
|
||||
<%= link_to imports_path,
|
||||
class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg font-normal" do %>
|
||||
<%= lucide_icon "hard-drive-upload", class: "w-5 h-5 text-gray-500" %>
|
||||
<span class="text-black"><%= t(".edit_imports") %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_import_path(enable_type_selector: true), class: "rounded-lg bg-gray-50 border border-gray-200 flex items-center gap-1 justify-center px-3 py-2", data: { turbo_frame: "modal" } do %>
|
||||
<%= lucide_icon("download", class: "text-gray-500 w-4 h-4") %>
|
||||
<p class="text-sm font-medium text-gray-900"><%= t(".import") %></p>
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_transaction_path, class: "rounded-lg bg-gray-900 text-white flex items-center gap-1 justify-center hover:bg-gray-700 px-3 py-2", data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||
<p class="text-sm font-medium">New transaction</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
|
@ -1,32 +0,0 @@
|
|||
<%# locals: (transactions:, pagy:) %>
|
||||
<div>
|
||||
<%= turbo_frame_tag "transactions_list" do %>
|
||||
<% if transactions.empty? %>
|
||||
<div class="flex flex-col items-center justify-center py-40">
|
||||
<p class="text-gray-500 mb-2">No transactions found</p>
|
||||
<p class="text-gray-400 max-w-xs text-center">Try adding a transaction, editing filters or refining your search</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="bg-gray-25 rounded-xl px-5 py-3 text-xs font-medium text-gray-500 flex items-center gap-6 mb-4">
|
||||
<div class="w-96">
|
||||
<p class="uppercase">transaction</p>
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<p class="uppercase">category</p>
|
||||
</div>
|
||||
<div class="grow uppercase flex justify-between items-center gap-5 text-xs font-medium text-gray-500">
|
||||
<p>account</p>
|
||||
<p>amount</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<%= render partial: "transactions/transaction_group", collection: transactions.group_by(&:date), as: :transaction_group %>
|
||||
</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 } %>
|
||||
</nav>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
16
app/views/transactions/_name.html.erb
Normal file
16
app/views/transactions/_name.html.erb
Normal file
|
@ -0,0 +1,16 @@
|
|||
<%= content_tag :div, class: ["flex items-center gap-2"] do %>
|
||||
<div class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-600/5 text-gray-600">
|
||||
<%= transaction.name[0].upcase %>
|
||||
</div>
|
||||
|
||||
<div class="text-gray-900 truncate">
|
||||
<% if transaction.new_record? %>
|
||||
<%= content_tag :p, transaction.name %>
|
||||
<% else %>
|
||||
<%= link_to transaction.name,
|
||||
transaction_path(transaction),
|
||||
data: { turbo_frame: "drawer" },
|
||||
class: "hover:underline hover:text-gray-800" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
|
@ -1,24 +1,7 @@
|
|||
<!-- start mobile pagination -->
|
||||
<div class="flex flex-1 justify-center md:hidden">
|
||||
<% if pagy.prev %>
|
||||
<%= link_to "Previous", pagy_url_for(pagy, pagy.prev), class: "relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-50" %>
|
||||
<% else %>
|
||||
<div class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-200">Previous</div>
|
||||
<% end %>
|
||||
|
||||
<% if pagy.next %>
|
||||
<%= link_to "Next", pagy_url_for(pagy, pagy.next), class: "relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-50" %>
|
||||
<% else %>
|
||||
<div class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-200">Next</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<!-- end mobile pagination -->
|
||||
|
||||
<!-- start desktop pagination -->
|
||||
<div class="hidden md:-mt-px md:flex">
|
||||
<nav class="flex items-center justify-center px-4 mt-4 sm:px-0">
|
||||
<div>
|
||||
<% if pagy.prev %>
|
||||
<%= link_to pagy_url_for(pagy, pagy.prev), class: "inline-flex items-center px-3 py-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" do %>
|
||||
<%= link_to pagy_url_for(pagy, pagy.prev), class: "inline-flex items-center px-3 py-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" do %>
|
||||
<%= lucide_icon("chevron-left", class: "w-5 h-5 text-gray-500") %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
@ -30,7 +13,7 @@
|
|||
<div class="bg-gray-25 rounded-xl">
|
||||
<% pagy.series.each do |series_item| %>
|
||||
<% if series_item.is_a?(Integer) %>
|
||||
<%= link_to pagy_url_for(pagy, series_item), class: "inline-flex items-center px-3 py-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" do %>
|
||||
<%= link_to pagy_url_for(pagy, series_item), class: "inline-flex items-center px-3 py-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" do %>
|
||||
<%= series_item %>
|
||||
<% end %>
|
||||
<% elsif series_item.is_a?(String) %>
|
||||
|
@ -42,16 +25,15 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div>
|
||||
<% if pagy.next %>
|
||||
<%= link_to pagy_url_for(pagy, pagy.next), class: "inline-flex items-center px-3 py-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" do %>
|
||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="inline-flex items-center px-3 py-3 text-sm font-medium hover:border-gray-300">
|
||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-200") %>
|
||||
</div>
|
||||
<div>
|
||||
<% if pagy.next %>
|
||||
<%= link_to pagy_url_for(pagy, pagy.next), class: "inline-flex items-center px-3 py-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" do %>
|
||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-500") %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end desktop pagination -->
|
||||
<% else %>
|
||||
<div class="inline-flex items-center px-3 py-3 text-sm font-medium hover:border-gray-300">
|
||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-200") %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
<%# locals: (q:) %>
|
||||
<div>
|
||||
<%= turbo_frame_tag "transactions_search_form" do %>
|
||||
<%= search_form_for @q, url: search_transactions_path, html: { method: :post, data: { turbo_frame: "transactions_list" } } do |form| %>
|
||||
<div class="flex gap-2 mb-4">
|
||||
<div class="grow">
|
||||
<%= render partial: "transactions/search_form/search_filter", locals: { form: form } %>
|
||||
</div>
|
||||
<div data-controller="menu" class="relative">
|
||||
<button data-menu-target="button" type="button" class="border border-gray-200 block h-full rounded-lg flex items-center gap-2 px-4">
|
||||
<%= lucide_icon("list-filter", class: "w-5 h-5 text-gray-500") %>
|
||||
<p class="text-sm font-medium text-gray-900">Filter</p>
|
||||
</button>
|
||||
<div
|
||||
data-menu-target="content"
|
||||
data-controller="tabs"
|
||||
data-tabs-active-class="bg-gray-25 text-gray-900"
|
||||
data-tabs-default-tab-value="<%= transaction_filter_id(transaction_filter_by_name("Account")) %>"
|
||||
class="hidden absolute flex z-10 h-80 w-[540px] top-12 right-0 border border-alpha-black-25 bg-white rounded-lg shadow-xs">
|
||||
<div class="flex w-44 flex-col items-start p-3 text-sm font-medium text-gray-500 border-r border-r-alpha-black-25">
|
||||
<% transaction_filters.each do |filter| %>
|
||||
<button
|
||||
class="flex text-gray-500 hover:bg-gray-25 items-center gap-2 px-3 rounded-md py-2 w-full"
|
||||
type="button"
|
||||
data-id="<%= transaction_filter_id(filter) %>"
|
||||
data-tabs-target="btn"
|
||||
data-action="tabs#select">
|
||||
<%= lucide_icon(filter[:icon], class: "w-5 h-5") %>
|
||||
<span class="text-sm font-medium"><%= filter[:name] %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex flex-col grow">
|
||||
<div class="grow p-2 border-b border-b-alpha-black-25 overflow-y-auto">
|
||||
<% transaction_filters.each do |filter| %>
|
||||
<div id="<%= transaction_filter_id(filter) %>" data-tabs-target="tab">
|
||||
<%= render partial: "transactions/search_form/#{filter[:partial]}", locals: { form: form } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex justify-end items-center gap-2 bg-white p-3">
|
||||
<%= button_tag type: "reset", data: { action: "menu#close" }, class: "py-2 px-3 bg-gray-50 rounded-lg text-sm text-gray-900 font-medium" do %>
|
||||
Cancel
|
||||
<% end %>
|
||||
<%= button_tag type: "submit", class: "py-2 px-3 bg-gray-900 rounded-lg text-sm text-white font-medium" do %>
|
||||
Apply
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -1,21 +1,19 @@
|
|||
<%# locals: (totals:) %>
|
||||
<%= turbo_frame_tag "transactions_summary" do %>
|
||||
<div class="grid grid-cols-3 bg-white rounded-xl border border-alpha-black-25 shadow-xs px-4 divide-x divide-alpha-black-100">
|
||||
<div class="p-4 space-y-2">
|
||||
<p class="text-sm text-gray-500">Total transactions</p>
|
||||
<p class="text-gray-900 font-medium text-xl"><%= totals[:count] %></p>
|
||||
</div>
|
||||
<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">
|
||||
<%= format_money totals[:income] %>
|
||||
</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">
|
||||
<%= format_money totals[:expense] %>
|
||||
</p>
|
||||
</div>
|
||||
<%# locals: (totals:) %>
|
||||
<div class="grid grid-cols-3 bg-white rounded-xl border border-alpha-black-25 shadow-xs px-4 divide-x divide-alpha-black-100">
|
||||
<div class="p-4 space-y-2">
|
||||
<p class="text-sm text-gray-500">Total transactions</p>
|
||||
<p class="text-gray-900 font-medium text-xl" id="total-transactions"><%= totals[:count] %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<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" id="total-income">
|
||||
<%= format_money totals[:income] %>
|
||||
</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" id="total-expense">
|
||||
<%= format_money totals[:expense] %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
<%# locals: (transaction:) %>
|
||||
<%= turbo_frame_tag dom_id(transaction), class:"text-gray-900 flex items-center gap-6 py-4 text-sm font-medium px-4" do %>
|
||||
<% if full_width_transaction_row?(request.path) %>
|
||||
<%= link_to transaction_path(transaction), data: { turbo_frame: "modal" }, class: "group" do %>
|
||||
<%= render partial: "transactions/transaction_name", locals: { name: transaction.name } %>
|
||||
<% end %>
|
||||
<div class="w-48">
|
||||
<%= render partial: "transactions/categories/menu", locals: { transaction: } %>
|
||||
</div>
|
||||
<div>
|
||||
<p><%= transaction.account.name %></p>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= render partial: "transactions/transaction_name", locals: { name: transaction.name } %>
|
||||
<div class="w-36">
|
||||
<%= render partial: "transactions/categories/badge", locals: { category: transaction.category } %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="ml-auto">
|
||||
<%= content_tag :p, format_money(-transaction.amount_money), class: ["whitespace-nowrap", { "text-green-600": transaction.amount.negative? }] %>
|
||||
<%= turbo_frame_tag dom_id(transaction), class: "grid grid-cols-12 items-center text-gray-900 py-4 text-sm font-medium px-4" do %>
|
||||
<div class="col-span-4">
|
||||
<%= render "transactions/name", transaction: transaction %>
|
||||
</div>
|
||||
|
||||
<div class="col-span-3">
|
||||
<%= render "transactions/categories/menu", transaction: transaction %>
|
||||
</div>
|
||||
|
||||
<%= link_to transaction.account.name,
|
||||
account_path(transaction.account),
|
||||
class: ["col-span-3 hover:underline"] %>
|
||||
|
||||
<div class="col-span-2 ml-auto">
|
||||
<%= render "transactions/amount", transaction: transaction %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<%# locals: (transaction_group:) %>
|
||||
<% date = transaction_group[0] %>
|
||||
<% transactions = transaction_group[1] %>
|
||||
|
||||
<div class="bg-gray-25 rounded-xl p-1 w-full">
|
||||
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-gray-500">
|
||||
<h4><%= date.strftime("%b %d, %Y") %> · <%= transactions.size %></h4>
|
||||
<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 %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
<%# locals: (name:) %>
|
||||
|
||||
<%= content_tag :div, class: ["flex items-center gap-2", { "w-40": !full_width_transaction_row?(request.path), "w-96": full_width_transaction_row?(request.path) }] do %>
|
||||
<div class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-600/5 text-gray-600">
|
||||
<%= name[0].upcase %>
|
||||
</div>
|
||||
<p class="text-gray-900 group-hover:underline group-hover:text-gray-800 truncate">
|
||||
<%= name %>
|
||||
</p>
|
||||
<% end %>
|
|
@ -2,7 +2,7 @@
|
|||
<% is_selected = category.id === @selected_category&.id %>
|
||||
|
||||
<%= content_tag :div, class: ["filterable-item flex justify-between items-center border-none rounded-lg px-2 py-1 group w-full", { "bg-gray-25": is_selected }], data: { filter_name: category.name } do %>
|
||||
<%= button_to transaction_path(@transaction, transaction: { category_id: category.id }), method: :patch, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
|
||||
<%= button_to transaction_row_path(@transaction, transaction: { category_id: category.id }), method: :patch, data: { turbo_frame: dom_id(@transaction) }, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
|
||||
<span class="w-5 h-5">
|
||||
<%= lucide_icon("check", class: "w-5 h-5 text-gray-500") if is_selected %>
|
||||
</span>
|
||||
|
|
|
@ -25,8 +25,9 @@
|
|||
<% end %>
|
||||
|
||||
<% if @transaction.category %>
|
||||
<%= button_to transaction_path(@transaction),
|
||||
<%= button_to transaction_row_path(@transaction),
|
||||
method: :patch,
|
||||
data: { turbo_frame: dom_id(@transaction) },
|
||||
params: { transaction: { category_id: nil } },
|
||||
class: "flex text-sm font-medium items-center gap-2 text-gray-500 w-full rounded-lg p-2 hover:bg-gray-100" do %>
|
||||
<%= lucide_icon "minus", class: "w-5 h-5" %>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<div class="mx-auto md:w-2/3 w-full">
|
||||
<h1 class="font-bold text-4xl">Editing transaction</h1>
|
||||
|
||||
<%= render "form", transaction: @transaction %>
|
||||
|
||||
<%= link_to "Show this transaction", @transaction, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||
<%= link_to "Back to transactions", transactions_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||
</div>
|
|
@ -1,43 +1,32 @@
|
|||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center text-gray-900 font-medium">
|
||||
<h1 class="text-xl">Transactions</h1>
|
||||
<div class="flex items-center gap-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= link_to transaction_categories_path,
|
||||
class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg font-normal" do %>
|
||||
<%= lucide_icon "tags", class: "w-5 h-5 text-gray-500" %>
|
||||
<span class="text-black"><%= t(".edit_categories") %></span>
|
||||
<% end %>
|
||||
|
||||
<%= link_to imports_path,
|
||||
class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg font-normal" do %>
|
||||
<%= lucide_icon "hard-drive-upload", class: "w-5 h-5 text-gray-500" %>
|
||||
<span class="text-black"><%= t(".edit_imports") %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render "header" %>
|
||||
|
||||
<% end %>
|
||||
<%= render partial: "transactions/summary", locals: { totals: @totals } %>
|
||||
|
||||
<%= link_to new_import_path(enable_type_selector: true), class: "rounded-lg bg-gray-50 border border-gray-200 flex items-center gap-1 justify-center px-3 py-2", data: { turbo_frame: "modal" } do %>
|
||||
<%= lucide_icon("download", class: "text-gray-500 w-4 h-4") %>
|
||||
<p class="text-sm font-medium text-gray-900"><%= t(".import") %></p>
|
||||
<% end %>
|
||||
<div id="transactions" class="bg-white rounded-xl border border-alpha-black-25 shadow-xs p-4 space-y-4">
|
||||
|
||||
<%= link_to new_transaction_path, class: "rounded-lg bg-gray-900 text-white flex items-center gap-1 justify-center hover:bg-gray-700 px-3 py-2", data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||
<p class="text-sm font-medium">New transaction</p>
|
||||
<%= render partial: "transactions/searches/search", locals: { transactions: @transactions } %>
|
||||
|
||||
<% if @transactions.present? %>
|
||||
<div class="grid grid-cols-12 bg-gray-25 rounded-xl px-5 py-3 text-xs uppercase font-medium text-gray-500 items-center mb-4">
|
||||
<p class="col-span-4">transaction</p>
|
||||
<p class="col-span-3 pl-4">category</p>
|
||||
<p class="col-span-3">account</p>
|
||||
<p class="col-span-2 justify-self-end">amount</p>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<% @transactions.group_by(&:date).each do |date, transactions| %>
|
||||
<%= transactions_group(date, transactions) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<%= render partial: "transactions/summary", locals: { totals: @totals } %>
|
||||
</div>
|
||||
<div id="transactions" class="bg-white rounded-xl border border-alpha-black-25 shadow-xs p-4 space-y-4">
|
||||
<%= render partial: "transactions/search_form", locals: { q: @q } %>
|
||||
<%= render partial: "transactions/filters", locals: { filters: @filter_list } %>
|
||||
<%= render partial: "transactions/list", locals: { transactions: @transactions, pagy: @pagy } %>
|
||||
<% else %>
|
||||
<%= render "empty" %>
|
||||
<% end %>
|
||||
|
||||
<% if @pagy.pages > 1 %>
|
||||
<%= render "pagination", pagy: @pagy %>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
1
app/views/transactions/rows/show.html.erb
Normal file
1
app/views/transactions/rows/show.html.erb
Normal file
|
@ -0,0 +1 @@
|
|||
<%= render "transactions/transaction", transaction: @transaction %>
|
|
@ -1,5 +0,0 @@
|
|||
<%# locals: (form:) %>
|
||||
<div class="p-3">
|
||||
<%= form.date_field :date_gteq, placeholder: "Start date", class: "block w-full border border-gray-200 rounded-md py-2 pl-3 pr-3 focus:border-gray-500 focus:ring-gray-500 sm:text-sm" %>
|
||||
<%= form.date_field :date_lteq, placeholder: "End date", class: "block w-full border border-gray-200 rounded-md py-2 pl-3 pr-3 focus:border-gray-500 focus:ring-gray-500 sm:text-sm mt-2" %>
|
||||
</div>
|
|
@ -1,8 +0,0 @@
|
|||
<%# locals: (form:) %>
|
||||
<div class="relative flex items-center bg-white border border-gray-200 rounded-lg">
|
||||
<%= form.search_field :category_name_or_merchant_name_or_account_name_or_name_cont,
|
||||
placeholder: "Search transaction by name, merchant, category or amount",
|
||||
class: "placeholder:text-sm placeholder:text-gray-500 relative pl-10 w-full border-none rounded-lg",
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-gray-500 ml-2 absolute inset-0 transform top-1/2 -translate-y-1/2") %>
|
||||
</div>
|
27
app/views/transactions/searches/_form.html.erb
Normal file
27
app/views/transactions/searches/_form.html.erb
Normal file
|
@ -0,0 +1,27 @@
|
|||
<%# locals: (transactions:) %>
|
||||
<%= form_with url: transactions_path,
|
||||
id: "transactions-search",
|
||||
scope: :q,
|
||||
method: :get,
|
||||
data: { controller: "auto-submit-form" } do |form| %>
|
||||
<div class="flex gap-2 mb-4">
|
||||
<div class="grow">
|
||||
<div class="relative flex items-center bg-white border border-gray-200 rounded-lg">
|
||||
<%= form.text_field :search,
|
||||
placeholder: "Search transactions by name",
|
||||
value: @q[:search],
|
||||
class: "placeholder:text-sm placeholder:text-gray-500 relative pl-10 w-full border-none rounded-lg",
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-gray-500 ml-2 absolute inset-0 transform top-1/2 -translate-y-1/2") %>
|
||||
</div>
|
||||
</div>
|
||||
<div data-controller="menu" class="relative">
|
||||
<button id="transaction-filters-button" data-menu-target="button" type="button" class="border border-gray-200 block h-full rounded-lg flex items-center gap-2 px-4">
|
||||
<%= lucide_icon("list-filter", class: "w-5 h-5 text-gray-500") %>
|
||||
<p class="text-sm font-medium text-gray-900">Filter</p>
|
||||
</button>
|
||||
|
||||
<%= render "transactions/searches/menu", form: form %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
37
app/views/transactions/searches/_menu.html.erb
Normal file
37
app/views/transactions/searches/_menu.html.erb
Normal file
|
@ -0,0 +1,37 @@
|
|||
<div
|
||||
id="transaction-filters-menu"
|
||||
data-menu-target="content"
|
||||
data-controller="tabs"
|
||||
data-tabs-active-class="bg-gray-25 text-gray-900"
|
||||
data-tabs-default-tab-value="<%= get_default_transaction_search_filter[:key] %>"
|
||||
class="hidden absolute flex z-10 h-80 w-[540px] top-12 right-0 border border-alpha-black-25 bg-white rounded-lg shadow-xs">
|
||||
<div class="flex w-44 flex-col items-start p-3 text-sm font-medium text-gray-500 border-r border-r-alpha-black-25">
|
||||
<% transaction_search_filters.each do |filter| %>
|
||||
<button
|
||||
class="flex text-gray-500 hover:bg-gray-25 items-center gap-2 px-3 rounded-md py-2 w-full"
|
||||
type="button"
|
||||
data-id="<%= filter[:key] %>"
|
||||
data-tabs-target="btn"
|
||||
data-action="tabs#select">
|
||||
<%= lucide_icon(filter[:icon], class: "w-5 h-5") %>
|
||||
<span class="text-sm font-medium"><%= filter[:name] %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col grow">
|
||||
<div class="grow p-2 border-b border-b-alpha-black-25 overflow-y-auto">
|
||||
<% transaction_search_filters.each do |filter| %>
|
||||
<div id="<%= filter[:key] %>" data-tabs-target="tab">
|
||||
<%= render partial: get_transaction_search_filter_partial_path(filter), locals: { form: form } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex justify-end items-center gap-2 bg-white p-3">
|
||||
<%= button_tag type: "reset", data: { action: "menu#close" }, class: "py-2 px-3 bg-gray-50 rounded-lg text-sm text-gray-900 font-medium" do %>
|
||||
Cancel
|
||||
<% end %>
|
||||
<%= form.submit "Apply", name: nil, class: "py-2 px-3 bg-gray-900 rounded-lg text-sm text-white font-medium" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
app/views/transactions/searches/_search.html.erb
Normal file
11
app/views/transactions/searches/_search.html.erb
Normal file
|
@ -0,0 +1,11 @@
|
|||
<%= render partial: "transactions/searches/form", locals: { transactions: transactions } %>
|
||||
|
||||
<ul id="transaction-search-filters" class="flex items-center flex-wrap gap-2">
|
||||
<% @q.each do |param_key, param_value| %>
|
||||
<% unless param_value.blank? %>
|
||||
<% Array(param_value).each do |value| %>
|
||||
<%= render partial: "transactions/searches/filters/badge", locals: { param_key: param_key, param_value: value } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
|
@ -7,8 +7,15 @@
|
|||
<div class="my-2" id="list" data-list-filter-target="list">
|
||||
<% Current.family.accounts.alphabetically.each do |account| %>
|
||||
<div class="filterable-item flex items-center gap-2 p-2" data-filter-name="<%= account.name %>">
|
||||
<%= form.check_box :account_id_in, { multiple: true, class: "rounded-sm border-gray-300 text-indigo-600 shadow-xs focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" }, account.id, nil %>
|
||||
<%= form.label :account_id_in, account.name, value: account.id, class: "text-sm text-gray-900" %>
|
||||
<%= form.check_box :accounts,
|
||||
{
|
||||
multiple: true,
|
||||
checked: @q[:accounts]&.include?(account.name),
|
||||
class: "rounded-sm border-gray-300 text-indigo-600 shadow-xs focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||
},
|
||||
account.name,
|
||||
nil %>
|
||||
<%= form.label :accounts, account.name, value: account.name, class: "text-sm text-gray-900" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
34
app/views/transactions/searches/filters/_badge.html.erb
Normal file
34
app/views/transactions/searches/filters/_badge.html.erb
Normal file
|
@ -0,0 +1,34 @@
|
|||
<%# locals: (param_key:, param_value:) %>
|
||||
<li class="flex items-center gap-1 text-sm border border-alpha-black-200 rounded-3xl p-1.5">
|
||||
|
||||
<% if param_key == "start_date" || param_key == "end_date" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= lucide_icon "calendar", class: "w-5 h-5 text-gray-500" %>
|
||||
<p>
|
||||
<% if param_key == "start_date" %>
|
||||
on or after <%= param_value %>
|
||||
<% else %>
|
||||
on or before <%= param_value %>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
<% elsif param_key == "search" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= lucide_icon "text", class: "w-5 h-5 text-gray-500" %>
|
||||
<p><%= "\"#{param_value}\"".truncate(20) %></p>
|
||||
</div>
|
||||
<% elsif param_key == "accounts" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-5 h-5 bg-blue-600/10 text-xs flex items-center justify-center rounded-full"><%= param_value[0].upcase %></div>
|
||||
<p><%= param_value %></p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="flex items-center gap-2">
|
||||
<p><%= param_value %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= link_to transactions_path_without_param(param_key, param_value), data: { id: "clear-param-btn", turbo: false }, class: "flex items-center" do %>
|
||||
<%= lucide_icon "x", class: "w-4 h-4 text-gray-500" %>
|
||||
<% end %>
|
||||
</li>
|
|
@ -7,8 +7,15 @@
|
|||
<div class="my-2" id="list" data-list-filter-target="list">
|
||||
<% Current.family.transaction_categories.alphabetically.each do |transaction_category| %>
|
||||
<div class="filterable-item flex items-center gap-2 p-2" data-filter-name="<%= transaction_category.name %>">
|
||||
<%= form.check_box :category_id_in, { "data-auto-submit-form-target": "auto", multiple: true, class: "rounded-sm border-gray-300 text-indigo-600 shadow-xs focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" }, transaction_category.id, nil %>
|
||||
<%= form.label :category_id_in, transaction_category.name, value: transaction_category.id, class: "text-sm text-gray-900 cursor-pointer" do %>
|
||||
<%= form.check_box :categories,
|
||||
{
|
||||
multiple: true,
|
||||
checked: @q[:categories]&.include?(transaction_category.name),
|
||||
class: "rounded-sm border-gray-300 text-indigo-600 shadow-xs focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||
},
|
||||
transaction_category.name,
|
||||
nil %>
|
||||
<%= form.label :categories, transaction_category.name, value: transaction_category.name, class: "text-sm text-gray-900 cursor-pointer" do %>
|
||||
<%= render partial: "transactions/categories/badge", locals: { category: transaction_category } %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -0,0 +1,11 @@
|
|||
<%# locals: (form:) %>
|
||||
<div class="p-3">
|
||||
<%= form.date_field :start_date,
|
||||
placeholder: "Start date",
|
||||
value: @q[:start_date],
|
||||
class: "block w-full border border-gray-200 rounded-md py-2 pl-3 pr-3 focus:border-gray-500 focus:ring-gray-500 sm:text-sm" %>
|
||||
<%= form.date_field :end_date,
|
||||
placeholder: "End date",
|
||||
value: @q[:end_date],
|
||||
class: "block w-full border border-gray-200 rounded-md py-2 pl-3 pr-3 focus:border-gray-500 focus:ring-gray-500 sm:text-sm mt-2" %>
|
||||
</div>
|
|
@ -7,8 +7,15 @@
|
|||
<div class="my-2" id="list" data-list-filter-target="list">
|
||||
<% Current.family.transaction_merchants.alphabetically.each do |merchant| %>
|
||||
<div class="filterable-item flex items-center gap-2 p-2" data-filter-name="<%= merchant.name %>">
|
||||
<%= form.check_box :merchant_id_in, { multiple: true, class: "rounded-sm border-gray-300 text-indigo-600 shadow-xs focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" }, merchant.id, nil %>
|
||||
<%= form.label :merchant_id_in, merchant.name, value: merchant.id, class: "text-sm text-gray-900" %>
|
||||
<%= form.check_box :merchants,
|
||||
{
|
||||
multiple: true,
|
||||
checked: @q[:merchants]&.include?(merchant.name),
|
||||
class: "rounded-sm border-gray-300 text-indigo-600 shadow-xs focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||
},
|
||||
merchant.name,
|
||||
nil %>
|
||||
<%= form.label :merchants, merchant.name, value: merchant.name, class: "text-sm text-gray-900" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||
<%= sidebar_modal do %>
|
||||
<%= drawer do %>
|
||||
<h3 class="font-medium mb-1">
|
||||
<span class="text-2xl"><%= format_money @transaction.amount_money %></span>
|
||||
<span class="text-lg text-gray-500"><%= @transaction.currency %></span>
|
||||
|
@ -62,22 +62,15 @@
|
|||
</summary>
|
||||
|
||||
<div class="mb-2">
|
||||
|
||||
<% if @transaction.tags.any? %>
|
||||
<div class="pt-3 pb-2 flex flex-wrap items-center gap-1">
|
||||
<% @transaction.tags.each do |tag| %>
|
||||
<div class="relative">
|
||||
<%= render partial: "tags/badge", locals: { tag: tag } %>
|
||||
<%= button_to transaction_path(@transaction, transaction: { remove_tag_id: tag.id }), method: :patch, "data-turbo": false, class: "absolute -top-2 -right-1 px-0.5 py rounded-full hover:bg-alpha-black-200 border border-alpha-black-100" do %>
|
||||
<%= lucide_icon("x", class: "w-3 h-3") %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_with model: @transaction, html: { data: { controller: "auto-submit-form", turbo: false } } do |f| %>
|
||||
<%= f.collection_select :tag_id, Current.family.tags.alphabetically.excluding(@transaction.tags), :id, :name, { prompt: "Select a tag", label: "Select a tag", class: "placeholder:text-gray-500" }, "data-auto-submit-form-target": "auto", "data-turbo": false %>
|
||||
<%= form_with model: @transaction, html: { data: { controller: "auto-submit-form" } } do |f| %>
|
||||
<%= f.select :tag_ids,
|
||||
options_for_select(Current.family.tags.alphabetically.pluck(:name, :id), @transaction.tag_ids),
|
||||
{
|
||||
multiple: true,
|
||||
label: t(".select_tags"),
|
||||
class: "placeholder:text-gray-500"
|
||||
},
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue