Compare commits

...

3 Commits

Author SHA1 Message Date
ucef 52fbd5fcf8 fix: refactoring de la gestion HTMX des filtres avec triggers precis
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m2s
AS Talange CI/CD Pipeline / Build & Run in Docker Container (push) Successful in 3m14s
2026-05-31 02:11:08 +02:00
ucef b6651fabb4 fix: resolution du conflit de formulaires imbriques dans la liste des adherents 2026-05-31 01:57:46 +02:00
ucef 9531019aae feat: redirection vers la page de modification lors de la creation d'un adherent 2026-05-31 01:51:11 +02:00
2 changed files with 97 additions and 91 deletions
@@ -213,11 +213,11 @@ public class AdherentController {
} else { } else {
adherentRepository.save(adherent); adherentRepository.save(adherent);
} }
return "redirect:/adherents";
} else { } else {
adherentRepository.save(adherent); Adherent saved = adherentRepository.save(adherent);
return "redirect:/adherents/" + saved.getId() + "/edit";
} }
return "redirect:/adherents";
} }
@GetMapping("/{id}/edit") @GetMapping("/{id}/edit")
@@ -34,7 +34,17 @@
</a> </a>
</div> </div>
<div id="adherents-table-container" class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden"> <!-- Table Container with Wrapper-level HTMX triggers for filters -->
<div id="adherents-table-container"
hx-get="/adherents"
hx-target="this"
hx-select="#adherents-table-container"
hx-trigger="change from:#filter-row, keyup changed delay:300ms from:#filter-row"
hx-include="#filter-row input, #filter-row select, input[name='query']"
hx-sync="this:replace"
hx-on::config-request="if (event.detail.trigger.tagName !== 'BUTTON') { event.detail.parameters['page'] = 0; }"
class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="p-4 border-b border-gray-200 flex justify-between items-center"> <div class="p-4 border-b border-gray-200 flex justify-between items-center">
<form hx-get="/adherents" hx-target="#adherents-table-container" hx-select="#adherents-table-container" class="flex items-center space-x-2"> <form hx-get="/adherents" hx-target="#adherents-table-container" hx-select="#adherents-table-container" class="flex items-center space-x-2">
<input type="text" name="query" th:value="${searchQuery}" placeholder="Nom, Prénom ou N° Licence..." <input type="text" name="query" th:value="${searchQuery}" placeholder="Nom, Prénom ou N° Licence..."
@@ -44,90 +54,86 @@
</form> </form>
</div> </div>
<form id="filter-form" hx-get="/adherents" hx-target="#adherents-table-container" hx-select="#adherents-table-container" hx-trigger="change, keyup changed delay:300ms" hx-sync="this:replace" hx-on::config-request="if (event.detail.trigger.tagName !== 'BUTTON') { event.detail.parameters['page'] = 0; }" class="m-0"> <table class="w-full text-left border-collapse">
<input type="hidden" name="query" th:value="${searchQuery}"> <thead>
<tr class="bg-gray-50 text-gray-500 text-sm uppercase tracking-wider border-b border-gray-200">
<table class="w-full text-left border-collapse"> <th class="py-3 px-6 font-medium text-left">Nom</th>
<thead> <th class="py-3 px-6 font-medium text-left">Prénom</th>
<tr class="bg-gray-50 text-gray-500 text-sm uppercase tracking-wider border-b border-gray-200"> <th class="py-3 px-6 font-medium text-left">Catégorie</th>
<th class="py-3 px-6 font-medium text-left">Nom</th> <th class="py-3 px-6 font-medium text-center">N° Licence</th>
<th class="py-3 px-6 font-medium text-left">Prénom</th> <th class="py-3 px-6 font-medium text-left">Email</th>
<th class="py-3 px-6 font-medium text-left">Catégorie</th> <th class="py-3 px-6 font-medium text-left">Téléphone</th>
<th class="py-3 px-6 font-medium text-center">N° Licence</th> <th class="py-3 px-6 font-medium text-center">Paiement</th>
<th class="py-3 px-6 font-medium text-left">Email</th> <th class="py-3 px-6 font-medium text-right">Actions</th>
<th class="py-3 px-6 font-medium text-left">Téléphone</th> </tr>
<th class="py-3 px-6 font-medium text-center">Paiement</th> <!-- Filter Row -->
<th class="py-3 px-6 font-medium text-right">Actions</th> <tr id="filter-row" class="bg-gray-100 border-b border-gray-200 text-xs">
</tr> <td class="py-2 px-3">
<!-- Filter Row --> <input type="text" id="filterNom" name="nom" th:value="${nomFilter}" placeholder="Filtrer Nom..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none">
<tr class="bg-gray-100 border-b border-gray-200 text-xs"> </td>
<td class="py-2 px-3"> <td class="py-2 px-3">
<input type="text" id="filterNom" name="nom" th:value="${nomFilter}" placeholder="Filtrer Nom..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none"> <input type="text" id="filterPrenom" name="prenom" th:value="${prenomFilter}" placeholder="Filtrer Prénom..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none">
</td> </td>
<td class="py-2 px-3"> <td class="py-2 px-3">
<input type="text" id="filterPrenom" name="prenom" th:value="${prenomFilter}" placeholder="Filtrer Prénom..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none"> <select id="filterCategorie" name="categorie" class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none">
</td> <option value="">Toutes</option>
<td class="py-2 px-3"> <option th:each="cat : ${categories}" th:value="${cat.nom}" th:text="${cat.nom}" th:selected="${categorieFilter == cat.nom}"></option>
<select id="filterCategorie" name="categorie" class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none"> </select>
<option value="">Toutes</option> </td>
<option th:each="cat : ${categories}" th:value="${cat.nom}" th:text="${cat.nom}" th:selected="${categorieFilter == cat.nom}"></option> <td class="py-2 px-3">
</select> <input type="text" id="filterLicence" name="licence" th:value="${licenceFilter}" placeholder="Filtrer N°..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none">
</td> </td>
<td class="py-2 px-3"> <td class="py-2 px-3">
<input type="text" id="filterLicence" name="licence" th:value="${licenceFilter}" placeholder="Filtrer ..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none"> <input type="text" id="filterEmail" name="email" th:value="${emailFilter}" placeholder="Filtrer Email..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none">
</td> </td>
<td class="py-2 px-3"> <td class="py-2 px-3">
<input type="text" id="filterEmail" name="email" th:value="${emailFilter}" placeholder="Filtrer Email..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none"> <input type="text" id="filterTelephone" name="telephone" th:value="${telephoneFilter}" placeholder="Filtrer l..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none">
</td> </td>
<td class="py-2 px-3"> <td class="py-2 px-3">
<input type="text" id="filterTelephone" name="telephone" th:value="${telephoneFilter}" placeholder="Filtrer Tél..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none"> <select id="filterPaiement" name="paiement" class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none">
</td> <option value="">Tous</option>
<td class="py-2 px-3"> <option value="A_JOUR" th:selected="${paiementFilter == 'A_JOUR'}">À jour</option>
<select id="filterPaiement" name="paiement" class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none"> <option value="RESTE" th:selected="${paiementFilter == 'RESTE'}">Reste à payer</option>
<option value="">Tous</option> <option value="AUCUNE" th:selected="${paiementFilter == 'AUCUNE'}">Aucune licence</option>
<option value="A_JOUR" th:selected="${paiementFilter == 'A_JOUR'}">À jour</option> </select>
<option value="RESTE" th:selected="${paiementFilter == 'RESTE'}">Reste à payer</option> </td>
<option value="AUCUNE" th:selected="${paiementFilter == 'AUCUNE'}">Aucune licence</option> <td class="py-2 px-3"></td>
</select> </tr>
</td> </thead>
<td class="py-2 px-3"></td> <tbody class="divide-y divide-gray-200 text-sm">
</tr> <tr th:if="${#lists.isEmpty(adherents)}">
</thead> <td colspan="8" class="py-8 text-center text-gray-500">Aucun adhérent enregistré.</td>
<tbody class="divide-y divide-gray-200 text-sm"> </tr>
<tr th:if="${#lists.isEmpty(adherents)}"> <tr th:each="adherent : ${adherents}" class="hover:bg-gray-50 transition-colors adherent-row">
<td colspan="8" class="py-8 text-center text-gray-500">Aucun adhérent enregistré.</td> <td class="py-4 px-6 font-medium text-gray-900">
</tr> <span th:text="${adherent.nom}">Dupont</span>
<tr th:each="adherent : ${adherents}" class="hover:bg-gray-50 transition-colors adherent-row"> <div class="text-[10px] text-gray-400 font-normal mt-0.5">
<td class="py-4 px-6 font-medium text-gray-900"> <span th:text="${adherent.sexe == 'FEMININ' ? 'Féminin' : 'Masculin'}">Masculin</span>
<span th:text="${adherent.nom}">Dupont</span> <span class="mx-1">|</span>
<div class="text-[10px] text-gray-400 font-normal mt-0.5"> <span th:text="${adherent.typeMaillot == 'GARDIEN' ? 'Gardien' : 'Joueur'}">Joueur</span>
<span th:text="${adherent.sexe == 'FEMININ' ? 'Féminin' : 'Masculin'}">Masculin</span> </div>
<span class="mx-1">|</span> </td>
<span th:text="${adherent.typeMaillot == 'GARDIEN' ? 'Gardien' : 'Joueur'}">Joueur</span> <td class="py-4 px-6 font-medium text-gray-900" th:text="${adherent.prenom}">Jean</td>
</div> <td class="py-4 px-6 text-gray-600 font-medium"
</td> th:text="${adherent.getLicenceActuelle() != null ? adherent.getLicenceActuelle().getCategorie().getNom() + (adherent.getLicenceActuelle().getEquipe() != null ? ' (' + adherent.getLicenceActuelle().getEquipe().getNom() + ')' : '') : '-'}">-</td>
<td class="py-4 px-6 font-medium text-gray-900" th:text="${adherent.prenom}">Jean</td> <td class="py-4 px-6 text-center text-gray-600 font-mono text-xs"
<td class="py-4 px-6 text-gray-600 font-medium" th:text="${adherent.getLicenceActuelle() != null and adherent.getLicenceActuelle().numeroLicence != null and !adherent.getLicenceActuelle().numeroLicence.isEmpty() ? adherent.getLicenceActuelle().numeroLicence : '-'}">-</td>
th:text="${adherent.getLicenceActuelle() != null ? adherent.getLicenceActuelle().getCategorie().getNom() + (adherent.getLicenceActuelle().getEquipe() != null ? ' (' + adherent.getLicenceActuelle().getEquipe().getNom() + ')' : '') : '-'}">-</td> <td class="py-4 px-6 text-gray-600" th:text="${adherent.email}">jean@example.com</td>
<td class="py-4 px-6 text-center text-gray-600 font-mono text-xs" <td class="py-4 px-6 text-gray-600" th:text="${adherent.telephone}">0600000000</td>
th:text="${adherent.getLicenceActuelle() != null and adherent.getLicenceActuelle().numeroLicence != null and !adherent.getLicenceActuelle().numeroLicence.isEmpty() ? adherent.getLicenceActuelle().numeroLicence : '-'}">-</td> <td class="py-4 px-6 text-center">
<td class="py-4 px-6 text-gray-600" th:text="${adherent.email}">jean@example.com</td> <span class="px-2 py-1 text-xs font-semibold rounded-full"
<td class="py-4 px-6 text-gray-600" th:text="${adherent.telephone}">0600000000</td> th:classappend="${adherent.getStatutPaiement() == 'À jour' ? 'bg-green-100 text-green-800' : (adherent.getStatutPaiement() == 'Aucune licence' ? 'bg-gray-100 text-gray-800' : 'bg-red-100 text-red-800')}"
<td class="py-4 px-6 text-center"> th:text="${adherent.getStatutPaiement()}">À jour</span>
<span class="px-2 py-1 text-xs font-semibold rounded-full" </td>
th:classappend="${adherent.getStatutPaiement() == 'À jour' ? 'bg-green-100 text-green-800' : (adherent.getStatutPaiement() == 'Aucune licence' ? 'bg-gray-100 text-gray-800' : 'bg-red-100 text-red-800')}" <td class="py-4 px-6 text-right flex justify-end space-x-3 items-center">
th:text="${adherent.getStatutPaiement()}">À jour</span> <a th:href="@{/adherents/{id}/edit(id=${adherent.id})}" class="text-indigo-600 hover:text-indigo-900 font-medium bg-indigo-50 px-3 py-1 rounded-lg">Voir / Modifier</a>
</td> <form th:action="@{/adherents/{id}/delete(id=${adherent.id})}" method="post" onsubmit="return confirm('Supprimer cet adhérent ?');">
<td class="py-4 px-6 text-right flex justify-end space-x-3 items-center"> <button type="submit" class="text-red-600 hover:text-red-800 font-medium">Supprimer</button>
<a th:href="@{/adherents/{id}/edit(id=${adherent.id})}" class="text-indigo-600 hover:text-indigo-900 font-medium bg-indigo-50 px-3 py-1 rounded-lg">Voir / Modifier</a> </form>
<form th:action="@{/adherents/{id}/delete(id=${adherent.id})}" method="post" onsubmit="return confirm('Supprimer cet adhérent ?');"> </td>
<button type="submit" class="text-red-600 hover:text-red-800 font-medium">Supprimer</button> </tr>
</form> </tbody>
</td> </table>
</tr>
</tbody>
</table>
</form>
<!-- Pagination Footer --> <!-- Pagination Footer -->
<div class="px-6 py-4 border-t border-gray-200 flex items-center justify-between bg-gray-50 text-sm text-gray-700"> <div class="px-6 py-4 border-t border-gray-200 flex items-center justify-between bg-gray-50 text-sm text-gray-700">
@@ -146,7 +152,7 @@
hx-get="/adherents" hx-get="/adherents"
hx-target="#adherents-table-container" hx-target="#adherents-table-container"
hx-select="#adherents-table-container" hx-select="#adherents-table-container"
hx-include="#filter-form" hx-include="#filter-row input, #filter-row select, input[name='query']"
th:attr="hx-vals=|{'page': ${currentPage - 1}}|" th:attr="hx-vals=|{'page': ${currentPage - 1}}|"
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors"> class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors">
Précédent Précédent
@@ -161,7 +167,7 @@
hx-get="/adherents" hx-get="/adherents"
hx-target="#adherents-table-container" hx-target="#adherents-table-container"
hx-select="#adherents-table-container" hx-select="#adherents-table-container"
hx-include="#filter-form" hx-include="#filter-row input, #filter-row select, input[name='query']"
th:attr="hx-vals=|{'page': ${pageNum}}|" th:attr="hx-vals=|{'page': ${pageNum}}|"
th:text="${pageNum + 1}" th:text="${pageNum + 1}"
class="px-3 py-1 rounded transition-colors" class="px-3 py-1 rounded transition-colors"
@@ -176,7 +182,7 @@
hx-get="/adherents" hx-get="/adherents"
hx-target="#adherents-table-container" hx-target="#adherents-table-container"
hx-select="#adherents-table-container" hx-select="#adherents-table-container"
hx-include="#filter-form" hx-include="#filter-row input, #filter-row select, input[name='query']"
th:attr="hx-vals=|{'page': ${currentPage + 1}}|" th:attr="hx-vals=|{'page': ${currentPage + 1}}|"
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors"> class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors">
Suivant Suivant