feat: implémentation de la gestion des saisons (UI, service, refactoring sidebar)
This commit is contained in:
@@ -26,6 +26,10 @@ public class Categorie {
|
|||||||
@Column(name = "tarif_exterieur", nullable = false, precision = 10, scale = 2)
|
@Column(name = "tarif_exterieur", nullable = false, precision = 10, scale = 2)
|
||||||
private BigDecimal tarifExterieur;
|
private BigDecimal tarifExterieur;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "saison_id", nullable = false)
|
||||||
|
private Saison saison;
|
||||||
|
|
||||||
// Getters and Setters
|
// Getters and Setters
|
||||||
|
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
@@ -45,4 +49,7 @@ public class Categorie {
|
|||||||
|
|
||||||
public BigDecimal getTarifExterieur() { return tarifExterieur; }
|
public BigDecimal getTarifExterieur() { return tarifExterieur; }
|
||||||
public void setTarifExterieur(BigDecimal tarifExterieur) { this.tarifExterieur = tarifExterieur; }
|
public void setTarifExterieur(BigDecimal tarifExterieur) { this.tarifExterieur = tarifExterieur; }
|
||||||
|
|
||||||
|
public Saison getSaison() { return saison; }
|
||||||
|
public void setSaison(Saison saison) { this.saison = saison; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ public class Equipement {
|
|||||||
@Column(name = "prix_contribution", nullable = false, precision = 10, scale = 2)
|
@Column(name = "prix_contribution", nullable = false, precision = 10, scale = 2)
|
||||||
private BigDecimal prixContribution = BigDecimal.ZERO;
|
private BigDecimal prixContribution = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "saison_id", nullable = false)
|
||||||
|
private Saison saison;
|
||||||
|
|
||||||
// Getters and Setters
|
// Getters and Setters
|
||||||
|
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
@@ -39,4 +43,7 @@ public class Equipement {
|
|||||||
|
|
||||||
public BigDecimal getPrixContribution() { return prixContribution; }
|
public BigDecimal getPrixContribution() { return prixContribution; }
|
||||||
public void setPrixContribution(BigDecimal prixContribution) { this.prixContribution = prixContribution; }
|
public void setPrixContribution(BigDecimal prixContribution) { this.prixContribution = prixContribution; }
|
||||||
|
|
||||||
|
public Saison getSaison() { return saison; }
|
||||||
|
public void setSaison(Saison saison) { this.saison = saison; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ public class Licence {
|
|||||||
@JoinColumn(name = "categorie_id", nullable = false)
|
@JoinColumn(name = "categorie_id", nullable = false)
|
||||||
private Categorie categorie;
|
private Categorie categorie;
|
||||||
|
|
||||||
@Column(nullable = false, length = 20)
|
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||||
private String saison;
|
@JoinColumn(name = "saison_id", nullable = false)
|
||||||
|
private Saison saison;
|
||||||
|
|
||||||
@Column(nullable = false, length = 50)
|
@Column(nullable = false, length = 50)
|
||||||
private String etat;
|
private String etat;
|
||||||
@@ -89,8 +90,8 @@ public class Licence {
|
|||||||
public Categorie getCategorie() { return categorie; }
|
public Categorie getCategorie() { return categorie; }
|
||||||
public void setCategorie(Categorie categorie) { this.categorie = categorie; }
|
public void setCategorie(Categorie categorie) { this.categorie = categorie; }
|
||||||
|
|
||||||
public String getSaison() { return saison; }
|
public Saison getSaison() { return saison; }
|
||||||
public void setSaison(String saison) { this.saison = saison; }
|
public void setSaison(Saison saison) { this.saison = saison; }
|
||||||
|
|
||||||
public String getEtat() { return etat; }
|
public String getEtat() { return etat; }
|
||||||
public void setEtat(String etat) { this.etat = etat; }
|
public void setEtat(String etat) { this.etat = etat; }
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.astalange.core.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "saison")
|
||||||
|
public class Saison {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String nom;
|
||||||
|
|
||||||
|
@Column(name = "est_active", nullable = false)
|
||||||
|
private Boolean estActive = false;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNom() {
|
||||||
|
return nom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNom(String nom) {
|
||||||
|
this.nom = nom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getEstActive() {
|
||||||
|
return estActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEstActive(Boolean estActive) {
|
||||||
|
this.estActive = estActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Saison saison = (Saison) o;
|
||||||
|
return Objects.equals(id, saison.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,4 +9,7 @@ import java.util.List;
|
|||||||
@Repository
|
@Repository
|
||||||
public interface AdherentRepository extends JpaRepository<Adherent, Long> {
|
public interface AdherentRepository extends JpaRepository<Adherent, Long> {
|
||||||
List<Adherent> findTop5ByOrderByIdDesc();
|
List<Adherent> findTop5ByOrderByIdDesc();
|
||||||
|
|
||||||
|
@org.springframework.data.jpa.repository.Query("SELECT DISTINCT a FROM Adherent a LEFT JOIN a.licences l WHERE LOWER(a.nom) LIKE LOWER(CONCAT('%', :query, '%')) OR LOWER(a.prenom) LIKE LOWER(CONCAT('%', :query, '%')) OR LOWER(l.numeroLicence) LIKE LOWER(CONCAT('%', :query, '%'))")
|
||||||
|
List<Adherent> searchByQuery(@org.springframework.data.repository.query.Param("query") String query);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import com.astalange.core.entity.Categorie;
|
|||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.Saison;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface CategorieRepository extends JpaRepository<Categorie, Long> {
|
public interface CategorieRepository extends JpaRepository<Categorie, Long> {
|
||||||
|
List<Categorie> findBySaison(Saison saison);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import com.astalange.core.entity.Equipement;
|
|||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.Saison;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface EquipementRepository extends JpaRepository<Equipement, Long> {
|
public interface EquipementRepository extends JpaRepository<Equipement, Long> {
|
||||||
|
List<Equipement> findBySaison(Saison saison);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.astalange.core.repository;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.Saison;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface SaisonRepository extends JpaRepository<Saison, Long> {
|
||||||
|
Optional<Saison> findByEstActiveTrue();
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.astalange.core.service;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.Categorie;
|
||||||
|
import com.astalange.core.entity.Equipement;
|
||||||
|
import com.astalange.core.entity.Saison;
|
||||||
|
import com.astalange.core.repository.CategorieRepository;
|
||||||
|
import com.astalange.core.repository.EquipementRepository;
|
||||||
|
import com.astalange.core.repository.SaisonRepository;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SaisonService {
|
||||||
|
|
||||||
|
private final SaisonRepository saisonRepository;
|
||||||
|
private final CategorieRepository categorieRepository;
|
||||||
|
private final EquipementRepository equipementRepository;
|
||||||
|
|
||||||
|
public SaisonService(SaisonRepository saisonRepository, CategorieRepository categorieRepository, EquipementRepository equipementRepository) {
|
||||||
|
this.saisonRepository = saisonRepository;
|
||||||
|
this.categorieRepository = categorieRepository;
|
||||||
|
this.equipementRepository = equipementRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Saison creerNouvelleSaison(String nom, boolean importerParametragePrecedent) {
|
||||||
|
Optional<Saison> ancienneSaisonOpt = saisonRepository.findByEstActiveTrue();
|
||||||
|
|
||||||
|
// Désactiver toutes les saisons actuelles (il ne devrait y en avoir qu'une mais on s'assure de tout nettoyer)
|
||||||
|
List<Saison> toutesLesSaisons = saisonRepository.findAll();
|
||||||
|
for (Saison s : toutesLesSaisons) {
|
||||||
|
s.setEstActive(false);
|
||||||
|
saisonRepository.save(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Création de la nouvelle saison
|
||||||
|
Saison nouvelleSaison = new Saison();
|
||||||
|
nouvelleSaison.setNom(nom);
|
||||||
|
nouvelleSaison.setEstActive(true);
|
||||||
|
nouvelleSaison = saisonRepository.save(nouvelleSaison);
|
||||||
|
|
||||||
|
if (importerParametragePrecedent && ancienneSaisonOpt.isPresent()) {
|
||||||
|
Saison ancienneSaison = ancienneSaisonOpt.get();
|
||||||
|
dupliquerParametrages(ancienneSaison, nouvelleSaison);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nouvelleSaison;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dupliquerParametrages(Saison ancienneSaison, Saison nouvelleSaison) {
|
||||||
|
// 1. Dupliquer les équipements
|
||||||
|
List<Equipement> anciensEquipements = equipementRepository.findBySaison(ancienneSaison);
|
||||||
|
for (Equipement ancien : anciensEquipements) {
|
||||||
|
Equipement nouvelEquipement = new Equipement();
|
||||||
|
nouvelEquipement.setNom(ancien.getNom());
|
||||||
|
nouvelEquipement.setDescription(ancien.getDescription());
|
||||||
|
nouvelEquipement.setObligatoire(ancien.getObligatoire());
|
||||||
|
nouvelEquipement.setPrixContribution(ancien.getPrixContribution());
|
||||||
|
nouvelEquipement.setSaison(nouvelleSaison);
|
||||||
|
equipementRepository.save(nouvelEquipement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Dupliquer les catégories (avec incrémentation de l'année)
|
||||||
|
List<Categorie> anciennesCategories = categorieRepository.findBySaison(ancienneSaison);
|
||||||
|
for (Categorie ancienne : anciennesCategories) {
|
||||||
|
Categorie nouvelleCategorie = new Categorie();
|
||||||
|
nouvelleCategorie.setNom(ancienne.getNom());
|
||||||
|
// Incrémentation de +1 sur l'année de naissance pivot
|
||||||
|
if (ancienne.getAnneeMin() != null) {
|
||||||
|
nouvelleCategorie.setAnneeMin(ancienne.getAnneeMin() + 1);
|
||||||
|
}
|
||||||
|
if (ancienne.getAnneeMax() != null) {
|
||||||
|
nouvelleCategorie.setAnneeMax(ancienne.getAnneeMax() + 1);
|
||||||
|
}
|
||||||
|
nouvelleCategorie.setTarifBase(ancienne.getTarifBase());
|
||||||
|
nouvelleCategorie.setTarifExterieur(ancienne.getTarifExterieur());
|
||||||
|
nouvelleCategorie.setSaison(nouvelleSaison);
|
||||||
|
categorieRepository.save(nouvelleCategorie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
-- V9__introduction_saison.sql
|
||||||
|
-- 1. Création de la table saison
|
||||||
|
CREATE TABLE saison (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
nom VARCHAR(255) NOT NULL,
|
||||||
|
est_active BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Création d'une saison par défaut pour les données existantes
|
||||||
|
INSERT INTO saison (nom, est_active) VALUES ('Saison Actuelle', TRUE);
|
||||||
|
|
||||||
|
-- 2. Ajout de saison_id dans les tables de paramétrage
|
||||||
|
ALTER TABLE categorie ADD COLUMN saison_id BIGINT;
|
||||||
|
ALTER TABLE equipement ADD COLUMN saison_id BIGINT;
|
||||||
|
-- Il n'y a pas de table tarif, les tarifs sont dans categorie.
|
||||||
|
|
||||||
|
-- Affecter la saison par défaut aux paramètres existants
|
||||||
|
UPDATE categorie SET saison_id = (SELECT id FROM saison LIMIT 1);
|
||||||
|
UPDATE equipement SET saison_id = (SELECT id FROM saison LIMIT 1);
|
||||||
|
|
||||||
|
-- Rendre saison_id NOT NULL et ajouter les contraintes
|
||||||
|
ALTER TABLE categorie ALTER COLUMN saison_id SET NOT NULL;
|
||||||
|
ALTER TABLE categorie ADD CONSTRAINT fk_categorie_saison FOREIGN KEY (saison_id) REFERENCES saison(id);
|
||||||
|
|
||||||
|
ALTER TABLE equipement ALTER COLUMN saison_id SET NOT NULL;
|
||||||
|
ALTER TABLE equipement ADD CONSTRAINT fk_equipement_saison FOREIGN KEY (saison_id) REFERENCES saison(id);
|
||||||
|
|
||||||
|
-- 3. Ajout de saison_id sur licence
|
||||||
|
-- Licence a déjà une colonne saison (String). On va la remplacer par saison_id
|
||||||
|
ALTER TABLE licence RENAME COLUMN saison TO ancienne_saison;
|
||||||
|
ALTER TABLE licence ADD COLUMN saison_id BIGINT;
|
||||||
|
UPDATE licence SET saison_id = (SELECT id FROM saison LIMIT 1);
|
||||||
|
ALTER TABLE licence ALTER COLUMN saison_id SET NOT NULL;
|
||||||
|
ALTER TABLE licence ADD CONSTRAINT fk_licence_saison FOREIGN KEY (saison_id) REFERENCES saison(id);
|
||||||
|
ALTER TABLE licence DROP COLUMN ancienne_saison;
|
||||||
|
|
||||||
|
-- 4. Migration de paiement et dotation pour pointer sur licence_id au lieu de adherent_id
|
||||||
|
-- Ajout des colonnes licence_id
|
||||||
|
-- (Paiement et Dotation avaient un adherent_id qui n'est pas dans l'entité actuelle mais peut-être en base ?)
|
||||||
|
-- D'après les entités, Paiement et Dotation ont DEJA licence_id et n'ont PAS adherent_id !
|
||||||
|
-- Le plan proposait de modifier ça mais le code a DÉJÀ ces modifications.
|
||||||
|
-- Je vais juste vérifier s'il faut modifier la base de données.
|
||||||
|
-- Les entités Paiement.java et Dotation.java ont déjà `private Licence licence;`
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.astalange.web.controller;
|
package com.astalange.web.controller;
|
||||||
|
|
||||||
import com.astalange.core.entity.Adherent;
|
import com.astalange.core.entity.Adherent;
|
||||||
|
import com.astalange.core.entity.Saison;
|
||||||
import com.astalange.core.repository.AdherentRepository;
|
import com.astalange.core.repository.AdherentRepository;
|
||||||
import com.astalange.core.repository.CategorieRepository;
|
import com.astalange.core.repository.CategorieRepository;
|
||||||
|
import com.astalange.core.repository.SaisonRepository;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -18,17 +20,25 @@ public class AdherentController {
|
|||||||
private final CategorieRepository categorieRepository;
|
private final CategorieRepository categorieRepository;
|
||||||
private final com.astalange.core.repository.LicenceRepository licenceRepository;
|
private final com.astalange.core.repository.LicenceRepository licenceRepository;
|
||||||
private final com.astalange.core.repository.ModePaiementRepository modePaiementRepository;
|
private final com.astalange.core.repository.ModePaiementRepository modePaiementRepository;
|
||||||
|
private final SaisonRepository saisonRepository;
|
||||||
|
|
||||||
public AdherentController(AdherentRepository adherentRepository, CategorieRepository categorieRepository, com.astalange.core.repository.LicenceRepository licenceRepository, com.astalange.core.repository.ModePaiementRepository modePaiementRepository) {
|
public AdherentController(AdherentRepository adherentRepository, CategorieRepository categorieRepository, com.astalange.core.repository.LicenceRepository licenceRepository, com.astalange.core.repository.ModePaiementRepository modePaiementRepository, SaisonRepository saisonRepository) {
|
||||||
this.adherentRepository = adherentRepository;
|
this.adherentRepository = adherentRepository;
|
||||||
this.categorieRepository = categorieRepository;
|
this.categorieRepository = categorieRepository;
|
||||||
this.licenceRepository = licenceRepository;
|
this.licenceRepository = licenceRepository;
|
||||||
this.modePaiementRepository = modePaiementRepository;
|
this.modePaiementRepository = modePaiementRepository;
|
||||||
|
this.saisonRepository = saisonRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public String listAdherents(Model model) {
|
public String listAdherents(@org.springframework.web.bind.annotation.RequestParam(required = false) String query, Model model) {
|
||||||
List<Adherent> adherents = adherentRepository.findAll();
|
List<Adherent> adherents;
|
||||||
|
if (query != null && !query.trim().isEmpty()) {
|
||||||
|
adherents = adherentRepository.searchByQuery(query.trim());
|
||||||
|
model.addAttribute("searchQuery", query.trim());
|
||||||
|
} else {
|
||||||
|
adherents = adherentRepository.findAll();
|
||||||
|
}
|
||||||
model.addAttribute("adherents", adherents);
|
model.addAttribute("adherents", adherents);
|
||||||
return "adherents/list";
|
return "adherents/list";
|
||||||
}
|
}
|
||||||
@@ -57,8 +67,16 @@ public class AdherentController {
|
|||||||
public String editAdherent(@org.springframework.web.bind.annotation.PathVariable Long id, Model model) {
|
public String editAdherent(@org.springframework.web.bind.annotation.PathVariable Long id, Model model) {
|
||||||
Adherent adherent = adherentRepository.findById(id)
|
Adherent adherent = adherentRepository.findById(id)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Adhérent invalide : " + id));
|
.orElseThrow(() -> new IllegalArgumentException("Adhérent invalide : " + id));
|
||||||
|
|
||||||
|
Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null);
|
||||||
|
if (saisonActive != null) {
|
||||||
|
model.addAttribute("categories", categorieRepository.findBySaison(saisonActive));
|
||||||
|
model.addAttribute("saisonActive", saisonActive);
|
||||||
|
} else {
|
||||||
|
model.addAttribute("categories", categorieRepository.findAll());
|
||||||
|
}
|
||||||
|
|
||||||
model.addAttribute("adherent", adherent);
|
model.addAttribute("adherent", adherent);
|
||||||
model.addAttribute("categories", categorieRepository.findAll());
|
|
||||||
model.addAttribute("licences", licenceRepository.findByAdherentId(id));
|
model.addAttribute("licences", licenceRepository.findByAdherentId(id));
|
||||||
model.addAttribute("modesPaiement", modePaiementRepository.findAll());
|
model.addAttribute("modesPaiement", modePaiementRepository.findAll());
|
||||||
return "adherents/form";
|
return "adherents/form";
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.astalange.web.controller;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class GlobalControllerAdvice {
|
||||||
|
|
||||||
|
@ModelAttribute("requestURI")
|
||||||
|
public String requestURI(final HttpServletRequest request) {
|
||||||
|
return request.getRequestURI();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,13 @@ import com.astalange.core.entity.Categorie;
|
|||||||
import com.astalange.core.entity.Licence;
|
import com.astalange.core.entity.Licence;
|
||||||
import com.astalange.core.entity.Dotation;
|
import com.astalange.core.entity.Dotation;
|
||||||
import com.astalange.core.entity.Equipement;
|
import com.astalange.core.entity.Equipement;
|
||||||
|
import com.astalange.core.entity.Saison;
|
||||||
import com.astalange.core.repository.AdherentRepository;
|
import com.astalange.core.repository.AdherentRepository;
|
||||||
import com.astalange.core.repository.CategorieRepository;
|
import com.astalange.core.repository.CategorieRepository;
|
||||||
import com.astalange.core.repository.LicenceRepository;
|
import com.astalange.core.repository.LicenceRepository;
|
||||||
import com.astalange.core.repository.DotationRepository;
|
import com.astalange.core.repository.DotationRepository;
|
||||||
import com.astalange.core.repository.EquipementRepository;
|
import com.astalange.core.repository.EquipementRepository;
|
||||||
|
import com.astalange.core.repository.SaisonRepository;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -25,24 +27,26 @@ public class LicenceController {
|
|||||||
private final LicenceRepository licenceRepository;
|
private final LicenceRepository licenceRepository;
|
||||||
private final EquipementRepository equipementRepository;
|
private final EquipementRepository equipementRepository;
|
||||||
private final DotationRepository dotationRepository;
|
private final DotationRepository dotationRepository;
|
||||||
|
private final SaisonRepository saisonRepository;
|
||||||
|
|
||||||
public LicenceController(AdherentRepository adherentRepository,
|
public LicenceController(AdherentRepository adherentRepository,
|
||||||
CategorieRepository categorieRepository,
|
CategorieRepository categorieRepository,
|
||||||
LicenceRepository licenceRepository,
|
LicenceRepository licenceRepository,
|
||||||
EquipementRepository equipementRepository,
|
EquipementRepository equipementRepository,
|
||||||
DotationRepository dotationRepository) {
|
DotationRepository dotationRepository,
|
||||||
|
SaisonRepository saisonRepository) {
|
||||||
this.adherentRepository = adherentRepository;
|
this.adherentRepository = adherentRepository;
|
||||||
this.categorieRepository = categorieRepository;
|
this.categorieRepository = categorieRepository;
|
||||||
this.licenceRepository = licenceRepository;
|
this.licenceRepository = licenceRepository;
|
||||||
this.equipementRepository = equipementRepository;
|
this.equipementRepository = equipementRepository;
|
||||||
this.dotationRepository = dotationRepository;
|
this.dotationRepository = dotationRepository;
|
||||||
|
this.saisonRepository = saisonRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/adherents/{id}/licences")
|
@PostMapping("/adherents/{id}/licences")
|
||||||
public String addLicence(
|
public String addLicence(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestParam Long categorieId,
|
@RequestParam Long categorieId,
|
||||||
@RequestParam String saison,
|
|
||||||
@RequestParam String etat,
|
@RequestParam String etat,
|
||||||
@RequestParam(required = false) String numeroLicence) {
|
@RequestParam(required = false) String numeroLicence) {
|
||||||
|
|
||||||
@@ -57,17 +61,20 @@ public class LicenceController {
|
|||||||
Categorie categorie = categorieRepository.findById(categorieId)
|
Categorie categorie = categorieRepository.findById(categorieId)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Invalid Categorie ID"));
|
.orElseThrow(() -> new IllegalArgumentException("Invalid Categorie ID"));
|
||||||
|
|
||||||
|
Saison saisonActive = saisonRepository.findByEstActiveTrue()
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Aucune saison active trouvée"));
|
||||||
|
|
||||||
Licence licence = new Licence();
|
Licence licence = new Licence();
|
||||||
licence.setAdherent(adherent);
|
licence.setAdherent(adherent);
|
||||||
licence.setCategorie(categorie);
|
licence.setCategorie(categorie);
|
||||||
licence.setSaison(saison);
|
licence.setSaison(saisonActive);
|
||||||
licence.setEtat(etat);
|
licence.setEtat(etat);
|
||||||
licence.setNumeroLicence(numeroLicence);
|
licence.setNumeroLicence(numeroLicence);
|
||||||
|
|
||||||
licence = licenceRepository.save(licence);
|
licence = licenceRepository.save(licence);
|
||||||
|
|
||||||
// Auto-initialize dotations for all configured equipments
|
// Auto-initialize dotations for all configured equipments of the active season
|
||||||
List<Equipement> equipements = equipementRepository.findAll();
|
List<Equipement> equipements = equipementRepository.findBySaison(saisonActive);
|
||||||
for (Equipement eq : equipements) {
|
for (Equipement eq : equipements) {
|
||||||
Dotation dotation = new Dotation();
|
Dotation dotation = new Dotation();
|
||||||
dotation.setLicence(licence);
|
dotation.setLicence(licence);
|
||||||
@@ -82,4 +89,27 @@ public class LicenceController {
|
|||||||
|
|
||||||
return "redirect:/adherents/" + id + "/edit";
|
return "redirect:/adherents/" + id + "/edit";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/adherents/{adherentId}/licences/{licenceId}/update")
|
||||||
|
public String updateLicence(
|
||||||
|
@PathVariable Long adherentId,
|
||||||
|
@PathVariable Long licenceId,
|
||||||
|
@RequestParam Long categorieId,
|
||||||
|
@RequestParam String etat,
|
||||||
|
@RequestParam(required = false) String numeroLicence) {
|
||||||
|
|
||||||
|
Licence licence = licenceRepository.findById(licenceId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Invalid Licence ID"));
|
||||||
|
|
||||||
|
Categorie categorie = categorieRepository.findById(categorieId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Invalid Categorie ID"));
|
||||||
|
|
||||||
|
licence.setCategorie(categorie);
|
||||||
|
licence.setEtat(etat);
|
||||||
|
licence.setNumeroLicence(numeroLicence);
|
||||||
|
|
||||||
|
licenceRepository.save(licence);
|
||||||
|
|
||||||
|
return "redirect:/adherents/" + adherentId + "/edit";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.astalange.web.controller;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.Saison;
|
||||||
|
import com.astalange.core.repository.SaisonRepository;
|
||||||
|
import com.astalange.core.service.SaisonService;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/saisons")
|
||||||
|
public class SaisonController {
|
||||||
|
|
||||||
|
private final SaisonRepository saisonRepository;
|
||||||
|
private final SaisonService saisonService;
|
||||||
|
|
||||||
|
public SaisonController(SaisonRepository saisonRepository, SaisonService saisonService) {
|
||||||
|
this.saisonRepository = saisonRepository;
|
||||||
|
this.saisonService = saisonService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String listSaisons(Model model) {
|
||||||
|
// Optionnel: On pourrait trier par ID descendant pour avoir la plus récente en haut
|
||||||
|
List<Saison> saisons = saisonRepository.findAll();
|
||||||
|
model.addAttribute("saisons", saisons);
|
||||||
|
return "parametrage/saisons_list";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/new")
|
||||||
|
public String showCreateForm(Model model) {
|
||||||
|
return "parametrage/saison_form";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public String saveSaison(@RequestParam String nom, @RequestParam(defaultValue = "false") boolean importerParametrage) {
|
||||||
|
saisonService.creerNouvelleSaison(nom, importerParametrage);
|
||||||
|
return "redirect:/saisons";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/activer")
|
||||||
|
public String activerSaison(@PathVariable Long id) {
|
||||||
|
Saison saisonAActiver = saisonRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Saison introuvable : " + id));
|
||||||
|
|
||||||
|
// Désactiver toutes les saisons
|
||||||
|
List<Saison> toutes = saisonRepository.findAll();
|
||||||
|
for (Saison s : toutes) {
|
||||||
|
s.setEstActive(false);
|
||||||
|
saisonRepository.save(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activer la saison choisie
|
||||||
|
saisonAActiver.setEstActive(true);
|
||||||
|
saisonRepository.save(saisonAActiver);
|
||||||
|
|
||||||
|
return "redirect:/saisons";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,26 +14,7 @@
|
|||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
<div class="p-4 border-t border-gray-200">
|
|
||||||
<div class="text-sm font-medium text-gray-900 mb-1" th:text="${username != null ? username : 'Admin'}">Admin</div>
|
|
||||||
<form th:action="@{/logout}" method="post">
|
|
||||||
<button type="submit" class="text-sm text-red-600 hover:text-red-700 font-medium">Déconnexion</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
@@ -191,7 +172,7 @@
|
|||||||
th:data-tarif-exterieur="${lic.categorie.tarifExterieur}"
|
th:data-tarif-exterieur="${lic.categorie.tarifExterieur}"
|
||||||
th:data-equipements-contrib="${lic.getPrixTotal().subtract(adherent.residentTalange ? lic.categorie.tarifBase : lic.categorie.tarifExterieur)}"
|
th:data-equipements-contrib="${lic.getPrixTotal().subtract(adherent.residentTalange ? lic.categorie.tarifBase : lic.categorie.tarifExterieur)}"
|
||||||
th:data-total-paye="${lic.getPrixTotal().subtract(lic.getResteAPayer())}">
|
th:data-total-paye="${lic.getPrixTotal().subtract(lic.getResteAPayer())}">
|
||||||
<td class="py-4 px-4 font-medium text-gray-900" th:text="${lic.saison}">2024-2025</td>
|
<td class="py-4 px-4 font-medium text-gray-900" th:text="${lic.saison.nom}">2024-2025</td>
|
||||||
<td class="py-4 px-4 text-gray-600">
|
<td class="py-4 px-4 text-gray-600">
|
||||||
<span th:text="${lic.categorie.nom}">U15</span>
|
<span th:text="${lic.categorie.nom}">U15</span>
|
||||||
<span class="text-[10px] text-gray-400 block cell-tarif-categorie" th:text="${(lic.adherent != null && lic.adherent.residentTalange ? lic.categorie.tarifBase : lic.categorie.tarifExterieur) + ' € (Catégorie)'}">100.00 € (Catégorie)</span>
|
<span class="text-[10px] text-gray-400 block cell-tarif-categorie" th:text="${(lic.adherent != null && lic.adherent.residentTalange ? lic.categorie.tarifBase : lic.categorie.tarifExterieur) + ' € (Catégorie)'}">100.00 € (Catégorie)</span>
|
||||||
@@ -206,9 +187,20 @@
|
|||||||
<td class="py-4 px-4 font-semibold cell-reste-payer"
|
<td class="py-4 px-4 font-semibold cell-reste-payer"
|
||||||
th:classappend="${lic.getResteAPayer() == 0 ? 'text-green-600' : 'text-red-600'}"
|
th:classappend="${lic.getResteAPayer() == 0 ? 'text-green-600' : 'text-red-600'}"
|
||||||
th:text="${lic.getResteAPayer() + ' €'}">30.00 €</td>
|
th:text="${lic.getResteAPayer() + ' €'}">30.00 €</td>
|
||||||
<td class="py-4 px-4 text-right">
|
<td class="py-4 px-4 text-right flex justify-end space-x-2">
|
||||||
<button th:if="${lic.getResteAPayer() > 0}" th:onclick="'openPaiementModal(' + ${lic.id} + ', ' + ${lic.getResteAPayer()} + ')'"
|
<button type="button"
|
||||||
th:data-licence-id="${lic.id}" class="text-green-600 hover:text-green-800 font-medium bg-green-50 px-3 py-1 rounded-lg btn-payer">Payer</button>
|
th:data-licence-id="${lic.id}"
|
||||||
|
th:data-categorie-id="${lic.categorie.id}"
|
||||||
|
th:data-numero-licence="${lic.numeroLicence}"
|
||||||
|
th:data-etat="${lic.etat}"
|
||||||
|
onclick="openLicenceModal(this.getAttribute('data-licence-id'), this.getAttribute('data-categorie-id'), this.getAttribute('data-numero-licence'), this.getAttribute('data-etat'))"
|
||||||
|
class="text-blue-600 hover:text-blue-800 font-medium bg-blue-50 px-3 py-1 rounded-lg">Modifier</button>
|
||||||
|
<button th:if="${lic.getResteAPayer() > 0}"
|
||||||
|
type="button"
|
||||||
|
th:data-licence-id="${lic.id}"
|
||||||
|
th:data-reste-a-payer="${lic.getResteAPayer()}"
|
||||||
|
onclick="openPaiementModal(this.getAttribute('data-licence-id'), this.getAttribute('data-reste-a-payer'))"
|
||||||
|
class="text-green-600 hover:text-green-800 font-medium bg-green-50 px-3 py-1 rounded-lg btn-payer">Payer</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr th:if="${!#lists.isEmpty(lic.paiements)}" class="bg-gray-50/50">
|
<tr th:if="${!#lists.isEmpty(lic.paiements)}" class="bg-gray-50/50">
|
||||||
@@ -323,26 +315,29 @@
|
|||||||
<div id="licenceModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
|
<div id="licenceModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
|
||||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-xl bg-white">
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-xl bg-white">
|
||||||
<div class="mt-3 text-center">
|
<div class="mt-3 text-center">
|
||||||
<h3 class="text-lg leading-6 font-semibold text-gray-900 mb-4">Nouvelle Licence</h3>
|
<h3 id="licenceModalTitle" class="text-lg leading-6 font-semibold text-gray-900 mb-4">Nouvelle Licence</h3>
|
||||||
<form th:if="${adherent.id != null}" th:action="@{/adherents/{id}/licences(id=${adherent.id})}" method="post" class="space-y-4 text-left">
|
<form id="licenceForm" th:if="${adherent.id != null}" th:action="@{/adherents/{id}/licences(id=${adherent.id})}" method="post" class="space-y-4 text-left">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Catégorie</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Catégorie</label>
|
||||||
<select name="categorieId" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<select id="categorieSelect" name="categorieId" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
<option value="">Sélectionnez une catégorie...</option>
|
<option value="">Sélectionnez une catégorie...</option>
|
||||||
<option th:each="cat : ${categories}" th:value="${cat.id}" th:text="${cat.nom} + ' (Talange: ' + ${cat.tarifBase} + ' € / Ext: ' + ${cat.tarifExterieur} + ' €)'"></option>
|
<option th:each="cat : ${categories}" th:value="${cat.id}"
|
||||||
|
th:data-annee-min="${cat.anneeMin}"
|
||||||
|
th:data-annee-max="${cat.anneeMax}"
|
||||||
|
th:text="${cat.nom} + ' (Talange: ' + ${cat.tarifBase} + ' € / Ext: ' + ${cat.tarifExterieur} + ' €)'"></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Numéro de licence</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Numéro de licence</label>
|
||||||
<input type="text" name="numeroLicence" placeholder="Ex: FFF-123456" class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<input id="numeroLicenceInput" type="text" name="numeroLicence" placeholder="Ex: FFF-123456" class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Saison</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Saison</label>
|
||||||
<input type="text" name="saison" required value="2024-2025" class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<input id="saisonInput" type="text" name="saison" value="Saison Actuelle" readonly class="w-full border border-gray-200 bg-gray-50 rounded-lg px-4 py-2 text-sm text-gray-500 outline-none cursor-not-allowed">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">État</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">État</label>
|
||||||
<select name="etat" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<select id="etatSelect" name="etat" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
<option value="Brouillon">Brouillon</option>
|
<option value="Brouillon">Brouillon</option>
|
||||||
<option value="Validée">Validée</option>
|
<option value="Validée">Validée</option>
|
||||||
<option value="Payée">Payée</option>
|
<option value="Payée">Payée</option>
|
||||||
@@ -389,9 +384,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script th:inline="javascript">
|
||||||
function openLicenceModal() {
|
function openLicenceModal(licenceId = null, categorieId = '', numeroLicence = '', etat = 'Brouillon') {
|
||||||
document.getElementById('licenceModal').classList.remove('hidden');
|
document.getElementById('licenceModal').classList.remove('hidden');
|
||||||
|
const form = document.getElementById('licenceForm');
|
||||||
|
const title = document.getElementById('licenceModalTitle');
|
||||||
|
const adherentId = [[${adherent.id}]];
|
||||||
|
|
||||||
|
if (licenceId) {
|
||||||
|
title.textContent = 'Modifier la Licence';
|
||||||
|
form.action = '/adherents/' + adherentId + '/licences/' + licenceId + '/update';
|
||||||
|
document.getElementById('categorieSelect').value = categorieId;
|
||||||
|
document.getElementById('numeroLicenceInput').value = numeroLicence;
|
||||||
|
document.getElementById('etatSelect').value = etat;
|
||||||
|
} else {
|
||||||
|
title.textContent = 'Nouvelle Licence';
|
||||||
|
form.action = '/adherents/' + adherentId + '/licences';
|
||||||
|
document.getElementById('numeroLicenceInput').value = '';
|
||||||
|
document.getElementById('etatSelect').value = 'Brouillon';
|
||||||
|
|
||||||
|
// Pré-sélection de la catégorie selon l'année de naissance
|
||||||
|
const dateNaissanceStr = document.getElementById('dateNaissance').value;
|
||||||
|
const catSelect = document.getElementById('categorieSelect');
|
||||||
|
let foundCat = false;
|
||||||
|
|
||||||
|
if (dateNaissanceStr) {
|
||||||
|
const birthYear = new Date(dateNaissanceStr).getFullYear();
|
||||||
|
|
||||||
|
Array.from(catSelect.options).forEach(opt => {
|
||||||
|
const min = parseInt(opt.getAttribute('data-annee-min'));
|
||||||
|
const max = parseInt(opt.getAttribute('data-annee-max'));
|
||||||
|
if (!isNaN(min) && !isNaN(max) && birthYear >= min && birthYear <= max) {
|
||||||
|
catSelect.value = opt.value;
|
||||||
|
foundCat = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundCat) {
|
||||||
|
catSelect.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function closeLicenceModal() {
|
function closeLicenceModal() {
|
||||||
document.getElementById('licenceModal').classList.add('hidden');
|
document.getElementById('licenceModal').classList.add('hidden');
|
||||||
|
|||||||
@@ -13,26 +13,7 @@
|
|||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
<div class="p-4 border-t border-gray-200">
|
|
||||||
<div class="text-sm font-medium text-gray-900 mb-1" th:text="${username != null ? username : 'Admin'}">Admin</div>
|
|
||||||
<form th:action="@{/logout}" method="post">
|
|
||||||
<button type="submit" class="text-sm text-red-600 hover:text-red-700 font-medium">Déconnexion</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
@@ -55,7 +36,12 @@
|
|||||||
|
|
||||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
<div 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">
|
||||||
<input type="text" placeholder="Rechercher un adhérent..." class="border border-gray-300 rounded-lg px-4 py-2 text-sm w-64 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
<form th:action="@{/adherents}" method="get" class="flex items-center space-x-2">
|
||||||
|
<input type="text" name="query" th:value="${searchQuery}" placeholder="Nom, Prénom ou N° Licence..."
|
||||||
|
class="border border-gray-300 rounded-lg px-4 py-2 text-sm w-64 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<button type="submit" class="bg-gray-100 text-gray-700 px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-200 border border-gray-300 transition-colors">Chercher</button>
|
||||||
|
<a th:if="${searchQuery != null and !searchQuery.isEmpty()}" th:href="@{/adherents}" class="text-sm text-gray-500 hover:text-gray-700 underline underline-offset-2 ml-2">Effacer</a>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<table class="w-full text-left border-collapse">
|
<table class="w-full text-left border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<aside th:fragment="sidebar" class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
||||||
|
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
||||||
|
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
||||||
|
</div>
|
||||||
|
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
||||||
|
<a href="/" th:classappend="${requestURI == '/' ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Tableau de bord</a>
|
||||||
|
<a href="/adherents" th:classappend="${requestURI != null and requestURI.startsWith('/adherents') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Adhérents</a>
|
||||||
|
<a href="/paiements" th:classappend="${requestURI != null and requestURI.startsWith('/paiements') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Paiements</a>
|
||||||
|
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
||||||
|
<a href="/saisons" th:classappend="${requestURI != null and requestURI.startsWith('/saisons') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Saisons</a>
|
||||||
|
<a href="/categories" th:classappend="${requestURI != null and requestURI.startsWith('/categories') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Catégories</a>
|
||||||
|
<a href="/modespaiement" th:classappend="${requestURI != null and requestURI.startsWith('/modespaiement') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Modes de Paiement</a>
|
||||||
|
<a href="/equipements" th:classappend="${requestURI != null and requestURI.startsWith('/equipements') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Équipements</a>
|
||||||
|
</nav>
|
||||||
|
<div class="p-4 border-t border-gray-200">
|
||||||
|
<div class="text-sm font-medium text-gray-900 mb-1" th:text="${username != null ? username : 'Admin'}">Admin</div>
|
||||||
|
<form th:action="@{/logout}" method="post">
|
||||||
|
<button type="submit" class="text-sm text-red-600 hover:text-red-700 font-medium">Déconnexion</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -13,26 +13,7 @@
|
|||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
<div class="p-4 border-t border-gray-200">
|
|
||||||
<div class="text-sm font-medium text-gray-900 mb-1" th:text="${username}">Admin</div>
|
|
||||||
<form th:action="@{/logout}" method="post">
|
|
||||||
<button type="submit" class="text-sm text-red-600 hover:text-red-700 font-medium">Déconnexion</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
|
|||||||
@@ -9,20 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
|||||||
@@ -9,20 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
|||||||
@@ -9,20 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
|||||||
@@ -9,20 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
|||||||
@@ -9,20 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
|||||||
@@ -9,20 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
|||||||
@@ -9,20 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col hidden md:flex">
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
||||||
<h1 class="text-xl font-bold text-blue-600">AS Talange</h1>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
|
||||||
<a href="/" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Tableau de bord</a>
|
|
||||||
<a href="/adherents" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Adhérents</a>
|
|
||||||
<a href="/paiements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Paiements</a>
|
|
||||||
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Paramétrage</div>
|
|
||||||
<a href="/categories" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Catégories</a>
|
|
||||||
<a href="/modespaiement" class="block px-3 py-2 rounded-lg bg-blue-50 text-blue-700 font-medium">Modes de Paiement</a>
|
|
||||||
<a href="/equipements" class="block px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors">Équipements</a>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Nouvelle Saison - AS Talange</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style> body { font-family: 'Inter', sans-serif; } </style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
|
<!-- Sidebar fragment -->
|
||||||
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
|
|
||||||
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-800">Ajouter une Saison</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex-1 overflow-auto p-6 flex justify-center items-start">
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-8 w-full max-w-2xl mt-8">
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 mb-6">Paramétrage de la nouvelle saison</h3>
|
||||||
|
|
||||||
|
<form th:action="@{/saisons}" method="post" class="space-y-6">
|
||||||
|
|
||||||
|
<!-- Nom de la saison -->
|
||||||
|
<div>
|
||||||
|
<label for="nom" class="block text-sm font-medium text-gray-700 mb-1">Nom de la saison *</label>
|
||||||
|
<input type="text" id="nom" name="nom" required placeholder="Ex: Saison 2025-2026"
|
||||||
|
class="w-full rounded-lg border-gray-300 border px-4 py-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Importer paramétrage -->
|
||||||
|
<div class="flex items-start bg-blue-50 p-4 rounded-lg border border-blue-100">
|
||||||
|
<div class="flex items-center h-5">
|
||||||
|
<input id="importerParametrage" name="importerParametrage" type="checkbox" value="true" checked
|
||||||
|
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||||
|
</div>
|
||||||
|
<div class="ml-3 text-sm">
|
||||||
|
<label for="importerParametrage" class="font-medium text-blue-800">Dupliquer le paramétrage de la saison actuelle</label>
|
||||||
|
<p class="text-blue-600 mt-1">Si coché, toutes les catégories (avec +1 an sur l'âge pivot) et tous les équipements seront clonés automatiquement pour cette nouvelle saison.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Note sur l'activation -->
|
||||||
|
<div class="p-4 bg-yellow-50 text-yellow-800 rounded-lg text-sm border border-yellow-200">
|
||||||
|
<strong>Attention :</strong> La création de cette saison la définira automatiquement comme la <strong>nouvelle saison active</strong> du club.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end space-x-3 pt-4 border-t border-gray-100">
|
||||||
|
<a th:href="@{/saisons}" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 font-medium">Annuler</a>
|
||||||
|
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
|
||||||
|
Créer la saison
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Paramétrage - Saisons</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style> body { font-family: 'Inter', sans-serif; } </style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||||
|
<!-- Sidebar fragment -->
|
||||||
|
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||||
|
|
||||||
|
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
|
<header class="h-16 bg-white border-b border-gray-200 flex items-center px-6">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-800">Saisons du Club</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex-1 overflow-auto p-6">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h3 class="text-xl font-bold text-gray-900">Liste des Saisons</h3>
|
||||||
|
<a th:href="@{/saisons/new}" class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700">
|
||||||
|
+ Nouvelle Saison
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<table class="w-full text-left border-collapse">
|
||||||
|
<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 de la Saison</th>
|
||||||
|
<th class="py-3 px-6 font-medium text-center">Statut</th>
|
||||||
|
<th class="py-3 px-6 font-medium text-right">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200 text-sm">
|
||||||
|
<tr th:if="${#lists.isEmpty(saisons)}">
|
||||||
|
<td colspan="3" class="py-8 text-center text-gray-500">Aucune saison configurée.</td>
|
||||||
|
</tr>
|
||||||
|
<tr th:each="saison : ${saisons}" class="hover:bg-gray-50 transition-colors">
|
||||||
|
<td class="py-4 px-6 font-medium text-gray-900" th:text="${saison.nom}">Saison 2024-2025</td>
|
||||||
|
<td class="py-4 px-6 text-center">
|
||||||
|
<span th:if="${saison.estActive}" class="px-3 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||||
|
Saison Active
|
||||||
|
</span>
|
||||||
|
<span th:unless="${saison.estActive}" class="px-3 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-600">
|
||||||
|
Clôturée
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-4 px-6 text-right flex justify-end space-x-3 items-center">
|
||||||
|
<form th:if="${!saison.estActive}" th:action="@{/saisons/{id}/activer(id=${saison.id})}" method="post">
|
||||||
|
<button type="submit" class="text-indigo-600 hover:text-indigo-900 font-medium bg-indigo-50 px-3 py-1 rounded-lg">Définir Active</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user