Skip to content

Commit

Permalink
Use HTMX on employee records list
Browse files Browse the repository at this point in the history
Rewrote the sort button dropdown to use native events and not jQuery
events, so that HTMX can listen to its change events.
  • Loading branch information
francoisfreitag committed Apr 30, 2024
1 parent 4c8a503 commit a47f689
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 95 deletions.
50 changes: 50 additions & 0 deletions itou/templates/employee_record/includes/list_results.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{% load str_filters %}

<div id="employee-records-container">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
{% with navigation_pages.paginator.count as counter %}
<h3 class="h4 m-0">{{ counter }} résultat{{ counter|pluralizefr }}</h3>
{% endwith %}
</div>
<div>
<span class="fs-sm">Trier par :</span>
<button type="button" class="btn btn-sm btn-link dropdown-toggle p-0" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ ordered_by_label }}
</button>
<div class="dropdown-menu dropdown-menu-end" id="order-form-group">
{% for order_value, order_label in form.order.field.choices %}
<button class="dropdown-item {% if order_value == form.order.value %}active{% endif %}" type="button" value="{{ order_value }}">
{{ order_label }}
</button>
{% endfor %}
</div>
</div>
</div>

{# "Real" employee records objects #}
<div class="employee-records-list">
{% if employee_records_list %}
{% for employee_record in navigation_pages %}
{% include "employee_record/includes/list_item.html" with employee_record=employee_record item=employee_record.job_application only %}
{% endfor %}
{# New employee records i.e. job applications #}
{% else %}
{% for job_application in navigation_pages %}
{% include "employee_record/includes/list_item.html" with employee_record=None item=job_application only %}
{% endfor %}
{% endif %}
</div>

{% if not navigation_pages %}
<div class="c-box c-box--results my-3 my-md-4">
<div class="c-box--results__body">
<p class="mb-0">Aucune fiche salarié avec le statut selectionné.</p>
</div>
</div>
{% endif %}
{% include "includes/pagination.html" with page=navigation_pages boost=True boost_target="#employee-records-container" boost_indicator="#employee-records-container" %}
</div>
{% if request.htmx %}
{% include "employee_record/includes/list_status_help.html" with request=request status=form.status.value only %}
{% endif %}
41 changes: 41 additions & 0 deletions itou/templates/employee_record/includes/list_status_help.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<div id="status-help"{% if request.htmx %} hx-swap-oob="true"{% endif %}>
{% if status == "NEW" %}
<p>
Vous trouverez ici les candidatures validées <b>à partir desquelles vous devez créer de nouvelles fiches salarié</b>.
</p>
{% elif status == "READY" %}
<p class="mb-0">
Vous trouverez ici les fiches salarié complétées
<b>en attente d’envoi à l’ASP, qui a lieu automatiquement à intervalles réguliers</b>.
</p>
<p>
À ce stade, seule la visualisation des informations de la fiche est
possible.
</p>
<p>Merci de votre patience.</p>
{% elif status == "SENT" %}
<p class="mb-0">Vous trouverez ici les fiches salarié complétées et envoyées à l'ASP.</p>
<p>
À ce stade, et en attendant un retour de l'ASP, seule la visualisation des informations de
la fiche est possible.
</p>
{% elif status == "REJECTED" %}
<p class="mb-0">
Vous trouverez ici les fiches salarié envoyées à l'ASP et retournées avec une
erreur.
</p>
<p>Vous pouvez modifier les fiches en erreur et les envoyer à nouveau.</p>
{% elif status == "PROCESSED" %}
<p class="mb-0">Vous trouverez ici les fiches salarié envoyées et validées par l'ASP.</p>
<p>
Aucune action ultérieure n'est possible à ce stade, mais vous pouvez consulter le détail de
la fiche salarié.
</p>
{% elif status == "DISABLED" %}
<p class="mb-0">Vous trouverez ici les fiches salarié que vous avez désactivées.</p>
<p>
En cas de besoin vous pouvez réactiver une fiche, elle sera transférée dans la catégorie
"Nouvelle".
</p>
{% endif %}
</div>
109 changes: 15 additions & 94 deletions itou/templates/employee_record/list.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% extends "layout/base.html" %}
{% load static %}
{% load str_filters %}
{% load list_filters %}
{% load django_bootstrap5 %}

Expand Down Expand Up @@ -40,45 +39,7 @@ <h2 class="h3">Nous transférons vos fiches salarié à l'ASP afin de vous faire
</li>
<li>La visualisation dans l’Extranet IAE 2.0 interviendra dans les 2 heures suivant l’envoi.</li>
</ul>
{% if form.status.value == "NEW" %}
<p>
Vous trouverez ici les candidatures validées <b>à partir desquelles vous devez créer de nouvelles fiches salarié</b>.
</p>
{% elif form.status.value == "READY" %}
<p class="mb-0">
Vous trouverez ici les fiches salarié complétées
<b>en attente d’envoi à l’ASP, qui a lieu automatiquement à intervalles réguliers</b>.
</p>
<p>
À ce stade, seule la visualisation des informations de la fiche est
possible.
</p>
<p>Merci de votre patience.</p>
{% elif form.status.value == "SENT" %}
<p class="mb-0">Vous trouverez ici les fiches salarié complétées et envoyées à l'ASP.</p>
<p>
À ce stade, et en attendant un retour de l'ASP, seule la visualisation des informations de
la fiche est possible.
</p>
{% elif form.status.value == "REJECTED" %}
<p class="mb-0">
Vous trouverez ici les fiches salarié envoyées à l'ASP et retournées avec une
erreur.
</p>
<p>Vous pouvez modifier les fiches en erreur et les envoyer à nouveau.</p>
{% elif form.status.value == "PROCESSED" %}
<p class="mb-0">Vous trouverez ici les fiches salarié envoyées et validées par l'ASP.</p>
<p>
Aucune action ultérieure n'est possible à ce stade, mais vous pouvez consulter le détail de
la fiche salarié.
</p>
{% elif form.status.value == "DISABLED" %}
<p class="mb-0">Vous trouverez ici les fiches salarié que vous avez désactivées.</p>
<p>
En cas de besoin vous pouvez réactiver une fiche, elle sera transférée dans la catégorie
"Nouvelle".
</p>
{% endif %}
{% include "employee_record/includes/list_status_help.html" with request=request status=form.status.value only %}
{% endblock %}

{% block content %}
Expand All @@ -100,7 +61,7 @@ <h2 class="h3">Nous transférons vos fiches salarié à l'ASP afin de vous faire
<span>Filtres des fiches salarié</span>
</button>
<div class="c-aside-filters__card collapse show" id="asideFiltersCollapse">
<form method="get">
<form hx-get="{% url 'employee_record_views:list' %}" hx-trigger="change delay:.5s" hx-indicator="#employee-records-container" hx-target="#employee-records-container" hx-swap="outerHTML" hx-push-url="true">
<div class="c-aside-filters__card__body">
<fieldset>
<legend>Statut</legend>
Expand All @@ -127,69 +88,29 @@ <h2 class="h3">Nous transférons vos fiches salarié à l'ASP afin de vous faire
</div>
</aside>
</div>

<div class="col-12 col-md-8">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
{% with navigation_pages.paginator.count as counter %}
<h3 class="h4 m-0">{{ counter }} résultat{{ counter|pluralizefr }}</h3>
{% endwith %}
</div>
<div>
<span class="fs-sm">Trier par :</span>
<button type="button" class="btn btn-sm btn-link dropdown-toggle p-0" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ ordered_by_label }}
</button>
<div class="dropdown-menu dropdown-menu-end" id="order-form-group">
{% for order_value, order_label in form.order.field.choices %}
<button class="dropdown-item {% if order_value == form.order.value %}active{% endif %}" type="button" value="{{ order_value }}">
{{ order_label }}
</button>
{% endfor %}
</div>
</div>
</div>

{# "Real" employee records objects #}
<div class="employee-records-list">
{% if employee_records_list %}
{% for employee_record in navigation_pages %}
{% include "employee_record/includes/list_item.html" with employee_record=employee_record item=employee_record.job_application only %}
{% endfor %}
{# New employee records i.e. job applications #}
{% else %}
{% for job_application in navigation_pages %}
{% include "employee_record/includes/list_item.html" with employee_record=None item=job_application only %}
{% endfor %}
{% endif %}
</div>

{% if not navigation_pages %}
<div class="c-box c-box--results my-3 my-md-4">
<div class="c-box--results__body">
<p class="mb-0">Aucune fiche salarié avec le statut selectionné.</p>
</div>
</div>
{% endif %}
{% include "includes/pagination.html" with page=navigation_pages %}
</div>
<div class="col-12 col-md-8">{% include "employee_record/includes/list_results.html" %}</div>
</div>
</div>
</section>
{% endblock %}

{% block script %}
{{ block.super }}
<script src='{% static "js/htmx_compat.js" %}'></script>
<!-- Needed to use Select2MultipleWidget. -->
{{ filters_form.media.js }}
<script nonce="{{ CSP_NONCE }}">
$("#asideFiltersCollapse :input").change(function() {
$("#asideFiltersCollapse form").submit();
});
$("#order-form-group :input").click(function(event) {
let input = $("#id_order")
input.val(event.target.value); // Fill the hidden order input of the form
input.change(); // Fire a change event to notify handlers
htmx.onLoad(function(target) {
const orderFormGroup = target.querySelector("#order-form-group");
if (orderFormGroup) {
orderFormGroup.addEventListener("click", function(event) {
const orderHidden = document.getElementById("id_order");
orderHidden.value = event.target.value;
orderHidden.dispatchEvent(new Event("change", {
bubbles: true
}));
});
}
});
</script>
{% endblock %}
2 changes: 1 addition & 1 deletion itou/www/employee_record_views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def list_employee_records(request, template_name="employee_record/list.html"):
"matomo_custom_title": "Fiches salarié ASP",
}

return render(request, template_name, context)
return render(request, "employee_record/includes/list_results.html" if request.htmx else template_name, context)


@login_required
Expand Down
18 changes: 18 additions & 0 deletions tests/www/employee_record_views/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from tests.employee_record import factories as employee_record_factories
from tests.employee_record.factories import EmployeeRecordFactory
from tests.job_applications.factories import JobApplicationWithApprovalNotCancellableFactory
from tests.utils.htmx.test import assertSoupEqual, update_page_with_htmx
from tests.utils.test import BASE_NUM_QUERIES, TestCase, parse_response_to_soup


Expand Down Expand Up @@ -468,6 +469,23 @@ def test_display_result_count(self):
response = self.client.get(self.URL + "?status=READY")
self.assertContains(response, "0 résultat")

def test_htmx(self):
self.client.force_login(self.user)
response = self.client.get(self.URL, {"status": "NEW"})
simulated_page = parse_response_to_soup(response)

[new_status] = simulated_page.find_all("input", attrs={"name": "status", "value": "NEW"})
del new_status["checked"]
[ready_status] = simulated_page.find_all("input", attrs={"name": "status", "value": "READY"})
ready_status["checked"] = ""

response = self.client.get(self.URL, {"status": "READY"}, headers={"HX-Request": "true"})
update_page_with_htmx(simulated_page, f"form[hx-get='{self.URL}']", response)

response = self.client.get(self.URL + "?status=READY")
fresh_page = parse_response_to_soup(response)
assertSoupEqual(simulated_page, fresh_page)


def test_an_active_siae_without_convention_can_not_access_the_view(client):
siae = CompanyFactory(
Expand Down

0 comments on commit a47f689

Please sign in to comment.