feat: implement educator management and display educator initials on training schedule
This commit is contained in:
@@ -52,6 +52,9 @@ public class CreneauEntrainement {
|
|||||||
@Transient
|
@Transient
|
||||||
private Set<String> categoriesEnConflit = new HashSet<>();
|
private Set<String> categoriesEnConflit = new HashSet<>();
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private String initialesEducateurs = "";
|
||||||
|
|
||||||
// Logic for Gantt diagram grid placement
|
// Logic for Gantt diagram grid placement
|
||||||
|
|
||||||
public int getStartColumn() {
|
public int getStartColumn() {
|
||||||
@@ -214,4 +217,12 @@ public class CreneauEntrainement {
|
|||||||
public void setCategoriesEnConflit(Set<String> categoriesEnConflit) {
|
public void setCategoriesEnConflit(Set<String> categoriesEnConflit) {
|
||||||
this.categoriesEnConflit = categoriesEnConflit;
|
this.categoriesEnConflit = categoriesEnConflit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getInitialesEducateurs() {
|
||||||
|
return initialesEducateurs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitialesEducateurs(String initialesEducateurs) {
|
||||||
|
this.initialesEducateurs = initialesEducateurs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ public class Educateur {
|
|||||||
@JoinColumn(name = "categorie_id")
|
@JoinColumn(name = "categorie_id")
|
||||||
private Categorie categorie;
|
private Categorie categorie;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "equipe_id")
|
||||||
|
private Equipe equipe;
|
||||||
|
|
||||||
// Getters and Setters
|
// Getters and Setters
|
||||||
|
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
@@ -45,4 +49,13 @@ public class Educateur {
|
|||||||
|
|
||||||
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 Equipe getEquipe() { return equipe; }
|
||||||
|
public void setEquipe(Equipe equipe) { this.equipe = equipe; }
|
||||||
|
|
||||||
|
public String getInitiales() {
|
||||||
|
String p = (prenom != null && !prenom.isEmpty()) ? prenom.substring(0, 1).toUpperCase() : "";
|
||||||
|
String n = (nom != null && !nom.isEmpty()) ? nom.substring(0, 1).toUpperCase() : "";
|
||||||
|
return p + n;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.astalange.core.repository;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.Educateur;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface EducateurRepository extends JpaRepository<Educateur, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT e FROM Educateur e LEFT JOIN FETCH e.categorie LEFT JOIN FETCH e.equipe")
|
||||||
|
List<Educateur> findAllWithCategorieAndEquipe();
|
||||||
|
}
|
||||||
@@ -4,10 +4,16 @@ import com.astalange.core.entity.Equipe;
|
|||||||
import com.astalange.core.entity.Saison;
|
import com.astalange.core.entity.Saison;
|
||||||
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 org.springframework.data.jpa.repository.Query;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface EquipeRepository extends JpaRepository<Equipe, Long> {
|
public interface EquipeRepository extends JpaRepository<Equipe, Long> {
|
||||||
List<Equipe> findByCategorieId(Long categorieId);
|
List<Equipe> findByCategorieId(Long categorieId);
|
||||||
|
|
||||||
|
@Query("SELECT e FROM Equipe e JOIN FETCH e.categorie WHERE e.saison = :saison")
|
||||||
List<Equipe> findBySaison(Saison saison);
|
List<Equipe> findBySaison(Saison saison);
|
||||||
|
|
||||||
|
@Query("SELECT e FROM Equipe e JOIN FETCH e.categorie")
|
||||||
|
List<Equipe> findAllWithCategorie();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- V13__add_equipe_to_educateur.sql
|
||||||
|
ALTER TABLE educateur ADD COLUMN equipe_id BIGINT;
|
||||||
|
ALTER TABLE educateur ADD CONSTRAINT fk_educateur_equipe FOREIGN KEY (equipe_id) REFERENCES equipe(id) ON DELETE SET NULL;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.astalange.core;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.*;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class EducateurTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEducateurEntityAndAssociations() {
|
||||||
|
Categorie U15 = new Categorie();
|
||||||
|
U15.setNom("U15");
|
||||||
|
|
||||||
|
Equipe equipeA = new Equipe();
|
||||||
|
equipeA.setNom("Équipe A");
|
||||||
|
equipeA.setCategorie(U15);
|
||||||
|
|
||||||
|
Educateur educateur = new Educateur();
|
||||||
|
educateur.setNom("Dupont");
|
||||||
|
educateur.setPrenom("Jean");
|
||||||
|
educateur.setTelephone("0612345678");
|
||||||
|
educateur.setEmail("jean.dupont@example.com");
|
||||||
|
educateur.setCategorie(U15);
|
||||||
|
educateur.setEquipe(equipeA);
|
||||||
|
|
||||||
|
assertEquals("Dupont", educateur.getNom());
|
||||||
|
assertEquals("Jean", educateur.getPrenom());
|
||||||
|
assertEquals("0612345678", educateur.getTelephone());
|
||||||
|
assertEquals("jean.dupont@example.com", educateur.getEmail());
|
||||||
|
assertEquals(U15, educateur.getCategorie());
|
||||||
|
assertEquals(equipeA, educateur.getEquipe());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.astalange.web.controller;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.Educateur;
|
||||||
|
import com.astalange.core.entity.Categorie;
|
||||||
|
import com.astalange.core.entity.Equipe;
|
||||||
|
import com.astalange.core.entity.Saison;
|
||||||
|
import com.astalange.core.repository.EducateurRepository;
|
||||||
|
import com.astalange.core.repository.CategorieRepository;
|
||||||
|
import com.astalange.core.repository.EquipeRepository;
|
||||||
|
import com.astalange.core.repository.SaisonRepository;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/educateurs")
|
||||||
|
public class EducateurController {
|
||||||
|
|
||||||
|
private final EducateurRepository educateurRepository;
|
||||||
|
private final CategorieRepository categorieRepository;
|
||||||
|
private final EquipeRepository equipeRepository;
|
||||||
|
private final SaisonRepository saisonRepository;
|
||||||
|
|
||||||
|
public EducateurController(EducateurRepository educateurRepository,
|
||||||
|
CategorieRepository categorieRepository,
|
||||||
|
EquipeRepository equipeRepository,
|
||||||
|
SaisonRepository saisonRepository) {
|
||||||
|
this.educateurRepository = educateurRepository;
|
||||||
|
this.categorieRepository = categorieRepository;
|
||||||
|
this.equipeRepository = equipeRepository;
|
||||||
|
this.saisonRepository = saisonRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String listEducateurs(Model model) {
|
||||||
|
model.addAttribute("educateurs", educateurRepository.findAllWithCategorieAndEquipe());
|
||||||
|
return "parametrage/educateurs_list";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/new")
|
||||||
|
public String showCreateForm(Model model) {
|
||||||
|
model.addAttribute("educateur", new Educateur());
|
||||||
|
populateModelAttributes(model);
|
||||||
|
return "parametrage/educateurs_form";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/edit")
|
||||||
|
public String showEditForm(@PathVariable Long id, Model model) {
|
||||||
|
Educateur educateur = educateurRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Invalid educator ID: " + id));
|
||||||
|
model.addAttribute("educateur", educateur);
|
||||||
|
populateModelAttributes(model);
|
||||||
|
return "parametrage/educateurs_form";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public String saveEducateur(@ModelAttribute("educateur") Educateur educateur) {
|
||||||
|
if (educateur.getCategorie() != null && educateur.getCategorie().getId() == null) {
|
||||||
|
educateur.setCategorie(null);
|
||||||
|
}
|
||||||
|
if (educateur.getEquipe() != null && educateur.getEquipe().getId() == null) {
|
||||||
|
educateur.setEquipe(null);
|
||||||
|
}
|
||||||
|
educateurRepository.save(educateur);
|
||||||
|
return "redirect:/educateurs";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/delete")
|
||||||
|
public String deleteEducateur(@PathVariable Long id) {
|
||||||
|
Educateur educateur = educateurRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Invalid educator ID: " + id));
|
||||||
|
educateurRepository.delete(educateur);
|
||||||
|
return "redirect:/educateurs";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateModelAttributes(Model model) {
|
||||||
|
Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null);
|
||||||
|
if (saisonActive != null) {
|
||||||
|
model.addAttribute("categories", categorieRepository.findBySaison(saisonActive));
|
||||||
|
model.addAttribute("equipes", equipeRepository.findBySaison(saisonActive));
|
||||||
|
} else {
|
||||||
|
model.addAttribute("categories", categorieRepository.findAll());
|
||||||
|
model.addAttribute("equipes", equipeRepository.findAllWithCategorie());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.astalange.web.controller;
|
|||||||
import com.astalange.core.entity.*;
|
import com.astalange.core.entity.*;
|
||||||
import com.astalange.core.repository.CategorieRepository;
|
import com.astalange.core.repository.CategorieRepository;
|
||||||
import com.astalange.core.repository.CreneauEntrainementRepository;
|
import com.astalange.core.repository.CreneauEntrainementRepository;
|
||||||
|
import com.astalange.core.repository.EducateurRepository;
|
||||||
import com.astalange.core.repository.EquipeRepository;
|
import com.astalange.core.repository.EquipeRepository;
|
||||||
import com.astalange.core.repository.SaisonRepository;
|
import com.astalange.core.repository.SaisonRepository;
|
||||||
import com.astalange.core.repository.TerrainRepository;
|
import com.astalange.core.repository.TerrainRepository;
|
||||||
@@ -23,17 +24,20 @@ public class PlanningController {
|
|||||||
private final CategorieRepository categorieRepository;
|
private final CategorieRepository categorieRepository;
|
||||||
private final SaisonRepository saisonRepository;
|
private final SaisonRepository saisonRepository;
|
||||||
private final EquipeRepository equipeRepository;
|
private final EquipeRepository equipeRepository;
|
||||||
|
private final EducateurRepository educateurRepository;
|
||||||
|
|
||||||
public PlanningController(CreneauEntrainementRepository creneauRepository,
|
public PlanningController(CreneauEntrainementRepository creneauRepository,
|
||||||
TerrainRepository terrainRepository,
|
TerrainRepository terrainRepository,
|
||||||
CategorieRepository categorieRepository,
|
CategorieRepository categorieRepository,
|
||||||
SaisonRepository saisonRepository,
|
SaisonRepository saisonRepository,
|
||||||
EquipeRepository equipeRepository) {
|
EquipeRepository equipeRepository,
|
||||||
|
EducateurRepository educateurRepository) {
|
||||||
this.creneauRepository = creneauRepository;
|
this.creneauRepository = creneauRepository;
|
||||||
this.terrainRepository = terrainRepository;
|
this.terrainRepository = terrainRepository;
|
||||||
this.categorieRepository = categorieRepository;
|
this.categorieRepository = categorieRepository;
|
||||||
this.saisonRepository = saisonRepository;
|
this.saisonRepository = saisonRepository;
|
||||||
this.equipeRepository = equipeRepository;
|
this.equipeRepository = equipeRepository;
|
||||||
|
this.educateurRepository = educateurRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@@ -56,10 +60,33 @@ public class PlanningController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<CreneauEntrainement> creneaux = creneauRepository.findBySaisonAndJourSemaineFetch(saisonActive, selectedDay);
|
List<CreneauEntrainement> creneaux = creneauRepository.findBySaisonAndJourSemaineFetch(saisonActive, selectedDay);
|
||||||
|
List<Educateur> educateurs = educateurRepository.findAllWithCategorieAndEquipe();
|
||||||
|
|
||||||
// Detect conflicts
|
// Detect conflicts and populate educator initials
|
||||||
for (int i = 0; i < creneaux.size(); i++) {
|
for (int i = 0; i < creneaux.size(); i++) {
|
||||||
CreneauEntrainement c1 = creneaux.get(i);
|
CreneauEntrainement c1 = creneaux.get(i);
|
||||||
|
|
||||||
|
// Populate educator initials
|
||||||
|
List<String> initials = new ArrayList<>();
|
||||||
|
for (Educateur ed : educateurs) {
|
||||||
|
boolean match = false;
|
||||||
|
if (c1.getEquipe() != null) {
|
||||||
|
if (ed.getEquipe() != null && ed.getEquipe().getId().equals(c1.getEquipe().getId())) {
|
||||||
|
match = true;
|
||||||
|
}
|
||||||
|
} else if (c1.getCategorie() != null) {
|
||||||
|
if (ed.getCategorie() != null && ed.getCategorie().getId().equals(c1.getCategorie().getId())) {
|
||||||
|
match = true;
|
||||||
|
} else if (ed.getEquipe() != null && ed.getEquipe().getCategorie() != null && ed.getEquipe().getCategorie().getId().equals(c1.getCategorie().getId())) {
|
||||||
|
match = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
initials.add(ed.getInitiales());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c1.setInitialesEducateurs(String.join(", ", initials));
|
||||||
|
|
||||||
for (int j = 0; j < creneaux.size(); j++) {
|
for (int j = 0; j < creneaux.size(); j++) {
|
||||||
if (i == j) continue;
|
if (i == j) continue;
|
||||||
CreneauEntrainement c2 = creneaux.get(j);
|
CreneauEntrainement c2 = creneaux.get(j);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
<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="/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="/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="/equipes" th:classappend="${requestURI != null and requestURI.startsWith('/equipes') ? '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">Équipes</a>
|
<a href="/equipes" th:classappend="${requestURI != null and requestURI.startsWith('/equipes') ? '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">Équipes</a>
|
||||||
|
<a href="/educateurs" th:classappend="${requestURI != null and requestURI.startsWith('/educateurs') ? '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">Éducateurs</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="/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>
|
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Éducateur - 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 -->
|
||||||
|
<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" th:text="${educateur.id == null ? 'Nouvel Éducateur' : 'Modifier Éducateur'}">Éducateur</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex-1 overflow-auto p-6">
|
||||||
|
<div class="max-w-xl mx-auto bg-white rounded-xl shadow-sm border border-gray-100 p-8">
|
||||||
|
<form th:action="@{/educateurs}" th:object="${educateur}" method="post" class="space-y-6">
|
||||||
|
<input type="hidden" th:field="*{id}" />
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1 font-semibold">Nom <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" th:field="*{nom}" 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">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1 font-semibold">Prénom <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" th:field="*{prenom}" 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">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1 font-semibold">Numéro de Téléphone (non obligatoire)</label>
|
||||||
|
<input type="tel" th:field="*{telephone}" 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 font-semibold">Adresse E-mail (facultatif)</label>
|
||||||
|
<input type="email" th:field="*{email}" 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 class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1 font-semibold">Catégorie</label>
|
||||||
|
<select th:field="*{categorie}" 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="">-- Aucune --</option>
|
||||||
|
<option th:each="cat : ${categories}" th:value="${cat.id}" th:text="${cat.nom}">U15</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1 font-semibold">Équipe</label>
|
||||||
|
<select th:field="*{equipe}" 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="">-- Aucune --</option>
|
||||||
|
<option th:each="equip : ${equipes}" th:value="${equip.id}" th:text="${equip.nom + ' (' + equip.categorie.nom + ')'}">Équipe A (U15)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-4 flex justify-end space-x-3 border-t border-gray-100">
|
||||||
|
<a th:href="@{/educateurs}" class="px-4 py-2 text-sm font-medium text-gray-700 border border-gray-300 rounded-lg hover:bg-gray-50">Annuler</a>
|
||||||
|
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Paramétrage - Éducateurs</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 -->
|
||||||
|
<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">Éducateurs / Entraîneurs</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 Éducateurs</h3>
|
||||||
|
<a th:href="@{/educateurs/new}" class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700">
|
||||||
|
+ Nouvel Éducateur
|
||||||
|
</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</th>
|
||||||
|
<th class="py-3 px-6 font-medium text-left">Prénom</th>
|
||||||
|
<th class="py-3 px-6 font-medium text-left">Téléphone</th>
|
||||||
|
<th class="py-3 px-6 font-medium text-left">Email</th>
|
||||||
|
<th class="py-3 px-6 font-medium text-left">Catégorie</th>
|
||||||
|
<th class="py-3 px-6 font-medium text-left">Équipe</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(educateurs)}">
|
||||||
|
<td colspan="7" class="py-8 text-center text-gray-500">Aucun éducateur n'est paramétré.</td>
|
||||||
|
</tr>
|
||||||
|
<tr th:each="ed : ${educateurs}" class="hover:bg-gray-50 transition-colors">
|
||||||
|
<td class="py-4 px-6 font-medium text-gray-900" th:text="${ed.nom}">Dupont</td>
|
||||||
|
<td class="py-4 px-6 text-gray-600" th:text="${ed.prenom}">Jean</td>
|
||||||
|
<td class="py-4 px-6 text-gray-600" th:text="${ed.telephone != null and !ed.telephone.isEmpty() ? ed.telephone : '-'}">-</td>
|
||||||
|
<td class="py-4 px-6 text-gray-600" th:text="${ed.email != null and !ed.email.isEmpty() ? ed.email : '-'}">-</td>
|
||||||
|
<td class="py-4 px-6 text-gray-600">
|
||||||
|
<span th:if="${ed.categorie != null}" th:text="${ed.categorie.nom}" class="px-2.5 py-1 rounded-full text-xs font-semibold bg-blue-100 text-blue-800">U15</span>
|
||||||
|
<span th:if="${ed.categorie == null}">-</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-4 px-6 text-gray-600">
|
||||||
|
<span th:if="${ed.equipe != null}" th:text="${ed.equipe.nom}" class="px-2.5 py-1 rounded-full text-xs font-semibold bg-green-100 text-green-800">Équipe A</span>
|
||||||
|
<span th:if="${ed.equipe == null}">-</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-4 px-6 text-right flex justify-end space-x-3 items-center">
|
||||||
|
<a th:href="@{/educateurs/{id}/edit(id=${ed.id})}" class="text-indigo-600 hover:text-indigo-900 font-medium bg-indigo-50 px-3 py-1 rounded-lg">Modifier</a>
|
||||||
|
<form th:action="@{/educateurs/{id}/delete(id=${ed.id})}" method="post" onsubmit="return confirm('Supprimer cet éducateur ?');">
|
||||||
|
<button type="submit" class="text-red-600 hover:text-red-800 font-medium">Supprimer</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
class="border-l-4 p-2 rounded shadow-sm flex flex-col justify-between overflow-hidden cursor-pointer hover:shadow transition-all relative z-10 m-0.5">
|
class="border-l-4 p-2 rounded shadow-sm flex flex-col justify-between overflow-hidden cursor-pointer hover:shadow transition-all relative z-10 m-0.5">
|
||||||
|
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<span class="font-bold text-xs" th:text="${c.categorie.nom + (c.equipe != null ? ' (' + c.equipe.nom + ')' : '')}">U15</span>
|
<span class="font-bold text-xs" th:text="${c.categorie.nom + (c.equipe != null ? ' (' + c.equipe.nom + ')' : '') + (c.initialesEducateurs != null and !c.initialesEducateurs.isEmpty() ? ' [' + c.initialesEducateurs + ']' : '')}">U15</span>
|
||||||
<!-- Delete button or info -->
|
<!-- Delete button or info -->
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<a th:href="@{/planning/{id}/edit(id=${c.id})}" class="text-[10px] text-gray-500 hover:underline">Modifier</a>
|
<a th:href="@{/planning/{id}/edit(id=${c.id})}" class="text-[10px] text-gray-500 hover:underline">Modifier</a>
|
||||||
@@ -192,7 +192,13 @@
|
|||||||
<td colspan="6" class="py-8 text-center text-gray-500">Aucun entraînement planifié pour ce jour.</td>
|
<td colspan="6" class="py-8 text-center text-gray-500">Aucun entraînement planifié pour ce jour.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr th:each="c : ${creneaux}" class="hover:bg-gray-50 transition-colors">
|
<tr th:each="c : ${creneaux}" class="hover:bg-gray-50 transition-colors">
|
||||||
<td class="py-4 px-6 font-semibold text-gray-900" th:text="${c.categorie.nom + (c.equipe != null ? ' (' + c.equipe.nom + ')' : '')}">U11</td>
|
<td class="py-4 px-6 text-gray-900">
|
||||||
|
<span class="font-semibold" th:text="${c.categorie.nom + (c.equipe != null ? ' (' + c.equipe.nom + ')' : '')}">U11</span>
|
||||||
|
<span th:if="${c.initialesEducateurs != null and !c.initialesEducateurs.isEmpty()}"
|
||||||
|
th:text="${c.initialesEducateurs}"
|
||||||
|
class="ml-2 px-2.5 py-1 bg-blue-100 text-blue-800 rounded text-xs font-semibold"
|
||||||
|
th:title="'Éducateurs : ' + ${c.initialesEducateurs}">JD</span>
|
||||||
|
</td>
|
||||||
<td class="py-4 px-6 text-gray-600" th:text="${c.terrain.nom}">Terrain Synthétique</td>
|
<td class="py-4 px-6 text-gray-600" th:text="${c.terrain.nom}">Terrain Synthétique</td>
|
||||||
<td class="py-4 px-6 text-gray-600">
|
<td class="py-4 px-6 text-gray-600">
|
||||||
<span class="px-2 py-1 bg-gray-100 text-gray-800 rounded text-xs" th:text="${c.zone.libelle}">Demi A</span>
|
<span class="px-2 py-1 bg-gray-100 text-gray-800 rounded text-xs" th:text="${c.zone.libelle}">Demi A</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user