Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
@@ -3,4 +3,5 @@
|
||||
background-color: var(--tint4);
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -7,6 +7,7 @@ import { LocalDataProvider } from './provider/LocalDataProvider';
|
||||
import EDT from './components/edt';
|
||||
import SwitchThemeColor from './components/SwitchThemeColor';
|
||||
import CreateSession from './components/createSession'
|
||||
import RessourcePanel from './components/ressourcePanel';
|
||||
|
||||
|
||||
const keycloakInitOptions = {
|
||||
@@ -14,7 +15,10 @@ const keycloakInitOptions = {
|
||||
checkLoginIframe: false
|
||||
}
|
||||
|
||||
|
||||
function App() {
|
||||
|
||||
|
||||
return (
|
||||
<ReactKeycloakProvider authClient={keycloak} /*initOptions={keycloakInitOptions}*/>
|
||||
<LocalDataProvider>
|
||||
@@ -22,6 +26,7 @@ function App() {
|
||||
<SwitchThemeColor/>
|
||||
<h1>Frisbyee</h1>
|
||||
<Login/>
|
||||
<RessourcePanel/>
|
||||
<EDT/>
|
||||
<CreateSession/>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ export class Admin extends User{
|
||||
}
|
||||
|
||||
export class Athlete extends User{
|
||||
nom!: String;
|
||||
groupe!: Groupe;
|
||||
|
||||
}
|
||||
@@ -23,7 +24,7 @@ export class Session{
|
||||
id!: number;
|
||||
name!: String;
|
||||
activites: Activite[] = [];
|
||||
isRecurent! : Boolean;
|
||||
isRecurrent! : Boolean;
|
||||
creneau!: Date;
|
||||
coach!: Coach;
|
||||
athletes!: Athlete[]
|
||||
@@ -60,10 +61,83 @@ export function getUserTest():User{
|
||||
s3.id = 3;
|
||||
s3.name = "entraintement3"
|
||||
|
||||
const a1:Activite = new Activite();
|
||||
const a2:Activite = new Activite();
|
||||
s1.activites.push(a1);
|
||||
s1.activites.push(a2);
|
||||
const athlete1 = new Athlete();
|
||||
athlete1.id = 1;
|
||||
athlete1.nom = "Alice Dupont";
|
||||
athlete1.groupe = "Entrainement";
|
||||
|
||||
const athlete2 = new Athlete();
|
||||
athlete2.id = 2;
|
||||
athlete2.nom = "Bob Martin";
|
||||
athlete2.groupe = "Competition";
|
||||
|
||||
const athlete3 = new Athlete();
|
||||
athlete3.id = 3;
|
||||
athlete3.nom = "Clara Lopez";
|
||||
athlete3.groupe = "Loisir";
|
||||
|
||||
s1.athletes = [athlete1, athlete2];
|
||||
s2.athletes = [athlete2, athlete3];
|
||||
s3.athletes = [athlete1, athlete3];
|
||||
|
||||
|
||||
const act1 = new Activite();
|
||||
act1.id = 1;
|
||||
act1.nom = "Échauffement";
|
||||
act1.theme = "Cardio";
|
||||
act1.duree = 15;
|
||||
act1.session = s1;
|
||||
act1.data = new Map([["objectif", "Préparer le corps"], ["matériel", "Ballon"]]);
|
||||
|
||||
const act2 = new Activite();
|
||||
act2.id = 2;
|
||||
act2.nom = "Dribbles et passes";
|
||||
act2.theme = "Technique";
|
||||
act2.duree = 30;
|
||||
act2.session = s1;
|
||||
act2.data = new Map([["objectif", "Améliorer les passes"], ["niveau", "Intermédiaire"]]);
|
||||
|
||||
const act3 = new Activite();
|
||||
act3.id = 3;
|
||||
act3.nom = "Renforcement musculaire";
|
||||
act3.theme = "Force";
|
||||
act3.duree = 25;
|
||||
act3.session = s2;
|
||||
act3.data = new Map([["objectif", "Renforcer les jambes"], ["matériel", "Haltères"]]);
|
||||
|
||||
const act4 = new Activite();
|
||||
act4.id = 4;
|
||||
act4.nom = "Sprint et agilité";
|
||||
act4.theme = "Vitesse";
|
||||
act4.duree = 20;
|
||||
act4.session = s2;
|
||||
act4.data = new Map([["objectif", "Améliorer les sprints"], ["matériel", "Plots"]]);
|
||||
|
||||
const act5 = new Activite();
|
||||
act5.id = 5;
|
||||
act5.nom = "Match 5v5";
|
||||
act5.theme = "Jeu";
|
||||
act5.duree = 60;
|
||||
act5.session = s3;
|
||||
act5.data = new Map([["objectif", "Appliquer les techniques"], ["niveau", "Avancé"]]);
|
||||
|
||||
const act6 = new Activite();
|
||||
act6.id = 6;
|
||||
act6.nom = "Étirements";
|
||||
act6.theme = "Récupération";
|
||||
act6.duree = 10;
|
||||
act6.session = s3;
|
||||
act6.data = new Map([["objectif", "Éviter les blessures"], ["matériel", "Tapis"]]);
|
||||
|
||||
|
||||
// attach the concrete activities to their sessions
|
||||
s1.activites.push(act1);
|
||||
s1.activites.push(act2);
|
||||
s2.activites.push(act3);
|
||||
s2.activites.push(act4);
|
||||
s3.activites.push(act5);
|
||||
s3.activites.push(act6);
|
||||
|
||||
user.sessions.push(s1);
|
||||
user.sessions.push(s2);
|
||||
user.sessions.push(s3);
|
||||
|
||||
@@ -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;
|
||||
|
||||
27
front_end/src/components/dropdownButton.tsx
Normal file
27
front_end/src/components/dropdownButton.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import Dropdown from 'react-bootstrap/Dropdown';
|
||||
import { Athlete } from '../classes';
|
||||
|
||||
type Props = {
|
||||
onAthletesClick: () => void;
|
||||
onActivitiesClick: () => void;
|
||||
}
|
||||
|
||||
function ListButton({ onAthletesClick, onActivitiesClick }: Props) {
|
||||
return (
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle>
|
||||
Sélectionner la ressource
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item as="button" onClick={onAthletesClick}>
|
||||
Athlètes
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item as="button" onClick={onActivitiesClick}>
|
||||
Activités
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default ListButton;
|
||||
@@ -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}/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 ? '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>
|
||||
|
||||
47
front_end/src/components/ressourceList.tsx
Normal file
47
front_end/src/components/ressourceList.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import ListGroup from "react-bootstrap/ListGroup";
|
||||
import { Athlete, Activite } from "../classes";
|
||||
|
||||
type Props = {
|
||||
athletes: Athlete[];
|
||||
activites: Activite[];
|
||||
|
||||
};
|
||||
|
||||
function AthleteList({ athletes }: Props) {
|
||||
return (
|
||||
<ListGroup>
|
||||
{athletes.map((athlete) => (
|
||||
<ListGroup.Item key={athlete.id}>
|
||||
<div>
|
||||
<strong>Nom:</strong> {athlete.nom}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Groupe:</strong> {athlete.groupe}
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function ActiviteList({ activites }: Props) {
|
||||
return (
|
||||
<ListGroup>
|
||||
{activites.map((activite) => (
|
||||
<ListGroup.Item key={activite.id}>
|
||||
<div>
|
||||
<strong>Nom:</strong> {activite.nom}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Thème:</strong> {activite.theme}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Durée:</strong> {activite.duree} minutes
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export { AthleteList, ActiviteList };
|
||||
52
front_end/src/components/ressourcePanel.tsx
Normal file
52
front_end/src/components/ressourcePanel.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useState } from "react";
|
||||
import { useLocalData } from "../context/useLocalData";
|
||||
import ListButton from "./dropdownButton";
|
||||
import { AthleteList, ActiviteList } from "./ressourceList";
|
||||
import { Activite, Athlete } from "../classes";
|
||||
|
||||
export default function RessourcePanel() {
|
||||
const { user } = useLocalData();
|
||||
const [showAthletes, setShowAthletes] = useState(false);
|
||||
const [showActivites, setShowActivites] = useState(false);
|
||||
|
||||
|
||||
const athleteMap: Map<number, Athlete> = new Map();
|
||||
user.sessions.forEach(session => {
|
||||
session.athletes?.forEach(a => athleteMap.set(a.id, a));
|
||||
});
|
||||
const allAthletes: Athlete[] = Array.from(athleteMap.values());
|
||||
|
||||
const activiteMap: Map<number, Activite> = new Map();
|
||||
user.sessions.forEach(session => {
|
||||
session.activites?.forEach(act => activiteMap.set(act.id, act));
|
||||
});
|
||||
const allActivites: Activite[] = Array.from(activiteMap.values());
|
||||
|
||||
return (
|
||||
<div className="ressource_panel">
|
||||
<ListButton
|
||||
onAthletesClick={() => {
|
||||
setShowAthletes(prev => !prev);
|
||||
setShowActivites(false);
|
||||
}}
|
||||
onActivitiesClick={() => {
|
||||
setShowActivites(prev => !prev);
|
||||
setShowAthletes(false);
|
||||
}}
|
||||
/>
|
||||
{showAthletes && (
|
||||
<div className="edt_athletes_panel">
|
||||
<h3>Liste des athlètes</h3>
|
||||
<AthleteList athletes={allAthletes} activites={[]}/>
|
||||
</div>
|
||||
)}
|
||||
{showActivites && (
|
||||
<div className="edt_activites_panel">
|
||||
<h3>Liste des activités</h3>
|
||||
<ActiviteList athletes={[]} activites={allActivites} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ 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 /////////////////////////////////////////////////////////
|
||||
|
||||
Reference in New Issue
Block a user