mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-05 05:25:24 +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
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>
|
|
@ -0,0 +1,22 @@
|
|||
<%# locals: (form:) %>
|
||||
<div data-controller="list-filter">
|
||||
<div class="relative">
|
||||
<input type="search" autocomplete="off" placeholder="Filter accounts" data-list-filter-target="input" data-action="input->list-filter#filter" class="block w-full border border-gray-200 rounded-md py-2 pl-10 pr-3 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-gray-500 absolute inset-y-0 left-2 top-1/2 transform -translate-y-1/2") %>
|
||||
</div>
|
||||
<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 :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>
|
||||
</div>
|
|
@ -0,0 +1,4 @@
|
|||
<%# locals: (form:) %>
|
||||
<div class="py-12 flex items-center justify-center">
|
||||
<p class="text-gray-500 text-sm">Filter by amount coming soon...</p>
|
||||
</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>
|
|
@ -0,0 +1,24 @@
|
|||
<%# locals: (form:) %>
|
||||
<div data-controller="list-filter">
|
||||
<div class="relative">
|
||||
<input type="search" autocomplete="off" placeholder="Filter category" data-list-filter-target="input" data-action="input->list-filter#filter" class="block w-full border border-gray-200 rounded-md py-2 pl-10 pr-3 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-gray-500 absolute inset-y-0 left-2 top-1/2 transform -translate-y-1/2") %>
|
||||
</div>
|
||||
<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 :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>
|
||||
<% end %>
|
||||
</div>
|
||||
</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>
|
|
@ -0,0 +1,22 @@
|
|||
<%# locals: (form:) %>
|
||||
<div data-controller="list-filter">
|
||||
<div class="relative">
|
||||
<input type="search" autocomplete="off" placeholder="Filter merchants" data-list-filter-target="input" data-action="input->list-filter#filter" class="block w-full border border-gray-200 rounded-md py-2 pl-10 pr-3 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-gray-500 absolute inset-y-0 left-2 top-1/2 transform -translate-y-1/2") %>
|
||||
</div>
|
||||
<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 :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>
|
||||
</div>
|
|
@ -0,0 +1,4 @@
|
|||
<%# locals: (form:) %>
|
||||
<div class="py-12 flex items-center justify-center">
|
||||
<p class="text-gray-500 text-sm">Filter by type coming soon...</p>
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue