feat: add pagination to equipment and licence search screens
AS Talange CI/CD Pipeline - Production / Build & Run Unit Tests (push) Successful in 1m53s
AS Talange CI/CD Pipeline - Production / Deploy to Prod Environment (push) Successful in 2m46s

This commit is contained in:
2026-08-03 16:09:52 +02:00
parent b9a7dc089c
commit 8d2a2e486a
4 changed files with 155 additions and 7 deletions
@@ -76,19 +76,35 @@ public class DotationController {
@RequestParam(required = false) Long equipementId, @RequestParam(required = false) Long equipementId,
@RequestParam(required = false) Long categorieId, @RequestParam(required = false) Long categorieId,
@RequestParam(required = false) Boolean fourni, @RequestParam(required = false) Boolean fourni,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
Model model) { Model model) {
Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null); Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null);
Specification<Dotation> spec = buildSpecification(equipementId, categorieId, fourni, saisonActive); Specification<Dotation> spec = buildSpecification(equipementId, categorieId, fourni, saisonActive);
List<Dotation> dotations = dotationRepository.findAll(spec); List<Dotation> allDotations = dotationRepository.findAll(spec);
model.addAttribute("dotations", dotations); int totalElements = allDotations.size();
int totalPages = (int) Math.ceil((double) totalElements / size);
if (page < 0) page = 0;
if (page >= totalPages && totalPages > 0) page = totalPages - 1;
int start = page * size;
int end = Math.min(start + size, totalElements);
List<Dotation> pageContent = (start < totalElements) ? allDotations.subList(start, end) : List.of();
model.addAttribute("dotations", pageContent);
model.addAttribute("equipements", equipementRepository.findAll()); model.addAttribute("equipements", equipementRepository.findAll());
model.addAttribute("categories", categorieRepository.findAll()); model.addAttribute("categories", categorieRepository.findAll());
model.addAttribute("equipementId", equipementId); model.addAttribute("equipementId", equipementId);
model.addAttribute("categorieId", categorieId); model.addAttribute("categorieId", categorieId);
model.addAttribute("fourni", fourni); model.addAttribute("fourni", fourni);
model.addAttribute("currentPage", page);
model.addAttribute("totalPages", totalPages);
model.addAttribute("totalElements", totalElements);
model.addAttribute("pageSize", size);
return "parametrage/equipements_recherche"; return "parametrage/equipements_recherche";
} }
@@ -139,14 +139,25 @@ public class LicenceController {
@RequestParam(required = false) String numeroLicence, @RequestParam(required = false) String numeroLicence,
@RequestParam(required = false) String email, @RequestParam(required = false) String email,
@RequestParam(required = false) String typeDemande, @RequestParam(required = false) String typeDemande,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
org.springframework.ui.Model model) { org.springframework.ui.Model model) {
Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null); Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null);
org.springframework.data.jpa.domain.Specification<Licence> spec = buildLicenceSpecification(nom, prenom, categorieId, numeroLicence, email, typeDemande, saisonActive); org.springframework.data.jpa.domain.Specification<Licence> spec = buildLicenceSpecification(nom, prenom, categorieId, numeroLicence, email, typeDemande, saisonActive);
List<Licence> licences = licenceRepository.findAll(spec); List<Licence> allLicences = licenceRepository.findAll(spec);
model.addAttribute("licences", licences); int totalElements = allLicences.size();
int totalPages = (int) Math.ceil((double) totalElements / size);
if (page < 0) page = 0;
if (page >= totalPages && totalPages > 0) page = totalPages - 1;
int start = page * size;
int end = Math.min(start + size, totalElements);
List<Licence> pageContent = (start < totalElements) ? allLicences.subList(start, end) : List.of();
model.addAttribute("licences", pageContent);
model.addAttribute("categories", categorieRepository.findAll()); model.addAttribute("categories", categorieRepository.findAll());
model.addAttribute("nomFilter", nom); model.addAttribute("nomFilter", nom);
model.addAttribute("prenomFilter", prenom); model.addAttribute("prenomFilter", prenom);
@@ -155,6 +166,11 @@ public class LicenceController {
model.addAttribute("emailFilter", email); model.addAttribute("emailFilter", email);
model.addAttribute("typeDemandeFilter", typeDemande); model.addAttribute("typeDemandeFilter", typeDemande);
model.addAttribute("currentPage", page);
model.addAttribute("totalPages", totalPages);
model.addAttribute("totalElements", totalElements);
model.addAttribute("pageSize", size);
return "parametrage/licences_recherche"; return "parametrage/licences_recherche";
} }
@@ -52,7 +52,7 @@
</form> </form>
</div> </div>
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden"> <div id="equipements-table-container" class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<table class="w-full text-left border-collapse"> <table class="w-full text-left border-collapse">
<thead> <thead>
<tr class="bg-gray-50 text-gray-500 text-xs uppercase tracking-wider border-b border-gray-200"> <tr class="bg-gray-50 text-gray-500 text-xs uppercase tracking-wider border-b border-gray-200">
@@ -67,7 +67,7 @@
</thead> </thead>
<tbody class="divide-y divide-gray-200 text-sm"> <tbody class="divide-y divide-gray-200 text-sm">
<tr th:if="${#lists.isEmpty(dotations)}"> <tr th:if="${#lists.isEmpty(dotations)}">
<td colspan="6" class="py-8 text-center text-gray-500">Aucun résultat trouvé pour cette recherche.</td> <td colspan="7" class="py-8 text-center text-gray-500">Aucun résultat trouvé pour cette recherche.</td>
</tr> </tr>
<tr th:each="dotation : ${dotations}" class="hover:bg-gray-50"> <tr th:each="dotation : ${dotations}" class="hover:bg-gray-50">
<td class="py-3 px-4 font-medium text-gray-900"> <td class="py-3 px-4 font-medium text-gray-900">
@@ -92,6 +92,64 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<!-- 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 th:if="${totalElements > 0}">
Affichage de <span class="font-semibold" th:text="${currentPage * pageSize + 1}">1</span> à
<span class="font-semibold" th:text="${T(java.lang.Math).min((currentPage + 1) * pageSize, totalElements)}">20</span> sur
<span class="font-semibold" th:text="${totalElements}">100</span> équipements
</div>
<div th:if="${totalElements == 0}">
Aucun équipement à afficher
</div>
<div class="flex items-center space-x-2" th:if="${totalPages > 1}">
<!-- Page Précédente -->
<a th:if="${currentPage > 0}"
th:href="@{/admin/equipements/recherche(equipementId=${equipementId},categorieId=${categorieId},fourni=${fourni},page=${currentPage - 1})}"
hx-get="/admin/equipements/recherche"
hx-target="#equipements-table-container"
hx-select="#equipements-table-container"
hx-push-url="true"
th:attr="hx-vals=|{&quot;equipementId&quot;: &quot;${equipementId != null ? equipementId : ''}&quot;, &quot;categorieId&quot;: &quot;${categorieId != null ? categorieId : ''}&quot;, &quot;fourni&quot;: &quot;${fourni != null ? fourni : ''}&quot;, &quot;page&quot;: ${currentPage - 1}}|"
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors">
Précédent
</a>
<span th:if="${currentPage == 0}" class="px-3 py-1 border border-gray-200 rounded text-gray-400 bg-gray-50 cursor-not-allowed">
Précédent
</span>
<!-- Numéros de pages -->
<th:block th:each="pageNum : ${#numbers.sequence(0, totalPages - 1)}" th:if="${totalPages > 0}">
<a th:href="@{/admin/equipements/recherche(equipementId=${equipementId},categorieId=${categorieId},fourni=${fourni},page=${pageNum})}"
hx-get="/admin/equipements/recherche"
hx-target="#equipements-table-container"
hx-select="#equipements-table-container"
hx-push-url="true"
th:attr="hx-vals=|{&quot;equipementId&quot;: &quot;${equipementId != null ? equipementId : ''}&quot;, &quot;categorieId&quot;: &quot;${categorieId != null ? categorieId : ''}&quot;, &quot;fourni&quot;: &quot;${fourni != null ? fourni : ''}&quot;, &quot;page&quot;: ${pageNum}}|"
th:text="${pageNum + 1}"
class="px-3 py-1 rounded transition-colors"
th:classappend="${currentPage == pageNum ? 'bg-blue-600 text-white' : 'border border-gray-300 hover:bg-gray-100'}">
1
</a>
</th:block>
<!-- Page Suivante -->
<a th:if="${currentPage < totalPages - 1}"
th:href="@{/admin/equipements/recherche(equipementId=${equipementId},categorieId=${categorieId},fourni=${fourni},page=${currentPage + 1})}"
hx-get="/admin/equipements/recherche"
hx-target="#equipements-table-container"
hx-select="#equipements-table-container"
hx-push-url="true"
th:attr="hx-vals=|{&quot;equipementId&quot;: &quot;${equipementId != null ? equipementId : ''}&quot;, &quot;categorieId&quot;: &quot;${categorieId != null ? categorieId : ''}&quot;, &quot;fourni&quot;: &quot;${fourni != null ? fourni : ''}&quot;, &quot;page&quot;: ${currentPage + 1}}|"
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors">
Suivant
</a>
<span th:if="${currentPage == totalPages - 1}" class="px-3 py-1 border border-gray-200 rounded text-gray-400 bg-gray-50 cursor-not-allowed">
Suivant
</span>
</div>
</div>
</div> </div>
</div> </div>
@@ -62,7 +62,7 @@
</form> </form>
</div> </div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden"> <div id="licences-table-container" class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full text-left border-collapse min-w-[900px]"> <table class="w-full text-left border-collapse min-w-[900px]">
<thead> <thead>
@@ -96,6 +96,64 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- 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 th:if="${totalElements > 0}">
Affichage de <span class="font-semibold" th:text="${currentPage * pageSize + 1}">1</span> à
<span class="font-semibold" th:text="${T(java.lang.Math).min((currentPage + 1) * pageSize, totalElements)}">20</span> sur
<span class="font-semibold" th:text="${totalElements}">100</span> licences
</div>
<div th:if="${totalElements == 0}">
Aucune licence à afficher
</div>
<div class="flex items-center space-x-2" th:if="${totalPages > 1}">
<!-- Page Précédente -->
<a th:if="${currentPage > 0}"
th:href="@{/admin/licences/recherche(nom=${nomFilter},prenom=${prenomFilter},categorieId=${categorieId},numeroLicence=${numeroLicenceFilter},email=${emailFilter},typeDemande=${typeDemandeFilter},page=${currentPage - 1})}"
hx-get="/admin/licences/recherche"
hx-target="#licences-table-container"
hx-select="#licences-table-container"
hx-push-url="true"
th:attr="hx-vals=|{&quot;nom&quot;: &quot;${nomFilter != null ? nomFilter : ''}&quot;, &quot;prenom&quot;: &quot;${prenomFilter != null ? prenomFilter : ''}&quot;, &quot;categorieId&quot;: &quot;${categorieId != null ? categorieId : ''}&quot;, &quot;numeroLicence&quot;: &quot;${numeroLicenceFilter != null ? numeroLicenceFilter : ''}&quot;, &quot;email&quot;: &quot;${emailFilter != null ? emailFilter : ''}&quot;, &quot;typeDemande&quot;: &quot;${typeDemandeFilter != null ? typeDemandeFilter : ''}&quot;, &quot;page&quot;: ${currentPage - 1}}|"
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors">
Précédent
</a>
<span th:if="${currentPage == 0}" class="px-3 py-1 border border-gray-200 rounded text-gray-400 bg-gray-50 cursor-not-allowed">
Précédent
</span>
<!-- Numéros de pages -->
<th:block th:each="pageNum : ${#numbers.sequence(0, totalPages - 1)}" th:if="${totalPages > 0}">
<a th:href="@{/admin/licences/recherche(nom=${nomFilter},prenom=${prenomFilter},categorieId=${categorieId},numeroLicence=${numeroLicenceFilter},email=${emailFilter},typeDemande=${typeDemandeFilter},page=${pageNum})}"
hx-get="/admin/licences/recherche"
hx-target="#licences-table-container"
hx-select="#licences-table-container"
hx-push-url="true"
th:attr="hx-vals=|{&quot;nom&quot;: &quot;${nomFilter != null ? nomFilter : ''}&quot;, &quot;prenom&quot;: &quot;${prenomFilter != null ? prenomFilter : ''}&quot;, &quot;categorieId&quot;: &quot;${categorieId != null ? categorieId : ''}&quot;, &quot;numeroLicence&quot;: &quot;${numeroLicenceFilter != null ? numeroLicenceFilter : ''}&quot;, &quot;email&quot;: &quot;${emailFilter != null ? emailFilter : ''}&quot;, &quot;typeDemande&quot;: &quot;${typeDemandeFilter != null ? typeDemandeFilter : ''}&quot;, &quot;page&quot;: ${pageNum}}|"
th:text="${pageNum + 1}"
class="px-3 py-1 rounded transition-colors"
th:classappend="${currentPage == pageNum ? 'bg-blue-600 text-white' : 'border border-gray-300 hover:bg-gray-100'}">
1
</a>
</th:block>
<!-- Page Suivante -->
<a th:if="${currentPage < totalPages - 1}"
th:href="@{/admin/licences/recherche(nom=${nomFilter},prenom=${prenomFilter},categorieId=${categorieId},numeroLicence=${numeroLicenceFilter},email=${emailFilter},typeDemande=${typeDemandeFilter},page=${currentPage + 1})}"
hx-get="/admin/licences/recherche"
hx-target="#licences-table-container"
hx-select="#licences-table-container"
hx-push-url="true"
th:attr="hx-vals=|{&quot;nom&quot;: &quot;${nomFilter != null ? nomFilter : ''}&quot;, &quot;prenom&quot;: &quot;${prenomFilter != null ? prenomFilter : ''}&quot;, &quot;categorieId&quot;: &quot;${categorieId != null ? categorieId : ''}&quot;, &quot;numeroLicence&quot;: &quot;${numeroLicenceFilter != null ? numeroLicenceFilter : ''}&quot;, &quot;email&quot;: &quot;${emailFilter != null ? emailFilter : ''}&quot;, &quot;typeDemande&quot;: &quot;${typeDemandeFilter != null ? typeDemandeFilter : ''}&quot;, &quot;page&quot;: ${currentPage + 1}}|"
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors">
Suivant
</a>
<span th:if="${currentPage == totalPages - 1}" class="px-3 py-1 border border-gray-200 rounded text-gray-400 bg-gray-50 cursor-not-allowed">
Suivant
</span>
</div>
</div>
</div> </div>
</div> </div>
</main> </main>