This commit is contained in:
trochas
2026-01-08 15:46:39 +01:00
14 changed files with 211 additions and 90 deletions

View File

@@ -11,21 +11,13 @@ import EdtCoach from './components/edt_coach'
import { Coach } from "./classes";
import RessourcePanel from './components/ressourcePanel';
import TestAPI from './components/test_api';
import EdtAthlete from './components/edt_athlete';
// Test
const testCoach = new Coach();
testCoach.id = 1;
testCoach.nom = "Coach Test";
const keycloakInitOptions = {
onLoad: 'login-required',
checkLoginIframe: false
}
function App() {
return (
<ReactKeycloakProvider authClient={keycloak} /*initOptions={keycloakInitOptions}*/>
<LocalDataProvider>
@@ -36,7 +28,6 @@ function App() {
<RessourcePanel/>
<EDT/>
<CreateSession/>
<EdtAthlete/>
<TestAPI/>
</div>
</LocalDataProvider>

View File

@@ -1,14 +1,18 @@
import axios from "axios";
import keycloak from "./keycloak";
const api = axios.create({
baseURL: "http://localhost:8081/api",
// backend listens on 8081 and controllers are mounted at root (no /api prefix)
baseURL: "http://localhost:8081",
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
// Simple interceptor to ensure headers object exists; actual token should be set via setAuthToken()
api.interceptors.request.use((config) => {
if (keycloak?.token) {
// eslint-disable-next-line no-param-reassign
@@ -18,10 +22,23 @@ api.interceptors.request.use((config) => {
return config;
});
// Helpers to set/clear the Authorization header programmatically (call after Keycloak login)
export function setAuthToken(token: string | null | undefined) {
if (token) {
api.defaults.headers.common["Authorization"] = `Bearer ${token}`;
} else {
delete api.defaults.headers.common["Authorization"];
}
}
export function clearAuthToken() {
delete api.defaults.headers.common["Authorization"];
}
export const athleteService = {
create: (data: any) => api.post("/athletes/create", data),
getAll: () => api.get("/athletes/all"),
getById: (id: number | string) => api.get(`/athletes/${id}`),
getByKeycloakId: (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}`),
@@ -63,7 +80,7 @@ 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}`),
getByKeycloakId: (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}`),
@@ -73,7 +90,7 @@ export const coachService = {
};
export const userService = {
getById: (id: number | string) => api.get(`/users/${id}`),
getByKeycloakId: (id: number | string) => api.get(`/users/${id}`),
getAll: () => api.get(`/users`),
};

View File

@@ -0,0 +1,94 @@
import React from "react";
import { Athlete, Session } from "../classes";
import { calculStatsAthlete, niveauAlerte, StatsAthlete } from "../utils/athleteUtils"
interface AthleteStatsProps {
athlete: Athlete;
sessions: Session[];
}
function StatAthlete({ athlete, sessions }: 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);
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())}`;
};
const handleCalculerStats = () => {
const statsCalculees = calculStatsAthlete(sessions, athlete, dateDebut, dateFin);
setStats(statsCalculees);
};
return (
<div className="creneau-stats">
<label>
Début :
<input
type="datetime-local"
value={dateToDatetimeLocal(dateDebut)}
onChange={e => setDateDebut(new Date(e.target.value))}
/>
</label>
<label>
Fin :
<input
type="datetime-local"
value={dateToDatetimeLocal(dateFin)}
onChange={e => setDateFin(new Date(e.target.value))}
/>
</label>
<label>
Seuil critique :
<input
type="number"
value={seuilCritique}
min={1}
onChange={e => setSeuilCritique(Number(e.target.value))}
/>
</label>
<label>
Seuil max :
<input
type="number"
value={seuilMax}
min={1}
onChange={e => setSeuilMax(Number(e.target.value))}
/>
</label>
<button onClick={handleCalculerStats}>Calculer les statistiques</button>
{stats && (
<div className="stats-display">
<h3>Statistiques de {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>
{stats.distributions.size > 0 && (
<>
<h4>Distribution des activités :</h4>
<ul>
{Array.from(stats.distributions.entries()).map(([nomActivite, count]) => (
<li key={String(nomActivite)}>
{nomActivite} : {count} session(s)
</li>
))}
</ul>
</>
)}
</div>
)}
</div>
);
}
export default StatAthlete;

View File

@@ -1,65 +0,0 @@
import React, { useState } from 'react';
export const EdtAthlete = () => {
const [formData, setFormData] = useState({
name: '',
prenom: '',
id_keycloak: '',
categorie: '',
niveau: ''
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const response = await fetch("http://localhost:8081/api/athlete/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
if (response.ok) {
alert("Athlete created successfully in PostgreSQL!");
setFormData({ name: '', prenom: '', id_keycloak: '', categorie: '', niveau: '' });
} else {
alert("Failed to create athlete. Status: " + response.status);
}
} catch (error) {
console.error("Error creating athlete:", error);
alert("Error: Check console");
}
};
return (
<div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
<h3>Test Create Athlete (PostgreSQL)</h3>
<form onSubmit={handleSubmit}>
<div>
<label>Nom: </label>
<input type="text" value={formData.name} onChange={(e) => setFormData({...formData, name: e.target.value})} />
</div>
<div>
<label>Prénom: </label>
<input type="text" value={formData.prenom} onChange={(e) => setFormData({...formData, prenom: e.target.value})} />
</div>
<div>
<label>Keycloak ID: </label>
<input type="text" value={formData.id_keycloak} onChange={(e) => setFormData({...formData, id_keycloak: e.target.value})} />
</div>
<div>
<label>Catégorie: </label>
<input type="text" value={formData.categorie} onChange={(e) => setFormData({...formData, categorie: e.target.value})} />
</div>
<div>
<label>Niveau: </label>
<input type="text" value={formData.niveau} onChange={(e) => setFormData({...formData, niveau: e.target.value})} />
</div>
<button type="submit" style={{ marginTop: '10px' }}>Créer l'athlète</button>
</form>
</div>
);
};
export default EdtAthlete;

View File

@@ -3,6 +3,7 @@ import { useEffect } from 'react';
import { Athlete, User } from '../classes';
import { useLocalData } from '../context/useLocalData';
import { postAthlete } from '../requetes';
import { clearAuthToken, setAuthToken } from '../api';
export const Login =() =>{
const {user,setUser} = useLocalData()
@@ -13,7 +14,8 @@ export const Login =() =>{
const newAthlete:Athlete = new Athlete()
const athlete:Athlete = await postAthlete(newAthlete)
setAuthToken(keycloak.token);
setUser(athlete);
/*postAthlete
if (keycloak.authenticated && keycloak.token) {
@@ -56,6 +58,7 @@ export const Login =() =>{
function handleLogout(): void {
keycloak.logout()
setUser(new User());
clearAuthToken();
}
return(
<div>

View File

@@ -8,7 +8,7 @@ import {delay} from "../../requetes";
import CreateActivite from '../createActivite';
import { useLocalData } from '../../context/useLocalData';
import ObjectSession from './session';
import StatAthlete from '../StatsAthlete';
type Props = {
admin?:Admin|null;
@@ -115,10 +115,15 @@ function ObjectUser({admin=null,athlete=null,coach=null}:Props){
{/* TODO */}
</div>
{athlete !== null && (
<div className="stats-container">
<StatAthlete athlete={athlete} sessions={sessions} />
</div>
)}
</div>
</Modal>
}
</div>
}
</div>
)
}