mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 05:09:38 +02:00
Finish remaining transaction filters (#1189)
* Add type filters to transaction search * Add amount filter
This commit is contained in:
parent
e06f0c76f9
commit
730e58d763
11 changed files with 164 additions and 30 deletions
|
@ -67,9 +67,6 @@ class TransactionsController < ApplicationController
|
|||
redirect_back_or_to transactions_url, notice: t(".success")
|
||||
end
|
||||
|
||||
def rules
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def amount
|
||||
|
@ -93,7 +90,8 @@ class TransactionsController < ApplicationController
|
|||
end
|
||||
|
||||
def search_params
|
||||
params.fetch(:q, {}).permit(:start_date, :end_date, :search, accounts: [], account_ids: [], categories: [], merchants: [])
|
||||
params.fetch(:q, {})
|
||||
.permit(:start_date, :end_date, :search, :amount, :amount_operator, accounts: [], account_ids: [], categories: [], merchants: [], types: [])
|
||||
end
|
||||
|
||||
def transaction_entry_params
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
module TransactionsHelper
|
||||
def transaction_search_filters
|
||||
[
|
||||
{ key: "account_filter", name: "Account", icon: "layers" },
|
||||
{ key: "date_filter", name: "Date", icon: "calendar" },
|
||||
{ key: "type_filter", name: "Type", icon: "shapes" },
|
||||
{ key: "amount_filter", name: "Amount", icon: "hash" },
|
||||
{ key: "category_filter", name: "Category", icon: "tag" },
|
||||
{ key: "merchant_filter", name: "Merchant", icon: "store" }
|
||||
{ key: "account_filter", icon: "layers" },
|
||||
{ key: "date_filter", icon: "calendar" },
|
||||
{ key: "type_filter", icon: "shapes" },
|
||||
{ key: "amount_filter", icon: "hash" },
|
||||
{ key: "category_filter", icon: "tag" },
|
||||
{ key: "merchant_filter", icon: "store" }
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -148,6 +148,27 @@ class Account::Entry < ApplicationRecord
|
|||
query = query.where("account_entries.date >= ?", params[:start_date]) if params[:start_date].present?
|
||||
query = query.where("account_entries.date <= ?", params[:end_date]) if params[:end_date].present?
|
||||
|
||||
if params[:types].present?
|
||||
query = query.where(marked_as_transfer: false) unless params[:types].include?("transfer")
|
||||
|
||||
if params[:types].include?("income") && !params[:types].include?("expense")
|
||||
query = query.where("account_entries.amount < 0")
|
||||
elsif params[:types].include?("expense") && !params[:types].include?("income")
|
||||
query = query.where("account_entries.amount >= 0")
|
||||
end
|
||||
end
|
||||
|
||||
if params[:amount].present? && params[:amount_operator].present?
|
||||
case params[:amount_operator]
|
||||
when "equal"
|
||||
query = query.where("ABS(ABS(account_entries.amount) - ?) <= 0.01", params[:amount].to_f.abs)
|
||||
when "less"
|
||||
query = query.where("ABS(account_entries.amount) < ?", params[:amount].to_f.abs)
|
||||
when "greater"
|
||||
query = query.where("ABS(account_entries.amount) > ?", params[:amount].to_f.abs)
|
||||
end
|
||||
end
|
||||
|
||||
if params[:accounts].present? || params[:account_ids].present?
|
||||
query = query.joins(:account)
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<%# locals: (form:) %>
|
||||
|
||||
<div
|
||||
id="transaction-filters-menu"
|
||||
data-menu-target="content"
|
||||
|
@ -14,24 +16,33 @@
|
|||
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>
|
||||
<span class="text-sm font-medium"><%= t(".#{filter[:key]}") %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col grow">
|
||||
<div class="grow p-2 border-b border-b-alpha-black-100 overflow-y-auto">
|
||||
<div class="grow p-3 border-b border-b-alpha-black-100 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 hover:bg-gray-700 rounded-lg text-sm text-white font-medium cursor-pointer" %>
|
||||
|
||||
<div class="flex justify-between items-center gap-2 bg-white p-3">
|
||||
<div>
|
||||
<% if @q.present? %>
|
||||
<%= link_to t(".clear_filters"), transactions_path, class: "text-sm underline text-gray-500" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= 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 %>
|
||||
<%= t(".cancel") %>
|
||||
<% end %>
|
||||
<%= form.submit t(".apply"), name: nil, class: "py-2 px-3 bg-gray-900 hover:bg-gray-700 rounded-lg text-sm text-white font-medium cursor-pointer" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,24 @@
|
|||
<%= render "transactions/searches/form" %>
|
||||
|
||||
<ul id="transaction-search-filters" class="flex items-center flex-wrap gap-2 mb-4">
|
||||
<% @q.each do |param_key, param_value| %>
|
||||
<% @q.reject { |key| key == "amount_operator" }.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 } %>
|
||||
<% if param_key == "amount" %>
|
||||
<% amount_operator = case @q[:amount_operator]
|
||||
when "equal"
|
||||
t(".equal_to")
|
||||
when "greater"
|
||||
t(".greater_than")
|
||||
when "less"
|
||||
t(".less_than")
|
||||
else
|
||||
t(@q[:amount_operator])
|
||||
end %>
|
||||
<%= render partial: "transactions/searches/filters/badge", locals: { param_key: "amount", param_value: "#{amount_operator} #{param_value}" } %>
|
||||
<% else %>
|
||||
<% Array(param_value).each do |value| %>
|
||||
<%= render partial: "transactions/searches/filters/badge", locals: { param_key: param_key, param_value: value } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
<%# 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 class="space-y-2">
|
||||
<div class="form-field">
|
||||
<%= form.select :amount_operator, options_for_select([
|
||||
[t(".equal_to"), "equal"],
|
||||
[t(".greater_than"), "greater"],
|
||||
[t(".less_than"), "less"]
|
||||
], @q[:amount_operator] || "equal"), {}, class: "form-field__input" %>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<%= form.number_field :amount, step: 0.01, class: "form-field__input", placeholder: t(".placeholder"), value: @q[:amount] %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
<%# 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 %>
|
||||
<%= t(".on_or_after", date: param_value) %>
|
||||
<% else %>
|
||||
on or before <%= param_value %>
|
||||
<%= t(".on_or_before", date: param_value) %>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -22,6 +21,20 @@
|
|||
<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>
|
||||
<% elsif param_key == "amount" %>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= lucide_icon "hash", class: "w-5 h-5 text-gray-500" %>
|
||||
<p><%= param_value %></p>
|
||||
</div>
|
||||
<% elsif param_key == "types" %>
|
||||
<div class="flex items-center gap-2 px-1">
|
||||
<div class="w-1 h-3 rounded-full <%= case param_value.downcase
|
||||
when "income" then "bg-green-500"
|
||||
when "expense" then "bg-red-500"
|
||||
when "transfer" then "bg-blue-500"
|
||||
end %>"></div>
|
||||
<p><%= t(".#{param_value.downcase}") %></p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="flex items-center gap-2">
|
||||
<p><%= param_value %></p>
|
||||
|
|
|
@ -1,4 +1,37 @@
|
|||
<%# 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 class="p-2 space-y-3">
|
||||
<div class="flex items-center gap-3" data-filter-name="income">
|
||||
<%= form.check_box :types,
|
||||
{
|
||||
multiple: true,
|
||||
checked: @q[:types]&.include?("income"),
|
||||
class: "maybe-checkbox maybe-checkbox--light"
|
||||
},
|
||||
"income",
|
||||
nil %>
|
||||
<%= form.label :types, t(".income"), value: "income", class: "text-sm text-gray-900" %>
|
||||
</div>
|
||||
<div class="flex items-center gap-3" data-filter-name="expense">
|
||||
<%= form.check_box :types,
|
||||
{
|
||||
multiple: true,
|
||||
checked: @q[:types]&.include?("expense"),
|
||||
class: "maybe-checkbox maybe-checkbox--light"
|
||||
},
|
||||
"expense",
|
||||
nil %>
|
||||
<%= form.label :types, t(".expense"), value: "expense", class: "text-sm text-gray-900" %>
|
||||
</div>
|
||||
<div class="flex items-center gap-3" data-filter-name="transfer">
|
||||
<%= form.check_box :types,
|
||||
{
|
||||
multiple: true,
|
||||
checked: @q[:types]&.include?("transfer"),
|
||||
class: "maybe-checkbox maybe-checkbox--light"
|
||||
},
|
||||
"transfer",
|
||||
nil %>
|
||||
<%= form.label :types, t(".transfer"), value: "transfer", class: "text-sm text-gray-900" %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -44,5 +44,36 @@ en:
|
|||
success: Marked as transfer
|
||||
new:
|
||||
new_transaction: New transaction
|
||||
searches:
|
||||
filters:
|
||||
amount_filter:
|
||||
equal_to: Equal to
|
||||
greater_than: Greater than
|
||||
less_than: Less than
|
||||
placeholder: '0'
|
||||
badge:
|
||||
expense: Expense
|
||||
income: Income
|
||||
on_or_after: on or after %{date}
|
||||
on_or_before: on or before %{date}
|
||||
transfer: Transfer
|
||||
type_filter:
|
||||
expense: Expense
|
||||
income: Income
|
||||
transfer: Transfer
|
||||
menu:
|
||||
account_filter: Account
|
||||
amount_filter: Amount
|
||||
apply: Apply
|
||||
cancel: Cancel
|
||||
category_filter: Category
|
||||
clear_filters: Clear filters
|
||||
date_filter: Date
|
||||
merchant_filter: Merchant
|
||||
type_filter: Type
|
||||
search:
|
||||
equal_to: equal to
|
||||
greater_than: greater than
|
||||
less_than: less than
|
||||
unmark_transfers:
|
||||
success: Transfer removed
|
||||
|
|
|
@ -91,7 +91,6 @@ Rails.application.routes.draw do
|
|||
post "bulk_update"
|
||||
post "mark_transfers"
|
||||
post "unmark_transfers"
|
||||
get "rules"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -77,10 +77,11 @@ class TransactionsTest < ApplicationSystemTestCase
|
|||
fill_in "q_end_date", with: 1.day.ago.to_date
|
||||
|
||||
click_button "Type"
|
||||
assert_text "Filter by type coming soon..."
|
||||
check("Income")
|
||||
|
||||
click_button "Amount"
|
||||
assert_text "Filter by amount coming soon..."
|
||||
select "Less than"
|
||||
fill_in "q_amount", with: 200
|
||||
|
||||
click_button "Category"
|
||||
check(category.name)
|
||||
|
@ -102,6 +103,8 @@ class TransactionsTest < ApplicationSystemTestCase
|
|||
find("li", text: account.name).first("a").click
|
||||
find("li", text: "on or after #{10.days.ago.to_date}").first("a").click
|
||||
find("li", text: "on or before #{1.day.ago.to_date}").first("a").click
|
||||
find("li", text: "Income").first("a").click
|
||||
find("li", text: "less than 200").first("a").click
|
||||
find("li", text: category.name).first("a").click
|
||||
find("li", text: merchant.name).first("a").click
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue