mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-22 22:59:39 +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")
|
redirect_back_or_to transactions_url, notice: t(".success")
|
||||||
end
|
end
|
||||||
|
|
||||||
def rules
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def amount
|
def amount
|
||||||
|
@ -93,7 +90,8 @@ class TransactionsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_params
|
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
|
end
|
||||||
|
|
||||||
def transaction_entry_params
|
def transaction_entry_params
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
module TransactionsHelper
|
module TransactionsHelper
|
||||||
def transaction_search_filters
|
def transaction_search_filters
|
||||||
[
|
[
|
||||||
{ key: "account_filter", name: "Account", icon: "layers" },
|
{ key: "account_filter", icon: "layers" },
|
||||||
{ key: "date_filter", name: "Date", icon: "calendar" },
|
{ key: "date_filter", icon: "calendar" },
|
||||||
{ key: "type_filter", name: "Type", icon: "shapes" },
|
{ key: "type_filter", icon: "shapes" },
|
||||||
{ key: "amount_filter", name: "Amount", icon: "hash" },
|
{ key: "amount_filter", icon: "hash" },
|
||||||
{ key: "category_filter", name: "Category", icon: "tag" },
|
{ key: "category_filter", icon: "tag" },
|
||||||
{ key: "merchant_filter", name: "Merchant", icon: "store" }
|
{ key: "merchant_filter", icon: "store" }
|
||||||
]
|
]
|
||||||
end
|
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[:start_date]) if params[:start_date].present?
|
||||||
query = query.where("account_entries.date <= ?", params[:end_date]) if params[:end_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?
|
if params[:accounts].present? || params[:account_ids].present?
|
||||||
query = query.joins(:account)
|
query = query.joins(:account)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
<%# locals: (form:) %>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="transaction-filters-menu"
|
id="transaction-filters-menu"
|
||||||
data-menu-target="content"
|
data-menu-target="content"
|
||||||
|
@ -14,24 +16,33 @@
|
||||||
data-tabs-target="btn"
|
data-tabs-target="btn"
|
||||||
data-action="tabs#select">
|
data-action="tabs#select">
|
||||||
<%= lucide_icon(filter[:icon], class: "w-5 h-5") %>
|
<%= 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>
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col grow">
|
<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| %>
|
<% transaction_search_filters.each do |filter| %>
|
||||||
<div id="<%= filter[:key] %>" data-tabs-target="tab">
|
<div id="<%= filter[:key] %>" data-tabs-target="tab">
|
||||||
<%= render partial: get_transaction_search_filter_partial_path(filter), locals: { form: form } %>
|
<%= render partial: get_transaction_search_filter_partial_path(filter), locals: { form: form } %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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 %>
|
<div class="flex justify-between items-center gap-2 bg-white p-3">
|
||||||
Cancel
|
<div>
|
||||||
|
<% if @q.present? %>
|
||||||
|
<%= link_to t(".clear_filters"), transactions_path, class: "text-sm underline text-gray-500" %>
|
||||||
<% end %>
|
<% 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>
|
||||||
|
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,25 @@
|
||||||
<%= render "transactions/searches/form" %>
|
<%= render "transactions/searches/form" %>
|
||||||
|
|
||||||
<ul id="transaction-search-filters" class="flex items-center flex-wrap gap-2 mb-4">
|
<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? %>
|
<% unless param_value.blank? %>
|
||||||
|
<% 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| %>
|
<% Array(param_value).each do |value| %>
|
||||||
<%= render partial: "transactions/searches/filters/badge", locals: { param_key: param_key, param_value: value } %>
|
<%= render partial: "transactions/searches/filters/badge", locals: { param_key: param_key, param_value: value } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
<%# locals: (form:) %>
|
<%# 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>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
<%# locals: (param_key:, param_value:) %>
|
<%# locals: (param_key:, param_value:) %>
|
||||||
<li class="flex items-center gap-1 text-sm border border-alpha-black-200 rounded-3xl p-1.5">
|
<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" %>
|
<% if param_key == "start_date" || param_key == "end_date" %>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<%= lucide_icon "calendar", class: "w-5 h-5 text-gray-500" %>
|
<%= lucide_icon "calendar", class: "w-5 h-5 text-gray-500" %>
|
||||||
<p>
|
<p>
|
||||||
<% if param_key == "start_date" %>
|
<% if param_key == "start_date" %>
|
||||||
on or after <%= param_value %>
|
<%= t(".on_or_after", date: param_value) %>
|
||||||
<% else %>
|
<% else %>
|
||||||
on or before <%= param_value %>
|
<%= t(".on_or_before", date: param_value) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
<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>
|
<p><%= param_value %></p>
|
||||||
</div>
|
</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 %>
|
<% else %>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<p><%= param_value %></p>
|
<p><%= param_value %></p>
|
||||||
|
|
|
@ -1,4 +1,37 @@
|
||||||
<%# locals: (form:) %>
|
<%# 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>
|
</div>
|
||||||
|
|
|
@ -44,5 +44,36 @@ en:
|
||||||
success: Marked as transfer
|
success: Marked as transfer
|
||||||
new:
|
new:
|
||||||
new_transaction: New transaction
|
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:
|
unmark_transfers:
|
||||||
success: Transfer removed
|
success: Transfer removed
|
||||||
|
|
|
@ -91,7 +91,6 @@ Rails.application.routes.draw do
|
||||||
post "bulk_update"
|
post "bulk_update"
|
||||||
post "mark_transfers"
|
post "mark_transfers"
|
||||||
post "unmark_transfers"
|
post "unmark_transfers"
|
||||||
get "rules"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -77,10 +77,11 @@ class TransactionsTest < ApplicationSystemTestCase
|
||||||
fill_in "q_end_date", with: 1.day.ago.to_date
|
fill_in "q_end_date", with: 1.day.ago.to_date
|
||||||
|
|
||||||
click_button "Type"
|
click_button "Type"
|
||||||
assert_text "Filter by type coming soon..."
|
check("Income")
|
||||||
|
|
||||||
click_button "Amount"
|
click_button "Amount"
|
||||||
assert_text "Filter by amount coming soon..."
|
select "Less than"
|
||||||
|
fill_in "q_amount", with: 200
|
||||||
|
|
||||||
click_button "Category"
|
click_button "Category"
|
||||||
check(category.name)
|
check(category.name)
|
||||||
|
@ -102,6 +103,8 @@ class TransactionsTest < ApplicationSystemTestCase
|
||||||
find("li", text: account.name).first("a").click
|
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 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: "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: category.name).first("a").click
|
||||||
find("li", text: merchant.name).first("a").click
|
find("li", text: merchant.name).first("a").click
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue