feat: display and manage adherent clothing and shoe sizes

This commit is contained in:
2026-07-07 21:54:52 +02:00
parent a7c5b23198
commit 3fc239683e
3 changed files with 112 additions and 19 deletions
@@ -48,6 +48,8 @@ public class AdherentController {
@org.springframework.web.bind.annotation.RequestParam(required = false) String licence,
@org.springframework.web.bind.annotation.RequestParam(required = false) String email,
@org.springframework.web.bind.annotation.RequestParam(required = false) String paiement,
@org.springframework.web.bind.annotation.RequestParam(required = false, defaultValue = "nom") String sortField,
@org.springframework.web.bind.annotation.RequestParam(required = false, defaultValue = "asc") String sortDirection,
@org.springframework.web.bind.annotation.RequestParam(defaultValue = "0") int page,
@org.springframework.web.bind.annotation.RequestParam(defaultValue = "20") int size,
Model model) {
@@ -113,7 +115,29 @@ public class AdherentController {
model.addAttribute("paiementFilter", paiement.trim());
}
// 3. Paginate
// 3. Sort
java.util.Comparator<Adherent> comparator = (a1, a2) -> 0;
if ("nom".equalsIgnoreCase(sortField)) {
comparator = java.util.Comparator.comparing(a -> a.getNom() != null ? a.getNom() : "");
} else if ("prenom".equalsIgnoreCase(sortField)) {
comparator = java.util.Comparator.comparing(a -> a.getPrenom() != null ? a.getPrenom() : "");
} else if ("categorie".equalsIgnoreCase(sortField)) {
comparator = java.util.Comparator.comparing(a -> (a.getLicenceActuelle() != null && a.getLicenceActuelle().getCategorie() != null && a.getLicenceActuelle().getCategorie().getNom() != null) ? a.getLicenceActuelle().getCategorie().getNom() : "");
} else if ("licence".equalsIgnoreCase(sortField)) {
comparator = java.util.Comparator.comparing(a -> (a.getLicenceActuelle() != null && a.getLicenceActuelle().getNumeroLicence() != null) ? a.getLicenceActuelle().getNumeroLicence() : "");
} else if ("email".equalsIgnoreCase(sortField)) {
comparator = java.util.Comparator.comparing(a -> a.getEmail() != null ? a.getEmail() : "");
}
if ("desc".equalsIgnoreCase(sortDirection)) {
comparator = comparator.reversed();
}
adherents.sort(comparator);
model.addAttribute("sortField", sortField);
model.addAttribute("sortDirection", sortDirection);
// 4. Paginate
int totalElements = adherents.size();
int totalPages = (int) Math.ceil((double) totalElements / size);
if (page < 0) page = 0;
@@ -207,7 +207,7 @@
</div>
</div>
<div class="mt-6">
<div class="mt-6" th:classappend="${adherent.typeDemande == 'RENOUVELLEMENT'} ? 'hidden-block' : ''">
<label for="commentConnuClub" class="block text-sm font-medium text-gray-700 mb-1">Comment a-t-il connu le club ?</label>
<input type="text" id="commentConnuClub" th:field="*{commentConnuClub}"
class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors">
@@ -413,26 +413,35 @@
<div th:if="${dot.equipement.taillesDisponibles != null and !dot.equipement.taillesDisponibles.trim().isEmpty()}">
<label class="block text-[9px] uppercase font-bold text-gray-400">Taille</label>
<select th:name="'taille_' + ${dot.id}" class="w-full text-xs border border-gray-300 rounded px-1 py-0.5 focus:ring-1 focus:ring-blue-500 outline-none">
<option value="">Choisir...</option>
<option value="" th:selected="${dot.taille == null or dot.taille.isEmpty()}">Choisir...</option>
<option th:if="${dot.taille != null and !dot.taille.isEmpty()}"
th:value="${dot.taille}"
th:text="${dot.taille} + ' (Actuelle)'"
selected></option>
<option th:each="t : ${#strings.arraySplit(dot.equipement.taillesDisponibles, ',')}"
th:if="${dot.taille == null or dot.taille != #strings.trim(t)}"
th:value="${#strings.trim(t)}"
th:text="${#strings.trim(t)}"
th:selected="${(dot.taille != null and !dot.taille.isEmpty()) ? (dot.taille == #strings.trim(t)) : (#strings.trim(t) == adherent.tailleVetement or #strings.trim(t) == adherent.pointure)}"></option>
th:selected="${dot.taille == null and (#strings.trim(t) == adherent.tailleVetement or #strings.trim(t) == adherent.pointure)}"></option>
</select>
</div>
<div th:if="${(dot.equipement.taillesDisponibles == null or dot.equipement.taillesDisponibles.trim().isEmpty())}">
<label class="block text-[9px] uppercase font-bold text-gray-400">Taille</label>
<input type="text" th:name="'taille_' + ${dot.id}" th:value="${dot.taille != null and !dot.taille.isEmpty() ? dot.taille : (adherent.tailleVetement != null ? adherent.tailleVetement : '')}" class="w-full text-xs border border-gray-300 rounded px-1 py-0.5 outline-none bg-gray-50 text-gray-500" placeholder="Taille/Pointure">
<input type="text" th:name="'taille_' + ${dot.id}" th:value="${dot.taille != null and !dot.taille.isEmpty() ? dot.taille : (adherent.tailleVetement != null ? adherent.tailleVetement : '')}" class="w-full text-xs border border-gray-300 rounded px-1 py-0.5 focus:ring-1 focus:ring-blue-500 outline-none" placeholder="Taille/Pointure">
</div>
<div th:if="${dot.equipement.couleursDisponibles != null and !dot.equipement.couleursDisponibles.trim().isEmpty()}">
<label class="block text-[9px] uppercase font-bold text-gray-400">Couleur</label>
<select th:name="'couleur_' + ${dot.id}" class="w-full text-xs border border-gray-300 rounded px-1 py-0.5 focus:ring-1 focus:ring-blue-500 outline-none">
<option value="">Choisir...</option>
<option value="" th:selected="${dot.couleur == null or dot.couleur.isEmpty()}">Choisir...</option>
<option th:if="${dot.couleur != null and !dot.couleur.isEmpty()}"
th:value="${dot.couleur}"
th:text="${dot.couleur} + ' (Actuelle)'"
selected></option>
<option th:each="c : ${#strings.arraySplit(dot.equipement.couleursDisponibles, ',')}"
th:if="${dot.couleur == null or dot.couleur != #strings.trim(c)}"
th:value="${#strings.trim(c)}"
th:text="${#strings.trim(c)}"
th:selected="${dot.couleur == #strings.trim(c)}"></option>
th:text="${#strings.trim(c)}"></option>
</select>
</div>
<div th:if="${(dot.equipement.couleursDisponibles == null or dot.equipement.couleursDisponibles.trim().isEmpty()) and (dot.couleur != null and !dot.couleur.isEmpty())}">
@@ -442,16 +451,16 @@
</div>
<div class="grid grid-cols-2 gap-1.5 mb-1.5">
<div>
<div th:if="${dot.equipement.hasFlocageNumero}">
<label class="block text-[9px] uppercase font-bold text-gray-400">Numéro</label>
<input type="text" th:name="'numero_' + ${dot.id}" th:value="${dot.numero}" placeholder="Ex: 10"
class="w-full text-xs border border-gray-300 rounded px-1 py-0.5 focus:ring-1 focus:ring-blue-500 outline-none">
</div>
<div>
<label class="block text-[9px] uppercase font-bold text-gray-400">Flocage</label>
<input type="text" th:name="'flocage_' + ${dot.id}" th:value="${dot.flocage}" placeholder="Nom du joueur"
class="w-full text-xs border border-gray-300 rounded px-1 py-0.5 focus:ring-1 focus:ring-blue-500 outline-none">
</div>
<div th:if="${dot.equipement.hasFlocagePrenom or dot.equipement.hasFlocageInitiales}">
<label class="block text-[9px] uppercase font-bold text-gray-400 whitespace-nowrap truncate" title="Flocage" th:text="'Flocage (' + (${dot.equipement.hasFlocagePrenom and dot.equipement.hasFlocageInitiales} ? 'Prénom/Init.' : (${dot.equipement.hasFlocagePrenom} ? 'Prénom' : 'Initiales')) + ')'">Flocage</label>
<input type="text" th:name="'flocage_' + ${dot.id}" th:value="${dot.flocage}" placeholder="À floquer"
class="w-full text-xs border border-gray-300 rounded px-1 py-0.5 focus:ring-1 focus:ring-blue-500 outline-none">
</div>
<div class="flex items-center pt-1.5">
<label class="flex items-center space-x-1 cursor-pointer select-none">
<input type="checkbox" th:name="'fourni_' + ${dot.id}" th:checked="${dot.fourni}" class="w-3.5 h-3.5 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
@@ -58,16 +58,63 @@
<table class="w-full text-left border-collapse min-w-[1000px]">
<thead>
<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">Nom</th>
<th class="py-3 px-6 font-medium text-left">Prénom</th>
<th class="py-3 px-6 font-medium text-left">Catégorie</th>
<th class="py-3 px-6 font-medium text-center">N° Licence</th>
<th class="py-3 px-6 font-medium text-left">Email</th>
<th class="py-3 px-6 font-medium text-left cursor-pointer hover:bg-gray-100 transition-colors group select-none" onclick="updateSort('nom')">
<div class="flex items-center space-x-1">
<span>Nom</span>
<span th:if="${sortField != 'nom'}"><svg class="w-3.5 h-3.5 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4"></path></svg></span>
<span th:if="${sortField == 'nom'}">
<svg th:if="${sortDirection == 'asc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 15l7-7 7 7"></path></svg>
<svg th:if="${sortDirection == 'desc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
</span>
</div>
</th>
<th class="py-3 px-6 font-medium text-left cursor-pointer hover:bg-gray-100 transition-colors group select-none" onclick="updateSort('prenom')">
<div class="flex items-center space-x-1">
<span>Prénom</span>
<span th:if="${sortField != 'prenom'}"><svg class="w-3.5 h-3.5 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4"></path></svg></span>
<span th:if="${sortField == 'prenom'}">
<svg th:if="${sortDirection == 'asc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 15l7-7 7 7"></path></svg>
<svg th:if="${sortDirection == 'desc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
</span>
</div>
</th>
<th class="py-3 px-6 font-medium text-left cursor-pointer hover:bg-gray-100 transition-colors group select-none" onclick="updateSort('categorie')">
<div class="flex items-center space-x-1">
<span>Catégorie</span>
<span th:if="${sortField != 'categorie'}"><svg class="w-3.5 h-3.5 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4"></path></svg></span>
<span th:if="${sortField == 'categorie'}">
<svg th:if="${sortDirection == 'asc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 15l7-7 7 7"></path></svg>
<svg th:if="${sortDirection == 'desc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
</span>
</div>
</th>
<th class="py-3 px-6 font-medium text-center cursor-pointer hover:bg-gray-100 transition-colors group select-none" onclick="updateSort('licence')">
<div class="flex items-center justify-center space-x-1">
<span>N° Licence</span>
<span th:if="${sortField != 'licence'}"><svg class="w-3.5 h-3.5 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4"></path></svg></span>
<span th:if="${sortField == 'licence'}">
<svg th:if="${sortDirection == 'asc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 15l7-7 7 7"></path></svg>
<svg th:if="${sortDirection == 'desc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
</span>
</div>
</th>
<th class="py-3 px-6 font-medium text-left cursor-pointer hover:bg-gray-100 transition-colors group select-none" onclick="updateSort('email')">
<div class="flex items-center space-x-1">
<span>Email</span>
<span th:if="${sortField != 'email'}"><svg class="w-3.5 h-3.5 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4"></path></svg></span>
<span th:if="${sortField == 'email'}">
<svg th:if="${sortDirection == 'asc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 15l7-7 7 7"></path></svg>
<svg th:if="${sortDirection == 'desc'}" class="w-3.5 h-3.5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
</span>
</div>
</th>
<th class="py-3 px-6 font-medium text-center">Paiement</th>
<th class="py-3 px-6 font-medium text-right">Actions</th>
</tr>
<!-- Filter Row -->
<tr id="filter-row" class="bg-gray-100 border-b border-gray-200 text-xs">
<input type="hidden" name="sortField" id="sortField" th:value="${sortField}">
<input type="hidden" name="sortDirection" id="sortDirection" th:value="${sortDirection}">
<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">
</td>
@@ -217,5 +264,18 @@
</div>
</div>
</main>
<script>
function updateSort(field) {
let sf = document.getElementById('sortField');
let sd = document.getElementById('sortDirection');
if (sf.value === field) {
sd.value = sd.value === 'asc' ? 'desc' : 'asc';
} else {
sf.value = field;
sd.value = 'asc';
}
sf.dispatchEvent(new Event('change', { bubbles: true }));
}
</script>
</body>
</html>