From fa70da8f2580651f8fbea9258dd4591b786f60a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ama=C3=ABl=20Kesteman?= Date: Thu, 8 Jan 2026 11:22:04 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20Ajout=20stats=20athl=C3=A8tes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front_end/src/classes.tsx | 11 ++- front_end/src/components/ressourceList.tsx | 79 ++++++++++++++++++--- front_end/src/components/ressourcePanel.tsx | 2 +- front_end/src/utils/athleteUtils.tsx | 46 ++++++++++++ 4 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 front_end/src/utils/athleteUtils.tsx diff --git a/front_end/src/classes.tsx b/front_end/src/classes.tsx index a7aa095..977f100 100644 --- a/front_end/src/classes.tsx +++ b/front_end/src/classes.tsx @@ -107,6 +107,7 @@ export function getUserTest():User{ s1.isRecurrent = true; s1.ligne = [ligne1]; s1.duree= 90; + s1.athletes = [athlete1,athlete2]; var date2 = new Date(); date2.setDate(date2.getDate() + 2); s2.creneau = date2; @@ -115,19 +116,15 @@ export function getUserTest():User{ s2.name = "entraintement 2" s2.ligne = [ligne2]; s2.duree= 120; + s2.athletes = [athlete1,athlete2, athlete3]; + s3.creneau = date2; s3.id = 3; s3.isRecurrent = false; s3.name = "entraintement 3" s3.ligne = [ligne3, ligne1]; s3.duree= 120; - - - - s1.athletes = [athlete1, athlete2]; - s2.athletes = [athlete2, athlete3]; - s3.athletes = [athlete1, athlete3]; - + s3.athletes = [athlete2, athlete3]; const act1 = new Activite(); act1.id = 1; diff --git a/front_end/src/components/ressourceList.tsx b/front_end/src/components/ressourceList.tsx index 0eca026..3788295 100644 --- a/front_end/src/components/ressourceList.tsx +++ b/front_end/src/components/ressourceList.tsx @@ -1,23 +1,82 @@ +import React from "react"; import { Athlete, Activite, Coach, Session, Ligne } from "../classes"; +import {calculStatsAthlete, niveauAlerte} from "../utils/athleteUtils"; import {calculTempsDeJeuParLigne} from "../utils/ligneUtils"; -type AthleteListProps = { athletes: Athlete[] }; +type AthleteListProps = { athletes: Athlete[], sessions: Session[]}; type ActiviteListProps = { activites: Activite[] }; type CoachListProps = { coachs: Coach[] }; type SessionListProps = { sessions: Session[]}; type LigneListProps = { lignes: Ligne[], tempsDeJeuParLigne: Map }; -function AthleteList({ athletes }: AthleteListProps) { +function AthleteList({ athletes, sessions }: AthleteListProps) { + 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 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())}`; + }; + return ( - + <> +
+ + + + + +
+ + + ); } diff --git a/front_end/src/components/ressourcePanel.tsx b/front_end/src/components/ressourcePanel.tsx index 63b1ab7..62515dc 100644 --- a/front_end/src/components/ressourcePanel.tsx +++ b/front_end/src/components/ressourcePanel.tsx @@ -89,7 +89,7 @@ import { unescapeLeadingUnderscores } from "typescript"; {value==="athletes" && (

Liste des athlètes

- +
)} diff --git a/front_end/src/utils/athleteUtils.tsx b/front_end/src/utils/athleteUtils.tsx new file mode 100644 index 0000000..1a870cb --- /dev/null +++ b/front_end/src/utils/athleteUtils.tsx @@ -0,0 +1,46 @@ +import { Athlete, Session } from '../classes'; + +export interface StatsAthlete { + nbSessions: number; + nbSessionsPerWeek: number; + isAlerte: boolean; + distributions: Map; //le nom de l'activité et son nombre +} + +export function niveauAlerte(stats: StatsAthlete, seuilCritique = 0, seuilMax = 0) { + if (stats.nbSessionsPerWeek > seuilMax) return "Alerte ! Niveau maximal atteint."; + if (stats.nbSessionsPerWeek > seuilCritique) return "Attention! Niveau critique atteint."; + return "Normal"; +} + +export function calculStatsAthlete(sessions: Session[], athlete: Athlete, debut: Date, fin: Date): StatsAthlete { + let nb_sessions = 0; + let nb_semaine = 1; //forcément une semaine + const distributions: Map = new Map(); + const timeDiff = Math.abs(fin.getTime() - debut.getTime()); + nb_semaine = Math.ceil(timeDiff / (1000 * 3600 * 24 * 7)); + + sessions.forEach(session => { + // verification session dans l'intervalle + if (session.creneau < debut || session.creneau > fin) return; + + // verification athlete dans session + if (!session.athletes.some(a => a.id === athlete.id)) return; + + //incrementation (verifie si recurent ou non) + const increment = session.isRecurrent ? nb_semaine : 1; + nb_sessions += increment; + + } + ); + + const nbSessionsPerWeek = nb_sessions / nb_semaine; + const isAlerte = nbSessionsPerWeek > 8; + + return { + nbSessions: nb_sessions, + nbSessionsPerWeek: nbSessionsPerWeek, + isAlerte: isAlerte, + distributions: distributions + }; +} \ No newline at end of file From 9a2d1ae5e66d6d0bfaff6479be38d41cd697f7c9 Mon Sep 17 00:00:00 2001 From: Alexis Leboeuf Date: Thu, 8 Jan 2026 11:26:16 +0100 Subject: [PATCH 2/2] Lot of things Refactored Role enum to be the same as Keycloak roles Managed CORS errors in backend Edited Keycloak config to avoid CORS error Edited frontend API to avoid CORS errors Changed Activite creation management Added debug print in Login (should be removed); --- back_end/package-lock.json | 6 +++++ .../FrisbYEE/config/WebSecurityConfig.java | 22 ++++++++++++++++++- .../hackathon/FrisbYEE/jpa/metier/Admin.java | 2 +- .../FrisbYEE/jpa/metier/Athlete.java | 2 +- .../hackathon/FrisbYEE/jpa/metier/Coach.java | 2 +- .../hackathon/FrisbYEE/jpa/metier/Role.java | 6 ++--- .../FrisbYEE/jpa/metier/Session.java | 4 ++-- .../FrisbYEE/rest/ActiviteResource.java | 2 ++ front_end/public/keycloak.json | 1 + front_end/src/api.ts | 1 + front_end/src/components/createSession.tsx | 4 ++-- front_end/src/components/login.tsx | 5 ++++- 12 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 back_end/package-lock.json diff --git a/back_end/package-lock.json b/back_end/package-lock.json new file mode 100644 index 0000000..fc3a8b4 --- /dev/null +++ b/back_end/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "back_end", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/back_end/src/main/java/hackathon/FrisbYEE/config/WebSecurityConfig.java b/back_end/src/main/java/hackathon/FrisbYEE/config/WebSecurityConfig.java index 2d807d7..c96ab6c 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/config/WebSecurityConfig.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/config/WebSecurityConfig.java @@ -6,11 +6,15 @@ import java.util.Map; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @EnableWebSecurity @@ -19,9 +23,10 @@ public class WebSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/public", "/coach/**").permitAll() // allow coach endpoints + .requestMatchers(HttpMethod.OPTIONS, "/", "/public", "/coach/**").permitAll() // allow coach endpoints .requestMatchers("/admin/**").hasRole("admin") .requestMatchers("/user/**").hasRole("user") .anyRequest().authenticated()) @@ -29,6 +34,7 @@ public class WebSecurityConfig { .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtToken -> { Map> realmAccess = jwtToken.getClaim("realm_access"); Collection roles = realmAccess.get("roles"); + System.out.println("ROLES FROM TOKEN " + roles); List authorities = roles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) .toList(); @@ -37,4 +43,18 @@ public class WebSecurityConfig { return http.build(); } + + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(List.of("http://localhost:3000")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowCredentials(true); + config.setAllowedHeaders(List.of("Authorization", "Content-Type")); + UrlBasedCorsConfigurationSource source = + new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } } diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Admin.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Admin.java index 6928f7c..0dc3a34 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Admin.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Admin.java @@ -14,7 +14,7 @@ import jakarta.persistence.Entity; public class Admin extends User{ public Admin(String id_keycloak, String name, String prenom){ - super(name, id_keycloak, prenom, Role.ADMIN ); + super(name, id_keycloak, prenom, Role.admin ); } @Override diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Athlete.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Athlete.java index a4a4865..1992780 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Athlete.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Athlete.java @@ -28,7 +28,7 @@ public class Athlete extends User{ private List sessions = new ArrayList<>(); // plusieurs sessions sont possibles public Athlete(String name, String id_keycloak, String prenom){ - super(name, id_keycloak, prenom, Role.ATHLETE); + super(name, id_keycloak, prenom, Role.athlete); } @Override diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Coach.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Coach.java index 77ad6ec..50b9c39 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Coach.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Coach.java @@ -20,7 +20,7 @@ public class Coach extends User{ private List sessions = new ArrayList<>(); // Un coach peut avoir plusieurs sessions public Coach(String name, String id_keycloak, String prenom){ - super(name, id_keycloak, prenom, Role.COACH ); + super(name, id_keycloak, prenom, Role.coach ); } @Override diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Role.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Role.java index dbdc97e..c80919c 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Role.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Role.java @@ -1,7 +1,7 @@ package hackathon.FrisbYEE.jpa.metier; public enum Role { - ADMIN, - COACH, - ATHLETE + admin, + coach, + athlete } \ No newline at end of file diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Session.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Session.java index 7711c2a..11273a4 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Session.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Session.java @@ -58,7 +58,7 @@ public class Session { } public void setCoach(Coach coach) { - if (coach.getRole() != Role.COACH) { + if (coach.getRole() != Role.coach) { throw new IllegalArgumentException("L'utilisateur n'est pas un coach"); } this.coach = coach; @@ -66,7 +66,7 @@ public class Session { public void setAthletes(List athletes) { for (Athlete athlete : athletes) { - if (athlete.getRole() != Role.ATHLETE) { + if (athlete.getRole() != Role.athlete) { throw new IllegalArgumentException("L'utilisateur n'est pas un athlète"); } } diff --git a/back_end/src/main/java/hackathon/FrisbYEE/rest/ActiviteResource.java b/back_end/src/main/java/hackathon/FrisbYEE/rest/ActiviteResource.java index b758823..27bf285 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/rest/ActiviteResource.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/rest/ActiviteResource.java @@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; + @CrossOrigin(origins = "http://localhost:3000") @Controller @RequestMapping("/activite") @@ -49,6 +50,7 @@ public class ActiviteResource { public ResponseEntity create(@RequestBody ActiviteDTO dto) { try { + System.out.println("ROLE TEST " + hackathon.FrisbYEE.jpa.metier.Role.coach); Session session = sessionDAO.findById(dto.getSessionId()).get(); Activite activite = mapToEntity(dto); activite.setSession(session); diff --git a/front_end/public/keycloak.json b/front_end/public/keycloak.json index 119260d..f236670 100644 --- a/front_end/public/keycloak.json +++ b/front_end/public/keycloak.json @@ -1,6 +1,7 @@ { "realm": "Frisbyee_realm", "resource": "Frisbyee_client", + "clientId": "Frisbyee_client", "auth-server-url": "http://localhost:8080", "public-client": true } \ No newline at end of file diff --git a/front_end/src/api.ts b/front_end/src/api.ts index aea29ba..9c8ae21 100644 --- a/front_end/src/api.ts +++ b/front_end/src/api.ts @@ -6,6 +6,7 @@ const api = axios.create({ headers: { "Content-Type": "application/json", }, + withCredentials: true, }); api.interceptors.request.use((config) => { diff --git a/front_end/src/components/createSession.tsx b/front_end/src/components/createSession.tsx index d7a10b9..6f385ba 100644 --- a/front_end/src/components/createSession.tsx +++ b/front_end/src/components/createSession.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { Session, User, Coach, Activite, Groupe } from "../classes"; import { useLocalData } from "../context/useLocalData"; -import { sessionService } from "../api"; +import { activiteService, sessionService } from "../api"; export const CreateSession = () => { const {user} = useLocalData() @@ -24,7 +24,7 @@ export const CreateSession = () => { newActivite.duree= activiteDuree; newActivite.data= new Map(); try{ - await sessionService.create(newActivite); + await activiteService.create(newActivite); console.log("Session créée"); setActivities([...activities, newActivite]); diff --git a/front_end/src/components/login.tsx b/front_end/src/components/login.tsx index 97b4465..25b3179 100644 --- a/front_end/src/components/login.tsx +++ b/front_end/src/components/login.tsx @@ -16,7 +16,7 @@ export const Login =() =>{ function handleLogin(): void { - keycloak.login() + keycloak.login(); //TODO setUser } @@ -39,6 +39,9 @@ export const Login =() =>{
User nom : { user.nom}
+
+ User role : { user.role} +
}