diff --git a/as-talange-core/src/main/java/com/astalange/core/entity/CreneauEntrainement.java b/as-talange-core/src/main/java/com/astalange/core/entity/CreneauEntrainement.java new file mode 100644 index 0000000..bc029fc --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/entity/CreneauEntrainement.java @@ -0,0 +1,205 @@ +package com.astalange.core.entity; + +import jakarta.persistence.*; +import java.time.LocalTime; +import java.time.Duration; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Entity +@Table(name = "creneau_entrainement") +public class CreneauEntrainement { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "categorie_id", nullable = false) + private Categorie categorie; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "terrain_id", nullable = false) + private Terrain terrain; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ZoneTerrain zone; + + @Enumerated(EnumType.STRING) + @Column(name = "jour_semaine", nullable = false) + private JourSemaine jourSemaine; + + @Column(name = "heure_debut", nullable = false) + private LocalTime heureDebut; + + @Column(name = "heure_fin", nullable = false) + private LocalTime heureFin; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "saison_id", nullable = false) + private Saison saison; + + @Transient + private boolean enConflit = false; + + @Transient + private Set categoriesEnConflit = new HashSet<>(); + + // Logic for Gantt diagram grid placement + + public int getStartColumn() { + if (heureDebut == null) return 3; + // Clip to working hours 08:00 to 22:00 + LocalTime time = heureDebut; + if (time.isBefore(LocalTime.of(8, 0))) { + time = LocalTime.of(8, 0); + } + long minutes = Duration.between(LocalTime.of(8, 0), time).toMinutes(); + return (int) (minutes / 15) + 3; // +3 because col 1 is Field name, col 2 is Zone name + } + + public int getEndColumn() { + if (heureFin == null) return 3; + LocalTime time = heureFin; + if (time.isAfter(LocalTime.of(22, 0))) { + time = LocalTime.of(22, 0); + } + long minutes = Duration.between(LocalTime.of(8, 0), time).toMinutes(); + return (int) (minutes / 15) + 3; + } + + public int getStartRow(int fieldIndex) { + int baseRow = 3 + (fieldIndex * 5); // Row index matching HTML layout (base starts at 3, 5 rows per field) + if (zone == null) return baseRow; + return switch (zone) { + case COMPLET, DEMI_A, QUART_1 -> baseRow; + case QUART_2 -> baseRow + 1; + case DEMI_B, QUART_3 -> baseRow + 2; + case QUART_4 -> baseRow + 3; + }; + } + + public int getEndRow(int fieldIndex) { + int baseRow = 3 + (fieldIndex * 5); + if (zone == null) return baseRow + 4; + return switch (zone) { + case QUART_1 -> baseRow + 1; + case DEMI_A, QUART_2 -> baseRow + 2; + case QUART_3 -> baseRow + 3; + case DEMI_B, QUART_4, COMPLET -> baseRow + 4; + }; + } + + // Overlap and conflict checking logic + + public boolean overlapsWith(CreneauEntrainement other) { + if (this.id != null && this.id.equals(other.id)) { + return false; // same slot + } + if (this.jourSemaine != other.jourSemaine) { + return false; + } + if (this.terrain == null || other.terrain == null || !Objects.equals(this.terrain.getId(), other.terrain.getId())) { + return false; + } + + // Time overlap check: start1 < end2 and start2 < end1 + boolean timeOverlap = this.heureDebut.isBefore(other.heureFin) && other.heureDebut.isBefore(this.heureFin); + if (!timeOverlap) { + return false; + } + + // Spatial overlap check + if (this.zone == null || other.zone == null) { + return false; + } + Set q1 = this.zone.getQuarters(); + Set q2 = other.zone.getQuarters(); + return !Collections.disjoint(q1, q2); + } + + // Getters and Setters + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Categorie getCategorie() { + return categorie; + } + + public void setCategorie(Categorie categorie) { + this.categorie = categorie; + } + + public Terrain getTerrain() { + return terrain; + } + + public void setTerrain(Terrain terrain) { + this.terrain = terrain; + } + + public ZoneTerrain getZone() { + return zone; + } + + public void setZone(ZoneTerrain zone) { + this.zone = zone; + } + + public JourSemaine getJourSemaine() { + return jourSemaine; + } + + public void setJourSemaine(JourSemaine jourSemaine) { + this.jourSemaine = jourSemaine; + } + + public LocalTime getHeureDebut() { + return heureDebut; + } + + public void setHeureDebut(LocalTime heureDebut) { + this.heureDebut = heureDebut; + } + + public LocalTime getHeureFin() { + return heureFin; + } + + public void setHeureFin(LocalTime heureFin) { + this.heureFin = heureFin; + } + + public Saison getSaison() { + return saison; + } + + public void setSaison(Saison saison) { + this.saison = saison; + } + + public boolean isEnConflit() { + return enConflit; + } + + public void setEnConflit(boolean enConflit) { + this.enConflit = enConflit; + } + + public Set getCategoriesEnConflit() { + return categoriesEnConflit; + } + + public void setCategoriesEnConflit(Set categoriesEnConflit) { + this.categoriesEnConflit = categoriesEnConflit; + } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/entity/JourSemaine.java b/as-talange-core/src/main/java/com/astalange/core/entity/JourSemaine.java new file mode 100644 index 0000000..d80d012 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/entity/JourSemaine.java @@ -0,0 +1,21 @@ +package com.astalange.core.entity; + +public enum JourSemaine { + LUNDI("Lundi"), + MARDI("Mardi"), + MERCREDI("Mercredi"), + JEUDI("Jeudi"), + VENDREDI("Vendredi"), + SAMEDI("Samedi"), + DIMANCHE("Dimanche"); + + private final String libelle; + + JourSemaine(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/entity/Terrain.java b/as-talange-core/src/main/java/com/astalange/core/entity/Terrain.java new file mode 100644 index 0000000..a74ad49 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/entity/Terrain.java @@ -0,0 +1,31 @@ +package com.astalange.core.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name = "terrain") +public class Terrain { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String nom; + + 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; + } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/entity/ZoneTerrain.java b/as-talange-core/src/main/java/com/astalange/core/entity/ZoneTerrain.java new file mode 100644 index 0000000..06d97cd --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/entity/ZoneTerrain.java @@ -0,0 +1,29 @@ +package com.astalange.core.entity; + +import java.util.Set; + +public enum ZoneTerrain { + COMPLET("Terrain Complet", Set.of(1, 2, 3, 4)), + DEMI_A("Demi-terrain A", Set.of(1, 2)), + DEMI_B("Demi-terrain B", Set.of(3, 4)), + QUART_1("Quart 1", Set.of(1)), + QUART_2("Quart 2", Set.of(2)), + QUART_3("Quart 3", Set.of(3)), + QUART_4("Quart 4", Set.of(4)); + + private final String libelle; + private final Set quarters; + + ZoneTerrain(String libelle, Set quarters) { + this.libelle = libelle; + this.quarters = quarters; + } + + public String getLibelle() { + return libelle; + } + + public Set getQuarters() { + return quarters; + } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/repository/CreneauEntrainementRepository.java b/as-talange-core/src/main/java/com/astalange/core/repository/CreneauEntrainementRepository.java new file mode 100644 index 0000000..f6f0a04 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/repository/CreneauEntrainementRepository.java @@ -0,0 +1,21 @@ +package com.astalange.core.repository; + +import com.astalange.core.entity.CreneauEntrainement; +import com.astalange.core.entity.JourSemaine; +import com.astalange.core.entity.Saison; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface CreneauEntrainementRepository extends JpaRepository { + + @Query("SELECT c FROM CreneauEntrainement c JOIN FETCH c.categorie JOIN FETCH c.terrain WHERE c.saison = :saison") + List findBySaisonFetch(@Param("saison") Saison saison); + + @Query("SELECT c FROM CreneauEntrainement c JOIN FETCH c.categorie JOIN FETCH c.terrain WHERE c.saison = :saison AND c.jourSemaine = :jourSemaine") + List findBySaisonAndJourSemaineFetch(@Param("saison") Saison saison, @Param("jourSemaine") JourSemaine jourSemaine); +} diff --git a/as-talange-core/src/main/java/com/astalange/core/repository/TerrainRepository.java b/as-talange-core/src/main/java/com/astalange/core/repository/TerrainRepository.java new file mode 100644 index 0000000..df1067c --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/repository/TerrainRepository.java @@ -0,0 +1,9 @@ +package com.astalange.core.repository; + +import com.astalange.core.entity.Terrain; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TerrainRepository extends JpaRepository { +} diff --git a/as-talange-core/src/main/resources/db/migration/V10__add_terrains_and_schedules.sql b/as-talange-core/src/main/resources/db/migration/V10__add_terrains_and_schedules.sql new file mode 100644 index 0000000..72cc07c --- /dev/null +++ b/as-talange-core/src/main/resources/db/migration/V10__add_terrains_and_schedules.sql @@ -0,0 +1,26 @@ +-- V10__add_terrains_and_schedules.sql + +-- 1. Table des terrains +CREATE TABLE terrain ( + id BIGSERIAL PRIMARY KEY, + nom VARCHAR(255) NOT NULL +); + +-- Insertion des deux terrains par défaut +INSERT INTO terrain (nom) VALUES ('Terrain Vert'); +INSERT INTO terrain (nom) VALUES ('Terrain Synthétique'); + +-- 2. Table des créneaux d'entraînement +CREATE TABLE creneau_entrainement ( + id BIGSERIAL PRIMARY KEY, + categorie_id BIGINT NOT NULL, + terrain_id BIGINT NOT NULL, + zone VARCHAR(50) NOT NULL, + jour_semaine VARCHAR(50) NOT NULL, + heure_debut TIME NOT NULL, + heure_fin TIME NOT NULL, + saison_id BIGINT NOT NULL, + CONSTRAINT fk_creneau_categorie FOREIGN KEY (categorie_id) REFERENCES categorie(id) ON DELETE CASCADE, + CONSTRAINT fk_creneau_terrain FOREIGN KEY (terrain_id) REFERENCES terrain(id), + CONSTRAINT fk_creneau_saison FOREIGN KEY (saison_id) REFERENCES saison(id) ON DELETE CASCADE +); diff --git a/as-talange-core/src/test/java/com/astalange/core/CreneauEntrainementTest.java b/as-talange-core/src/test/java/com/astalange/core/CreneauEntrainementTest.java new file mode 100644 index 0000000..d324f64 --- /dev/null +++ b/as-talange-core/src/test/java/com/astalange/core/CreneauEntrainementTest.java @@ -0,0 +1,107 @@ +package com.astalange.core; + +import com.astalange.core.entity.*; +import org.junit.jupiter.api.Test; + +import java.time.LocalTime; + +import static org.junit.jupiter.api.Assertions.*; + +public class CreneauEntrainementTest { + + @Test + public void testStartAndEndColumns() { + CreneauEntrainement c1 = new CreneauEntrainement(); + c1.setHeureDebut(LocalTime.of(8, 0)); + c1.setHeureFin(LocalTime.of(9, 30)); + + // Start time 08:00 is (8 * 60) = 480 mins. Offset from 08:00 is 0 mins. + // Column is (0 / 15) + 3 = 3. + assertEquals(3, c1.getStartColumn()); + + // End time 09:30 is (9*60 + 30) = 570 mins. Offset from 08:00 is 90 mins. + // Column is (90 / 15) + 3 = 9. + assertEquals(9, c1.getEndColumn()); + } + + @Test + public void testStartAndEndRows() { + CreneauEntrainement c1 = new CreneauEntrainement(); + + // Test terrain complet (baseRow index 0 is 3, index 1 is 8) + c1.setZone(ZoneTerrain.COMPLET); + assertEquals(3, c1.getStartRow(0)); + assertEquals(7, c1.getEndRow(0)); + + assertEquals(8, c1.getStartRow(1)); + assertEquals(12, c1.getEndRow(1)); + + // Test demi-terrain A (Quarts 1 and 2) + c1.setZone(ZoneTerrain.DEMI_A); + assertEquals(3, c1.getStartRow(0)); + assertEquals(5, c1.getEndRow(0)); + + // Test demi-terrain B (Quarts 3 and 4) + c1.setZone(ZoneTerrain.DEMI_B); + assertEquals(5, c1.getStartRow(0)); + assertEquals(7, c1.getEndRow(0)); + + // Test single Quarts + c1.setZone(ZoneTerrain.QUART_1); + assertEquals(3, c1.getStartRow(0)); + assertEquals(4, c1.getEndRow(0)); + + c1.setZone(ZoneTerrain.QUART_4); + assertEquals(6, c1.getStartRow(0)); + assertEquals(7, c1.getEndRow(0)); + } + + @Test + public void testOverlapsWith() { + Terrain t1 = new Terrain(); + t1.setId(1L); + Terrain t2 = new Terrain(); + t2.setId(2L); + + CreneauEntrainement c1 = new CreneauEntrainement(); + c1.setJourSemaine(JourSemaine.LUNDI); + c1.setTerrain(t1); + c1.setZone(ZoneTerrain.DEMI_A); + c1.setHeureDebut(LocalTime.of(18, 0)); + c1.setHeureFin(LocalTime.of(19, 30)); + + // 1. Different day -> No overlap + CreneauEntrainement c2 = new CreneauEntrainement(); + c2.setJourSemaine(JourSemaine.MARDI); + c2.setTerrain(t1); + c2.setZone(ZoneTerrain.DEMI_A); + c2.setHeureDebut(LocalTime.of(18, 0)); + c2.setHeureFin(LocalTime.of(19, 30)); + assertFalse(c1.overlapsWith(c2)); + + // 2. Different terrain -> No overlap + c2.setJourSemaine(JourSemaine.LUNDI); + c2.setTerrain(t2); + assertFalse(c1.overlapsWith(c2)); + + // 3. Same day, same terrain, non-overlapping times -> No overlap + c2.setTerrain(t1); + c2.setHeureDebut(LocalTime.of(19, 30)); + c2.setHeureFin(LocalTime.of(21, 0)); + assertFalse(c1.overlapsWith(c2)); + + // 4. Same day, same terrain, overlapping times, non-overlapping zones (Demi_A and Demi_B don't overlap) + c2.setHeureDebut(LocalTime.of(18, 30)); + c2.setHeureFin(LocalTime.of(20, 0)); + c2.setZone(ZoneTerrain.DEMI_B); + assertFalse(c1.overlapsWith(c2)); + + // 5. Same day, same terrain, overlapping times, overlapping zones (Demi_A and Complet overlap!) + c2.setZone(ZoneTerrain.COMPLET); + assertTrue(c1.overlapsWith(c2)); + + // 6. Same day, same terrain, overlapping times, overlapping zones (Demi_A and Quart_1 overlap!) + c2.setZone(ZoneTerrain.QUART_1); + assertTrue(c1.overlapsWith(c2)); + } +} diff --git a/as-talange-web/src/main/java/com/astalange/web/controller/PlanningController.java b/as-talange-web/src/main/java/com/astalange/web/controller/PlanningController.java new file mode 100644 index 0000000..e79f172 --- /dev/null +++ b/as-talange-web/src/main/java/com/astalange/web/controller/PlanningController.java @@ -0,0 +1,158 @@ +package com.astalange.web.controller; + +import com.astalange.core.entity.*; +import com.astalange.core.repository.CategorieRepository; +import com.astalange.core.repository.CreneauEntrainementRepository; +import com.astalange.core.repository.SaisonRepository; +import com.astalange.core.repository.TerrainRepository; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.util.*; + +@Controller +@RequestMapping("/planning") +public class PlanningController { + + private final CreneauEntrainementRepository creneauRepository; + private final TerrainRepository terrainRepository; + private final CategorieRepository categorieRepository; + private final SaisonRepository saisonRepository; + + public PlanningController(CreneauEntrainementRepository creneauRepository, + TerrainRepository terrainRepository, + CategorieRepository categorieRepository, + SaisonRepository saisonRepository) { + this.creneauRepository = creneauRepository; + this.terrainRepository = terrainRepository; + this.categorieRepository = categorieRepository; + this.saisonRepository = saisonRepository; + } + + @GetMapping + public String showPlanning(@RequestParam(required = false) JourSemaine jour, Model model) { + Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null); + if (saisonActive == null) { + model.addAttribute("noActiveSeason", true); + return "planning/planning"; + } + + JourSemaine selectedDay = (jour != null) ? jour : JourSemaine.LUNDI; + List terrains = terrainRepository.findAll(); + + // Sort terrains to ensure consistent order (Terrain Vert then Terrain Synthétique) + terrains.sort(Comparator.comparing(Terrain::getId)); + + Map terrainIndices = new HashMap<>(); + for (int i = 0; i < terrains.size(); i++) { + terrainIndices.put(terrains.get(i).getId(), i); + } + + List creneaux = creneauRepository.findBySaisonAndJourSemaineFetch(saisonActive, selectedDay); + + // Detect conflicts + for (int i = 0; i < creneaux.size(); i++) { + CreneauEntrainement c1 = creneaux.get(i); + for (int j = 0; j < creneaux.size(); j++) { + if (i == j) continue; + CreneauEntrainement c2 = creneaux.get(j); + if (c1.overlapsWith(c2)) { + c1.setEnConflit(true); + c1.getCategoriesEnConflit().add(c2.getCategorie().getNom()); + } + } + } + + model.addAttribute("saisonActive", saisonActive); + model.addAttribute("selectedDay", selectedDay); + model.addAttribute("jours", JourSemaine.values()); + model.addAttribute("terrains", terrains); + model.addAttribute("terrainIndices", terrainIndices); + model.addAttribute("creneaux", creneaux); + + return "planning/planning"; + } + + @GetMapping("/new") + public String showCreateForm(Model model, RedirectAttributes redirectAttributes) { + Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null); + if (saisonActive == null) { + redirectAttributes.addFlashAttribute("error", "Veuillez activer une saison avant de planifier un créneau."); + return "redirect:/saisons"; + } + + CreneauEntrainement creneau = new CreneauEntrainement(); + creneau.setSaison(saisonActive); + + model.addAttribute("creneau", creneau); + model.addAttribute("categories", categorieRepository.findBySaison(saisonActive)); + model.addAttribute("terrains", terrainRepository.findAll()); + model.addAttribute("zones", ZoneTerrain.values()); + model.addAttribute("jours", JourSemaine.values()); + + return "planning/form"; + } + + @GetMapping("/{id}/edit") + public String showEditForm(@PathVariable Long id, Model model, RedirectAttributes redirectAttributes) { + CreneauEntrainement creneau = creneauRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("Créneau introuvable : " + id)); + + model.addAttribute("creneau", creneau); + model.addAttribute("categories", categorieRepository.findBySaison(creneau.getSaison())); + model.addAttribute("terrains", terrainRepository.findAll()); + model.addAttribute("zones", ZoneTerrain.values()); + model.addAttribute("jours", JourSemaine.values()); + + return "planning/form"; + } + + @PostMapping + public String saveCreneau(@ModelAttribute("creneau") CreneauEntrainement creneau, BindingResult result, Model model, RedirectAttributes redirectAttributes) { + // Validation + if (creneau.getHeureDebut() != null && creneau.getHeureFin() != null) { + if (!creneau.getHeureDebut().isBefore(creneau.getHeureFin())) { + result.rejectValue("heureDebut", "error.heureDebut", "L'heure de début doit être antérieure à l'heure de fin."); + } + if (creneau.getHeureDebut().isBefore(java.time.LocalTime.of(8, 0)) || creneau.getHeureFin().isAfter(java.time.LocalTime.of(22, 0))) { + result.rejectValue("heureDebut", "error.heureDebut", "Les entraînements doivent se dérouler entre 08:00 et 22:00."); + } + } + + if (result.hasErrors()) { + Saison saison = creneau.getSaison(); + if (saison == null) { + saison = saisonRepository.findByEstActiveTrue().orElseThrow(); + creneau.setSaison(saison); + } + model.addAttribute("categories", categorieRepository.findBySaison(saison)); + model.addAttribute("terrains", terrainRepository.findAll()); + model.addAttribute("zones", ZoneTerrain.values()); + model.addAttribute("jours", JourSemaine.values()); + return "planning/form"; + } + + // Set active season if not set (for new items) + if (creneau.getSaison() == null) { + Saison saisonActive = saisonRepository.findByEstActiveTrue().orElseThrow(); + creneau.setSaison(saisonActive); + } + + creneauRepository.save(creneau); + redirectAttributes.addFlashAttribute("success", "Créneau d'entraînement enregistré avec succès."); + return "redirect:/planning?jour=" + creneau.getJourSemaine().name(); + } + + @PostMapping("/{id}/delete") + public String deleteCreneau(@PathVariable Long id, RedirectAttributes redirectAttributes) { + CreneauEntrainement creneau = creneauRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("Créneau introuvable : " + id)); + JourSemaine jour = creneau.getJourSemaine(); + creneauRepository.delete(creneau); + redirectAttributes.addFlashAttribute("success", "Créneau d'entraînement supprimé avec succès."); + return "redirect:/planning?jour=" + jour.name(); + } +} diff --git a/as-talange-web/src/main/resources/templates/fragments/sidebar.html b/as-talange-web/src/main/resources/templates/fragments/sidebar.html index 2b0f000..8d6b15c 100644 --- a/as-talange-web/src/main/resources/templates/fragments/sidebar.html +++ b/as-talange-web/src/main/resources/templates/fragments/sidebar.html @@ -12,6 +12,8 @@ Tableau de bord Adhérents Paiements +
Planning
+ Entraînements
Paramétrage
Saisons Catégories diff --git a/as-talange-web/src/main/resources/templates/planning/form.html b/as-talange-web/src/main/resources/templates/planning/form.html new file mode 100644 index 0000000..c40c170 --- /dev/null +++ b/as-talange-web/src/main/resources/templates/planning/form.html @@ -0,0 +1,99 @@ + + + + + Créneau d'Entraînement - AS Talange + + + + + + +
+ +
+ +
+

Créneau

+
+ + +
+
+
+ + + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+

+
+ + +
+

Information sur les plages horaires :

+

Les entraînements doivent être planifiés entre 08:00 et 22:00.

+

Les éventuels conflits de superposition d'horaires et de zones de terrain seront calculés et signalés automatiquement sur le tableau de bord.

+
+ + +
+ Annuler + +
+
+
+
+
+ + diff --git a/as-talange-web/src/main/resources/templates/planning/planning.html b/as-talange-web/src/main/resources/templates/planning/planning.html new file mode 100644 index 0000000..a279c08 --- /dev/null +++ b/as-talange-web/src/main/resources/templates/planning/planning.html @@ -0,0 +1,225 @@ + + + + + Planning des Entraînements - AS Talange + + + + + + +
+ +
+ +
+

Planning & Occupation des Terrains

+
+ Saison Actuelle +
+
+ + +
+ +
+
+
+ + + +
+
+

+ Aucune saison active. Veuillez activer une saison dans le paramétrage pour planifier les entraînements. +

+
+
+
+ +
+

+
+ + +
+

Planning Hebdomadaire

+ + + Ajouter un Créneau + +
+ + +
+ +
+ + +
+
+
+ +
Terrain / Zone
+
+ 08:00 +
+ + + +
+ Terrain Vert +
+ + + +
V
+
Quart 1
+ + +
V
+
Quart 2
+ + +
V
+
Quart 3
+ + +
V
+
Quart 4
+ + + + +
+ Terrain Synthétique +
+ + + +
S
+
Quart 1
+ + +
S
+
Quart 2
+ + +
S
+
Quart 3
+ + +
S
+
Quart 4
+ + + +
+
+
+ + +
+
+
+ + +
+ +
+ U15 + +
+ Modifier +
+
+ +
+ 18:00 - 19:30 + | + Demi-terrain A +
+ + +
+ +
+
+ +
+
+
+ + +
+
+

Liste des entraînements - Lundi

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
CatégorieTerrainZoneHorairesStatutActions
Aucun entraînement planifié pour ce jour.
U11Terrain Synthétique + Demi A + 14:00 - 15:30 + + Conflit + + + Disponible + + + Modifier +
+ +
+
+
+ +
+
+ +