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)
|
||||
private BigDecimal tarifExterieur;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "saison_id", nullable = false)
|
||||
private Saison saison;
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public Long getId() { return id; }
|
||||
@@ -45,4 +49,7 @@ public class Categorie {
|
||||
|
||||
public BigDecimal getTarifExterieur() { return 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)
|
||||
private BigDecimal prixContribution = BigDecimal.ZERO;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "saison_id", nullable = false)
|
||||
private Saison saison;
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public Long getId() { return id; }
|
||||
@@ -39,4 +43,7 @@ public class Equipement {
|
||||
|
||||
public BigDecimal getPrixContribution() { return 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)
|
||||
private Categorie categorie;
|
||||
|
||||
@Column(nullable = false, length = 20)
|
||||
private String saison;
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "saison_id", nullable = false)
|
||||
private Saison saison;
|
||||
|
||||
@Column(nullable = false, length = 50)
|
||||
private String etat;
|
||||
@@ -89,8 +90,8 @@ public class Licence {
|
||||
public Categorie getCategorie() { return categorie; }
|
||||
public void setCategorie(Categorie categorie) { this.categorie = categorie; }
|
||||
|
||||
public String getSaison() { return saison; }
|
||||
public void setSaison(String saison) { this.saison = saison; }
|
||||
public Saison getSaison() { return saison; }
|
||||
public void setSaison(Saison saison) { this.saison = saison; }
|
||||
|
||||
public String getEtat() { return 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
|
||||
public interface AdherentRepository extends JpaRepository<Adherent, Long> {
|
||||
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.stereotype.Repository;
|
||||
|
||||
import com.astalange.core.entity.Saison;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
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.stereotype.Repository;
|
||||
|
||||
import com.astalange.core.entity.Saison;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
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;
|
||||
|
||||
import com.astalange.core.entity.Adherent;
|
||||
import com.astalange.core.entity.Saison;
|
||||
import com.astalange.core.repository.AdherentRepository;
|
||||
import com.astalange.core.repository.CategorieRepository;
|
||||
import com.astalange.core.repository.SaisonRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -18,17 +20,25 @@ public class AdherentController {
|
||||
private final CategorieRepository categorieRepository;
|
||||
private final com.astalange.core.repository.LicenceRepository licenceRepository;
|
||||
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.categorieRepository = categorieRepository;
|
||||
this.licenceRepository = licenceRepository;
|
||||
this.modePaiementRepository = modePaiementRepository;
|
||||
this.saisonRepository = saisonRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String listAdherents(Model model) {
|
||||
List<Adherent> adherents = adherentRepository.findAll();
|
||||
public String listAdherents(@org.springframework.web.bind.annotation.RequestParam(required = false) String query, Model model) {
|
||||
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);
|
||||
return "adherents/list";
|
||||
}
|
||||
@@ -57,8 +67,16 @@ public class AdherentController {
|
||||
public String editAdherent(@org.springframework.web.bind.annotation.PathVariable Long id, Model model) {
|
||||
Adherent adherent = adherentRepository.findById(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("categories", categorieRepository.findAll());
|
||||
model.addAttribute("licences", licenceRepository.findByAdherentId(id));
|
||||
model.addAttribute("modesPaiement", modePaiementRepository.findAll());
|
||||
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.Dotation;
|
||||
import com.astalange.core.entity.Equipement;
|
||||
import com.astalange.core.entity.Saison;
|
||||
import com.astalange.core.repository.AdherentRepository;
|
||||
import com.astalange.core.repository.CategorieRepository;
|
||||
import com.astalange.core.repository.LicenceRepository;
|
||||
import com.astalange.core.repository.DotationRepository;
|
||||
import com.astalange.core.repository.EquipementRepository;
|
||||
import com.astalange.core.repository.SaisonRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -25,24 +27,26 @@ public class LicenceController {
|
||||
private final LicenceRepository licenceRepository;
|
||||
private final EquipementRepository equipementRepository;
|
||||
private final DotationRepository dotationRepository;
|
||||
private final SaisonRepository saisonRepository;
|
||||
|
||||
public LicenceController(AdherentRepository adherentRepository,
|
||||
CategorieRepository categorieRepository,
|
||||
LicenceRepository licenceRepository,
|
||||
EquipementRepository equipementRepository,
|
||||
DotationRepository dotationRepository) {
|
||||
DotationRepository dotationRepository,
|
||||
SaisonRepository saisonRepository) {
|
||||
this.adherentRepository = adherentRepository;
|
||||
this.categorieRepository = categorieRepository;
|
||||
this.licenceRepository = licenceRepository;
|
||||
this.equipementRepository = equipementRepository;
|
||||
this.dotationRepository = dotationRepository;
|
||||
this.saisonRepository = saisonRepository;
|
||||
}
|
||||
|
||||
@PostMapping("/adherents/{id}/licences")
|
||||
public String addLicence(
|
||||
@PathVariable Long id,
|
||||
@RequestParam Long categorieId,
|
||||
@RequestParam String saison,
|
||||
@RequestParam String etat,
|
||||
@RequestParam(required = false) String numeroLicence) {
|
||||
|
||||
@@ -57,17 +61,20 @@ public class LicenceController {
|
||||
Categorie categorie = categorieRepository.findById(categorieId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Invalid Categorie ID"));
|
||||
|
||||
Saison saisonActive = saisonRepository.findByEstActiveTrue()
|
||||
.orElseThrow(() -> new IllegalStateException("Aucune saison active trouvée"));
|
||||
|
||||
Licence licence = new Licence();
|
||||
licence.setAdherent(adherent);
|
||||
licence.setCategorie(categorie);
|
||||
licence.setSaison(saison);
|
||||
licence.setSaison(saisonActive);
|
||||
licence.setEtat(etat);
|
||||
licence.setNumeroLicence(numeroLicence);
|
||||
|
||||
licence = licenceRepository.save(licence);
|
||||
|
||||
// Auto-initialize dotations for all configured equipments
|
||||
List<Equipement> equipements = equipementRepository.findAll();
|
||||
// Auto-initialize dotations for all configured equipments of the active season
|
||||
List<Equipement> equipements = equipementRepository.findBySaison(saisonActive);
|
||||
for (Equipement eq : equipements) {
|
||||
Dotation dotation = new Dotation();
|
||||
dotation.setLicence(licence);
|
||||
@@ -82,4 +89,27 @@ public class LicenceController {
|
||||
|
||||
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">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||
@@ -191,7 +172,7 @@
|
||||
th:data-tarif-exterieur="${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())}">
|
||||
<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">
|
||||
<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>
|
||||
@@ -206,9 +187,20 @@
|
||||
<td class="py-4 px-4 font-semibold cell-reste-payer"
|
||||
th:classappend="${lic.getResteAPayer() == 0 ? 'text-green-600' : 'text-red-600'}"
|
||||
th:text="${lic.getResteAPayer() + ' €'}">30.00 €</td>
|
||||
<td class="py-4 px-4 text-right">
|
||||
<button th:if="${lic.getResteAPayer() > 0}" th:onclick="'openPaiementModal(' + ${lic.id} + ', ' + ${lic.getResteAPayer()} + ')'"
|
||||
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>
|
||||
<td class="py-4 px-4 text-right flex justify-end space-x-2">
|
||||
<button type="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>
|
||||
</tr>
|
||||
<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 class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3 text-center">
|
||||
<h3 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">
|
||||
<h3 id="licenceModalTitle" class="text-lg leading-6 font-semibold text-gray-900 mb-4">Nouvelle Licence</h3>
|
||||
<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>
|
||||
<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 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>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
<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>
|
||||
<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="Validée">Validée</option>
|
||||
<option value="Payée">Payée</option>
|
||||
@@ -389,9 +384,47 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openLicenceModal() {
|
||||
<script th:inline="javascript">
|
||||
function openLicenceModal(licenceId = null, categorieId = '', numeroLicence = '', etat = 'Brouillon') {
|
||||
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() {
|
||||
document.getElementById('licenceModal').classList.add('hidden');
|
||||
|
||||
@@ -13,26 +13,7 @@
|
||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<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="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>
|
||||
<table class="w-full text-left border-collapse">
|
||||
<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">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<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">
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<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">
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<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">
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<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">
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<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">
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<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">
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<aside 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="/" 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>
|
||||
<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">
|
||||
|
||||
@@ -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