Compare commits
21 Commits
test_thème
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3920016f3b | ||
|
|
3fce71b893 | ||
|
|
4d1d8d70df | ||
|
|
bbd33a7ea2 | ||
|
|
e4dd334832 | ||
|
|
4bd0e0a299 | ||
|
|
c205b1b396 | ||
|
|
796f973f7f | ||
|
|
fdca74fef1 | ||
|
|
158ee781c6 | ||
|
|
6a104edebf | ||
|
|
eadd180422 | ||
|
|
8c56880964 | ||
|
|
70d6f9b01b | ||
|
|
903da8e11a | ||
|
|
cbd53ba471 | ||
|
|
55ac436e5b | ||
|
|
0186370adc | ||
|
|
6ed94c17b5 | ||
|
|
c8c98cadeb | ||
|
|
ddb2b93489 |
162
README.md
162
README.md
@@ -1,44 +1,152 @@
|
||||
# hackathon
|
||||
# FrisbyEE (Projet Hackathon)
|
||||
|
||||
## Pour lancer
|
||||
Ce projet contient : un backend Spring Boot et un frontend React en TypeScript développé lors du hackathon. L'authentification est gérée par Keycloak et la base de données est PostgreSQL; les deux peuvent être lancés via Docker.
|
||||
---
|
||||
|
||||
|
||||
## Présentation rapide
|
||||
- **Backend** : Spring Boot (Java 17) dans `back_end/`
|
||||
- **Frontend** : React et TypeScript dans `front_end/`
|
||||
- **Auth** : Keycloak (voir `keycloak/` pour la thème et configuré via `docker-compose.yml`)
|
||||
- **Base de données** : PostgreSQL (configuré via `docker-compose.yml`)
|
||||
|
||||
## Pré-requis
|
||||
- Java 17
|
||||
- Maven (utiliez `mvn`)
|
||||
- Node.js et npm
|
||||
- Docker et Docker Compose
|
||||
|
||||
## Installation et démarrage
|
||||
1. Récupérez le dépôt et placez-vous à la racine du projet.
|
||||
2. Démarrez Keycloak et PostgreSQL avec Docker:
|
||||
|
||||
```bash
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
### back_end
|
||||
3. Backend: construire et lancer (depuis la racine du projet):
|
||||
|
||||
```bash
|
||||
cd back_end
|
||||
./mvn clean install
|
||||
./mvn spring-boot:run
|
||||
```
|
||||
|
||||
#### Pour installer java 17
|
||||
sudo apt install openjdk-17-jdk
|
||||
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
4. Frontend: installer les dépendances et démarrer :
|
||||
|
||||
#### Clean
|
||||
mvn clean install
|
||||
|
||||
#### Pour lancer back_end
|
||||
mvn spring-boot:run
|
||||
|
||||
### front_end
|
||||
```bash
|
||||
cd front_end
|
||||
|
||||
#### Install
|
||||
npm install
|
||||
|
||||
#### Lancer front_end
|
||||
npm start
|
||||
```
|
||||
|
||||
#### Docker
|
||||
Le serveur de développement du frontend écoute par défaut sur `http://localhost:3000`, le backend sur `http://localhost:8080`, et api sur `http://localhost:8081` sauf configuration différente.
|
||||
|
||||
## Commandes utiles
|
||||
- Arrêter et supprimer tous les conteneurs Docker:
|
||||
|
||||
```bash
|
||||
sudo docker stop $(sudo docker ps -a -q)
|
||||
sudo docker rm $(sudo docker ps -a -q)
|
||||
```
|
||||
|
||||
#### Pour acceder sur docker
|
||||
- Ouvrir un shell Postgres dans le conteneur en cours d'exécution (exemple : conteneur `frisbyee-postgres`):
|
||||
|
||||
```bash
|
||||
sudo docker exec -it frisbyee-postgres psql -U frisbyee_user -d frisbyee
|
||||
# puis, par exemple : \dt ou SELECT * FROM session;
|
||||
```
|
||||
|
||||
#### Voir les données:
|
||||
\dt
|
||||
SELECT * FROM session;
|
||||
## Keycloak / Thème de connexion
|
||||
- Au début, il faut crée run realm qui s'appelle `Frisbyee_realm`, ensuite dans ce realm, créez un `Frisbyee_client` avec ce config debug:
|
||||
```
|
||||
ROOT URL: http://localhost:3000/
|
||||
HOME URL: http://localhost:3000/
|
||||
Valid redirect URIs: http://localhost:3000/*
|
||||
Web origins: *
|
||||
```
|
||||
- Après, ajoutez les rôles : `admin`, `coach`, `athlete`
|
||||
- Et, mettez chaque groupe `ADMIN`, `COACH`, `ATHLETE` et mapping chaque rôle pour chaque groupe.
|
||||
- Dans User Registration, mettez le `defaut groupe`: `ATHLETE` et activez le user self registration.
|
||||
|
||||
Pour appliquer le thème de connexion personnalisé fourni dans `keycloak/themes/frisbyee` :
|
||||
- Ouvrez la console d'administration Keycloak -> sélectionnez le realm -> `Realm Settings` -> modifiez le `Display name` si vous le souhaitez.
|
||||
- Dans `Themes`, définissez `Login Theme` sur `frisbyee` puis enregistrez.
|
||||
|
||||
|
||||
#### Appliquer le CSS pour la page de login Keycloak
|
||||
## Configuration / Environnement
|
||||
- Les propriétés du backend se trouvent dans `back_end/src/main/resources/application.properties`.
|
||||
- Le frontend utilise `public/keycloak.json` pour la configuration du client Keycloak.
|
||||
- Assurez-vous que le client Keycloak et le realm correspondent aux valeurs utilisées par les deux applications.
|
||||
|
||||
Sur la console Keycloak aller dans realm setting
|
||||
-> Changer le display name (par exemple: Bienvenue sur Frisbyee !)
|
||||
-> Theme puis changer le login theme sur frisbyee
|
||||
## Contribution
|
||||
- Thibaut ROCHAS
|
||||
- Tuan Minh VU
|
||||
- Amäel KESTEMAN
|
||||
- Alexis LEBOEUF
|
||||
## Domain model class diagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
%% Classes and attributes (inferred from metier package)
|
||||
class User {
|
||||
+Long id
|
||||
+String keycloakId
|
||||
+String email
|
||||
+String nom
|
||||
+String prenom
|
||||
+Role role
|
||||
}
|
||||
|
||||
class Athlete {
|
||||
+String categorie
|
||||
+String niveau
|
||||
+List<String> groupe
|
||||
+List<Session> sessions
|
||||
}
|
||||
|
||||
%% Represente en enum
|
||||
class Role {
|
||||
+String role
|
||||
}
|
||||
|
||||
class Coach {
|
||||
+List<Session> sessions
|
||||
}
|
||||
|
||||
class Admin {
|
||||
}
|
||||
|
||||
class Activite {
|
||||
+Long id
|
||||
+String nom
|
||||
+String theme
|
||||
+String description
|
||||
+Integer dureeMinutes
|
||||
+List<Activite> activites
|
||||
+Session session
|
||||
}
|
||||
|
||||
class Session {
|
||||
+Long id
|
||||
+String name
|
||||
+LocalDateTime creneau
|
||||
+Integer duree
|
||||
+String group
|
||||
+bool isRecurrent
|
||||
+Coach coach
|
||||
+List<Athlete> athletes
|
||||
}
|
||||
|
||||
%% Inheritance (if User is a base class for domain actors)
|
||||
User <|-- Athlete
|
||||
User <|-- Coach
|
||||
User <|-- Admin
|
||||
|
||||
%% Associations with cardinality
|
||||
Coach "1" -- "0..*" Session : manages
|
||||
Role "1" -- "0..*" User : is
|
||||
Session "0..*" -- "0..*" Activite : contains
|
||||
Session "0..*" -- "0..*" Athlete : participants
|
||||
Activite "0..*" -- "0..*" Session : usedIn
|
||||
```
|
||||
|
||||
@@ -30,11 +30,9 @@ public class WebSecurityConfig {
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||
// 2. Allow public endpoints BEFORE any authenticated() calls
|
||||
.requestMatchers("/athlete/create", "/", "/public").permitAll()
|
||||
.requestMatchers("/coach/**").permitAll()
|
||||
// 3. Authenticated endpoints
|
||||
.requestMatchers("/users/sync").authenticated()
|
||||
.requestMatchers("/coach/**").hasRole("coach")
|
||||
.requestMatchers("/admin/**").hasRole("admin")
|
||||
.requestMatchers("/user/**").hasRole("user")
|
||||
.requestMatchers("/athlete/**").hasRole("athlete")
|
||||
.anyRequest().authenticated())
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtToken -> {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package hackathon.FrisbYEE.jpa.dto;
|
||||
import hackathon.FrisbYEE.jpa.metier.Role;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AdminDTO {
|
||||
private String id_keycloak;
|
||||
private Integer id;
|
||||
private String id_keycloak;
|
||||
private String name;
|
||||
private String prenom;
|
||||
private Role role;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package hackathon.FrisbYEE.jpa.service;
|
||||
|
||||
import hackathon.FrisbYEE.jpa.metier.Session;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SessionDAO extends JpaRepository<Session, Integer> {
|
||||
List<Session> findByAthletes_Id(Integer athleteId);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,17 +3,12 @@ package hackathon.FrisbYEE.rest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import hackathon.FrisbYEE.jpa.dto.AdminDTO;
|
||||
import hackathon.FrisbYEE.jpa.metier.Admin;
|
||||
import hackathon.FrisbYEE.jpa.service.AdminDAO;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import hackathon.FrisbYEE.jpa.service.UserDAO;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
@@ -22,12 +17,24 @@ public class AdminResource {
|
||||
|
||||
@Autowired
|
||||
private AdminDAO adminDAO;
|
||||
@Autowired
|
||||
private UserDAO userDAO;
|
||||
|
||||
|
||||
@PostMapping("/create")
|
||||
@PreAuthorize("hasRole('Admin')") // Only admin can create
|
||||
@PreAuthorize("hasRole('admin')") // Only admin can create
|
||||
public ResponseEntity<AdminDTO> create(@RequestBody AdminDTO dto) {
|
||||
|
||||
userDAO.findByKeycloakId(dto.getId_keycloak())
|
||||
.ifPresent(existing -> {
|
||||
if (!(existing instanceof Admin)) {
|
||||
userDAO.delete(existing);
|
||||
userDAO.flush();
|
||||
}
|
||||
});
|
||||
|
||||
Admin admin = mapToEntity(dto);
|
||||
|
||||
if(adminDAO.findByKeycloakId(admin.getKeycloakId()).isPresent()) {
|
||||
return ResponseEntity.status(200).body(mapToDTO(adminDAO.findByKeycloakId(admin.getKeycloakId()).get()));
|
||||
}
|
||||
@@ -55,7 +62,6 @@ public class AdminResource {
|
||||
dto.setId_keycloak(admin.getKeycloakId());
|
||||
dto.setName(admin.getName());
|
||||
dto.setPrenom(admin.getPrenom());
|
||||
dto.setRole(admin.getRole());
|
||||
|
||||
return dto;
|
||||
}
|
||||
@@ -65,7 +71,7 @@ public class AdminResource {
|
||||
admin.setKeycloakId(dto.getId_keycloak());
|
||||
admin.setName(dto.getName());
|
||||
admin.setPrenom(dto.getPrenom());
|
||||
admin.setRole(dto.getRole());
|
||||
admin.setRole(hackathon.FrisbYEE.jpa.metier.Role.admin);
|
||||
|
||||
return admin;
|
||||
}
|
||||
|
||||
@@ -51,9 +51,12 @@ public class AthleteResource {
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<AthleteDTO> create(@RequestBody AthleteDTO dto) {
|
||||
Athlete athlete = mapToEntity(dto);
|
||||
if (athleteDAO.existsByKeycloakId(athlete.getKeycloakId())) {
|
||||
return ResponseEntity.status(200)
|
||||
.body(mapToDTO(athleteDAO.findByKeycloakId(athlete.getKeycloakId()).get()));
|
||||
if(athleteDAO.existsByKeycloakId(athlete.getKeycloakId())) {
|
||||
Athlete existing = athleteDAO.findByKeycloakId(athlete.getKeycloakId()).orElse(null);
|
||||
if (existing != null) {
|
||||
return ResponseEntity.status(200).body(mapToDTO(existing));
|
||||
}
|
||||
return ResponseEntity.status(200).build();
|
||||
}
|
||||
athleteDAO.save(athlete);
|
||||
return ResponseEntity.status(201).body(mapToDTO(athlete));
|
||||
@@ -74,6 +77,36 @@ public class AthleteResource {
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@Operation(summary = "Récupère tous les athlètes d'un groupe")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Récupère tous les athlètes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = List.class)))
|
||||
})
|
||||
@GetMapping("/all/group/{groupe}")
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach')")
|
||||
public ResponseEntity<List<AthleteDTO>> allByGroup(@PathVariable String groupe) {
|
||||
List<Athlete> athletes = athleteDAO.findAll();
|
||||
List<AthleteDTO> dtos = new ArrayList<>();
|
||||
|
||||
for (Athlete athlete : athletes) {
|
||||
if(groupe.equals("None")){
|
||||
|
||||
if(athlete.getGroupe().size()==0){
|
||||
dtos.add(mapToDTO(athlete));
|
||||
}
|
||||
}
|
||||
else{
|
||||
boolean containsGroupe = false;
|
||||
for (String g : athlete.getGroupe()) {
|
||||
containsGroupe = containsGroupe || g.equals(groupe);
|
||||
}
|
||||
if(containsGroupe){
|
||||
dtos.add(mapToDTO(athlete));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@Operation(summary = "Récupère l'athlète ayant l'identifiant correspondant")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Récupération effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AthleteDTO.class)))
|
||||
@@ -81,14 +114,16 @@ public class AthleteResource {
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<AthleteDTO> getById(@PathVariable String id) {
|
||||
Athlete athlete = athleteDAO.findByKeycloakId(id).get();
|
||||
Athlete athlete = athleteDAO.findByKeycloakId(id).orElse(null);
|
||||
if (athlete == null) return ResponseEntity.notFound().build();
|
||||
return ResponseEntity.ok(mapToDTO(athlete));
|
||||
}
|
||||
|
||||
@GetMapping("/keycloak/{keycloak_id}")
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<AthleteDTO> getByKeycloakId(@PathVariable String keycloak_id) {
|
||||
Athlete athlete = athleteDAO.findByKeycloakId(keycloak_id).get();
|
||||
Athlete athlete = athleteDAO.findByKeycloakId(keycloak_id).orElse(null);
|
||||
if (athlete == null) return ResponseEntity.notFound().build();
|
||||
return ResponseEntity.ok(mapToDTO(athlete));
|
||||
}
|
||||
|
||||
@@ -100,11 +135,11 @@ public class AthleteResource {
|
||||
@PreAuthorize("hasRole('admin') or #id == principal.id")
|
||||
public ResponseEntity<AthleteDTO> update(@PathVariable Integer id, @RequestBody AthleteDTO dto) {
|
||||
try {
|
||||
Athlete athlete = athleteDAO.findById(id).get();
|
||||
Athlete athlete = athleteDAO.findById(id).orElse(null);
|
||||
if (athlete == null) return ResponseEntity.notFound().build();
|
||||
athlete.setName(dto.getName());
|
||||
athlete.setCategorie(dto.getCategorie());
|
||||
athlete.setNiveau(dto.getNiveau());
|
||||
|
||||
// Relationship: sessionId → session
|
||||
if (dto.getSessionIds() != null) {
|
||||
List<Session> sessions = new ArrayList<>();
|
||||
@@ -145,6 +180,7 @@ public class AthleteResource {
|
||||
dto.setPrenom(athlete.getPrenom());
|
||||
dto.setCategorie(athlete.getCategorie());
|
||||
dto.setNiveau(athlete.getNiveau());
|
||||
dto.setGroupes(athlete.getGroupe());
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -163,32 +199,34 @@ public class AthleteResource {
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Récupération effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SessionDTO.class)))
|
||||
})
|
||||
@GetMapping("/athlete/{athleteId}/session")
|
||||
@GetMapping("/{athleteId}/session")
|
||||
public List<SessionDTO> getSessionsAthlete(@PathVariable Integer athleteId) {
|
||||
// return pet
|
||||
System.out.println("ID A CHERCHER" + athleteId);
|
||||
java.util.Optional<Athlete> j = athleteDAO.findById(athleteId);
|
||||
List<Session> sessions = sessionDAO.findAll();
|
||||
Athlete athlete = athleteDAO.findById(athleteId).orElse(null);
|
||||
if (athlete == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<Session> sessions = sessionDAO.findByAthletes_Id(athleteId);
|
||||
List<SessionDTO> athleteSessions = new ArrayList<>();
|
||||
|
||||
for (Session s : sessions) {
|
||||
if (s.getAthletes().contains(j.get())) {
|
||||
if (s.getAthletes().contains(athlete)) {
|
||||
SessionDTO dto = new SessionDTO();
|
||||
|
||||
dto.setId(s.getId());
|
||||
dto.setName(s.getName());
|
||||
dto.setCreneau(s.getCreneau());
|
||||
List<Integer> activiteIDs = new ArrayList<>();
|
||||
for (Activite activite : s.getActivites()) {
|
||||
for (Activite activite : s.getActivites()) {
|
||||
activiteIDs.add(activite.getId());
|
||||
}
|
||||
dto.setActiviteIds(activiteIDs);
|
||||
dto.setCoachId(s.getCoach().getId());
|
||||
dto.setCoachId(s.getCoach() != null ? s.getCoach().getId() : null);
|
||||
dto.setDuree(s.getDuree());
|
||||
dto.setGroupe(s.getGroupe());
|
||||
dto.setIsRecurrent(s.getIsRecurrent());
|
||||
List<Integer> athleteIds = new ArrayList<>();
|
||||
for (Athlete athlete : s.getAthletes()) {
|
||||
athleteIds.add(athlete.getId());
|
||||
for (Athlete athlete2 : s.getAthletes()) {
|
||||
athleteIds.add(athlete2.getId());
|
||||
}
|
||||
dto.setAthleteIds(athleteIds);
|
||||
|
||||
@@ -196,10 +234,25 @@ public class AthleteResource {
|
||||
athleteSessions.add(dto);
|
||||
}
|
||||
}
|
||||
System.out.println(j);
|
||||
System.out.println(athlete);
|
||||
return athleteSessions;
|
||||
}
|
||||
|
||||
@GetMapping("/{athleteId}/groupes")
|
||||
public List<String> getGroupesByAthlete(@PathVariable Integer athleteId) {
|
||||
java.util.Optional<Athlete> athleteOptional = athleteDAO.findById(athleteId);
|
||||
List<String> groupes = new ArrayList<>();
|
||||
if (athleteOptional.isPresent()) {
|
||||
Athlete athlete = athleteOptional.get();
|
||||
for (Session session : athlete.getSessions()) {
|
||||
if (!groupes.contains(session.getGroupe())) {
|
||||
groupes.add(session.getGroupe());
|
||||
}
|
||||
}
|
||||
}
|
||||
return groupes;
|
||||
}
|
||||
|
||||
@Operation(summary = "Récupère toutes les sessions")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Récupération effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SessionDTO.class)))
|
||||
@@ -225,9 +278,8 @@ public class AthleteResource {
|
||||
@GetMapping("/athletes/session/{id}/activities")
|
||||
public List<ActiviteDTO> getActivitiesForSession(@PathVariable Integer id) {
|
||||
// Récupérer la session par ID
|
||||
java.util.Optional<Session> sessionOpt = sessionDAO.findById(id);
|
||||
if (sessionOpt.isPresent()) {
|
||||
Session session = sessionOpt.get();
|
||||
Session session = sessionDAO.findById(id).orElse(null);
|
||||
if (session != null) {
|
||||
// Retourner les activités de la session
|
||||
List<ActiviteDTO> activiteDTOs = new ArrayList<>();
|
||||
for (Activite activite : session.getActivites()) {
|
||||
@@ -249,9 +301,8 @@ public class AthleteResource {
|
||||
@GetMapping("/athletes/{id}/session/after/{date}")
|
||||
public List<SessionDTO> getSessionsAfterDate(@PathVariable Integer id, @PathVariable String date) {
|
||||
// Récupérer l'athlète par ID
|
||||
java.util.Optional<Athlete> athleteOpt = athleteDAO.findById(id);
|
||||
if (athleteOpt.isPresent()) {
|
||||
Athlete athlete = athleteOpt.get();
|
||||
Athlete athlete = athleteDAO.findById(id).orElse(null);
|
||||
if (athlete != null) {
|
||||
// Récupérer les sessions de l'athlète après la date donnée
|
||||
List<Session> sessions = sessionDAO.findAll();
|
||||
List<SessionDTO> filteredSessions = new ArrayList<>();
|
||||
@@ -280,9 +331,8 @@ public class AthleteResource {
|
||||
public List<SessionDTO> getSessionsBetweenDates(@PathVariable Integer id, @PathVariable String startDate,
|
||||
@PathVariable String endDate) {
|
||||
// Récupérer l'athlète par ID
|
||||
java.util.Optional<Athlete> athleteOpt = athleteDAO.findById(id);
|
||||
if (athleteOpt.isPresent()) {
|
||||
Athlete athlete = athleteOpt.get();
|
||||
Athlete athlete = athleteDAO.findById(id).orElse(null);
|
||||
if (athlete != null) {
|
||||
// Récupérer les sessions de l'athlète entre les deux dates données
|
||||
List<Session> sessions = sessionDAO.findAll();
|
||||
List<SessionDTO> filteredSessions = new ArrayList<>();
|
||||
@@ -301,18 +351,5 @@ public class AthleteResource {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@GetMapping("/{athleteId}/groupes")
|
||||
public List<String> getGroupesByAthlete(@PathVariable Integer athleteId) {
|
||||
java.util.Optional<Athlete> athleteOptional = athleteDAO.findById(athleteId);
|
||||
List<String> groupes = new ArrayList<>();
|
||||
if (athleteOptional.isPresent()) {
|
||||
Athlete athlete = athleteOptional.get();
|
||||
for (Session session : athlete.getSessions()) {
|
||||
if (!groupes.contains(session.getGroupe())) {
|
||||
groupes.add(session.getGroupe());
|
||||
}
|
||||
}
|
||||
}
|
||||
return groupes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package hackathon.FrisbYEE.rest;
|
||||
|
||||
import hackathon.FrisbYEE.jpa.dto.CoachDTO;
|
||||
import hackathon.FrisbYEE.jpa.dto.SessionDTO;
|
||||
import hackathon.FrisbYEE.jpa.metier.Activite;
|
||||
import hackathon.FrisbYEE.jpa.metier.Admin;
|
||||
import hackathon.FrisbYEE.jpa.metier.Athlete;
|
||||
import hackathon.FrisbYEE.jpa.metier.Coach;
|
||||
import hackathon.FrisbYEE.jpa.metier.Session;
|
||||
import hackathon.FrisbYEE.jpa.service.CoachDAO;
|
||||
import hackathon.FrisbYEE.jpa.service.SessionDAO;
|
||||
import hackathon.FrisbYEE.jpa.service.UserDAO;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -17,13 +25,28 @@ import java.util.List;
|
||||
@RestController
|
||||
@RequestMapping("/coach")
|
||||
public class CoachResource {
|
||||
|
||||
@Autowired
|
||||
private CoachDAO coachDAO;
|
||||
@Autowired
|
||||
private UserDAO userDAO;
|
||||
@Autowired
|
||||
private SessionDAO sessionDAO;
|
||||
|
||||
@PostMapping("/create")
|
||||
@PreAuthorize("hasRole('Admin')") // Only admin can create
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach')") // Only admin can create
|
||||
public ResponseEntity<CoachDTO> create(@RequestBody CoachDTO dto) {
|
||||
|
||||
userDAO.findByKeycloakId(dto.getId_keycloak())
|
||||
.ifPresent(existing -> {
|
||||
if (!(existing instanceof Coach)) {
|
||||
userDAO.delete(existing);
|
||||
userDAO.flush();
|
||||
}
|
||||
});
|
||||
|
||||
Coach coach = mapToEntity(dto);
|
||||
|
||||
if(coachDAO.existsByKeycloakId(coach.getKeycloakId())) {
|
||||
return ResponseEntity.status(200).body(mapToDTO(coachDAO.findByKeycloakId(coach.getKeycloakId()).get()));
|
||||
}
|
||||
@@ -72,11 +95,38 @@ public class CoachResource {
|
||||
|
||||
@GetMapping("/{id}/session")
|
||||
@PreAuthorize("hasRole('Admin') or hasRole('Coach')")
|
||||
public ResponseEntity<List<?>> getSessionsForCoach(@PathVariable Integer id) {
|
||||
public ResponseEntity<List<SessionDTO>> getSessionsForCoach(@PathVariable Integer id) {
|
||||
Coach coach = coachDAO.findById(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Coach not found"));
|
||||
List<?> sessions = coach.getSessions();
|
||||
return ResponseEntity.ok(sessions);
|
||||
List<Session> sessions = sessionDAO.findAll();
|
||||
List<SessionDTO> coachSessions = new ArrayList<>();
|
||||
for (Session s : sessions) {
|
||||
if (s.getCoach().equals(coach)) {
|
||||
SessionDTO dto = new SessionDTO();
|
||||
|
||||
dto.setId(s.getId());
|
||||
dto.setName(s.getName());
|
||||
dto.setCreneau(s.getCreneau());
|
||||
List<Integer> activiteIDs = new ArrayList<>();
|
||||
for (Activite activite : s.getActivites()) {
|
||||
activiteIDs.add(activite.getId());
|
||||
}
|
||||
dto.setActiviteIds(activiteIDs);
|
||||
dto.setCoachId(s.getCoach().getId());
|
||||
dto.setDuree(s.getDuree());
|
||||
dto.setGroupe(s.getGroupe());
|
||||
dto.setIsRecurrent(s.getIsRecurrent());
|
||||
List<Integer> athleteIds = new ArrayList<>();
|
||||
for (Athlete athlete : s.getAthletes()) {
|
||||
athleteIds.add(athlete.getId());
|
||||
}
|
||||
dto.setAthleteIds(athleteIds);
|
||||
|
||||
// Map other fields as necessary
|
||||
coachSessions.add(dto);
|
||||
}
|
||||
}
|
||||
return ResponseEntity.ok(coachSessions);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{id}")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package hackathon.FrisbYEE.rest;
|
||||
|
||||
import hackathon.FrisbYEE.jpa.dto.ActiviteDTO;
|
||||
import hackathon.FrisbYEE.jpa.dto.CoachDTO;
|
||||
import hackathon.FrisbYEE.jpa.dto.SessionDTO;
|
||||
import hackathon.FrisbYEE.jpa.metier.Activite;
|
||||
import hackathon.FrisbYEE.jpa.metier.Athlete;
|
||||
@@ -19,6 +20,7 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -43,8 +45,12 @@ public class SessionResource {
|
||||
@ResponseBody
|
||||
@PreAuthorize("hasRole('coach')")
|
||||
public ResponseEntity<?> create(@RequestBody SessionDTO dto) {
|
||||
System.out.println("=== SESSION DTO RECEIVED ===");
|
||||
System.out.println(dto);
|
||||
System.out.println("Coach ID: " + dto.getCoachId());
|
||||
System.out.println("ID null");
|
||||
try {
|
||||
if (sessionDAO.findById(dto.getId()).isPresent()) {
|
||||
if (dto.getId() != null && sessionDAO.findById(dto.getId()).isPresent()) {
|
||||
return ResponseEntity.status(HttpStatus.OK).body("Session with ID " + dto.getId() + " already exists.");
|
||||
|
||||
}
|
||||
@@ -54,6 +60,7 @@ public class SessionResource {
|
||||
sessionDAO.save(session);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(maptoDTO(session));
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -69,6 +76,72 @@ public class SessionResource {
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping("/all/group/{groupe}")
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<List<SessionDTO>> getAllByGroup(@PathVariable String groupe) {
|
||||
List<Session> sessions = sessionDAO.findAll();
|
||||
List<SessionDTO> dtos = new ArrayList<>();
|
||||
String groupeStr = groupe;
|
||||
if(groupe.equals("None")) groupeStr = "";
|
||||
for (Session session : sessions) {
|
||||
if(session.getGroupe().equals(groupeStr)){
|
||||
dtos.add(maptoDTO(session));
|
||||
}
|
||||
}
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@GetMapping("/all-between-dates")
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<List<SessionDTO>> getAllBetweenDates(
|
||||
@RequestParam LocalDate startDate,
|
||||
@RequestParam LocalDate endDate
|
||||
) {
|
||||
List<Session> sessions = sessionDAO.findAll();
|
||||
List<SessionDTO> dtos = new ArrayList<>();
|
||||
System.out.println("date : " + startDate + " " + endDate);
|
||||
for (Session session : sessions) {
|
||||
LocalDate sessionDate = session.getCreneau().toLocalDate();
|
||||
|
||||
boolean isBetween =
|
||||
(!sessionDate.isBefore(startDate) || session.getIsRecurrent()) &&
|
||||
!sessionDate.isAfter(endDate);
|
||||
|
||||
if (isBetween) {
|
||||
dtos.add(maptoDTO(session));
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@GetMapping("/all-between-dates/group/{groupe}")
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<List<SessionDTO>> getAllBetweenDates(@RequestParam LocalDate startDate,@RequestParam LocalDate endDate,@PathVariable String groupe) {
|
||||
List<Session> sessions = sessionDAO.findAll();
|
||||
List<SessionDTO> dtos = new ArrayList<>();
|
||||
System.out.println("date : " + startDate + " " + endDate);
|
||||
String groupeStr = groupe;
|
||||
if(groupe.equals("None")) groupeStr = "";
|
||||
for (Session session : sessions) {
|
||||
if(session.getGroupe().equals(groupeStr)){
|
||||
LocalDate sessionDate = session.getCreneau().toLocalDate();
|
||||
|
||||
boolean isBetween =
|
||||
(!sessionDate.isBefore(startDate) || session.getIsRecurrent()) &&
|
||||
!sessionDate.isAfter(endDate);
|
||||
|
||||
if (isBetween) {
|
||||
dtos.add(maptoDTO(session));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<?> getById(@PathVariable Integer id) {
|
||||
@@ -80,17 +153,6 @@ public class SessionResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("{id}/groupe")
|
||||
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<?> getGroupeById(@PathVariable Integer id) {
|
||||
try {
|
||||
Session session = sessionDAO.findById(id).orElseThrow();
|
||||
return ResponseEntity.ok(session.getGroupe());
|
||||
} catch (Exception ex) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{id}")
|
||||
@ResponseBody
|
||||
@PreAuthorize("hasRole('coach')")
|
||||
@@ -104,6 +166,17 @@ public class SessionResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("{id}/groupe")
|
||||
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<?> getGroupeById(@PathVariable Integer id) {
|
||||
try {
|
||||
Session session = sessionDAO.findById(id).orElseThrow();
|
||||
return ResponseEntity.ok(session.getGroupe());
|
||||
} catch (Exception ex) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/update/{id}")
|
||||
@PreAuthorize("hasRole('coach')")
|
||||
public ResponseEntity<Void> updateSession(@PathVariable Integer id, @RequestBody SessionDTO dto) {
|
||||
@@ -125,9 +198,10 @@ public class SessionResource {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/{id}/subscribe/{userId}")
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<Void> subscribe(@PathVariable Integer id, @PathVariable Integer userId) {
|
||||
public ResponseEntity<Void> subscribe(@PathVariable Integer id,@PathVariable Integer userId){
|
||||
Session session = sessionDAO.findById(id).orElseThrow(() -> new ResponseStatusException(
|
||||
HttpStatus.NOT_FOUND, "Session not found with id " + id));
|
||||
Athlete athlete = athleteDAO.findById(userId).orElseThrow(() -> new ResponseStatusException(
|
||||
@@ -136,12 +210,13 @@ public class SessionResource {
|
||||
session.getAthletes().add(athlete);
|
||||
sessionDAO.save(session);
|
||||
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/unsubscribe/{userId}")
|
||||
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<Void> unsubscribe(@PathVariable Integer id, @PathVariable Integer userId) {
|
||||
public ResponseEntity<Void> unsubscribe(@PathVariable Integer id,@PathVariable Integer userId){
|
||||
Session session = sessionDAO.findById(id).orElseThrow(() -> new ResponseStatusException(
|
||||
HttpStatus.NOT_FOUND, "Session not found with id " + id));
|
||||
Athlete athlete = athleteDAO.findById(userId).orElseThrow(() -> new ResponseStatusException(
|
||||
@@ -150,9 +225,34 @@ public class SessionResource {
|
||||
session.getAthletes().remove(athlete);
|
||||
sessionDAO.save(session);
|
||||
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/coach")
|
||||
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<CoachDTO> getCoachBySessionId(@PathVariable Integer id) {
|
||||
try {
|
||||
Session session = sessionDAO.findById(id).orElseThrow();
|
||||
Coach coach = session.getCoach();
|
||||
|
||||
CoachDTO dto = new CoachDTO();
|
||||
dto.setId(coach.getId());
|
||||
dto.setName(coach.getName());
|
||||
dto.setPrenom(coach.getPrenom());
|
||||
List<Integer> listSession = new ArrayList<Integer>();
|
||||
for (Session s : coach.getSessions()) {
|
||||
listSession.add(session.getId());
|
||||
}
|
||||
dto.setSessionIds(listSession);
|
||||
|
||||
return ResponseEntity.ok(dto);
|
||||
} catch (Exception ex) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new CoachDTO());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/{id}/activities")
|
||||
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||
public ResponseEntity<?> getActivitiesBySessionId(@PathVariable Integer id) {
|
||||
|
||||
93
front_end/package-lock.json
generated
93
front_end/package-lock.json
generated
@@ -20,9 +20,10 @@
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"keycloak-js": "^26.2.2",
|
||||
"react": "^19.2.3",
|
||||
"react": "18.2.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-dom": "18.2.0",
|
||||
"react-router-dom": "^7.12.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
@@ -13851,11 +13852,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -14014,16 +14018,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.3"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-error-overlay": {
|
||||
@@ -14060,6 +14065,57 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
|
||||
"integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz",
|
||||
"integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router/node_modules/cookie": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scripts": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||
@@ -14732,10 +14788,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT"
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.3",
|
||||
@@ -14963,6 +15022,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"keycloak-js": "^26.2.2",
|
||||
"react": "^19.2.3",
|
||||
"react": "18.2.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-dom": "18.2.0",
|
||||
"react-router-dom": "^7.12.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
|
||||
@@ -56,6 +56,11 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,body{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen',
|
||||
@@ -63,10 +68,12 @@ body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background: linear-gradient(135deg, var(--tint0) 0%, var(--tint1) 100%);
|
||||
height: 100%;
|
||||
color: var(--text);
|
||||
transition: background 0.4s ease, color 0.4s ease;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.padding {
|
||||
@@ -114,19 +121,16 @@ code {
|
||||
}
|
||||
|
||||
.composant-container {
|
||||
display: grid;
|
||||
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint3) 100%);
|
||||
border-radius: 24px;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--tint4);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
gap:15px;
|
||||
}
|
||||
|
||||
.composant-container:hover {
|
||||
border-color: var(--green-primary);
|
||||
box-shadow: 0 12px 40px var(--green-A-primary);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Header / Navigation */
|
||||
.app-header {
|
||||
@@ -166,7 +170,7 @@ code {
|
||||
}
|
||||
|
||||
/* Cards et containers */
|
||||
.card {
|
||||
/* .card {
|
||||
background: linear-gradient(135deg, var(--tint1) 0%, var(--tint2) 100%);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
@@ -192,7 +196,7 @@ code {
|
||||
|
||||
.card-body {
|
||||
color: var(--text);
|
||||
}
|
||||
} */
|
||||
|
||||
/* Inputs */
|
||||
input[type="text"],
|
||||
@@ -238,9 +242,9 @@ textarea::placeholder {
|
||||
|
||||
/* Select */
|
||||
select {
|
||||
background-color: var(--tint2);
|
||||
color: var(--text);
|
||||
background-color: var(--tint3);
|
||||
border: 2px solid var(--tint5);
|
||||
border: 1px solid var(--tint4);
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
@@ -256,6 +260,7 @@ select:focus {
|
||||
select option {
|
||||
background-color: var(--tint2);
|
||||
color: var(--text);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
@@ -330,7 +335,7 @@ button.secondary:hover {
|
||||
.deleteButton,
|
||||
button.delete,
|
||||
.btn-danger {
|
||||
background-color: #dc2626;
|
||||
background: #dc2626;
|
||||
border: 2px solid #991b1b;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
@@ -339,7 +344,7 @@ button.delete,
|
||||
.deleteButton:hover,
|
||||
button.delete:hover,
|
||||
.btn-danger:hover {
|
||||
background-color: #b91c1c;
|
||||
background: #b91c1c;
|
||||
border-color: #7f1d1d;
|
||||
}
|
||||
|
||||
@@ -347,7 +352,7 @@ button.delete:hover,
|
||||
.addButton,
|
||||
button.add,
|
||||
.btn-success {
|
||||
background-color: var(--green-primary);
|
||||
background: var(--green-primary);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
@@ -360,7 +365,7 @@ button.add:hover,
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
.modalContent {
|
||||
background-color: var(--tint2);
|
||||
padding: 20px;
|
||||
min-width: 300px;
|
||||
@@ -369,16 +374,31 @@ button.add:hover,
|
||||
border: 2px solid var(--green-primary);
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
max-height:100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
.modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
height:100vh;
|
||||
width:100vw;
|
||||
}
|
||||
|
||||
|
||||
/* .modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(4px);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
z-index: 1000;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
@@ -407,7 +427,7 @@ button.add:hover,
|
||||
.modal-close:hover {
|
||||
background-color: #dc2626;
|
||||
color: white;
|
||||
}
|
||||
} */
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
|
||||
@@ -10,8 +10,13 @@ import CreateSession from './components/createSession'
|
||||
import EdtCoach from './components/edt_coach'
|
||||
import { Coach } from "./classes";
|
||||
import RessourcePanel from './components/ressourcePanel';
|
||||
import TestAPI from './components/test_api';
|
||||
import TopBar from './components/topBar';
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import Home from './components/pages/pageHome';
|
||||
import SelectionSession from './components/pages/pageSectionSession';
|
||||
import Gestion from './components/pages/pageGestion';
|
||||
import Admin from './components/pages/pageAdmin';
|
||||
|
||||
|
||||
const keycloakInitOptions = {
|
||||
onLoad: 'login-required',
|
||||
@@ -24,11 +29,12 @@ function App() {
|
||||
<LocalDataProvider>
|
||||
<div className="App">
|
||||
<TopBar/>
|
||||
<h1>Frisbyee</h1>
|
||||
<RessourcePanel/>
|
||||
<EDT/>
|
||||
<CreateSession/>
|
||||
<TestAPI/>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home/>}/>
|
||||
<Route path="/sessions" element={<SelectionSession/>}/>
|
||||
<Route path="/gestion" element={<Gestion/>}/>
|
||||
<Route path="/admin" element={<Admin/>}/>
|
||||
</Routes>
|
||||
</div>
|
||||
</LocalDataProvider>
|
||||
</ReactKeycloakProvider>
|
||||
|
||||
@@ -39,19 +39,20 @@ export const athleteService = {
|
||||
// controller is mounted at /athlete
|
||||
create: (data: any) => api.post<AthleteDTO>("/athlete/create", data),
|
||||
getAll: () => api.get<AthleteDTO[]>("/athlete/all"),
|
||||
getAllByGroup: (group:String) => api.get<AthleteDTO[]>(`/athlete/all/group/${group}`),
|
||||
getById: (id: number | string) => api.get(`/athlete/${id}`),
|
||||
getByKeycloakId: (keycloakId: string) => api.get(`/athlete/keycloak/${encodeURIComponent(keycloakId)}`),
|
||||
update: (id: number | string, data: any) => api.put(`/athlete/${id}`, data),
|
||||
delete: (id: number | string) => api.delete(`/athlete/${id}`),
|
||||
|
||||
// session-related endpoints exposed by AthleteResource
|
||||
getSessionsForAthlete: (athleteId: number | null) => api.get<SessionDTO[]>(`/athlete/athlete/${athleteId}/session`),
|
||||
getSessionsForAthlete: (athleteId: number | null) => api.get<SessionDTO[]>(`/athlete/${athleteId}/session`),
|
||||
getAllSessions: () => api.get(`/athletes/session`),
|
||||
getActivitiesForSession: (sessionId: number | string) => api.get(`/athletes/session/${sessionId}/activities`),
|
||||
getSessionsAfterDate: (athleteId: number | string, date: string) => api.get(`/athletes/${athleteId}/session/after/${encodeURIComponent(date)}`),
|
||||
getSessionsBetweenDates: (athleteId: number | string, startDate: string, endDate: string) => api.get(`/athletes/${athleteId}/session/between/${encodeURIComponent(startDate)}/${encodeURIComponent(endDate)}`),
|
||||
addActivity: (id_sess: number, id_act: number) => api.get(`/${id_sess}/activities/add/${id_act}`),
|
||||
getGroupes: (athleteId: number | string) => api.get(`/athlete/${athleteId}/groupes`),
|
||||
getGroupes: (athleteId: number | string) => api.get(`/athlete/${athleteId}/groupes`)
|
||||
};
|
||||
|
||||
export const activiteService = {
|
||||
@@ -63,11 +64,18 @@ export const activiteService = {
|
||||
getByTheme: (theme: string) => api.get(`/activite/theme/${encodeURIComponent(theme)}`),
|
||||
getDataActivite: (id: number | string) => api.get(`/activite/${id}`),
|
||||
};
|
||||
type DateBetween = {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
||||
export const sessionService = {
|
||||
// controller uses singular /session/* endpoints
|
||||
create: (data: any) => api.post(`/session/create`, data),
|
||||
create: (data: SessionDTO) => api.post(`/session/create`, data),
|
||||
getAll: () => api.get<SessionDTO[]>(`/session/all`),
|
||||
getAllByGroup: (group: String) => api.get<SessionDTO[]>(`/session/all/group/${group}`),
|
||||
getAllBetweenDate: (data: any) => api.get<SessionDTO[]>(`/session/all-between-dates`,{params: data,}),
|
||||
getAllBetweenDateByGroup: (group:String,data: any) => api.get<SessionDTO[]>(`/session/all-between-dates/group/${group}`,{params: data,}),
|
||||
getById: (id: number | null) => api.get(`/session/${id}`),
|
||||
delete: (id: number | null) => api.delete(`/session/delete/${id}`),
|
||||
update: (id: number | null, data: any) => api.put(`/session/update/${id}`, data),
|
||||
@@ -75,13 +83,14 @@ export const sessionService = {
|
||||
getActivities: (sessionId: number | null) => api.get<ActiviteDTO[]>(`/session/${sessionId}/activities`),
|
||||
addActivity: (sessionId: number | null, activityId: number) => api.post(`/session/${sessionId}/activities/${activityId}`),
|
||||
getGroupe: (sessionId: number | null) => api.get(`/session/${sessionId}/groupe`),
|
||||
getCoach: (sessionId: number | null) => api.get<CoachDTO>(`/session/${sessionId}/coach`),
|
||||
subscribe: (sessionId: number | null, userId: number) => api.put(`/session/${sessionId}/subscribe/${userId}`),
|
||||
unsubscribe: (sessionId: number | null, userId: number) => api.put(`/session/${sessionId}/unsubscribe/${userId}`),
|
||||
};
|
||||
|
||||
export const coachService = {
|
||||
// controller doesn't declare a class-level path consistently; support both common patterns
|
||||
create: (data: any) => api.post<CoachDTO>(`/coach/create`, data),
|
||||
create: (data: CoachDTO) => api.post<CoachDTO>(`/coach/create`, data),
|
||||
getAll: () => api.get<CoachDTO[]>(`/coach/all`),
|
||||
getById: (id: number) => api.get(`/coach/${id}`),
|
||||
getByKeycloakId: (keycloakId: string) => api.get(`/coach/keycloak/${keycloakId}`),
|
||||
@@ -99,7 +108,8 @@ export const userService = {
|
||||
|
||||
export const adminService = {
|
||||
getByKeycloakId: (keycloak_id: string) => api.get(`/admin/keycloak/${keycloak_id}`),
|
||||
getById: (id: number | string) => api.get(`/admin/${id}`),
|
||||
getById: (id: number | string) => api.get<AdminDTO>(`/admin/${id}`),
|
||||
create: (data: AdminDTO) => api.post<AdminDTO>("/admin/create", data),
|
||||
};
|
||||
|
||||
export default api;
|
||||
@@ -46,7 +46,7 @@ export class Admin extends User{
|
||||
}
|
||||
|
||||
export class Athlete extends User{
|
||||
groupes!: Groupe[];
|
||||
groupes: Groupe[] = [];
|
||||
sessionsID!: number[];
|
||||
sessions: Session[] = [];
|
||||
|
||||
@@ -55,11 +55,11 @@ export class Athlete extends User{
|
||||
|
||||
constructor(dto?:AthleteDTO){
|
||||
super();
|
||||
this.id = dto?.id ?? 0;
|
||||
this.id = dto?.id ?? null;
|
||||
this.keycloakId = dto?.id_keycloak ?? "";
|
||||
this.nom = dto?.name ?? "";
|
||||
this.prenom = dto?.prenom ?? "" ;
|
||||
this.email = ""; //TODO
|
||||
this.email = "";
|
||||
this.groupes = dto?.groupes ?? [];
|
||||
this.sessionsID = dto?.sessionIds ?? [];
|
||||
this.sessions = [];
|
||||
@@ -90,7 +90,7 @@ export class Coach extends User{
|
||||
|
||||
constructor(dto?:CoachDTO){
|
||||
super();
|
||||
this.id = dto?.id ?? 0;
|
||||
this.id = dto?.id ?? null;
|
||||
this.keycloakId = dto?.id_keycloak ?? "";
|
||||
this.nom = dto?.name ?? "";
|
||||
this.prenom = dto?.prenom ?? "";
|
||||
@@ -153,7 +153,7 @@ export class Session{
|
||||
isRecurrent: this.isRecurrent,
|
||||
creneau: this.creneau.toISOString(),
|
||||
duree: this.duree,
|
||||
groupe: "", //TODO
|
||||
groupe: this.groupe,
|
||||
coachId: this.coach?.id ?? null,
|
||||
athleteIds: [],
|
||||
activiteIds: []
|
||||
|
||||
@@ -13,22 +13,10 @@ export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.25)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className="modal" onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div className="modal" onClick={onClose}>
|
||||
<div className="modalContent" onClick={(e) => e.stopPropagation()}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,31 +1,83 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Athlete, Session } from "../classes";
|
||||
import { calculStatsAthlete, niveauAlerte, StatsAthlete } from "../utils/athleteUtils"
|
||||
import { getSessionsByAthleteId, getSessionsOfUserAPI } from "../requetes";
|
||||
|
||||
interface AthleteStatsProps {
|
||||
athlete: Athlete;
|
||||
}
|
||||
|
||||
function StatAthlete({ athlete }: AthleteStatsProps) {
|
||||
const [dateDebut, setDateDebut] = React.useState(new Date());
|
||||
const [dateFin, setDateFin] = React.useState(new Date());
|
||||
const [seuilCritique, setSeuilCritique] = React.useState(0);
|
||||
const [seuilMax, setSeuilMax] = React.useState(0);
|
||||
const [stats, setStats] = React.useState<StatsAthlete | null>(null);
|
||||
|
||||
console.log("Athlete:", athlete);
|
||||
console.log("Sessions:", athlete.sessions);
|
||||
const [sessions, setSessions] = useState<Session[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dateDebut, setDateDebut] = useState(new Date());
|
||||
const [dateFin, setDateFin] = useState(new Date());
|
||||
const [seuilCritique, setSeuilCritique] = useState(0);
|
||||
const [seuilMax, setSeuilMax] = useState(0);
|
||||
const [stats, setStats] = useState<StatsAthlete | null>(null);
|
||||
const dateToDatetimeLocal = (date: Date) => {
|
||||
const pad = (n: number) => n.toString().padStart(2, "0");
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("useEffect déclenché, athlete.id =", athlete.id);
|
||||
async function loadSessions() {
|
||||
if (!athlete.id) {
|
||||
console.error("Athlete ID is null");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
console.log("Début du chargement des sessions");
|
||||
setLoading(true);
|
||||
try {
|
||||
const sessionsData = await getSessionsByAthleteId(athlete.id);
|
||||
console.log("Sessions reçues de l'API:", sessionsData);
|
||||
setSessions(sessionsData);
|
||||
console.log("Sessions chargées:", sessionsData);
|
||||
console.log("Première session:", sessionsData[0]);
|
||||
console.log("Athletes de la première session:", sessionsData[0]?.athletes);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement des sessions:", error);
|
||||
setSessions([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
console.log("Fin du chargement");
|
||||
}
|
||||
}
|
||||
|
||||
loadSessions();
|
||||
}, [athlete.id]);
|
||||
|
||||
const handleCalculerStats = () => {
|
||||
const statsCalculees = calculStatsAthlete(athlete.sessions, athlete, dateDebut, dateFin);
|
||||
console.log("Sessions au moment du calcul:", sessions);
|
||||
console.log("Nombre de sessions:", sessions.length);
|
||||
let safeDebut = dateDebut;
|
||||
let safeFin = dateFin;
|
||||
if (sessions.length > 0) {
|
||||
const times = sessions
|
||||
.map(s => s.creneau?.getTime())
|
||||
.filter(t => typeof t === 'number') as number[];
|
||||
if (times.length > 0) {
|
||||
const min = Math.min(...times);
|
||||
const max = Math.max(...times);
|
||||
// if user range is zero-length or inverted, default to sessions span
|
||||
if (safeDebut.getTime() === safeFin.getTime() || safeDebut.getTime() > safeFin.getTime()) {
|
||||
safeDebut = new Date(min);
|
||||
safeFin = new Date(max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const statsCalculees = calculStatsAthlete(sessions, athlete, safeDebut, safeFin);
|
||||
setStats(statsCalculees);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="creneau-stats">
|
||||
<div>Nb Session : {athlete.sessions.length};</div>
|
||||
<div>Nb Session : {sessions.length};</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Début :</th>
|
||||
@@ -52,7 +104,7 @@ function StatAthlete({ athlete }: AthleteStatsProps) {
|
||||
|
||||
{stats && (
|
||||
<div className="stats-display">
|
||||
<h3>Statistiques de {athlete.nom}</h3>
|
||||
<h3>Statistiques de {athlete.prenom} {athlete.nom}</h3>
|
||||
<p><strong>Nombre total de sessions :</strong> {stats.nbSessions}</p>
|
||||
<p><strong>Sessions par semaine :</strong> {stats.nbSessionsPerWeek.toFixed(2)}</p>
|
||||
<p><strong>Statut :</strong> {niveauAlerte(stats, seuilCritique, seuilMax)}</p>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createSessionAPI, postSession } from "../requetes";
|
||||
import './style/createSession.css';
|
||||
|
||||
export const CreateSession = () => {
|
||||
const {user} = useLocalData();
|
||||
const {userLocal: user} = useLocalData();
|
||||
const [session,setSession] = useState<Session>(new Session());
|
||||
const [activities, setActivities] = useState<Activite[]>([]);
|
||||
const [name,setName] = useState("");
|
||||
@@ -74,6 +74,20 @@ export const CreateSession = () => {
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Groupe:</th>
|
||||
<th>
|
||||
<select
|
||||
onChange={(e) => {
|
||||
const v = (e.target as HTMLSelectElement).value;
|
||||
setGroupe(v as Groupe)
|
||||
console.log(v);
|
||||
}}>
|
||||
<option value=""> Sans groupe</option>
|
||||
<option value="Loisir">Loisir</option>
|
||||
<option value="Entrainement">Entrainement</option>
|
||||
<option value="Competition">Competition</option>
|
||||
|
||||
</select>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Creneau:</th>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { Athlete, Coach, Session} from "../classes"
|
||||
import { Admin, Athlete, Coach, Session} from "../classes"
|
||||
import { useLocalData } from "../context/useLocalData"
|
||||
import './style/edt.css';
|
||||
import {getSessionsOfUserAPI } from "../requetes";
|
||||
import {getAllSessionsAPI, getAllSessionsBetweenAPI, getSessionsOfUserAPI } from "../requetes";
|
||||
import EdtSession from "./edt_session";
|
||||
import {delay} from "../requetes";
|
||||
import Loading from "./loading";
|
||||
@@ -26,7 +26,8 @@ export function hoursToString(date:Date){
|
||||
|
||||
|
||||
export const EDT =() =>{
|
||||
const {user,setUser} = useLocalData()
|
||||
const {userLocal} = useLocalData()
|
||||
const {sessionsLocal,setSessionsLocal} = useLocalData()
|
||||
const [sessions, setSessions] = useState<Session[]>([])
|
||||
const [week,setWeek] = useState<Date>(getFirstDay(new Date()));
|
||||
const [loadedWeek,setLoadedWeek] = useState<Date|null>(null);
|
||||
@@ -35,12 +36,21 @@ export const EDT =() =>{
|
||||
const week_days_nums:number[] = [1,2,3,4,5,6,0];
|
||||
|
||||
function loadSessions(date:Date){
|
||||
var maxDate = getNextDay(date,6)
|
||||
var maxDate = toDateOnly(getNextDay(date,6));
|
||||
|
||||
var newWeek: Session[] = []
|
||||
if(user instanceof Athlete || user instanceof Coach){
|
||||
user.sessions.forEach(session => {
|
||||
if((session.creneau >= date && session.creneau <= maxDate && !session.isRecurrent) || (session.isRecurrent && session.creneau<maxDate)){
|
||||
if(userLocal instanceof Athlete || userLocal instanceof Coach){
|
||||
userLocal.sessions.forEach(session => {
|
||||
const creneau = toDateOnly(session.creneau);
|
||||
if((creneau >= date || session.isRecurrent) && (creneau <= maxDate)){
|
||||
newWeek.push(session);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if(userLocal instanceof Admin){
|
||||
sessionsLocal.forEach(session => {
|
||||
const creneau = toDateOnly(session.creneau);
|
||||
if((creneau >= date || session.isRecurrent) && (creneau <= maxDate)){
|
||||
newWeek.push(session);
|
||||
}
|
||||
});
|
||||
@@ -50,7 +60,7 @@ export const EDT =() =>{
|
||||
}
|
||||
|
||||
function changeWeek(date:Date){
|
||||
setWeek(date);
|
||||
setWeek(toDateOnly(date));
|
||||
}
|
||||
|
||||
function isSameDay(date1:Date,date2:Date){
|
||||
@@ -65,7 +75,7 @@ export const EDT =() =>{
|
||||
updateWeek(week);
|
||||
loadSessions(week)
|
||||
setLoading(true);
|
||||
},[week,user])
|
||||
},[week,userLocal])
|
||||
|
||||
useEffect(() => {
|
||||
if(loadedWeek!==null){
|
||||
@@ -83,9 +93,22 @@ export const EDT =() =>{
|
||||
//TODO updateSession
|
||||
//await delay(2000);
|
||||
//await updateSessionsOfUser(user,null,null);
|
||||
if(user instanceof Athlete || user instanceof Coach){
|
||||
const newSessions:Session[] = await getSessionsOfUserAPI(user);
|
||||
user.sessions = newSessions;
|
||||
if(userLocal instanceof Athlete || userLocal instanceof Coach){
|
||||
const newSessions:Session[] = await getSessionsOfUserAPI(userLocal);
|
||||
userLocal.sessions = newSessions;
|
||||
}
|
||||
else if(userLocal instanceof Admin){
|
||||
const newSessions:Session[] = await getAllSessionsBetweenAPI(week,getNextDay(week,6));
|
||||
const date = toDateOnly(week);
|
||||
var maxDate = toDateOnly(getNextDay(date,6));
|
||||
sessionsLocal.forEach(sessionLocal => { //update seulement la semaine
|
||||
const creneau = toDateOnly(sessionLocal.creneau);
|
||||
if(!((creneau >= date || sessionLocal.isRecurrent) && (creneau <= maxDate))){
|
||||
newSessions.push(sessionLocal);
|
||||
}
|
||||
});
|
||||
|
||||
setSessionsLocal(newSessions);
|
||||
}
|
||||
setLoadedWeek(week);
|
||||
}
|
||||
@@ -108,7 +131,15 @@ export const EDT =() =>{
|
||||
else{
|
||||
firstDate = getNextDay(date,-numWeek+1);
|
||||
}
|
||||
return firstDate;
|
||||
return toDateOnly(firstDate);
|
||||
}
|
||||
|
||||
function toDateOnly(date: Date): Date {
|
||||
return new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate()
|
||||
);
|
||||
}
|
||||
|
||||
function getNextDay(date:Date,nb:number):Date{
|
||||
@@ -139,7 +170,7 @@ export const EDT =() =>{
|
||||
<div className="edt_colonnes">
|
||||
<div className="top_left_loading">{loading && <Loading/>}</div>
|
||||
{week_days_nums.map((num,index)=>(
|
||||
<div className={`edt_colonne`}>
|
||||
<div className={`edt_colonne ${sameDay(getNextDay(week, index), new Date()) ? "today" : ""}`}>
|
||||
<div className={`edt_day_header ${sameDay(getNextDay(week, index), new Date()) ? "today" : ""}`}>
|
||||
<div> {week_days[index]} </div>
|
||||
<div className="edt_date"> {dateToString(getNextDay(week,index))} </div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useKeycloak } from '@react-keycloak/web'
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Admin, Athlete, Coach, User } from '../classes';
|
||||
import { useLocalData } from '../context/useLocalData';
|
||||
import { loginOrRegister, postAthlete } from '../requetes';
|
||||
@@ -9,9 +9,26 @@ import { Modal } from './Modal';
|
||||
import './style/topBar.css';
|
||||
|
||||
export const Login =() =>{
|
||||
const {user,setUser} = useLocalData()
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const {userLocal: user,setUserLocal: setUser} = useLocalData()
|
||||
const { keycloak } = useKeycloak();
|
||||
const [open,setOpen] = useState<boolean>(false);
|
||||
const [openGroupe,setOpenGroupe] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
setOpenGroupe(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
async function loginUser(){
|
||||
@@ -29,6 +46,10 @@ export const Login =() =>{
|
||||
}
|
||||
}
|
||||
|
||||
function openGestionGroupe(): void {
|
||||
setOpenGroupe(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loginUser()
|
||||
},[keycloak.authenticated])
|
||||
@@ -42,6 +63,7 @@ export const Login =() =>{
|
||||
setUser(new User());
|
||||
clearAuthToken();
|
||||
setOpen(false);
|
||||
setOpenGroupe(false);
|
||||
}
|
||||
|
||||
function handleOpen(): void {
|
||||
@@ -50,7 +72,7 @@ export const Login =() =>{
|
||||
|
||||
if(keycloak.authenticated){
|
||||
return(
|
||||
<div className='loginContainer'>
|
||||
<div ref={ref} className='loginContainer'>
|
||||
<button className="loginButton" onClick={()=>handleOpen()}>{user.prenom}</button>
|
||||
{open &&
|
||||
<div className='login'>
|
||||
@@ -65,12 +87,37 @@ export const Login =() =>{
|
||||
{user instanceof Athlete && <div>Role : Athlete</div>}
|
||||
{user instanceof Coach && <div>Role : Coach</div>}
|
||||
{user instanceof Admin && <div>Role : Admin</div>}
|
||||
{user instanceof Athlete &&
|
||||
<div>
|
||||
<div>Groupes : </div>
|
||||
{(user.groupes.length > 0) ?
|
||||
<div>
|
||||
{user.groupes.map((groupe) => (
|
||||
groupe
|
||||
))}
|
||||
</div>
|
||||
:
|
||||
<div>aucun groupe</div>}
|
||||
<button onClick={() => openGestionGroupe()}>gérer les groupes</button>
|
||||
{/*user instanceof Athlete &&
|
||||
<div>
|
||||
<div>
|
||||
<button>{user.groupes.includes("Loisir")?"Quitter":"Rejoindre"} groupe Loisir</button>
|
||||
<button>{user.groupes.includes("Entrainement")?"Quitter":"Rejoindre"}Rejoindre groupe Entrainement</button>
|
||||
<button>{user.groupes.includes("Competition")?"Quitter":"Rejoindre"}Rejoindre groupe Compétition</button>
|
||||
</div>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
|
||||
<button onClick={() => handleLogout()}>
|
||||
Se déconnecter
|
||||
</button>
|
||||
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { dateToString, hoursToString } from "../edt";
|
||||
import { Modal } from "../Modal";
|
||||
import CreateActivite from "../createActivite";
|
||||
import Loading from "../loading";
|
||||
import { addActiviteToSession, createActivityAPI, delay, deletActiviteFromSession, getSessionOfActivite, subscribeSessionAPI, unsubscribeSessionAPI } from "../../requetes";
|
||||
import { addActiviteToSession, createActivityAPI, delay, deletActiviteFromSession, getCoachOfSession, getSessionOfActivite, subscribeSessionAPI, unsubscribeSessionAPI } from "../../requetes";
|
||||
import { useLocalData } from "../../context/useLocalData";
|
||||
|
||||
type Props = {
|
||||
@@ -15,7 +15,7 @@ type Props = {
|
||||
|
||||
function DetailSession({session,open,setOpen}:Props){
|
||||
|
||||
const {user,setUser} = useLocalData()
|
||||
const {userLocal: user,setUserLocal: setUser} = useLocalData()
|
||||
|
||||
const [activites,setActivites] = useState<Activite[]>([]);
|
||||
const [open2, setOpen2] = useState<boolean>(false);
|
||||
@@ -93,10 +93,13 @@ function DetailSession({session,open,setOpen}:Props){
|
||||
|
||||
return(
|
||||
<Modal isOpen={open} onClose={() => setOpen(false)}>
|
||||
<div className="object_modal">
|
||||
<div>
|
||||
<h2>{session.name}</h2>
|
||||
|
||||
<div>{hoursToString(sDate)}</div>
|
||||
<div>{dateToString(sDate)}</div>
|
||||
<div>encadré par :</div>
|
||||
<div>{session.coach?.prenom} {session.coach?.nom}</div>
|
||||
{user instanceof Athlete &&
|
||||
<div>
|
||||
{user.sessions.includes(session) ? <button onClick={()=>unsubscribeSession()}>quitter</button>
|
||||
|
||||
@@ -104,6 +104,9 @@ function ObjectUser({user}:Props){
|
||||
<div className="object_modal">
|
||||
<div>{user.prenom}</div>
|
||||
<div>{user.nom}</div>
|
||||
{user instanceof Athlete && <div>Role : Athlete</div>}
|
||||
{user instanceof Coach && <div>Role : Coach</div>}
|
||||
{user instanceof Admin && <div>Role : Admin</div>}
|
||||
{(user instanceof Athlete || user instanceof Coach) &&
|
||||
<div className='padding'>
|
||||
<div className='list_object_modal'>
|
||||
|
||||
13
front_end/src/components/pages/pageAdmin.tsx
Normal file
13
front_end/src/components/pages/pageAdmin.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import CreateSession from "../createSession"
|
||||
import TopBar from "../topBar"
|
||||
|
||||
function Admin() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Admin</h1>
|
||||
rien pour l'instant
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Admin
|
||||
13
front_end/src/components/pages/pageGestion.tsx
Normal file
13
front_end/src/components/pages/pageGestion.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import CreateSession from "../createSession"
|
||||
import TopBar from "../topBar"
|
||||
|
||||
function Gestion() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Gestion</h1>
|
||||
<CreateSession/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Gestion
|
||||
13
front_end/src/components/pages/pageHome.tsx
Normal file
13
front_end/src/components/pages/pageHome.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import EDT from "../edt"
|
||||
import TopBar from "../topBar"
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Home</h1>
|
||||
<EDT/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
13
front_end/src/components/pages/pageSectionSession.tsx
Normal file
13
front_end/src/components/pages/pageSectionSession.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import RessourcePanel from "../ressourcePanel"
|
||||
import TopBar from "../topBar"
|
||||
|
||||
function SelectionSession() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Selection Session</h1>
|
||||
<RessourcePanel/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectionSession
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLocalData } from "../context/useLocalData";
|
||||
import { Activite, Athlete, Coach , Session, Ligne, Admin } from "../classes";
|
||||
import { Activite, Athlete, Coach , Session, Ligne, Admin, Groupe } from "../classes";
|
||||
import {calculTempsDeJeuParLigne} from "../utils/ligneUtils";
|
||||
import { keyboard } from "@testing-library/user-event/dist/keyboard";
|
||||
import ObjectSession from "./object/session";
|
||||
@@ -15,7 +15,7 @@ import ObjectLigne from "./object/lignes";
|
||||
|
||||
export default function RessourcePanel() {
|
||||
const { keycloak } = useKeycloak();
|
||||
const { user } = useLocalData();
|
||||
const { userLocal: user } = useLocalData();
|
||||
//const user = getUserTest(); //TODO
|
||||
const [value,setValue] = useState<keyWord>("sessions");
|
||||
|
||||
@@ -24,10 +24,11 @@ import ObjectLigne from "./object/lignes";
|
||||
const[allCoachs,setAllCoachs] = useState<Coach[]>([]);
|
||||
const[allSessions,setAllSessions] = useState<Session[]>([]);
|
||||
const[allLignes,setAllLignes] = useState<Ligne[]>([]);
|
||||
const [groupe, setGroupe] = useState<String>("Tous");
|
||||
|
||||
|
||||
async function updateAthletes() {
|
||||
const athletes:Athlete[] = await getAllAthlete();
|
||||
const athletes:Athlete[] = await getAllAthlete(groupe);
|
||||
setAllAthletes(athletes);
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ import ObjectLigne from "./object/lignes";
|
||||
}
|
||||
|
||||
async function updateSessions() {
|
||||
const sessions:Session[] = await getAllSessionsAPI();
|
||||
const sessions:Session[] = await getAllSessionsAPI(groupe);
|
||||
setAllSessions(sessions);
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ import ObjectLigne from "./object/lignes";
|
||||
|
||||
useEffect(() => {
|
||||
update();
|
||||
},[user,value])
|
||||
},[user,value,groupe])
|
||||
|
||||
function update(){
|
||||
if(keycloak.authenticated){
|
||||
@@ -89,9 +90,22 @@ import ObjectLigne from "./object/lignes";
|
||||
{(user instanceof Admin || user instanceof Coach) &&<option value="lignes"> Lignes</option>}
|
||||
|
||||
</select>
|
||||
{(value == "athletes" || value == "sessions") &&
|
||||
<select
|
||||
onChange={(e) => {
|
||||
const v = (e.target as HTMLSelectElement).value;
|
||||
setGroupe(v as Groupe)
|
||||
console.log(v);
|
||||
}}>
|
||||
<option value="Tous"> Tous</option>
|
||||
<option value="None"> Sans groupe</option>
|
||||
<option value="Loisir">Loisir</option>
|
||||
<option value="Entrainement">Entrainement</option>
|
||||
<option value="Competition">Competition</option>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="edt_sessions_panel">
|
||||
<h3>Liste des {value}</h3>
|
||||
<button onClick={()=>update()}> Actualiser </button>
|
||||
|
||||
@@ -6,8 +6,3 @@
|
||||
box-shadow: var(--shadow-md);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.createActivite:hover {
|
||||
border-color: var(--green-primary);
|
||||
box-shadow: 0 8px 24px var(--green-A-primary);
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
gap: 16px;
|
||||
color: var(--text);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.edt_loading {
|
||||
@@ -39,7 +40,7 @@
|
||||
}
|
||||
|
||||
.edt_colonne {
|
||||
background: linear-gradient(135deg, var(--tint3) 0%, var(--tint4) 100%);
|
||||
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint4) 100%);
|
||||
border-radius: 16px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
height: 100%;
|
||||
@@ -47,6 +48,11 @@
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.edt_colonne.today {
|
||||
background: linear-gradient(135deg, var(--tint1) 25%, var(--tint2) 100%);
|
||||
border: 1px solid var(--green-primary);
|
||||
}
|
||||
|
||||
|
||||
.edt_day_header {
|
||||
font-size: clamp(5px, 1vw, 18px);
|
||||
@@ -61,13 +67,13 @@
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.today {
|
||||
background: linear-gradient(135deg, var(--green-primary) 0%, var(--cyan-accent) 100%);
|
||||
.edt_day_header.today {
|
||||
background: linear-gradient(135deg, var(--green-dark) 0%, var(--cyan-accent) 100%);
|
||||
color: #FFFFFF;
|
||||
box-shadow: 0 6px 20px var(--green-A-primary);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.edt_day_content {
|
||||
@@ -90,6 +96,7 @@
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-sm);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.edt_session:hover {
|
||||
|
||||
@@ -64,10 +64,6 @@
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.object_modal:hover {
|
||||
border-color: var(--green-primary);
|
||||
box-shadow: 0 4px 12px var(--green-A-primary);
|
||||
}
|
||||
|
||||
.session_modal_activite_list{
|
||||
display: grid;
|
||||
|
||||
@@ -18,10 +18,24 @@
|
||||
box-shadow: 0 8px 32px var(--green-A-primary);
|
||||
}
|
||||
|
||||
.toBarLeft {
|
||||
.toBarLeft{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.toBarMidle{
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
.topBarRight{
|
||||
flex: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -40,10 +54,13 @@
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.loginContainer {
|
||||
position: relative;
|
||||
z-index: 9999;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.login {
|
||||
@@ -65,6 +82,9 @@
|
||||
}
|
||||
|
||||
.loginButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 120px;
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
@@ -72,6 +92,9 @@
|
||||
|
||||
|
||||
.ButtonTheme {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
width: 44px;
|
||||
color: var(--text);
|
||||
@@ -81,14 +104,11 @@
|
||||
font-size: 18px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ButtonTheme:hover {
|
||||
border-color: var(--themeButtonColor);
|
||||
color: var(--themeButtonColor);
|
||||
/* color: var(--themeButtonColor); */
|
||||
box-shadow: 0 4px 12px rgba(0, 170, 255, 0.3);
|
||||
transform: scale(1.05) rotateZ(-180deg);
|
||||
}
|
||||
@@ -102,3 +122,11 @@
|
||||
.logo:hover {
|
||||
transform: scale(1.1) rotateY(10deg);
|
||||
}
|
||||
|
||||
.logoLink:hover{
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.logoLink{
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { useKeycloak } from "@react-keycloak/web"
|
||||
import { getAllCoach } from "../requetes"
|
||||
import { Admin } from "../classes";
|
||||
|
||||
|
||||
function TestAPI(){
|
||||
const { keycloak } = useKeycloak()
|
||||
|
||||
function handleGetUsers(): void {
|
||||
getAllCoach();
|
||||
}
|
||||
|
||||
function handleSendAdmin(): void {
|
||||
const admin = new Admin;
|
||||
admin.nom = "admin";
|
||||
admin.email = "admin@gmail.com";
|
||||
|
||||
//createAdminAPI(admin);
|
||||
}
|
||||
|
||||
return(
|
||||
<div style={{padding:30, backgroundColor:"#000000"}}>
|
||||
<button onClick={()=>handleGetUsers()}>getUsers</button>
|
||||
<button onClick={()=>handleSendAdmin()}>sendAdmin</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TestAPI
|
||||
@@ -1,22 +1,30 @@
|
||||
import { Link } from "react-router-dom"
|
||||
import { Admin, Athlete, Coach } from "../classes"
|
||||
import { useLocalData } from "../context/useLocalData"
|
||||
import Login from "./login"
|
||||
import SwitchThemeColor from "./SwitchThemeColor"
|
||||
|
||||
function TopBar(){
|
||||
|
||||
|
||||
const {userLocal} = useLocalData()
|
||||
|
||||
|
||||
return(
|
||||
<div className="topBar">
|
||||
<div className="toBarLeft">
|
||||
<img className="logo" src="/Frisbyee_logo.png"/>
|
||||
<Link className="logoLink" to="/"><img className="logo" src="/Frisbyee_logo.png"/></Link>
|
||||
<h2>Frisbyee</h2>
|
||||
</div>
|
||||
|
||||
<div className="toBarMidle">
|
||||
<Link to="/">Home</Link>
|
||||
<Link to="/sessions">{(userLocal instanceof Athlete) ? "Sessions" : "List"}</Link>
|
||||
{userLocal instanceof Coach &&<Link to="/gestion">Gestion</Link>}
|
||||
{userLocal instanceof Admin &&<Link to="/admin">Admin</Link>}
|
||||
</div>
|
||||
|
||||
<div className="topBarRight">
|
||||
<SwitchThemeColor/>
|
||||
<Login/>
|
||||
<SwitchThemeColor/>
|
||||
<Login/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,12 +2,10 @@ import { createContext } from 'react'
|
||||
import { Session, User } from '../classes';
|
||||
|
||||
interface LocalDataContextType {
|
||||
user:User;
|
||||
setUser: React.Dispatch<React.SetStateAction<User>>
|
||||
sessions: Session[];
|
||||
setSessions: React.Dispatch<React.SetStateAction<Session[]>>
|
||||
users: User[];
|
||||
setUsers: React.Dispatch<React.SetStateAction<User[]>>
|
||||
userLocal:User;
|
||||
setUserLocal: React.Dispatch<React.SetStateAction<User>>
|
||||
sessionsLocal: Session[];
|
||||
setSessionsLocal: React.Dispatch<React.SetStateAction<Session[]>>
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,17 @@ import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
|
||||
@@ -3,15 +3,14 @@ import { Session, User } from '../classes'
|
||||
import { LocalDataContext } from '../context/LocalDataContext'
|
||||
|
||||
export const LocalDataProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [user, setUser] = useState<User>(new User())
|
||||
const [sessions, setSessions] = useState<Session[]>([])
|
||||
const [users, setUsers] = useState<User[]>([])
|
||||
const [userLocal, setUserLocal] = useState<User>(new User())
|
||||
const [sessionsLocal, setSessionsLocal] = useState<Session[]>([])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<LocalDataContext.Provider
|
||||
value={{ user, setUser, sessions, setSessions, users, setUsers }}>
|
||||
value={{ userLocal, setUserLocal, sessionsLocal,setSessionsLocal}}>
|
||||
{children}
|
||||
</LocalDataContext.Provider>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import api, { activiteService, athleteService, coachService, sessionService } from "./api";
|
||||
import api, { activiteService, adminService, athleteService, coachService, sessionService } from "./api";
|
||||
import { Activite, Admin, Athlete, Coach, Session, User } from "./classes";
|
||||
import Keycloak from 'keycloak-js'
|
||||
import { AdminDTO, AthleteDTO, CoachDTO, SessionDTO } from "./classesDTO";
|
||||
@@ -27,7 +27,7 @@ export async function loginOrRegister(keycloak:Keycloak): Promise<User|null>{
|
||||
newAdmin.email = keycloak.tokenParsed.email || "";
|
||||
newAdmin.nom = keycloak.tokenParsed.family_name || "";
|
||||
newAdmin.prenom = keycloak.tokenParsed.given_name || "";
|
||||
const response = await athleteService.create(newAdmin.toDTO());
|
||||
const response = await adminService.create(newAdmin.toDTO());
|
||||
const admin = new Admin(response.data);
|
||||
return admin;
|
||||
}
|
||||
@@ -161,6 +161,7 @@ export async function createSessionAPI(session: Session): Promise<Session> {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("GROUPE = " + session.groupe);
|
||||
const response = await api.post<SessionDTO>("/session/create", session.toDTO());
|
||||
const sessionRes:Session = new Session(response.data);
|
||||
await Promise.all(
|
||||
@@ -217,28 +218,11 @@ export async function postAthlete(athlete: Athlete):Promise<Athlete>{
|
||||
|
||||
export async function postSession(session: Session){
|
||||
try {
|
||||
const data = {
|
||||
name: session.name,
|
||||
creneau: session.creneau, // string ISO OK
|
||||
duree: session.duree,
|
||||
isRecurrent: session.isRecurrent,
|
||||
|
||||
coachId: session.coach?.id,
|
||||
groupe: session.groupe ? session.groupe : undefined,
|
||||
}
|
||||
|
||||
const response = await sessionService.create(data);
|
||||
const response = await sessionService.create(session.toDTO());
|
||||
session.id = response.data.id; //TODO ?
|
||||
|
||||
session.activites.forEach(activite => {
|
||||
const data2 = {
|
||||
name: activite.nom,
|
||||
duree: activite.duree,
|
||||
date: activite.data,
|
||||
theme: activite.theme,
|
||||
sessionId: session.id, //TODO
|
||||
}
|
||||
activiteService.create(data2);
|
||||
activiteService.create(activite.toDTO());
|
||||
// console.log("Session créée");
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -298,9 +282,14 @@ export async function getSessionsOfUserAPI(user:User): Promise<Session[]>{
|
||||
sessionsDTO = response.data;
|
||||
}
|
||||
const sessions:Session[] = [];
|
||||
sessionsDTO.forEach(sessionDTO => {
|
||||
sessions.push(new Session(sessionDTO));
|
||||
});
|
||||
for (const sessionDTO of sessionsDTO) {
|
||||
const session = new Session(sessionDTO);
|
||||
const coach = await getCoachByIdAPI(sessionDTO.coachId);
|
||||
if(coach!=null){
|
||||
session.coach = coach;
|
||||
}
|
||||
sessions.push(session);
|
||||
}
|
||||
return sessions;
|
||||
|
||||
}catch (error) {
|
||||
@@ -309,9 +298,15 @@ export async function getSessionsOfUserAPI(user:User): Promise<Session[]>{
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllSessionsAPI():Promise<Session[]>{
|
||||
export async function getAllSessionsAPI(groupe?:String):Promise<Session[]>{
|
||||
try {
|
||||
const response = await sessionService.getAll();
|
||||
var response;
|
||||
if(groupe!=null && groupe!="Tous"){
|
||||
response = await sessionService.getAllByGroup(groupe);
|
||||
}
|
||||
else{
|
||||
response = await sessionService.getAll();
|
||||
}
|
||||
const sessions = await Promise.all(
|
||||
response.data.map(async sessionDTO => {
|
||||
const session = new Session(sessionDTO);
|
||||
@@ -331,6 +326,59 @@ export async function getAllSessionsAPI():Promise<Session[]>{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function formatDateLocal(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
|
||||
export async function getAllSessionsBetweenAPI(d1:Date,d2:Date,groupe?:String):Promise<Session[]>{
|
||||
try {
|
||||
const data = {
|
||||
startDate: formatDateLocal(d1),
|
||||
endDate: formatDateLocal(d2)
|
||||
}
|
||||
var response;
|
||||
if(groupe!=null && groupe!="Tous"){
|
||||
response = await sessionService.getAllBetweenDateByGroup(groupe,data);
|
||||
}
|
||||
else {
|
||||
response = await sessionService.getAllBetweenDate(data);
|
||||
}
|
||||
const sessions = await Promise.all(
|
||||
response.data.map(async sessionDTO => {
|
||||
const session = new Session(sessionDTO);
|
||||
const coach = await getCoachByIdAPI(sessionDTO.coachId);
|
||||
|
||||
if (coach != null) {
|
||||
session.coach = coach;
|
||||
}
|
||||
return session;
|
||||
})
|
||||
);
|
||||
|
||||
return sessions;
|
||||
} catch (error) {
|
||||
console.error("Error fetching sessions:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCoachOfSession(session:Session): Promise<Coach>{
|
||||
try {
|
||||
const response = await sessionService.getCoach(session.id);
|
||||
const coach:Coach = new Coach(response.data);
|
||||
return coach;
|
||||
} catch (error) {
|
||||
console.error("Error fetching coachs:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//COACH
|
||||
export async function getAllCoach(): Promise<Coach[]> {
|
||||
try {
|
||||
@@ -347,9 +395,15 @@ export async function getAllCoach(): Promise<Coach[]> {
|
||||
}
|
||||
|
||||
//ATHLETE
|
||||
export async function getAllAthlete(): Promise<Athlete[]> {
|
||||
export async function getAllAthlete(groupe?:String): Promise<Athlete[]> {
|
||||
try {
|
||||
const response = await athleteService.getAll();
|
||||
var response;
|
||||
if(groupe!=null && groupe!="Tous"){
|
||||
response = await athleteService.getAllByGroup(groupe);
|
||||
}
|
||||
else{
|
||||
response = await athleteService.getAll();
|
||||
}
|
||||
const athletes:Athlete[] = []
|
||||
response.data.forEach(dto => {
|
||||
athletes.push(new Athlete(dto));
|
||||
@@ -361,3 +415,29 @@ export async function getAllAthlete(): Promise<Athlete[]> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSessionsByAthleteId(athleteId: number): Promise<Session[]> {
|
||||
try {
|
||||
const response = await api.get(`/athlete/${athleteId}/session`);
|
||||
const sessions: Session[] = [];
|
||||
|
||||
const allAthletes = await getAllAthlete();
|
||||
|
||||
response.data.forEach((sessionDTO: SessionDTO) => {
|
||||
const session = new Session(sessionDTO);
|
||||
|
||||
if (sessionDTO.athleteIds && sessionDTO.athleteIds.length > 0) {
|
||||
session.athletes = allAthletes.filter(a =>
|
||||
sessionDTO.athleteIds.includes(a.id!)
|
||||
);
|
||||
}
|
||||
|
||||
sessions.push(session);
|
||||
});
|
||||
|
||||
console.log(`Sessions chargées pour l'athlète ${athleteId}:`, sessions);
|
||||
return sessions;
|
||||
} catch (error) {
|
||||
console.error("Error fetching sessions for athlete:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@ export function calculStatsAthlete(sessions: Session[], athlete: Athlete, debut:
|
||||
let nb_semaine = 1; //forcément une semaine
|
||||
const distributions: Map<string, number> = new Map();
|
||||
const timeDiff = Math.abs(fin.getTime() - debut.getTime());
|
||||
nb_semaine = Math.ceil(timeDiff / (1000 * 3600 * 24 * 7));
|
||||
const msPerWeek = 1000 * 3600 * 24 * 7;
|
||||
nb_semaine = Math.max(1, Math.ceil(timeDiff / msPerWeek));
|
||||
|
||||
sessions.forEach(session => {
|
||||
// verification session dans l'intervalle
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
background-color: rgba(255, 255, 255, 0.98);
|
||||
overflow: hidden;
|
||||
transition: transform 0.3s ease;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card-pf:hover {
|
||||
@@ -38,6 +40,7 @@
|
||||
0 8px 20px rgba(16, 185, 129, 0.15);
|
||||
}
|
||||
|
||||
|
||||
/* Header de la card */
|
||||
#kc-form-login .card-pf h1,
|
||||
.login-pf-page h1 {
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "hackathon",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
5
package.json
Normal file
5
package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"react-router-dom": "^7.12.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user