feat: add weekly training schedule planning with Gantt chart and conflict detection
This commit is contained in:
@@ -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<Terrain> terrains = terrainRepository.findAll();
|
||||
|
||||
// Sort terrains to ensure consistent order (Terrain Vert then Terrain Synthétique)
|
||||
terrains.sort(Comparator.comparing(Terrain::getId));
|
||||
|
||||
Map<Long, Integer> terrainIndices = new HashMap<>();
|
||||
for (int i = 0; i < terrains.size(); i++) {
|
||||
terrainIndices.put(terrains.get(i).getId(), i);
|
||||
}
|
||||
|
||||
List<CreneauEntrainement> 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user