Skip to content

Commit

Permalink
Preserve transaction filters and transaction focus across page visits (
Browse files Browse the repository at this point in the history
…maybe-finance#1733)

* Preserve transaction filters across page visits

* Preserve params when per_page is updated

* Autofocus selected transactions

* Lint fixes

* Fix syntax error

* Fix filter clearing

* Update e2e tests for new UI

* Consolidate focus behavior into concern

* Lint fixes
  • Loading branch information
zachgoll authored Jan 30, 2025
1 parent 0b17976 commit 282c053
Show file tree
Hide file tree
Showing 34 changed files with 310 additions and 243 deletions.
26 changes: 0 additions & 26 deletions app/controllers/account/entries_controller.rb

This file was deleted.

8 changes: 8 additions & 0 deletions app/controllers/concerns/accountable_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module AccountableResource
extend ActiveSupport::Concern

included do
include ScrollFocusable

layout :with_sidebar
before_action :set_account, only: [ :show, :edit, :update, :destroy ]
before_action :set_link_token, only: :new
Expand All @@ -22,6 +24,12 @@ def new
end

def show
@q = params.fetch(:q, {}).permit(:search)
entries = @account.entries.search(@q).reverse_chronological

set_focused_record(entries, params[:focused_record_id])

@pagy, @entries = pagy(entries, limit: params[:per_page] || "10", params: ->(params) { params.except(:focused_record_id) })
end

def edit
Expand Down
21 changes: 21 additions & 0 deletions app/controllers/concerns/scroll_focusable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module ScrollFocusable
extend ActiveSupport::Concern

def set_focused_record(record_scope, record_id, default_per_page: 10)
return unless record_id.present?

@focused_record = record_scope.find_by(id: record_id)

record_index = record_scope.pluck(:id).index(record_id)

return unless record_index

page_of_focused_record = (record_index / (params[:per_page]&.to_i || default_per_page)) + 1

if params[:page]&.to_i != page_of_focused_record
(
redirect_to(url_for(page: page_of_focused_record, focused_record_id: record_id))
)
end
end
end
78 changes: 76 additions & 2 deletions app/controllers/transactions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
class TransactionsController < ApplicationController
include ScrollFocusable

layout :with_sidebar

before_action :store_params!, only: :index

def index
@q = search_params
search_query = Current.family.transactions.search(@q).reverse_chronological
@pagy, @transaction_entries = pagy(search_query, limit: params[:per_page] || "50")

set_focused_record(search_query, params[:focused_record_id], default_per_page: 50)

@pagy, @transaction_entries = pagy(
search_query,
limit: params[:per_page].presence || default_params[:per_page],
params: ->(params) { params.except(:focused_record_id) }
)

totals_query = search_query.incomes_and_expenses
family_currency = Current.family.currency
Expand All @@ -18,13 +29,76 @@ def index
}
end

def clear_filter
updated_params = stored_params.deep_dup

q_params = updated_params["q"] || {}

param_key = params[:param_key]
param_value = params[:param_value]

if q_params[param_key].is_a?(Array)
q_params[param_key].delete(param_value)
q_params.delete(param_key) if q_params[param_key].empty?
else
q_params.delete(param_key)
end

updated_params["q"] = q_params.presence
Current.session.update!(prev_transaction_page_params: updated_params)

redirect_to transactions_path(updated_params)
end

private
def search_params
params.fetch(:q, {})
cleaned_params = params.fetch(:q, {})
.permit(
:start_date, :end_date, :search, :amount,
:amount_operator, accounts: [], account_ids: [],
categories: [], merchants: [], types: [], tags: []
)
.to_h
.compact_blank

cleaned_params.delete(:amount_operator) unless cleaned_params[:amount].present?

cleaned_params
end

def store_params!
if should_restore_params?
params_to_restore = {}

params_to_restore[:q] = stored_params["q"].presence || default_params[:q]
params_to_restore[:page] = stored_params["page"].presence || default_params[:page]
params_to_restore[:per_page] = stored_params["per_page"].presence || default_params[:per_page]

redirect_to transactions_path(params_to_restore)
else
Current.session.update!(
prev_transaction_page_params: {
q: search_params,
page: params[:page],
per_page: params[:per_page]
}
)
end
end

def should_restore_params?
request.query_parameters.blank? && (stored_params["q"].present? || stored_params["page"].present? || stored_params["per_page"].present?)
end

def stored_params
Current.session.prev_transaction_page_params
end

def default_params
{
q: {},
page: 1,
per_page: 50
}
end
end
20 changes: 0 additions & 20 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,24 +176,4 @@ def show_super_admin_bar?

cookies[:admin] == "true"
end

def custom_pagy_url_for(pagy, page, current_path: nil)
if current_path.blank?
pagy_url_for(pagy, page)
else
uri = URI.parse(current_path)
params = URI.decode_www_form(uri.query || "").to_h

# Delete existing page param if it exists
params.delete("page")
# Add new page param unless it's page 1
params["page"] = page unless page == 1

if params.empty?
uri.path
else
"#{uri.path}?#{URI.encode_www_form(params)}"
end
end
end
end
17 changes: 0 additions & 17 deletions app/helpers/transactions_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,4 @@ def get_transaction_search_filter_partial_path(filter)
def get_default_transaction_search_filter
transaction_search_filters[0]
end

def transactions_path_without_param(param_key, param_value)
updated_params = request.query_parameters.deep_dup

q_params = updated_params[:q] || {}

current_value = q_params[param_key]
if current_value.is_a?(Array)
q_params[param_key] = current_value - [ param_value ]
else
q_params.delete(param_key)
end

updated_params[:q] = q_params

transactions_path(updated_params)
end
end
21 changes: 21 additions & 0 deletions app/javascript/controllers/focus_record_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="focus-record"
export default class extends Controller {
static values = {
id: String,
};

connect() {
const element = document.getElementById(this.idValue);

if (element) {
element.scrollIntoView({ behavior: "smooth" });

// Remove the focused_record_id parameter from URL
const url = new URL(window.location);
url.searchParams.delete("focused_record_id");
window.history.replaceState({}, "", url);
}
}
}
20 changes: 20 additions & 0 deletions app/javascript/controllers/selectable_link_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="selectable-link"
export default class extends Controller {
connect() {
this.element.addEventListener("change", this.handleChange.bind(this));
}

disconnect() {
this.element.removeEventListener("change", this.handleChange.bind(this));
}

handleChange(event) {
const paramName = this.element.name;
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set(paramName, event.target.value);

Turbo.visit(currentUrl.toString());
}
}
93 changes: 0 additions & 93 deletions app/views/account/entries/index.html.erb

This file was deleted.

4 changes: 2 additions & 2 deletions app/views/account/transactions/_transaction.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<%# locals: (entry:, selectable: true, balance_trend: nil) %>
<% transaction, account = entry.account_transaction, entry.account %>

<div class="grid grid-cols-12 items-center text-gray-900 text-sm font-medium p-4">
<div class="grid grid-cols-12 items-center text-gray-900 text-sm font-medium p-4 <%= @focused_record == entry ? "border border-gray-900 rounded-lg" : "" %>">
<div class="pr-10 flex items-center gap-4 <%= balance_trend ? "col-span-6" : "col-span-8" %>">
<% if selectable %>
<%= check_box_tag dom_id(entry, "selection"),
Expand Down Expand Up @@ -45,7 +45,7 @@
<% if entry.account_transaction.transfer? %>
<%= render "transfers/account_links", transfer: entry.account_transaction.transfer, is_inflow: entry.account_transaction.transfer_as_inflow.present? %>
<% else %>
<%= link_to entry.account.name, account_path(entry.account, tab: "transactions"), data: { turbo_frame: "_top" }, class: "hover:underline" %>
<%= link_to entry.account.name, account_path(entry.account, tab: "transactions", focused_record_id: entry.id), data: { turbo_frame: "_top" }, class: "hover:underline" %>
<% end %>
</div>
</div>
Expand Down
Loading

0 comments on commit 282c053

Please sign in to comment.