feat(all-transactions): turn filter, order and search into a single bar

This commit is contained in:
Herculino Trotta
2025-08-19 14:01:35 -03:00
parent 7301d9f475
commit 791e1000a3
3 changed files with 180 additions and 117 deletions
@@ -152,7 +152,7 @@
<i class="fa-solid fa-filter fa-fw"></i> <i class="fa-solid fa-filter fa-fw"></i>
</button> </button>
{# 2. Search box #} {# Search box #}
<label for="quick-search"> <label for="quick-search">
</label> </label>
<input type="search" <input type="search"
@@ -170,7 +170,7 @@
show <.transaction/> in <#transactions-list/> show <.transaction/> in <#transactions-list/>
when its textContent.toLowerCase() contains my value.toLowerCase()"> when its textContent.toLowerCase() contains my value.toLowerCase()">
{# 3. Order by icon dropdown #} {# Order by icon dropdown #}
<button class="btn btn-secondary dropdown-toggle dropdown-toggle-no-icon" type="button" <button class="btn btn-secondary dropdown-toggle dropdown-toggle-no-icon" type="button"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-toggle="dropdown" aria-expanded="false"
title="{% translate 'Order by' %}"> title="{% translate 'Order by' %}">
@@ -5,121 +5,128 @@
<div id="transactions-list"> <div id="transactions-list">
{% for x in transactions_by_date %} {% for x in transactions_by_date %}
<div id="{{ x.grouper|slugify }}" <div id="{{ x.grouper|slugify }}" class="transactions-divider"
_="on htmx:afterSettle from #transactions if sessionStorage.getItem(my id) is null then sessionStorage.setItem(my id, 'true')"> _="on htmx:afterSwap from #transactions if sessionStorage.getItem(my id) is null then sessionStorage.setItem(my id, 'true')">
<div class="mt-3 mb-1 w-100 tw:text-base border-bottom bg-body"> <div class="mt-3 mb-1 w-100 tw:text-base border-bottom bg-body transactions-divider-title">
<a class="text-decoration-none d-inline-block w-100" <a class="text-decoration-none d-inline-block w-100"
role="button" role="button"
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#c-{{ x.grouper|slugify }}-collapse" data-bs-target="#c-{{ x.grouper|slugify }}-collapse"
id="c-{{ x.grouper|slugify }}-collapsible" id="c-{{ x.grouper|slugify }}-collapsible"
aria-expanded="true" aria-expanded="false"
aria-controls="c-{{ x.grouper|slugify }}-collapse"> aria-controls="c-{{ x.grouper|slugify }}-collapse">
{{ x.grouper }} {{ x.grouper }}
</a> </a>
</div> </div>
<div class="collapse" id="c-{{ x.grouper|slugify }}-collapse" <div class="collapse transactions-divider-collapse" id="c-{{ x.grouper|slugify }}-collapse"
_="on shown.bs.collapse sessionStorage.setItem(the closest parent @id, 'true') _="on shown.bs.collapse sessionStorage.setItem(the closest parent @id, 'true')
on hidden.bs.collapse sessionStorage.setItem(the closest parent @id, 'false') on hidden.bs.collapse sessionStorage.setItem(the closest parent @id, 'false')
on htmx:afterSettle from #transactions on htmx:afterSettle from #transactions or toggle
set state to sessionStorage.getItem(the closest parent @id) set state to sessionStorage.getItem(the closest parent @id)
if state is 'true' or state is null if state is 'true' or state is null
add .show to me add .show to me
set @aria-expanded of #c-{{ x.grouper|slugify }}-collapsible to true set @aria-expanded of #c-{{ x.grouper|slugify }}-collapsible to true
end"> else
remove .show from me
set @aria-expanded of #c-{{ x.grouper|slugify }}-collapsible to false
end
on show
add .show to me
set @aria-expanded of #c-{{ x.grouper|slugify }}-collapsible to true">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
{% for transaction in x.list %} {% for transaction in x.list %}
<c-transaction.item :transaction="transaction"></c-transaction.item> <c-transaction.item
:transaction="transaction"></c-transaction.item>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
{% empty %} {% empty %}
<c-msg.empty <c-msg.empty
title="{% translate "No transactions found" %}" title="{% translate "No transactions found" %}"
subtitle="{% translate "Try adding one" %}"></c-msg.empty> subtitle="{% translate "Try adding one" %}"></c-msg.empty>
{% endfor %} {% endfor %}
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<div class="mt-auto"> <div class="mt-auto">
<input value="{{ page_obj.number }}" name="page" type="hidden" id="page"> <input value="{{ page_obj.number }}" name="page" type="hidden" id="page">
<nav aria-label="{% translate 'Page navigation' %}"> <nav aria-label="{% translate 'Page navigation' %}">
<ul class="pagination justify-content-center mt-5"> <ul class="pagination justify-content-center mt-5">
<li class="page-item"> <li class="page-item">
<a class="page-link tw:cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}" <a class="page-link tw:cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}"
hx-get="{% if page_obj.has_previous %}{% url 'transactions_all_list' %}{% endif %}" hx-get="{% if page_obj.has_previous %}{% url 'transactions_all_list' %}{% endif %}"
hx-vals='{"page": 1}' hx-vals='{"page": 1}'
hx-include="#filter, #order" hx-include="#filter, #order"
hx-target="#transactions-list" hx-target="#transactions-list"
aria-label="Primeira página" aria-label="Primeira página"
hx-swap="show:top"> hx-swap="show:top">
<span aria-hidden="true">&laquo;</span> <span aria-hidden="true">&laquo;</span>
</a> </a>
</li> </li>
{% for page_number in page_obj.paginator.page_range %} {% for page_number in page_obj.paginator.page_range %}
{% comment %} {% comment %}
This conditional allows us to display up to 3 pages before and after the current page This conditional allows us to display up to 3 pages before and after the current page
If you decide to remove this conditional, all the pages will be displayed If you decide to remove this conditional, all the pages will be displayed
You can change the 3 to any number you want e.g You can change the 3 to any number you want e.g
To display only 5 pagination items, change the 3 to 2 (2 before and 2 after the current page) To display only 5 pagination items, change the 3 to 2 (2 before and 2 after the current page)
{% endcomment %} {% endcomment %}
{% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %} {% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %}
{% if page_obj.number == page_number %} {% if page_obj.number == page_number %}
<li class="page-item active"> <li class="page-item active">
<a class="page-link tw:cursor-pointer"> <a class="page-link tw:cursor-pointer">
{{ page_number }} {{ page_number }}
</a> </a>
</li> </li>
{% else %} {% else %}
<li class="page-item"> <li class="page-item">
<a class="page-link tw:cursor-pointer" <a class="page-link tw:cursor-pointer"
hx-get="{% url 'transactions_all_list' %}" hx-get="{% url 'transactions_all_list' %}"
hx-vals='{"page": {{ page_number }}}' hx-vals='{"page": {{ page_number }}}'
hx-include="#filter, #order" hx-include="#filter, #order"
hx-target="#transactions-list" hx-target="#transactions-list"
hx-swap="show:top"> hx-swap="show:top">
{{ page_number }} {{ page_number }}
</a> </a>
</li> </li>
{% endif %}
{% endif %} {% endif %}
{% endfor %} {% endif %}
{% if page_obj.number|add:3 < page_obj.paginator.num_pages %} {% endfor %}
<li class="page-item"> {% if page_obj.number|add:3 < page_obj.paginator.num_pages %}
<a class="page-link disabled" <li class="page-item">
aria-label="..."> <a class="page-link disabled"
<span aria-hidden="true">...</span> aria-label="...">
</a> <span aria-hidden="true">...</span>
</li> </a>
<li class="page-item"> </li>
<a class="page-link tw:cursor-pointer" <li class="page-item">
hx-get="{% url 'transactions_all_list' %}" hx-target="#transactions-list" <a class="page-link tw:cursor-pointer"
hx-vals='{"page": {{ page_obj.paginator.num_pages }}}' hx-get="{% url 'transactions_all_list' %}" hx-target="#transactions-list"
hx-include="#filter, #order" hx-vals='{"page": {{ page_obj.paginator.num_pages }}}'
hx-swap="show:top" hx-include="#filter, #order"
aria-label="Última página"> hx-swap="show:top"
<span aria-hidden="true">{{ page_obj.paginator.num_pages }}</span> aria-label="Última página">
</a> <span aria-hidden="true">{{ page_obj.paginator.num_pages }}</span>
</li> </a>
{% endif %} </li>
<li class="page-item"> {% endif %}
<a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw:cursor-pointer" <li class="page-item">
hx-get="{% if page_obj.has_next %}{% url 'transactions_all_list' %}{% endif %}" <a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw:cursor-pointer"
hx-vals='{"page": {{ page_obj.paginator.num_pages }}}' hx-get="{% if page_obj.has_next %}{% url 'transactions_all_list' %}{% endif %}"
hx-include="#filter, #order" hx-vals='{"page": {{ page_obj.paginator.num_pages }}}'
hx-swap="show:top" hx-include="#filter, #order"
hx-target="#transactions-list" hx-swap="show:top"
aria-label="Next"> hx-target="#transactions-list"
<span aria-hidden="true">&raquo;</span> aria-label="Next">
</a> <span aria-hidden="true">&raquo;</span>
</li> </a>
</ul> </li>
</nav> </ul>
</div> </nav>
</div>
{% endif %} {% endif %}
{# Floating bar#} {# Floating bar#}
<c-ui.transactions-action-bar></c-ui.transactions-action-bar> <c-ui.transactions-action-bar></c-ui.transactions-action-bar>
</div> </div>
@@ -8,45 +8,101 @@
<div class="container px-md-3 py-3 column-gap-5"> <div class="container px-md-3 py-3 column-gap-5">
<div class="row gx-xl-4 gy-3"> <div class="row gx-xl-4 gy-3">
<div class="col-12 col-xl-8 order-2 order-xl-1"> <div class="col-12 col-xl-8 order-2 order-xl-1">
<div class="row mb-1"> <div class="mb-3">
<div class="col-sm-6 col-12"> {# Hidden select to hold the order value and preserve the original update trigger #}
{# Filter transactions button #} <select name="order" id="order" class="d-none" _="on change trigger updated on window">
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" <option value="default" {% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-expanded="false" <option value="older" {% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option>
aria-controls="collapse-filter"> <option value="newer" {% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option>
<i class="fa-solid fa-filter fa-fw me-2"></i>{% translate 'Filter transactions' %} </select>
{# Main control bar with filter, search, and ordering #}
<div class="input-group">
<button class="btn btn-secondary position-relative" type="button"
data-bs-toggle="collapse" data-bs-target="#collapse-filter"
aria-expanded="false" aria-controls="collapse-filter" id="filter-button" hx-preserve
title="{% translate 'Filter transactions' %}">
<i class="fa-solid fa-filter fa-fw"></i>
</button> </button>
{# Search box #}
<label for="quick-search">
</label>
<input type="search"
class="form-control"
placeholder="{% translate 'Search' %}"
hx-preserve
id="quick-search"
_="on input or search or htmx:afterSwap from window
if my value is empty
trigger toggle on <.transactions-divider-collapse/>
else
trigger show on <.transactions-divider-collapse/>
end
show <.transactions-divider-title/> when my value is empty
show <.transaction/> in <#transactions-list/>
when its textContent.toLowerCase() contains my value.toLowerCase()">
{# Order by icon dropdown #}
<button class="btn btn-secondary dropdown-toggle dropdown-toggle-no-icon" type="button"
data-bs-toggle="dropdown" aria-expanded="false"
title="{% translate 'Order by' %}">
<i class="fa-solid fa-sort fa-fw"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button class="dropdown-item {% if order == 'default' %}active{% endif %}" type="button"
_="on click remove .active from .dropdown-item in the closest <ul/>
then add .active to me
then set the value of #order to 'default'
then trigger change on #order">
{% translate 'Default' %}
</button>
</li>
<li>
<button class="dropdown-item {% if order == 'older' %}active{% endif %}" type="button"
_="on click remove .active from .dropdown-item in the closest <ul/>
then add .active to me
then set the value of #order to 'older'
then trigger change on #order">
{% translate 'Oldest first' %}
</button>
</li>
<li>
<button class="dropdown-item {% if order == 'newer' %}active{% endif %}" type="button"
_="on click remove .active from .dropdown-item in the closest <ul/>
then add .active to me
then set the value of #order to 'newer'
then trigger change on #order">
{% translate 'Newest first' %}
</button>
</li>
</ul>
</div> </div>
{# Ordering button#}
<div class="col-sm-6 col-12 tw:content-center my-3 my-sm-0"> {# Filter transactions form #}
<div class="text-sm-end" _="on change trigger updated on window"> <div class="collapse" id="collapse-filter" hx-preserve>
<label for="order">{% translate "Order by" %}</label> <div class="card card-body">
<select <div class="text-end">
class="tw:border-0 tw:focus-visible:outline-0 w-full pe-2 tw:leading-normal text-bg-tertiary tw:font-medium rounded bg-body text-body" <button class="btn btn-outline-danger btn-sm tw:w-fit"
name="order" id="order"> _="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
<option value="default" </div>
{% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
<option value="older" <form _="on change or submit or search trigger updated on window
{% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option> install init_tom_select
<option value="newer" install init_datepicker"
{% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option> id="filter" class="mt-3">
</select> {% crispy filter.form %}
</form>
<div class="text-end">
<button class="btn btn-outline-danger btn-sm tw:w-fit"
_="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
</div>
</div> </div>
</div> </div>
</div> </div>
{# Filter transactions form#}
<div class="collapse" id="collapse-filter">
<div class="card card-body">
<form _="on change or submit or search trigger updated on window end
install init_tom_select
install init_datepicker"
id="filter">
{% crispy filter.form %}
</form>
<button class="btn btn-outline-danger btn-sm"
_="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
</div>
</div>
<div id="transactions" <div id="transactions"
class="show-loading" class="show-loading"
hx-get="{% url 'transactions_all_list' %}" hx-get="{% url 'transactions_all_list' %}"