1
0
Fork 0
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:
Zach Gollwitzer 2024-09-17 10:38:02 -04:00 committed by GitHub
parent e06f0c76f9
commit 730e58d763
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 164 additions and 30 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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 %>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -91,7 +91,6 @@ Rails.application.routes.draw do
post "bulk_update"
post "mark_transfers"
post "unmark_transfers"
get "rules"
end
end

View file

@ -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