This commit is contained in:
Amaël Kesteman
2026-01-07 12:02:27 +01:00
23 changed files with 327 additions and 279 deletions

View File

@@ -3,4 +3,5 @@
background-color: var(--tint4);
color: var(--text);
min-height: 100vh;
padding: 16px;
}

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import './App.css';
import { ReactKeycloakProvider } from '@react-keycloak/web'
import keycloak from './keycloak'
@@ -15,7 +15,10 @@ const keycloakInitOptions = {
checkLoginIframe: false
}
function App() {
return (
<ReactKeycloakProvider authClient={keycloak} /*initOptions={keycloakInitOptions}*/>
<LocalDataProvider>

View File

@@ -3,13 +3,76 @@ import keycloak from "./keycloak";
const api = axios.create({
baseURL: "http://localhost:8081/api",
headers: {
"Content-Type": "application/json",
},
});
api.interceptors.request.use((config) => {
if (keycloak?.token) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${keycloak.token}`;
}
return config;
});
export const athleteService = {
create: (data: any) => api.post("/athletes/create", data),
getAll: () => api.get("/athletes/all"),
getById: (id: number | string) => api.get(`/athletes/${id}`),
update: (id: number | string, data: any) => api.put(`/athletes/${id}`, data),
delete: (id: number | string) => api.delete(`/athletes/${id}`),
// session-related endpoints exposed by AthleteResource
getSessionsForAthlete: (athleteId: number | string) => api.get(`/athletes/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)}`),
};
export const activiteService = {
create: (data: any) => api.post("/activite/create", data),
delete: (id: number | string) => api.delete(`/activite/delete/${id}`),
update: (id: number | string, data: any) => api.post(`/activite/update/${id}`, data),
getById: (id: number | string) => api.get(`/activite/${id}`),
getAll: () => api.get(`/activite/all`),
getByTheme: (theme: string) => api.get(`/activite/theme/${encodeURIComponent(theme)}`),
};
export const sessionService = {
// controller uses singular /session/* endpoints
create: (data: any) => api.post(`/session/create`, data),
getAll: () => api.get(`/session/all`),
getById: (id: number | string) => api.get(`/session/${id}`),
delete: (id: number | string) => api.delete(`/session/delete/${id}`),
update: (id: number | string, data: any) => api.put(`/session/update/${id}`, data),
// plural variants used around the frontend (keep for compatibility)
createPlural: (data: any) => api.post(`/sessions`, data),
getAllPlural: () => api.get(`/sessions`),
getActivities: (sessionId: number | string) => api.get(`/sessions/${sessionId}/activities`),
addActivity: (sessionId: number | string, activity: any) => api.post(`/sessions/${sessionId}/activities`, activity),
subscribe: (sessionId: number | string, userId: number | string) => api.post(`/sessions/${sessionId}/subscribe`, { userId }),
unsubscribe: (sessionId: number | string, userId: number | string) => api.post(`/sessions/${sessionId}/unsubscribe`, { userId }),
};
export const coachService = {
// controller doesn't declare a class-level path consistently; support both common patterns
create: (data: any) => api.post(`/coach/create`, data),
getAll: () => api.get(`/coach/all`),
getById: (id: number | string) => api.get(`/coach/${id}`),
update: (id: number | string, data: any) => api.put(`/coach/update/${id}`, data),
delete: (id: number | string) => api.delete(`/coach/delete/${id}`),
// plural convenience
createPlural: (data: any) => api.post(`/coaches`, data),
getAllPlural: () => api.get(`/coaches`),
};
export const userService = {
getById: (id: number | string) => api.get(`/users/${id}`),
getAll: () => api.get(`/users`),
};
export default api;

View File

@@ -24,7 +24,7 @@ export class Session{
id!: number;
name!: String;
activites: Activite[] = [];
isRecurent! : Boolean;
isRecurrent! : Boolean;
creneau!: Date;
coach!: Coach;
athletes!: Athlete[]

View File

@@ -37,7 +37,7 @@ export const CreateSession = () => {
newSession.groupe = groupe;
newSession.creneau = new Date(creneau);
newSession.duree= duree;
newSession.isRecurent= isRecurent;
newSession.isRecurrent= isRecurent;
newSession.coach= user as Coach;
newSession.athletes= [];
newSession.activites= activities;

View File

@@ -4,7 +4,8 @@ import { useLocalData } from "../context/useLocalData"
import './style/edt.css';
import {updateSessionsOfUserAPI } from "../requetes";
import EdtSession from "./edt_session";
import {delay} from "../requetes";
import Loading from "./loading";
export function dateToString(date:Date){
const dd_prefix = date.getDate()<10 ? "0" : "";
@@ -28,11 +29,14 @@ export const EDT =() =>{
const {user,setUser} = useLocalData()
const [sessions, setSessions] = useState<Session[]>([])
const [week,setWeek] = useState<Date>(getFirstDay(new Date()));
const [loadedWeek,setLoadedWeek] = useState<Date|null>(null);
const [loading,setLoading] = useState<boolean>(false);
const week_days:String[] = ["Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi","Dimanche"];
const week_days_nums:number[] = [1,2,3,4,5,6,0];
function loadSessions(date:Date){
var maxDate = getNextDay(date,6)
@@ -47,18 +51,39 @@ export const EDT =() =>{
function changeWeek(date:Date){
setWeek(date);
loadSessions(date)
}
function isSameDay(date1:Date,date2:Date){
return (
date1.getDay()===date2.getDay() &&
date1.getMonth()===date2.getMonth() &&
date1.getFullYear()===date2.getFullYear());
}
useEffect(() => {
updateWeek();
},[week])
setLoadedWeek(null);
updateWeek(week);
loadSessions(week)
setLoading(true);
},[week,user])
useEffect(() => {
if(loadedWeek!==null){
if(isSameDay(week,loadedWeek)){
loadSessions(week)
setLoading(false);
}
else{
setLoadedWeek(null);
}
}
},[loadedWeek])
async function updateWeek(){
async function updateWeek(week:Date){
//TODO updateSession
await delay(2000);
//await updateSessionsOfUser(user,null,null);
loadSessions(week);
setUser(getUserTest())
setLoadedWeek(week);
}
@@ -90,20 +115,20 @@ export const EDT =() =>{
return(
<div className="edt">
<div className="edt">
<div className="edt_header">
<button className="edt_button_week_select" onClick={() =>handlePrev()}>Prev</button>
<button className="edt_button_week_select" onClick={() => handleNext()}>Next</button>
</div>
<div className="edt_colonnes">
<div className="edt_loading">{loading && <Loading/>}</div>
{week_days_nums.map((num,index)=>(
<div className="edt_colonne">
<div className="edt_day_header">
<div> {week_days[index]} </div>
<div className="edt_date"> {dateToString(getNextDay(week,index))} </div>
</div>
<div className="edt_day_contedt">
<div className="edt_day_content">
{sessions.map((session,index2)=>(
session.creneau.getDay()===num &&
<EdtSession session={session}/>

View File

@@ -4,6 +4,7 @@ import { dateToString, hoursToString } from './edt';
import './style/edt.css';
import { Modal } from './Modal';
import Loading from './loading';
import {delay} from "../requetes";
type Props = {
@@ -21,6 +22,7 @@ function EdtSession({session}:Props){
async function updateActivites(){
//TODO
await delay(2000);
//await updateActivitiesOfSessionAPI(session);
setLoading(false);
}
@@ -37,19 +39,24 @@ function EdtSession({session}:Props){
return(
<div>
<div className="edt_session" onClick={() => handleOpen()}>
<div className="edt_date">{hoursToString(sDate)}</div>
<div className="edt_session_header">
<div className="edt_date">{hoursToString(sDate)}</div>
{!session.isRecurrent && <div className="edt_date"> recurrent</div>}
</div>
<div>{session.name}</div>
</div>
{open &&
<Modal isOpen={open} onClose={() => setOpen(false)}>
<div>{session.name}</div>
<div>{hoursToString(sDate)}</div>
<div>{dateToString(sDate)}</div>
{session.activites.map((activite,index)=>(
<div>activite</div>
))}
{loading && <Loading/>}
<div className="edt_session_modal">
<div>{session.name}</div>
<div>{hoursToString(sDate)}</div>
<div>{dateToString(sDate)}</div>
{session.activites.map((activite,index)=>(
<div>activite</div>
))}
{loading && <div className='edt_loading'><Loading/></div>}
</div>
</Modal>
}
</div>

View File

@@ -1,19 +1,40 @@
import { useKeycloak } from '@react-keycloak/web'
import { useEffect } from 'react';
import { getUserTest, User } from '../classes';
import { useLocalData } from '../context/useLocalData';
export const Login =() =>{
const {user,setUser} = useLocalData()
useEffect(() => { //TODO à supprimer
setUser(getUserTest())
},[]);
function handleLogin(): void {
keycloak.login()
//TODO setUser
}
function handleLogout(): void {
keycloak.logout()
setUser(new User());
}
const { keycloak } = useKeycloak()
return(
<div>
<div>
Authenticated : {keycloak.authenticated ? '' : ''}
Authenticated : {keycloak.authenticated ? 'oui' : 'non'}
</div>
<button onClick={() => keycloak.login()}>
<button onClick={() => handleLogin()}>
Se connecter
</button>
<button onClick={() => keycloak.logout()}>
<button onClick={() => handleLogout()}>
Se déconnecter
</button>
</div>

View File

@@ -1,56 +1,74 @@
.edt{
justify-contedt: cedter;
justify-content: center;
background-color: var(--tint1);
border-radius: 30px;
padding: 10px;
}
.edt_header{
justify-contedt: cedter;
justify-content: center;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-columns: repeat(2, 0.5fr);
/* background-color: #0000FF; */
padding-bottom: 10px;
gap: 30%;
}
.edt_colonnes {
position: relative;
display: grid;
align-items: flex-start;
grid-template-columns: repeat(7, 1fr);
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 16px;
color: var(--text);
/* background-color: #00FF00; */
width: 100%;
}
.edt_loading{
position: absolute;
inset: 0;
display: grid;
/* place-items: center; */
pointer-events: none;
}
.edt_colonne {
display: grid;
background-color: var(--tint3);
border-radius: 20px;
container-type: inline-size;
}
.edt_day_header{
font-size: clamp(5px, 1vw, 18px);
padding: 8px;
/* background-color: var(--tint2); */
border-radius: 20px;
height: 30px;
text-align: cedter;
text-align: center;
font-size: 1em;
}
.edt_day_contedt{
display: grid;
.edt_day_content{
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px;
border-radius: 20px;
min-width: 0;
/* background-color: #FF0000; */
}
.edt_session {
font-size: clamp(1px, 8cqi, 18px);
gap: 8px;
background-color: var(--tint4);
border-radius: 12px;
padding: 8px;
padding: 5%;
min-width: 0;
}
.edt_session:hover {
@@ -61,6 +79,11 @@
background-color: var(--tint5);
}
.edt_session_header{
display: flex;
gap: 5px;
}
.edt_date{
font-size: 0.75em;
}
@@ -72,3 +95,11 @@
border-radius: 20px;
}
.edt_session_modal{
background-color: var(--tint2);
padding: 20px;
border-radius: 20px;
position: relative;
}

View File

@@ -49,16 +49,14 @@ code {
}
.modal{
background-color: var(--tint2);
padding: 10px;
border-radius: 20px;
min-width: 200px;
min-height: 100px;
}
.loading{
width: 24px;
height: 24px;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}

View File

@@ -2,14 +2,21 @@ import api from "./api";
import { Activite, Athlete, Coach, Session, User } from "./classes";
import { useKeycloak } from '@react-keycloak/web'
const { keycloak } = useKeycloak()
/*const { keycloak } = useKeycloak()
const useAuthHeader = () => {
return keycloak?.token
? { Authorization: `Bearer ${keycloak.token}` }
: {}
}*/
//debug:
export function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
//UPDATE /////////////////////////////////////////////////////////
//COACH / ATHLETE