Compare commits
215 Commits
endpoints
...
test_thème
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bf66c8c08 | ||
|
|
40917f52d4 | ||
|
|
ecbddd3a58 | ||
|
|
b9e67589ed | ||
|
|
62b9231d38 | ||
|
|
3a74f6b52d | ||
|
|
e85f76c810 | ||
|
|
9aeef08e65 | ||
|
|
c720bc93ff | ||
|
|
0cdce29e40 | ||
|
|
1feda556ff | ||
|
|
8d844d2b2f | ||
|
|
5af249e0ca | ||
|
|
fca7362bb7 | ||
|
|
864ea784b1 | ||
|
|
f8866efc13 | ||
|
|
9340145200 | ||
|
|
8ea47c5ca1 | ||
|
|
c234dd3f48 | ||
|
|
29d483a2a6 | ||
|
|
8a078410b1 | ||
|
|
aa877339fa | ||
|
|
8e4d1cce57 | ||
|
|
536f2a7162 | ||
|
|
8638e962c9 | ||
|
|
a4963aed6a | ||
|
|
f79433d0fb | ||
|
|
f477d94e55 | ||
|
|
7067153072 | ||
|
|
aa21d046cf | ||
|
|
7a2b72e0b1 | ||
|
|
0036dfa3df | ||
|
|
316bc247c4 | ||
|
|
1d99d5f097 | ||
|
|
1cbc8f91d8 | ||
|
|
970844c33e | ||
|
|
aa82614227 | ||
|
|
247a05f70e | ||
|
|
58e89779a5 | ||
|
|
c2c5c3ff6a | ||
|
|
b6cffcdb44 | ||
|
|
27438f50fb | ||
|
|
fce2c22fa7 | ||
|
|
9269989130 | ||
|
|
6156df6fef | ||
|
|
5ce2fe71a5 | ||
|
|
1cf7f85eea | ||
|
|
2240487915 | ||
|
|
142e51f3e6 | ||
|
|
b806446763 | ||
|
|
38c15dc408 | ||
|
|
fd2412e7ef | ||
|
|
3ec62df156 | ||
|
|
1ec1f318f6 | ||
|
|
56071f0448 | ||
|
|
95933b62df | ||
|
|
c368462ccb | ||
|
|
e52ba2fe3b | ||
|
|
2651c34df5 | ||
|
|
39e8be7427 | ||
|
|
42134e0db6 | ||
|
|
b707167627 | ||
|
|
29bb452d19 | ||
|
|
9ac25f9ab6 | ||
|
|
7e41266d80 | ||
|
|
4e8587d445 | ||
|
|
d23ae32379 | ||
|
|
e40c648536 | ||
|
|
b4200cc029 | ||
|
|
908dd8090f | ||
|
|
46396d035b | ||
|
|
34f37b99cc | ||
|
|
3d00b0ad2d | ||
|
|
3eadabfa4c | ||
|
|
dc814d4a7b | ||
|
|
cf509d1a7c | ||
|
|
dddbd6afd3 | ||
|
|
748bb97139 | ||
|
|
787eafbfae | ||
|
|
b4779f6007 | ||
|
|
4eed2e2954 | ||
|
|
43488884ff | ||
|
|
e3043b021d | ||
|
|
8a2cfbd2f7 | ||
|
|
ba45985394 | ||
|
|
9bb487c369 | ||
|
|
f20d20dd40 | ||
|
|
d68662e91c | ||
|
|
0bd93ac824 | ||
|
|
95ce13181f | ||
|
|
ca7444315e | ||
|
|
f2a0f8ca86 | ||
|
|
286fa78eb0 | ||
|
|
e72243d355 | ||
|
|
6bc8b165ff | ||
|
|
2818bdae8a | ||
|
|
365b7f5bdd | ||
|
|
b82a32d0eb | ||
|
|
0e8ba63be5 | ||
|
|
78b82fcfee | ||
|
|
1b44116936 | ||
|
|
42a6c57369 | ||
|
|
57762f4340 | ||
|
|
85daf4647a | ||
|
|
3daff2511e | ||
|
|
be4ab7d7cf | ||
|
|
a3cf9b821a | ||
|
|
919149e012 | ||
|
|
13e49a6229 | ||
|
|
40c9d091b8 | ||
|
|
988a7c16b3 | ||
|
|
ff5750ffae | ||
|
|
1eef3c4944 | ||
|
|
90ecf2767c | ||
|
|
25bae7652e | ||
|
|
f77b401628 | ||
|
|
6c2ae936ac | ||
|
|
e28b126838 | ||
|
|
824d5f4388 | ||
|
|
9a2d1ae5e6 | ||
|
|
5d99d8325b | ||
|
|
fa70da8f25 | ||
|
|
41f574bc94 | ||
|
|
fc98b7aef9 | ||
|
|
b6793f0a0e | ||
|
|
068eb7f611 | ||
|
|
ea0e8eceb9 | ||
|
|
0a5d7bccd5 | ||
|
|
94cba5e60c | ||
|
|
7c6ee6b65f | ||
|
|
f03629ba33 | ||
|
|
8b94f4ca74 | ||
|
|
6787495bc1 | ||
|
|
a695f5deb1 | ||
|
|
9802acb80a | ||
|
|
8a108a3c08 | ||
|
|
153035cefa | ||
|
|
cd4fb99429 | ||
|
|
c760510ffb | ||
|
|
203d0fe157 | ||
|
|
c8d1407bcc | ||
|
|
816b1e3965 | ||
|
|
9878357c71 | ||
|
|
99d6aabe12 | ||
|
|
1a0fc33167 | ||
|
|
30b27e7420 | ||
|
|
57cd52ca3d | ||
|
|
7fdd9681dc | ||
|
|
677f6cdc17 | ||
|
|
2e06c53b62 | ||
|
|
abbf4cb726 | ||
|
|
3423042646 | ||
|
|
928fe11842 | ||
|
|
b5cdf4e699 | ||
|
|
435dc6171a | ||
|
|
68d4373d05 | ||
|
|
a4536d85a4 | ||
|
|
bb4c0f67ad | ||
|
|
070a719054 | ||
|
|
4574994bc8 | ||
|
|
50230ea682 | ||
|
|
4ed877a258 | ||
|
|
b5050f489b | ||
|
|
c9ecc4c808 | ||
|
|
e775024545 | ||
|
|
54e0f86f87 | ||
|
|
ac0d0b9328 | ||
|
|
8b1d65040a | ||
|
|
7a0bbb410f | ||
|
|
d7abc913de | ||
|
|
5f62ba2a54 | ||
|
|
f5b582bab4 | ||
|
|
f6634338a7 | ||
|
|
8ccc23696b | ||
|
|
9f4f1a7b92 | ||
|
|
56402455e6 | ||
|
|
20712412a8 | ||
|
|
d35405842d | ||
|
|
fb9515cc3c | ||
|
|
7094373ca2 | ||
|
|
3ec0110aab | ||
|
|
ad5ca2189b | ||
|
|
7b2b864d86 | ||
|
|
39d603e7e9 | ||
|
|
bf2c6a5874 | ||
|
|
f90387ba45 | ||
|
|
d5ea854dcb | ||
|
|
7558343761 | ||
|
|
934991caac | ||
|
|
d38b88e68c | ||
|
|
90b55bea38 | ||
|
|
0afb619d40 | ||
|
|
8b240b8b60 | ||
|
|
c9891ae7e8 | ||
|
|
5e5661635e | ||
|
|
b3399f377b | ||
|
|
5085320a1f | ||
|
|
741d01bcd2 | ||
|
|
defefd2c79 | ||
|
|
0c82691a40 | ||
|
|
20197a69ce | ||
|
|
9494bb3458 | ||
|
|
4de8e2da22 | ||
|
|
d13572347f | ||
|
|
5c191bcff0 | ||
|
|
fad05e8bb1 | ||
|
|
98bd9c636b | ||
|
|
d124f2bda6 | ||
|
|
f4b6698090 | ||
|
|
c6f8e552eb | ||
|
|
609cc1b66f | ||
|
|
59117be5bb | ||
|
|
d17fee3ee9 | ||
|
|
fddfe32984 | ||
|
|
94dbc95437 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,7 +1,8 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
front_end/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"java.configuration.updateBuildConfiguration": "interactive"
|
|
||||||
}
|
|
||||||
127
README.md
127
README.md
@@ -1,93 +1,44 @@
|
|||||||
# hackathon
|
# hackathon
|
||||||
|
|
||||||
|
## Pour lancer
|
||||||
|
sudo docker compose up -d
|
||||||
|
|
||||||
|
### back_end
|
||||||
|
cd back_end
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
|
#### Clean
|
||||||
|
mvn clean install
|
||||||
|
|
||||||
|
#### Pour lancer back_end
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
### front_end
|
||||||
|
cd front_end
|
||||||
|
|
||||||
|
#### Install
|
||||||
|
npm install
|
||||||
|
|
||||||
|
#### Lancer front_end
|
||||||
|
npm start
|
||||||
|
|
||||||
|
#### Docker
|
||||||
|
sudo docker stop $(sudo docker ps -a -q)
|
||||||
|
|
||||||
|
#### Pour acceder sur docker
|
||||||
|
sudo docker exec -it frisbyee-postgres psql -U frisbyee_user -d frisbyee
|
||||||
|
|
||||||
|
#### Voir les données:
|
||||||
|
\dt
|
||||||
|
SELECT * FROM session;
|
||||||
|
|
||||||
|
|
||||||
## Getting started
|
#### Appliquer le CSS pour la page de login Keycloak
|
||||||
|
|
||||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
Sur la console Keycloak aller dans realm setting
|
||||||
|
-> Changer le display name (par exemple: Bienvenue sur Frisbyee !)
|
||||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
-> Theme puis changer le login theme sur frisbyee
|
||||||
|
|
||||||
## Add your files
|
|
||||||
|
|
||||||
* [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
|
||||||
* [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
cd existing_repo
|
|
||||||
git remote add origin https://gitlab2.istic.univ-rennes1.fr/tuvu/hackathon.git
|
|
||||||
git branch -M main
|
|
||||||
git push -uf origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integrate with your tools
|
|
||||||
|
|
||||||
* [Set up project integrations](https://gitlab2.istic.univ-rennes1.fr/tuvu/hackathon/-/settings/integrations)
|
|
||||||
|
|
||||||
## Collaborate with your team
|
|
||||||
|
|
||||||
* [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
|
||||||
* [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
|
||||||
* [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
|
||||||
* [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
|
||||||
* [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
|
|
||||||
|
|
||||||
## Test and Deploy
|
|
||||||
|
|
||||||
Use the built-in continuous integration in GitLab.
|
|
||||||
|
|
||||||
* [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
|
|
||||||
* [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
|
||||||
* [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
|
||||||
* [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
|
||||||
* [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
# Editing this README
|
|
||||||
|
|
||||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
|
||||||
|
|
||||||
## Suggestions for a good README
|
|
||||||
|
|
||||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
|
||||||
|
|
||||||
## Name
|
|
||||||
Choose a self-explaining name for your project.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
|
||||||
|
|
||||||
## Badges
|
|
||||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
|
||||||
|
|
||||||
## Visuals
|
|
||||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
|
||||||
|
|
||||||
## Support
|
|
||||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
State if you are open to contributions and what your requirements are for accepting them.
|
|
||||||
|
|
||||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
|
||||||
|
|
||||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
|
||||||
|
|
||||||
## Authors and acknowledgment
|
|
||||||
Show your appreciation to those who have contributed to the project.
|
|
||||||
|
|
||||||
## License
|
|
||||||
For open source projects, say how it is licensed.
|
|
||||||
|
|
||||||
## Project status
|
|
||||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: 'postgres:latest'
|
|
||||||
environment:
|
|
||||||
- 'POSTGRES_DB=mydatabase'
|
|
||||||
- 'POSTGRES_PASSWORD=secret'
|
|
||||||
- 'POSTGRES_USER=myuser'
|
|
||||||
ports:
|
|
||||||
- '5432'
|
|
||||||
6
back_end/package-lock.json
generated
Normal file
6
back_end/package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "back_end",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<groupId>hackathon</groupId>
|
<groupId>hackathon</groupId>
|
||||||
<artifactId>FrisbYEE</artifactId>
|
<artifactId>FrisbYEE</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<packaging>war</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>FrisbYEE</name>
|
<name>FrisbYEE</name>
|
||||||
<description>Demo project for Spring Boot</description>
|
<description>Demo project for Spring Boot</description>
|
||||||
<url/>
|
<url/>
|
||||||
@@ -37,72 +37,31 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-security-oauth2-client</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security-oauth2-resource-server</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-webmvc</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.thymeleaf.extras</groupId>
|
|
||||||
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-docker-compose</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-data-jpa-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security-oauth2-client-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security-oauth2-resource-server-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-thymeleaf-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<version>1.18.32</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package hackathon.FrisbYEE.config;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
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
|
||||||
|
public class WebSecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
|
.csrf(csrf -> csrf.disable())
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
// TODO //TODO // T O D O
|
||||||
|
.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("/admin/**").hasRole("admin")
|
||||||
|
.requestMatchers("/user/**").hasRole("user")
|
||||||
|
.anyRequest().authenticated())
|
||||||
|
.oauth2ResourceServer(oauth2 -> oauth2
|
||||||
|
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtToken -> {
|
||||||
|
Map<String, Collection<String>> realmAccess = jwtToken.getClaim("realm_access");
|
||||||
|
Collection<String> roles = realmAccess.get("roles");
|
||||||
|
System.out.println("ROLES FROM TOKEN " + roles);
|
||||||
|
List<SimpleGrantedAuthority> authorities = roles.stream()
|
||||||
|
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
||||||
|
.toList();
|
||||||
|
return new JwtAuthenticationToken(jwtToken, authorities);
|
||||||
|
})));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ActiviteDTO {
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
private String theme;
|
||||||
|
private Long duree; // optional, can be null
|
||||||
|
private List<String> dataActivite;
|
||||||
|
private Integer sessionId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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 name;
|
||||||
|
private String prenom;
|
||||||
|
private Role role;
|
||||||
|
}
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
package hackathon.FrisbYEE.jpa.dto;
|
package hackathon.FrisbYEE.jpa.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AthleteDTO implements java.io.Serializable {
|
@Data
|
||||||
|
public class AthleteDTO {
|
||||||
private String nom;
|
private Integer id;
|
||||||
private String niveau;
|
private String id_keycloak;
|
||||||
|
private String name;
|
||||||
|
private String prenom;
|
||||||
private String categorie;
|
private String categorie;
|
||||||
private List<String> groupes;
|
private String niveau;
|
||||||
|
private List<String> groupes = new ArrayList<>();
|
||||||
|
private List<Integer> sessionIds = new ArrayList<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CoachDTO {
|
||||||
|
private Integer id;
|
||||||
|
private String id_keycloak;
|
||||||
|
private String name;
|
||||||
|
private String prenom;
|
||||||
|
private List<Integer> sessionIds;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SessionDTO {
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
private Boolean isRecurrent;
|
||||||
|
private LocalDateTime creneau;
|
||||||
|
private Long duree;
|
||||||
|
private String groupe;
|
||||||
|
|
||||||
|
private Integer coachId;
|
||||||
|
private List<Integer> athleteIds;
|
||||||
|
private List<Integer> activiteIds;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.dto;
|
||||||
|
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Role;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UserDTO {
|
||||||
|
private Integer id;
|
||||||
|
private String id_keycloak;
|
||||||
|
private String name;
|
||||||
|
private String prenom;
|
||||||
|
private String email;
|
||||||
|
private Role role;
|
||||||
|
}
|
||||||
@@ -3,9 +3,8 @@ package hackathon.FrisbYEE.jpa.metier;
|
|||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import lombok.Getter;
|
import lombok.*;
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -22,7 +21,7 @@ public class Activite implements Serializable {
|
|||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
private Long id;
|
private Integer id;
|
||||||
private String name;
|
private String name;
|
||||||
private String theme;
|
private String theme;
|
||||||
private Long duree;
|
private Long duree;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package hackathon.FrisbYEE.jpa.metier;
|
package hackathon.FrisbYEE.jpa.metier;
|
||||||
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@@ -13,18 +11,40 @@ import jakarta.persistence.Entity;
|
|||||||
@Getter @Setter @NoArgsConstructor
|
@Getter @Setter @NoArgsConstructor
|
||||||
@Access(AccessType.FIELD)
|
@Access(AccessType.FIELD)
|
||||||
|
|
||||||
public class Admin {
|
public class Admin extends User{
|
||||||
|
|
||||||
@Id
|
public Admin(String id_keycloak, String name, String prenom){
|
||||||
@GeneratedValue
|
super(name, id_keycloak, prenom, Role.admin );
|
||||||
private Long id;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public Admin(String name){
|
|
||||||
this.name = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Admin [id=" + id + " , name=" + name + "]";
|
return "Admin [id=" + super.getId() + " , name=" + super.getName() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
super.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return super.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrenom() {
|
||||||
|
return super.getPrenom();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrenom(String prenom) {
|
||||||
|
super.setPrenom(prenom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Role getRole() {
|
||||||
|
return super.getRole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package hackathon.FrisbYEE.jpa.metier;
|
package hackathon.FrisbYEE.jpa.metier;
|
||||||
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.ManyToMany;
|
import jakarta.persistence.ManyToMany;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import jakarta.persistence.Access;
|
import jakarta.persistence.Access;
|
||||||
@@ -17,12 +16,8 @@ import jakarta.persistence.Entity;
|
|||||||
@Getter @Setter @NoArgsConstructor
|
@Getter @Setter @NoArgsConstructor
|
||||||
@Access(AccessType.FIELD)
|
@Access(AccessType.FIELD)
|
||||||
|
|
||||||
public class Athlete {
|
public class Athlete extends User{
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
private Long id;
|
|
||||||
private String name;
|
|
||||||
private String categorie;
|
private String categorie;
|
||||||
private String niveau;
|
private String niveau;
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@@ -32,19 +27,37 @@ public class Athlete {
|
|||||||
@ManyToMany(mappedBy = "athletes")
|
@ManyToMany(mappedBy = "athletes")
|
||||||
private List<Session> sessions = new ArrayList<>(); // plusieurs sessions sont possibles
|
private List<Session> sessions = new ArrayList<>(); // plusieurs sessions sont possibles
|
||||||
|
|
||||||
public Athlete(String name){
|
public Athlete(String name, String id_keycloak, String prenom){
|
||||||
this.name = name;
|
super(name, id_keycloak, prenom, Role.athlete);
|
||||||
}
|
|
||||||
|
|
||||||
public Athlete(String name, String categorie, String niveau, List<String> groupe){
|
|
||||||
this.name = name;
|
|
||||||
this.categorie = categorie;
|
|
||||||
this.niveau = niveau;
|
|
||||||
this.groupe = groupe;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Athlete [id=" + id + " , name=" + name + "]";
|
return "Athlete [id=" + super.getId() + " , name=" + super.getName() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
super.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return super.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrenom() {
|
||||||
|
return super.getPrenom();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrenom(String prenom) {
|
||||||
|
super.setPrenom(prenom);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Role getRole() {
|
||||||
|
return super.getRole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package hackathon.FrisbYEE.jpa.metier;
|
package hackathon.FrisbYEE.jpa.metier;
|
||||||
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.OneToMany;
|
import jakarta.persistence.OneToMany;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -16,21 +14,43 @@ import jakarta.persistence.Entity;
|
|||||||
@Getter @Setter @NoArgsConstructor
|
@Getter @Setter @NoArgsConstructor
|
||||||
@Access(AccessType.FIELD)
|
@Access(AccessType.FIELD)
|
||||||
|
|
||||||
public class Coach {
|
public class Coach extends User{
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
private Long id;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "coach")
|
@OneToMany(mappedBy = "coach")
|
||||||
private List<Session> sessions = new ArrayList<>(); // Un coach peut avoir plusieurs sessions
|
private List<Session> sessions = new ArrayList<>(); // Un coach peut avoir plusieurs sessions
|
||||||
|
|
||||||
public Coach(String name){
|
public Coach(String name, String id_keycloak, String prenom){
|
||||||
this.name = name;
|
super(name, id_keycloak, prenom, Role.coach );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Coach [id=" + id + " , name=" + name + "]";
|
return "Coach [id=" + super.getId() + " , name=" + super.getName() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
super.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return super.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrenom() {
|
||||||
|
return super.getPrenom();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrenom(String prenom) {
|
||||||
|
super.setPrenom(prenom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Role getRole() {
|
||||||
|
return super.getRole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.metier;
|
||||||
|
|
||||||
|
public enum Role {
|
||||||
|
admin,
|
||||||
|
coach,
|
||||||
|
athlete
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ public class Session {
|
|||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
private Long id;
|
private Integer id;
|
||||||
private String name;
|
private String name;
|
||||||
private Boolean isRecurrent;
|
private Boolean isRecurrent;
|
||||||
private LocalDateTime creneau;
|
private LocalDateTime creneau;
|
||||||
@@ -56,4 +56,21 @@ public class Session {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "Session [id=" + id + " , name=" + name + "]";
|
return "Session [id=" + id + " , name=" + name + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCoach(Coach coach) {
|
||||||
|
if (coach.getRole() != Role.coach) {
|
||||||
|
throw new IllegalArgumentException("L'utilisateur n'est pas un coach");
|
||||||
|
}
|
||||||
|
this.coach = coach;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAthletes(List<Athlete> athletes) {
|
||||||
|
for (Athlete athlete : athletes) {
|
||||||
|
if (athlete.getRole() != Role.athlete) {
|
||||||
|
throw new IllegalArgumentException("L'utilisateur n'est pas un athlète");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.athletes = athletes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.metier;
|
||||||
|
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import jakarta.persistence.Access;
|
||||||
|
import jakarta.persistence.AccessType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Access(AccessType.FIELD)
|
||||||
|
@Table(name = "app_user")
|
||||||
|
public class User implements Serializable {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
@Column(unique = true, nullable = false)
|
||||||
|
private Integer id;
|
||||||
|
@Column(name = "id_keycloak", unique = true, nullable = false)
|
||||||
|
private String keycloakId;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String prenom;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Role role;
|
||||||
|
|
||||||
|
public User(String name, String id_keycloak, String prenom, Role role) {
|
||||||
|
this.name = name;
|
||||||
|
this.keycloakId = id_keycloak;
|
||||||
|
this.prenom = prenom;
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "User [id=" + id + " , name=" + name + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,12 @@ package hackathon.FrisbYEE.jpa.service;
|
|||||||
|
|
||||||
import hackathon.FrisbYEE.jpa.metier.Activite;
|
import hackathon.FrisbYEE.jpa.metier.Activite;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
public interface ActiviteDAO extends JpaRepository<Activite, Integer> {
|
public interface ActiviteDAO extends JpaRepository<Activite, Integer> {
|
||||||
Activite findByKeycloakId(String keycloakId);
|
|
||||||
|
List<Activite> findByTheme(String theme);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.service;
|
||||||
|
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Admin;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface AdminDAO extends JpaRepository<Admin, Integer> {
|
||||||
|
Optional<Admin> findByKeycloakId(String keycloakId);
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package hackathon.FrisbYEE.jpa.service;
|
package hackathon.FrisbYEE.jpa.service;
|
||||||
|
|
||||||
import hackathon.FrisbYEE.jpa.metier.Activite;
|
|
||||||
import hackathon.FrisbYEE.jpa.metier.Athlete;
|
import hackathon.FrisbYEE.jpa.metier.Athlete;
|
||||||
|
import java.util.Optional;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
public interface AthleteDAO extends JpaRepository<Athlete, Integer> {
|
public interface AthleteDAO extends JpaRepository<Athlete, Integer> {
|
||||||
|
boolean existsByKeycloakId(String keycloakId);
|
||||||
|
Optional<Athlete> findByKeycloakId(String keycloakId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
package hackathon.FrisbYEE.jpa.service;
|
package hackathon.FrisbYEE.jpa.service;
|
||||||
|
|
||||||
public class CoachDAO {
|
import hackathon.FrisbYEE.jpa.metier.Coach;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CoachDAO extends JpaRepository<Coach, Integer> {
|
||||||
|
boolean existsByKeycloakId(String keycloakId);
|
||||||
|
Optional<Coach> findByKeycloakId(String keycloakId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.service;
|
||||||
|
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Session;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface SessionDAO extends JpaRepository<Session, Integer> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package hackathon.FrisbYEE.jpa.service;
|
||||||
|
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.User;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserDAO extends JpaRepository<User, Integer> {
|
||||||
|
Optional<User> findByKeycloakId(String keycloakId);
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package hackathon.FrisbYEE.jpa.web;
|
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("/activite")
|
|
||||||
|
|
||||||
public class ActiviteController {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
package hackathon.FrisbYEE.rest;
|
||||||
|
|
||||||
|
import hackathon.FrisbYEE.jpa.dto.ActiviteDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Activite;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Session;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.ActiviteDAO;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.SessionDAO;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "http://localhost:3000")
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/activite")
|
||||||
|
|
||||||
|
public class ActiviteResource {
|
||||||
|
@Autowired
|
||||||
|
private ActiviteDAO activiteDAO;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SessionDAO sessionDAO;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* POST /activite/create
|
||||||
|
* DELETE /activite/delete/{id}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Operation(summary = "Créer une activité")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Création effectuée",
|
||||||
|
content = @Content(mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = ActiviteDTO.class)))
|
||||||
|
})
|
||||||
|
@PostMapping("/create")
|
||||||
|
@ResponseBody
|
||||||
|
@PreAuthorize("hasRole('coach')")
|
||||||
|
public ResponseEntity<ActiviteDTO> create(@RequestBody ActiviteDTO dto) {
|
||||||
|
|
||||||
|
System.out.println("ROLE TEST " + hackathon.FrisbYEE.jpa.metier.Role.coach);
|
||||||
|
Session session = sessionDAO.findById(dto.getSessionId()).get();
|
||||||
|
if(activiteDAO.existsById(dto.getId())){
|
||||||
|
return ResponseEntity.status(200).body(mapToDTO(activiteDAO.findById(dto.getId()).get()));
|
||||||
|
}
|
||||||
|
Activite activite = mapToEntity(dto);
|
||||||
|
activite.setSession(session);
|
||||||
|
activiteDAO.save(activite);
|
||||||
|
return ResponseEntity.status(201).body(mapToDTO(activite));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Supprime l'activité ayant l'identifiant correspondant")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Suppression effectuée",
|
||||||
|
content = @Content(mediaType = "application/json"))
|
||||||
|
})
|
||||||
|
@DeleteMapping("/delete/{id}")
|
||||||
|
@ResponseBody
|
||||||
|
@PreAuthorize("hasRole('coach')")
|
||||||
|
public ResponseEntity<String> delete(@PathVariable("id") int id) {
|
||||||
|
try {
|
||||||
|
Activite activite = activiteDAO.findById(id).get();
|
||||||
|
activiteDAO.delete(activite);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error: " + ex.getMessage());
|
||||||
|
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok("Activity deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Modifie l'activité ayant l'identifiant correspondant")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Modification effectuée",
|
||||||
|
content = @Content(mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = ActiviteDTO.class)))
|
||||||
|
})
|
||||||
|
@PostMapping("/update/{id}")
|
||||||
|
@ResponseBody
|
||||||
|
@PreAuthorize("hasRole('coach')")
|
||||||
|
public ResponseEntity<String> modifyById(@PathVariable("id") int id, @RequestBody ActiviteDTO dto) {
|
||||||
|
try {
|
||||||
|
Session session = sessionDAO.findById(dto.getSessionId()).get();
|
||||||
|
Activite activite = activiteDAO.findById(id).get();
|
||||||
|
activite.setName(dto.getName());
|
||||||
|
activite.setTheme(dto.getTheme());
|
||||||
|
activite.setDuree(dto.getDuree() != null ? dto.getDuree() : 0L);
|
||||||
|
activite.setDataActivite(dto.getDataActivite());
|
||||||
|
activite.setSession(session);
|
||||||
|
activiteDAO.save(activite);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok("Activity modified");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère l'activité ayant l'identifiant correspondant")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Récupération effectuée",
|
||||||
|
content = @Content(mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = ActiviteDTO.class)))
|
||||||
|
})
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<ActiviteDTO> getActivityById(@PathVariable("id") int id) {
|
||||||
|
try {
|
||||||
|
Activite activite = activiteDAO.findById(id).get();
|
||||||
|
ActiviteDTO dto = mapToDTO(activite);
|
||||||
|
return ResponseEntity.ok(dto);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère toutes les activités")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Récupération effectuée",
|
||||||
|
content = @Content(mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = ActiviteDTO.class)))
|
||||||
|
})
|
||||||
|
@GetMapping("/all")
|
||||||
|
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<List<ActiviteDTO>> getAllActivity() {
|
||||||
|
try {
|
||||||
|
List<Activite> activites = activiteDAO.findAll();
|
||||||
|
List<ActiviteDTO> dtos = activites.stream().map(this::mapToDTO).collect(Collectors.toList());
|
||||||
|
return ResponseEntity.ok(dtos);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère les activités correspondant au thème donné")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Récupération effectuée",
|
||||||
|
content = @Content(mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = ActiviteDTO.class)))
|
||||||
|
})
|
||||||
|
@GetMapping("/theme/{theme}")
|
||||||
|
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<List<ActiviteDTO>> getActivityByTheme(@PathVariable("theme") String theme) {
|
||||||
|
try {
|
||||||
|
List<Activite> activites = activiteDAO.findByTheme(theme);
|
||||||
|
List<ActiviteDTO> dtos = activites.stream().map(this::mapToDTO).collect(Collectors.toList());
|
||||||
|
return ResponseEntity.ok(dtos);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Activite mapToEntity(ActiviteDTO dto) {
|
||||||
|
Activite activite = new Activite();
|
||||||
|
activite.setName(dto.getName());
|
||||||
|
activite.setTheme(dto.getTheme());
|
||||||
|
activite.setDuree(dto.getDuree());
|
||||||
|
activite.setDataActivite(dto.getDataActivite());
|
||||||
|
return activite;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActiviteDTO mapToDTO(Activite activite) {
|
||||||
|
ActiviteDTO dto = new ActiviteDTO();
|
||||||
|
dto.setId(activite.getId());
|
||||||
|
dto.setName(activite.getName());
|
||||||
|
dto.setTheme(activite.getTheme());
|
||||||
|
dto.setDuree(activite.getDuree());
|
||||||
|
dto.setDataActivite(activite.getDataActivite());
|
||||||
|
dto.setSessionId(activite.getSession() != null ? activite.getSession().getId() : null);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package hackathon.FrisbYEE.rest;
|
|
||||||
|
|
||||||
public class ActiviteResources {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
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 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;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/admin")
|
||||||
|
@CrossOrigin(origins = "http://localhost:3000")
|
||||||
|
public class AdminResource {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AdminDAO adminDAO;
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
@PreAuthorize("hasRole('Admin')") // Only admin can create
|
||||||
|
public ResponseEntity<AdminDTO> create(@RequestBody AdminDTO dto) {
|
||||||
|
Admin admin = mapToEntity(dto);
|
||||||
|
if(adminDAO.findByKeycloakId(admin.getKeycloakId()).isPresent()) {
|
||||||
|
return ResponseEntity.status(200).body(mapToDTO(adminDAO.findByKeycloakId(admin.getKeycloakId()).get()));
|
||||||
|
}
|
||||||
|
adminDAO.save(admin);
|
||||||
|
return ResponseEntity.status(201).body(mapToDTO(admin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('admin')")
|
||||||
|
public ResponseEntity<AdminDTO> getAdmin(@PathVariable Integer id) {
|
||||||
|
Admin admin = adminDAO.findById(id).get();
|
||||||
|
return ResponseEntity.ok(mapToDTO(admin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/keycloak/{keycloak_id}")
|
||||||
|
@PreAuthorize("hasRole('admin')")
|
||||||
|
public ResponseEntity<AdminDTO> getByKeycloakId(@PathVariable String keycloak_id) {
|
||||||
|
Admin admin = adminDAO.findByKeycloakId(keycloak_id).get();
|
||||||
|
return ResponseEntity.ok(mapToDTO(admin));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminDTO mapToDTO(Admin admin) {
|
||||||
|
AdminDTO dto = new AdminDTO();
|
||||||
|
dto.setId(admin.getId());
|
||||||
|
dto.setId_keycloak(admin.getKeycloakId());
|
||||||
|
dto.setName(admin.getName());
|
||||||
|
dto.setPrenom(admin.getPrenom());
|
||||||
|
dto.setRole(admin.getRole());
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Admin mapToEntity(AdminDTO dto) {
|
||||||
|
Admin admin = new Admin();
|
||||||
|
admin.setKeycloakId(dto.getId_keycloak());
|
||||||
|
admin.setName(dto.getName());
|
||||||
|
admin.setPrenom(dto.getPrenom());
|
||||||
|
admin.setRole(dto.getRole());
|
||||||
|
|
||||||
|
return admin;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +1,318 @@
|
|||||||
package hackathon.FrisbYEE.rest;
|
package hackathon.FrisbYEE.rest;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.chrono.ChronoLocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
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.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import hackathon.FrisbYEE.jpa.dto.ActiviteDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.dto.AthleteDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.dto.SessionDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Activite;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Athlete;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Session;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.AthleteDAO;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.SessionDAO;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import hackathon.FrisbYEE.jpa.service.AthleteDAO;
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/athlete")
|
||||||
|
@CrossOrigin(origins = "http://localhost:3000")
|
||||||
public class AthleteResource {
|
public class AthleteResource {
|
||||||
|
@Autowired
|
||||||
private AthleteDAO athleteDAO;
|
private AthleteDAO athleteDAO;
|
||||||
|
@Autowired
|
||||||
|
private SessionDAO sessionDAO;
|
||||||
|
|
||||||
@Operation(summary = "Récupère tous les utilisateurs")
|
@Operation(summary = "Crée un Athlète avec les informations fournies")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "200", description = "Récupère le Joueur ayant l'identifiant correspondant",
|
@ApiResponse(responseCode = "200", description = "Renvoie l'athlète créé", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AthleteDTO.class)))
|
||||||
content = @Content(mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = JoueurDTO.class)))
|
|
||||||
})
|
})
|
||||||
@GetMapping("/joueur/{id}")
|
@PostMapping("/create")
|
||||||
public JoueurDTO getJoueurById(@PathVariable Integer joueurId) {
|
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||||
// return pet
|
public ResponseEntity<AthleteDTO> create(@RequestBody AthleteDTO dto) {
|
||||||
System.out.println("ID A CHERCHER" + joueurId);
|
Athlete athlete = mapToEntity(dto);
|
||||||
IJoueur j = dao.findOne(joueurId);
|
if (athleteDAO.existsByKeycloakId(athlete.getKeycloakId())) {
|
||||||
JoueurDTO jDTO = new JoueurDTO(null, null);
|
return ResponseEntity.status(200)
|
||||||
System.out.println(j);
|
.body(mapToDTO(athleteDAO.findByKeycloakId(athlete.getKeycloakId()).get()));
|
||||||
return jDTO;
|
}
|
||||||
|
athleteDAO.save(athlete);
|
||||||
|
return ResponseEntity.status(201).body(mapToDTO(athlete));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère tous les athlètes")
|
||||||
|
@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")
|
||||||
|
@PreAuthorize("hasRole('admin') or hasRole('coach')")
|
||||||
|
public ResponseEntity<List<AthleteDTO>> all() {
|
||||||
|
List<Athlete> athletes = athleteDAO.findAll();
|
||||||
|
List<AthleteDTO> dtos = new ArrayList<>();
|
||||||
|
for (Athlete athlete : athletes) {
|
||||||
|
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)))
|
||||||
|
})
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||||
|
public ResponseEntity<AthleteDTO> getById(@PathVariable String id) {
|
||||||
|
Athlete athlete = athleteDAO.findByKeycloakId(id).get();
|
||||||
|
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();
|
||||||
|
return ResponseEntity.ok(mapToDTO(athlete));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Met à jour l'athlète ayant l'identifiant correspondant")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Mise à jour effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AthleteDTO.class)))
|
||||||
|
})
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
@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.setName(dto.getName());
|
||||||
|
athlete.setCategorie(dto.getCategorie());
|
||||||
|
athlete.setNiveau(dto.getNiveau());
|
||||||
|
|
||||||
|
// Relationship: sessionId → session
|
||||||
|
if (dto.getSessionIds() != null) {
|
||||||
|
List<Session> sessions = new ArrayList<>();
|
||||||
|
for (Integer sessionId : dto.getSessionIds()) {
|
||||||
|
Session session = sessionDAO.findById(sessionId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Session not found"));
|
||||||
|
sessions.add(session);
|
||||||
|
}
|
||||||
|
athlete.setSessions(sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
athleteDAO.save(athlete);
|
||||||
|
return ResponseEntity.ok(mapToDTO(athlete));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Supprime l'athlète ayant l'identifiant correspondant")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Suppression effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AthleteDTO.class)))
|
||||||
|
})
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('admin')")
|
||||||
|
public ResponseEntity<Void> delete(@PathVariable Integer id) {
|
||||||
|
if (!athleteDAO.existsById(id)) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
athleteDAO.deleteById(id);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AthleteDTO mapToDTO(Athlete athlete) {
|
||||||
|
AthleteDTO dto = new AthleteDTO();
|
||||||
|
dto.setId(athlete.getId());
|
||||||
|
dto.setId_keycloak(athlete.getKeycloakId());
|
||||||
|
dto.setName(athlete.getName());
|
||||||
|
dto.setPrenom(athlete.getPrenom());
|
||||||
|
dto.setCategorie(athlete.getCategorie());
|
||||||
|
dto.setNiveau(athlete.getNiveau());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Athlete mapToEntity(AthleteDTO dto) {
|
||||||
|
Athlete athlete = new Athlete();
|
||||||
|
athlete.setName(dto.getName());
|
||||||
|
athlete.setPrenom(dto.getPrenom());
|
||||||
|
athlete.setKeycloakId(dto.getId_keycloak());
|
||||||
|
athlete.setCategorie(dto.getCategorie());
|
||||||
|
athlete.setNiveau(dto.getNiveau());
|
||||||
|
athlete.setRole(hackathon.FrisbYEE.jpa.metier.Role.athlete);
|
||||||
|
return athlete;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère les sessions correspondant à l'athlète donné")
|
||||||
|
@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")
|
||||||
|
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();
|
||||||
|
List<SessionDTO> athleteSessions = new ArrayList<>();
|
||||||
|
for (Session s : sessions) {
|
||||||
|
if (s.getAthletes().contains(j.get())) {
|
||||||
|
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
|
||||||
|
athleteSessions.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println(j);
|
||||||
|
return athleteSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)))
|
||||||
|
})
|
||||||
|
@GetMapping("/athletes/session")
|
||||||
|
public List<SessionDTO> getAllSessions() {
|
||||||
|
List<Session> sessions = sessionDAO.findAll();
|
||||||
|
System.out.println(sessions);
|
||||||
|
List<SessionDTO> sessionDTOs = new ArrayList<>();
|
||||||
|
for (Session session : sessions) {
|
||||||
|
SessionDTO dto = new SessionDTO();
|
||||||
|
dto.setName(session.getName());
|
||||||
|
// Map other fields as necessary
|
||||||
|
sessionDTOs.add(dto);
|
||||||
|
}
|
||||||
|
return sessionDTOs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère les activités correspondant à la session donnée")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Récupération effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ActiviteDTO.class)))
|
||||||
|
})
|
||||||
|
@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();
|
||||||
|
// Retourner les activités de la session
|
||||||
|
List<ActiviteDTO> activiteDTOs = new ArrayList<>();
|
||||||
|
for (Activite activite : session.getActivites()) {
|
||||||
|
ActiviteDTO dto = new ActiviteDTO();
|
||||||
|
dto.setId(activite.getId());
|
||||||
|
dto.setName(activite.getName());
|
||||||
|
// Map other fields as necessary
|
||||||
|
activiteDTOs.add(dto);
|
||||||
|
}
|
||||||
|
return activiteDTOs;
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère toutes les sessions après une date donnée")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Récupération effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SessionDTO.class)))
|
||||||
|
})
|
||||||
|
@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();
|
||||||
|
// 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<>();
|
||||||
|
for (Session session : sessions) {
|
||||||
|
if (session.getAthletes().contains(athlete)
|
||||||
|
&& session.getCreneau().isAfter(ChronoLocalDateTime.from(LocalDate.parse(date)))) { // WTF
|
||||||
|
// toujours
|
||||||
|
// sympa les
|
||||||
|
// dates
|
||||||
|
SessionDTO dto = new SessionDTO();
|
||||||
|
dto.setName(session.getName());
|
||||||
|
// Map other fields as necessary
|
||||||
|
filteredSessions.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredSessions;
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère les sessions entre deux dates")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Récupération effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SessionDTO.class)))
|
||||||
|
})
|
||||||
|
@GetMapping("/athletes/{id}/session/between/{startDate}/{endDate}")
|
||||||
|
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();
|
||||||
|
// 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<>();
|
||||||
|
for (Session session : sessions) {
|
||||||
|
if (session.getAthletes().contains(athlete)
|
||||||
|
&& session.getCreneau().isAfter(ChronoLocalDateTime.from(LocalDate.parse(startDate)))
|
||||||
|
&& session.getCreneau().isBefore(ChronoLocalDateTime.from(LocalDate.parse(endDate)))) {
|
||||||
|
SessionDTO dto = new SessionDTO();
|
||||||
|
dto.setName(session.getName());
|
||||||
|
// Map other fields as necessary
|
||||||
|
filteredSessions.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredSessions;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package hackathon.FrisbYEE.rest;
|
||||||
|
|
||||||
|
import hackathon.FrisbYEE.jpa.dto.CoachDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Coach;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.CoachDAO;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "http://localhost:3000")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/coach")
|
||||||
|
public class CoachResource {
|
||||||
|
@Autowired
|
||||||
|
private CoachDAO coachDAO;
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
@PreAuthorize("hasRole('Admin')") // Only admin can create
|
||||||
|
public ResponseEntity<CoachDTO> create(@RequestBody CoachDTO dto) {
|
||||||
|
Coach coach = mapToEntity(dto);
|
||||||
|
if(coachDAO.existsByKeycloakId(coach.getKeycloakId())) {
|
||||||
|
return ResponseEntity.status(200).body(mapToDTO(coachDAO.findByKeycloakId(coach.getKeycloakId()).get()));
|
||||||
|
}
|
||||||
|
coachDAO.save(coach);
|
||||||
|
return ResponseEntity.status(201).body(mapToDTO(coach));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/all")
|
||||||
|
public List<CoachDTO> getAll() {
|
||||||
|
System.out.println("GET /coach/all called");
|
||||||
|
List<Coach> coaches = coachDAO.findAll();
|
||||||
|
List<CoachDTO> dtos = new ArrayList<>();
|
||||||
|
for (Coach coach : coaches) {
|
||||||
|
dtos.add(mapToDTO(coach));
|
||||||
|
}
|
||||||
|
return dtos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/keycloak/{keycloak_id}")
|
||||||
|
@PreAuthorize("hasRole('Admin') or hasRole('Coach')")
|
||||||
|
public CoachDTO getByKeycloakId(@PathVariable String keycloak_id) {
|
||||||
|
Coach coach = coachDAO.findByKeycloakId(keycloak_id)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Coach not found"));
|
||||||
|
return mapToDTO(coach);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('Admin') or hasRole('Coach')")
|
||||||
|
public CoachDTO getById(@PathVariable Integer id) {
|
||||||
|
Coach coach = coachDAO.findById(id)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Coach not found"));
|
||||||
|
return mapToDTO(coach);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/update/{id}")
|
||||||
|
@PreAuthorize("hasRole('Admin')")
|
||||||
|
public ResponseEntity<CoachDTO> update(@PathVariable Integer id, @RequestBody CoachDTO dto) {
|
||||||
|
Coach coach = coachDAO.findById(id)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Coach not found"));
|
||||||
|
if (dto.getName() != null)
|
||||||
|
coach.setName(dto.getName());
|
||||||
|
coachDAO.save(coach);
|
||||||
|
CoachDTO updatedDto = mapToDTO(coach);
|
||||||
|
return ResponseEntity.ok(updatedDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/session")
|
||||||
|
@PreAuthorize("hasRole('Admin') or hasRole('Coach')")
|
||||||
|
public ResponseEntity<List<?>> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/delete/{id}")
|
||||||
|
@PreAuthorize("hasRole('Admin')")
|
||||||
|
public ResponseEntity<Void> delete(@PathVariable Integer id) {
|
||||||
|
Coach coach = coachDAO.findById(id)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Coach not found"));
|
||||||
|
coachDAO.delete(coach);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CoachDTO mapToDTO(Coach coach) {
|
||||||
|
CoachDTO dto = new CoachDTO();
|
||||||
|
dto.setId(coach.getId());
|
||||||
|
dto.setId_keycloak(coach.getKeycloakId());
|
||||||
|
dto.setName(coach.getName());
|
||||||
|
dto.setPrenom(coach.getPrenom());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Coach mapToEntity(CoachDTO dto) {
|
||||||
|
Coach coach = new Coach();
|
||||||
|
coach.setKeycloakId(dto.getId_keycloak());
|
||||||
|
coach.setName(dto.getName());
|
||||||
|
coach.setRole(hackathon.FrisbYEE.jpa.metier.Role.coach);
|
||||||
|
coach.setPrenom(dto.getPrenom());
|
||||||
|
return coach;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
package hackathon.FrisbYEE.rest;
|
||||||
|
|
||||||
|
import hackathon.FrisbYEE.jpa.dto.ActiviteDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.dto.SessionDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Activite;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Athlete;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Coach;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Session;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.ActiviteDAO;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.AthleteDAO;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.CoachDAO;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.SessionDAO;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "http://localhost:3000")
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/session")
|
||||||
|
|
||||||
|
public class SessionResource {
|
||||||
|
@Autowired
|
||||||
|
private SessionDAO sessionDAO;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CoachDAO coachDAO;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AthleteDAO athleteDAO;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ActiviteDAO activiteDAO;
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
@ResponseBody
|
||||||
|
@PreAuthorize("hasRole('coach')")
|
||||||
|
public ResponseEntity<?> create(@RequestBody SessionDTO dto) {
|
||||||
|
try {
|
||||||
|
if (sessionDAO.findById(dto.getId()).isPresent()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.OK).body("Session with ID " + dto.getId() + " already exists.");
|
||||||
|
|
||||||
|
}
|
||||||
|
Session session = maptoEntity(dto);
|
||||||
|
Coach c = coachDAO.findById(dto.getCoachId()).orElse(null);
|
||||||
|
session.setCoach(c);
|
||||||
|
sessionDAO.save(session);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(maptoDTO(session));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/all")
|
||||||
|
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||||
|
public ResponseEntity<List<SessionDTO>> getAll() {
|
||||||
|
List<Session> sessions = sessionDAO.findAll();
|
||||||
|
List<SessionDTO> dtos = new ArrayList<>();
|
||||||
|
for (Session session : sessions) {
|
||||||
|
dtos.add(maptoDTO(session));
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(dtos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||||
|
public ResponseEntity<?> getById(@PathVariable Integer id) {
|
||||||
|
try {
|
||||||
|
Session session = sessionDAO.findById(id).orElseThrow();
|
||||||
|
return ResponseEntity.ok(maptoDTO(session));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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')")
|
||||||
|
public ResponseEntity<String> delete(@PathVariable("id") int id) {
|
||||||
|
try {
|
||||||
|
Session session = sessionDAO.findById(id).get();
|
||||||
|
sessionDAO.delete(session);
|
||||||
|
return ResponseEntity.ok("Session deleted successfully");
|
||||||
|
} 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) {
|
||||||
|
Session session = sessionDAO.findById(id).orElseThrow(() -> new ResponseStatusException(
|
||||||
|
HttpStatus.NOT_FOUND, "Session not found with id " + id));
|
||||||
|
|
||||||
|
if (dto.getDuree() != null) {
|
||||||
|
session.setDuree(dto.getDuree());
|
||||||
|
}
|
||||||
|
if (dto.getAthleteIds() != null) {
|
||||||
|
List<Athlete> athletes = athleteDAO.findAllById(dto.getAthleteIds());
|
||||||
|
session.setAthletes(athletes);
|
||||||
|
}
|
||||||
|
if (dto.getActiviteIds() != null) {
|
||||||
|
List<Activite> activites = activiteDAO.findAllById(dto.getActiviteIds());
|
||||||
|
session.setActivites(activites);
|
||||||
|
}
|
||||||
|
sessionDAO.save(session);
|
||||||
|
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) {
|
||||||
|
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(
|
||||||
|
HttpStatus.NOT_FOUND, "Athlete not found with id " + userId));
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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(
|
||||||
|
HttpStatus.NOT_FOUND, "Athlete not found with id " + userId));
|
||||||
|
|
||||||
|
session.getAthletes().remove(athlete);
|
||||||
|
sessionDAO.save(session);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/activities")
|
||||||
|
@PreAuthorize("hasRole('coach') or hasRole('athlete')")
|
||||||
|
public ResponseEntity<?> getActivitiesBySessionId(@PathVariable Integer id) {
|
||||||
|
try {
|
||||||
|
Session session = sessionDAO.findById(id).orElseThrow();
|
||||||
|
List<Activite> activites = session.getActivites();
|
||||||
|
List<ActiviteDTO> activiteDTOs = new ArrayList<>();
|
||||||
|
for (Activite activite : activites) {
|
||||||
|
ActiviteDTO dto = new ActiviteDTO();
|
||||||
|
dto.setId(activite.getId());
|
||||||
|
dto.setName(activite.getName());
|
||||||
|
activiteDTOs.add(dto);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(activiteDTOs);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id_session}/activities/add/{id_act}")
|
||||||
|
@PreAuthorize("hasRole('coach') or hasRole('admin')")
|
||||||
|
public ResponseEntity<?> addActivity(@PathVariable Integer id_sess, @PathVariable Integer id_act) {
|
||||||
|
Session s = sessionDAO.findById(id_sess).get();
|
||||||
|
if (s.equals(null)) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Session not found");
|
||||||
|
}
|
||||||
|
Activite a = activiteDAO.findById(id_act).get();
|
||||||
|
if (a.equals(null)) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Activite not found");
|
||||||
|
}
|
||||||
|
List<Activite> l = s.getActivites();
|
||||||
|
l.add(a);
|
||||||
|
s.setActivites(l);
|
||||||
|
sessionDAO.save(s);
|
||||||
|
return ResponseEntity.status(200).body(maptoDTO(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionDTO maptoDTO(Session s) {
|
||||||
|
SessionDTO dto = new SessionDTO();
|
||||||
|
dto.setId(s.getId());
|
||||||
|
dto.setName(s.getName());
|
||||||
|
dto.setIsRecurrent(s.getIsRecurrent());
|
||||||
|
dto.setCreneau(s.getCreneau());
|
||||||
|
dto.setDuree(s.getDuree());
|
||||||
|
dto.setGroupe(s.getGroupe());
|
||||||
|
// Coach
|
||||||
|
if (s.getCoach() != null) {
|
||||||
|
dto.setCoachId(s.getCoach().getId());
|
||||||
|
}
|
||||||
|
// Athletes
|
||||||
|
if (s.getAthletes() != null) {
|
||||||
|
List<Integer> athleteIds = new ArrayList<>();
|
||||||
|
for (Athlete athlete : s.getAthletes()) {
|
||||||
|
athleteIds.add(athlete.getId());
|
||||||
|
}
|
||||||
|
dto.setAthleteIds(athleteIds);
|
||||||
|
}
|
||||||
|
// Activites
|
||||||
|
if (s.getActivites() != null) {
|
||||||
|
List<Integer> activiteIds = new ArrayList<>();
|
||||||
|
for (Activite activite : s.getActivites()) {
|
||||||
|
activiteIds.add(activite.getId());
|
||||||
|
}
|
||||||
|
dto.setActiviteIds(activiteIds);
|
||||||
|
}
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Session maptoEntity(SessionDTO dto) {
|
||||||
|
Session session = new Session();
|
||||||
|
System.out.println("ID " + session.getId());
|
||||||
|
session.setName(dto.getName());
|
||||||
|
session.setIsRecurrent(dto.getIsRecurrent());
|
||||||
|
session.setCreneau(dto.getCreneau());
|
||||||
|
session.setDuree(dto.getDuree());
|
||||||
|
session.setGroupe(dto.getGroupe());
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package hackathon.FrisbYEE.rest;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
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 hackathon.FrisbYEE.jpa.dto.AthleteDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.dto.UserDTO;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.Athlete;
|
||||||
|
import hackathon.FrisbYEE.jpa.metier.User;
|
||||||
|
import hackathon.FrisbYEE.jpa.service.UserDAO;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/users")
|
||||||
|
@CrossOrigin(origins = "http://localhost:3000")
|
||||||
|
public class UserResource {
|
||||||
|
@Autowired
|
||||||
|
private UserDAO userDAO;
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère tous les utilisateurs")
|
||||||
|
@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")
|
||||||
|
@PreAuthorize("hasRole('admin') or hasRole('coach')")
|
||||||
|
public ResponseEntity<List<UserDTO>> all() {
|
||||||
|
List<User> users = userDAO.findAll();
|
||||||
|
List<UserDTO> dtos = new ArrayList<>();
|
||||||
|
for (User user : users) {
|
||||||
|
dtos.add(mapToDTO(user));
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(dtos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Récupère l'utilisateur ayant l'identifiant correspondant")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Récupération effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = UserDTO.class)))
|
||||||
|
})
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||||
|
public ResponseEntity<UserDTO> getById(@PathVariable Integer id) {
|
||||||
|
User user = userDAO.findById(id).get();
|
||||||
|
return ResponseEntity.ok(mapToDTO(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/keycloak/{keycloak_id}")
|
||||||
|
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
|
||||||
|
public ResponseEntity<UserDTO> getByKeycloakId(@PathVariable String keycloak_id) {
|
||||||
|
User user = userDAO.findByKeycloakId(keycloak_id).get();
|
||||||
|
return ResponseEntity.ok(mapToDTO(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserDTO mapToDTO(User user) {
|
||||||
|
UserDTO dto = new UserDTO();
|
||||||
|
dto.setId(user.getId());
|
||||||
|
dto.setId_keycloak(user.getKeycloakId());
|
||||||
|
dto.setName(user.getName());
|
||||||
|
dto.setPrenom(user.getPrenom());
|
||||||
|
dto.setRole(user.getRole());
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
spring.datasource.url=jdbc:postgresql://localhost:5432/frisbyee
|
spring.datasource.url=jdbc:postgresql://localhost:5432/frisbyee
|
||||||
spring.datasource.username=postgres
|
spring.datasource.username=frisbyee_user
|
||||||
spring.datasource.password=postgres
|
spring.datasource.password=secret
|
||||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
|
||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
||||||
spring.datasource.url=jdbc:postgresql://localhost:5432/frisbyee
|
server.port=8081
|
||||||
spring.datasource.username=frisbyee_user
|
server.servlet.context-path=/api
|
||||||
spring.datasource.password=secret
|
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/Frisbyee_realm
|
||||||
|
spring.security.oauth2.resourceserver.jwt.jwk-set-uri: http://localhost:8080/realms/Frisbyee_realm/protocol/openid-connect/certs
|
||||||
@@ -1,13 +1,27 @@
|
|||||||
package hackathon.FrisbYEE;
|
package hackathon.FrisbYEE;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.test.web.servlet.client.RestTestClient;
|
||||||
|
|
||||||
@SpringBootTest
|
import hackathon.FrisbYEE.rest.AthleteResource;
|
||||||
class FrisbYeeApplicationTests {
|
|
||||||
|
|
||||||
|
class FrisbYEEApplicationTests {
|
||||||
|
|
||||||
|
//Controller
|
||||||
|
private AthleteResource athleteResource;
|
||||||
|
private RestTestClient mockMvc;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
athleteResource = new AthleteResource();
|
||||||
|
mockMvc = RestTestClient.bindToController(athleteResource).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void testGetUsers() throws Exception {
|
||||||
|
//mockMvc.perform(get("/api/users"))
|
||||||
|
// .andExpect(status().isOk());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
19
backend.log
Normal file
19
backend.log
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
nohup: ignoring input
|
||||||
|
[INFO] Scanning for projects...
|
||||||
|
Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml
|
||||||
|
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml
|
||||||
|
Progress (1): 3.4 kB
|
||||||
|
Progress (1): 7.3 kB
|
||||||
|
Progress (2): 7.3 kB | 4.6 kB
|
||||||
|
Progress (2): 13 kB | 4.6 kB
|
||||||
|
Progress (2): 13 kB | 9.9 kB
|
||||||
|
Progress (2): 19 kB | 9.9 kB
|
||||||
|
Progress (2): 20 kB | 9.9 kB
|
||||||
|
Progress (2): 20 kB | 14 kB
|
||||||
|
|
||||||
|
Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml (20 kB at 8.9 kB/s)
|
||||||
|
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml (14 kB at 6.3 kB/s)
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] BUILD FAILURE
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] Total time: 5.475 s
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
services:
|
services:
|
||||||
keycloak:
|
keycloak:
|
||||||
container_name: baeldung-keycloak.openid-provider
|
container_name: baeldung-keycloak
|
||||||
image: quay.io/keycloak/keycloak:26.4
|
image: quay.io/keycloak/keycloak:26.4
|
||||||
command:
|
command:
|
||||||
- start-dev
|
- start-dev
|
||||||
- --import-realm
|
- --import-realm
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./keycloak/:/opt/keycloak/data/import/
|
- ./keycloak/:/opt/keycloak/data/import/
|
||||||
|
- keycloak_data:/opt/keycloak/data
|
||||||
|
- ./keycloak/themes:/opt/keycloak/themes
|
||||||
environment:
|
environment:
|
||||||
KEYCLOAK_ADMIN: admin
|
KEYCLOAK_ADMIN: admin
|
||||||
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
|
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
|
||||||
@@ -16,10 +18,6 @@ services:
|
|||||||
KC_HOSTNAME_URL: http://localhost:8080
|
KC_HOSTNAME_URL: http://localhost:8080
|
||||||
KC_HOSTNAME_ADMIN_URL: http://localhost:8080
|
KC_HOSTNAME_ADMIN_URL: http://localhost:8080
|
||||||
KC_HOSTNAME_STRICT_BACKCHANNEL: true
|
KC_HOSTNAME_STRICT_BACKCHANNEL: true
|
||||||
KC_HTTP_RELATIVE_PATH: /
|
|
||||||
KC_HTTP_ENABLED: true
|
|
||||||
KC_HEALTH_ENABLED: true
|
|
||||||
KC_METRICS_ENABLED: true
|
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -41,6 +39,6 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
keycloak_data:
|
||||||
|
|
||||||
version: "3.9"
|
version: "3.9"
|
||||||
|
|
||||||
|
|||||||
2213
front_end/package-lock.json
generated
2213
front_end/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-keycloak/web": "^3.4.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.1",
|
"@testing-library/react": "^16.3.1",
|
||||||
@@ -11,7 +12,11 @@
|
|||||||
"@types/node": "^16.18.126",
|
"@types/node": "^16.18.126",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"axios": "^1.13.2",
|
||||||
|
"bootstrap": "^5.3.8",
|
||||||
|
"keycloak-js": "^26.2.2",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
|
"react-bootstrap": "^2.10.10",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
|
|||||||
BIN
front_end/public/Frisbyee_logo.png
Normal file
BIN
front_end/public/Frisbyee_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 44 KiB |
7
front_end/public/keycloak.json
Normal file
7
front_end/public/keycloak.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"realm": "Frisbyee_realm",
|
||||||
|
"resource": "Frisbyee_client",
|
||||||
|
"clientId": "Frisbyee_client",
|
||||||
|
"auth-server-url": "http://localhost:8080",
|
||||||
|
"public-client": true
|
||||||
|
}
|
||||||
BIN
front_end/public/loading.png
Normal file
BIN
front_end/public/loading.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
@@ -1,38 +1,633 @@
|
|||||||
|
/* Variables de thème */
|
||||||
|
[data-theme='dark'] {
|
||||||
|
--tint0: #0a0e27;
|
||||||
|
--tint1: #1a1f3a;
|
||||||
|
--tint2: #232d4a;
|
||||||
|
--tint3: #2e3a59;
|
||||||
|
--tint4: #3d4a6f;
|
||||||
|
--tint5: #4a5a85;
|
||||||
|
--text: #f0f4f8;
|
||||||
|
--text2: #000000;
|
||||||
|
--disable: #02291d;
|
||||||
|
--green-primary: #10b981;
|
||||||
|
--green-secondary: #059669;
|
||||||
|
--green-dark: #047857;
|
||||||
|
--green-A-primary: #10b98130;
|
||||||
|
--green-A-secondary: #05966930;
|
||||||
|
--green-A-dark: #04785730;
|
||||||
|
--themeButtonColor: #00AAFF;
|
||||||
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||||
|
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
--blue-accent: #3b82f6;
|
||||||
|
--purple-accent: #a78bfa;
|
||||||
|
--cyan-accent: #06b6d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='light'] {
|
||||||
|
--tint0: #f8fafc;
|
||||||
|
--tint1: #f1f5f9;
|
||||||
|
--tint2: #e2e8f0;
|
||||||
|
--tint3: #cbd5e1;
|
||||||
|
--tint4: #b0bac4;
|
||||||
|
--tint5: #94a3b8;
|
||||||
|
--text: #0f172a;
|
||||||
|
--text2: #FFFFFF;
|
||||||
|
--disable: #02291d;
|
||||||
|
--green-primary: #10b981;
|
||||||
|
--green-secondary: #059669;
|
||||||
|
--green-dark: #047857;
|
||||||
|
--green-A-primary: #10b98125;
|
||||||
|
--green-A-secondary: #05966925;
|
||||||
|
--green-A-dark: #04785725;
|
||||||
|
--themeButtonColor: #f59e0b;
|
||||||
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.15);
|
||||||
|
--blue-accent: #3b82f6;
|
||||||
|
--purple-accent: #a855f7;
|
||||||
|
--cyan-accent: #06b6d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset et base */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
background: linear-gradient(135deg, var(--tint0) 0%, var(--tint1) 100%);
|
||||||
|
color: var(--text);
|
||||||
|
transition: background 0.4s ease, color 0.4s ease;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding {
|
||||||
|
padding: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
background-color: var(--tint2);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--green-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.App {
|
.App {
|
||||||
text-align: center;
|
display: grid;
|
||||||
|
padding: 20px;
|
||||||
|
gap: 24px;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-logo {
|
.App h1 {
|
||||||
height: 40vmin;
|
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||||
pointer-events: none;
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, var(--green-primary) 0%, var(--cyan-accent) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
/* Container principal */
|
||||||
.App-logo {
|
.app-container {
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(180deg, var(--tint0) 0%, var(--tint1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.composant-padding {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composant-container {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.composant-container:hover {
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
box-shadow: 0 12px 40px var(--green-A-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header / Navigation */
|
||||||
|
.app-header {
|
||||||
|
background-color: var(--tint1);
|
||||||
|
border-bottom: 2px solid var(--green-primary);
|
||||||
|
padding: 16px 24px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
gap: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
}
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
|
.app-nav a,
|
||||||
|
.nav-link {
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-nav a:hover,
|
||||||
|
.nav-link:hover {
|
||||||
|
background-color: var(--tint3);
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-nav a.active,
|
||||||
|
.nav-link.active {
|
||||||
|
background-color: var(--green-primary);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-link {
|
/* Cards et containers */
|
||||||
color: #61dafb;
|
.card {
|
||||||
|
background: linear-gradient(135deg, var(--tint1) 0%, var(--tint2) 100%);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 24px;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
.card:hover {
|
||||||
from {
|
border-color: var(--green-primary);
|
||||||
transform: rotate(0deg);
|
box-shadow: 0 8px 24px var(--green-A-primary);
|
||||||
|
transform: translateY(-4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
border-bottom: 2px solid var(--green-primary);
|
||||||
|
padding-bottom: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inputs */
|
||||||
|
input[type="text"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="date"],
|
||||||
|
input[type="time"],
|
||||||
|
input[type="search"],
|
||||||
|
input[type="datetime-local"],
|
||||||
|
textarea {
|
||||||
|
background-color: var(--tint2);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
font-size: 15px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
background-color: var(--tint1);
|
||||||
|
box-shadow: 0 0 0 3px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Conteneur de boutons inline */
|
||||||
|
.button-group,
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
color: var(--tint5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select */
|
||||||
|
select {
|
||||||
|
color: var(--text);
|
||||||
|
background-color: var(--tint3);
|
||||||
|
border: 2px solid var(--tint5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
box-shadow: 0 0 0 3px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
select option {
|
||||||
|
background-color: var(--tint2);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
button {
|
||||||
|
color: var(--text);
|
||||||
|
background: linear-gradient(135deg, var(--tint3) 0%, var(--tint4) 100%);
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: linear-gradient(135deg, var(--green-primary) 0%, var(--green-secondary) 100%);
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background-color: var(--disable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton primaire */
|
||||||
|
.btn-primary,
|
||||||
|
button.primary {
|
||||||
|
background: linear-gradient(135deg, var(--green-primary), var(--green-secondary));
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 4px 6px var(--green-A-primary);
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover,
|
||||||
|
button.primary:hover {
|
||||||
|
background: linear-gradient(135deg, var(--green-secondary), var(--green-dark));
|
||||||
|
box-shadow: 0 6px 12px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton secondaire */
|
||||||
|
.btn-secondary,
|
||||||
|
button.secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--green-primary);
|
||||||
|
border: 2px solid var(--green-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover,
|
||||||
|
button.secondary:hover {
|
||||||
|
background-color: var(--green-primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton supprimer */
|
||||||
|
.deleteButton,
|
||||||
|
button.delete,
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #dc2626;
|
||||||
|
border: 2px solid #991b1b;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteButton:hover,
|
||||||
|
button.delete:hover,
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #b91c1c;
|
||||||
|
border-color: #7f1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton ajouter */
|
||||||
|
.addButton,
|
||||||
|
button.add,
|
||||||
|
.btn-success {
|
||||||
|
background-color: var(--green-primary);
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton:hover,
|
||||||
|
button.add:hover,
|
||||||
|
.btn-success:hover {
|
||||||
|
background-color: var(--green-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal {
|
||||||
|
background-color: var(--tint2);
|
||||||
|
padding: 20px;
|
||||||
|
min-width: 300px;
|
||||||
|
min-height: 150px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 2px solid var(--green-primary);
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: 2px solid var(--green-primary);
|
||||||
|
padding-bottom: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background-color: var(--tint4);
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background-color: #dc2626;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading */
|
||||||
|
.loading {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid var(--tint5);
|
||||||
|
border-top-color: var(--green-primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top_left_loading {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center_loading {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background-color: var(--tint3);
|
||||||
|
border-bottom: 2px solid var(--green-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid var(--tint4);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Lists */
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
color: var(--text);
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
a {
|
||||||
|
color: var(--green-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--green-secondary);
|
||||||
|
border-bottom-color: var(--green-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges et tags */
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: var(--tint3);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--tint5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
background-color: rgba(16, 185, 129, 0.2);
|
||||||
|
color: var(--green-primary);
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-danger {
|
||||||
|
background-color: rgba(220, 38, 38, 0.2);
|
||||||
|
color: #ef4444;
|
||||||
|
border-color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
background-color: rgba(245, 158, 11, 0.2);
|
||||||
|
color: #f59e0b;
|
||||||
|
border-color: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alerts */
|
||||||
|
.alert {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 12px 0;
|
||||||
|
border-left: 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background-color: rgba(16, 185, 129, 0.15);
|
||||||
|
border-left-color: var(--green-primary);
|
||||||
|
color: #6ee7b7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background-color: rgba(220, 38, 38, 0.15);
|
||||||
|
border-left-color: #ef4444;
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background-color: rgba(245, 158, 11, 0.15);
|
||||||
|
border-left-color: #f59e0b;
|
||||||
|
color: #fcd34d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background-color: rgba(59, 130, 246, 0.15);
|
||||||
|
border-left-color: #3b82f6;
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--tint2);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--tint5);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--green-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox et radio */
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
accent-color: var(--green-primary);
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up {
|
||||||
|
animation: slideUp 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.card {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
min-width: 280px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,37 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import logo from './logo.svg';
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import { ReactKeycloakProvider } from '@react-keycloak/web'
|
||||||
|
import keycloak from './keycloak'
|
||||||
|
import Login from './components/login';
|
||||||
|
import { LocalDataProvider } from './provider/LocalDataProvider';
|
||||||
|
import EDT from './components/edt';
|
||||||
|
import SwitchThemeColor from './components/SwitchThemeColor';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const keycloakInitOptions = {
|
||||||
|
onLoad: 'login-required',
|
||||||
|
checkLoginIframe: false
|
||||||
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
|
<ReactKeycloakProvider authClient={keycloak} /*initOptions={keycloakInitOptions}*/>
|
||||||
|
<LocalDataProvider>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
<TopBar/>
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<h1>Frisbyee</h1>
|
||||||
<p>
|
<RessourcePanel/>
|
||||||
Edit <code>src/App.tsx</code> and save to reload.
|
<EDT/>
|
||||||
</p>
|
<CreateSession/>
|
||||||
<a
|
<TestAPI/>
|
||||||
className="App-link"
|
|
||||||
href="https://reactjs.org"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Learn React
|
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
</div>
|
</div>
|
||||||
|
</LocalDataProvider>
|
||||||
|
</ReactKeycloakProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
105
front_end/src/api.ts
Normal file
105
front_end/src/api.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
import keycloak from "./keycloak";
|
||||||
|
import { get } from "http";
|
||||||
|
import { ActiviteDTO, AdminDTO, AthleteDTO, CoachDTO, SessionDTO } from "./classesDTO";
|
||||||
|
|
||||||
|
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: "http://localhost:8081/api",
|
||||||
|
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
|
||||||
|
config.headers.Authorization = `Bearer ${keycloak.token}`;
|
||||||
|
}
|
||||||
|
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 = {
|
||||||
|
// controller is mounted at /athlete
|
||||||
|
create: (data: any) => api.post<AthleteDTO>("/athlete/create", data),
|
||||||
|
getAll: () => api.get<AthleteDTO[]>("/athlete/all"),
|
||||||
|
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`),
|
||||||
|
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`),
|
||||||
|
};
|
||||||
|
|
||||||
|
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: ActiviteDTO) => api.post(`/activite/update/${id}`, data),
|
||||||
|
getById: (id: number | string) => api.get(`/activite/${id}`),
|
||||||
|
getAll: () => api.get<ActiviteDTO[]>(`/activite/all`),
|
||||||
|
getByTheme: (theme: string) => api.get(`/activite/theme/${encodeURIComponent(theme)}`),
|
||||||
|
getDataActivite: (id: number | string) => api.get(`/activite/${id}`),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sessionService = {
|
||||||
|
// controller uses singular /session/* endpoints
|
||||||
|
create: (data: any) => api.post(`/session/create`, data),
|
||||||
|
getAll: () => api.get<SessionDTO[]>(`/session/all`),
|
||||||
|
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),
|
||||||
|
|
||||||
|
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`),
|
||||||
|
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),
|
||||||
|
getAll: () => api.get<CoachDTO[]>(`/coach/all`),
|
||||||
|
getById: (id: number) => api.get(`/coach/${id}`),
|
||||||
|
getByKeycloakId: (keycloakId: string) => api.get(`/coach/keycloak/${keycloakId}`),
|
||||||
|
update: (id: number | string, data: any) => api.put(`/coach/update/${id}`, data),
|
||||||
|
delete: (id: number | string) => api.delete(`/coach/delete/${id}`),
|
||||||
|
getSessionsForCoach: (coachId: number | null) => api.get<SessionDTO[]>(`/coach/${coachId}/session`),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userService = {
|
||||||
|
getById: (id: number) => api.get(`/users/${id}`),
|
||||||
|
getByKeycloakId: (keycloak_id: string) => api.get(`/users/keycloak/${keycloak_id}`),
|
||||||
|
getAll: () => api.get(`/users/all`),
|
||||||
|
sync: () => api.post(`/users/sync`),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const adminService = {
|
||||||
|
getByKeycloakId: (keycloak_id: string) => api.get(`/admin/keycloak/${keycloak_id}`),
|
||||||
|
getById: (id: number | string) => api.get(`/admin/${id}`),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default api;
|
||||||
333
front_end/src/classes.tsx
Normal file
333
front_end/src/classes.tsx
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
import { ActiviteDTO, AdminDTO, AthleteDTO, CoachDTO, SessionDTO } from "./classesDTO";
|
||||||
|
|
||||||
|
export type Groupe = "Entrainement" | "Competition" | "Loisir"| "";
|
||||||
|
|
||||||
|
|
||||||
|
export class Ligne{
|
||||||
|
id: number|null = null;
|
||||||
|
nom!: string;
|
||||||
|
composition!: Athlete[] //les joueurs compososant la ligne
|
||||||
|
tempsDeJeu!: number; // en minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
export class User{
|
||||||
|
id: number|null = null;
|
||||||
|
keycloakId!: string;
|
||||||
|
nom!: string;
|
||||||
|
prenom!:string;
|
||||||
|
email!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class Admin extends User{
|
||||||
|
|
||||||
|
constructor(dto:AdminDTO);
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
constructor(dto?:AdminDTO){
|
||||||
|
super();
|
||||||
|
this.id = dto?.id ?? null;
|
||||||
|
this.keycloakId = dto?.id_keycloak ?? "";
|
||||||
|
this.nom = dto?.name ?? "";
|
||||||
|
this.prenom = dto?.prenom ?? "";
|
||||||
|
this.email = ""; //TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
toDTO():AdminDTO{
|
||||||
|
const dto:AdminDTO = {
|
||||||
|
id: this.id,
|
||||||
|
id_keycloak: this.keycloakId,
|
||||||
|
name: this.nom,
|
||||||
|
prenom: this.prenom,
|
||||||
|
};
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Athlete extends User{
|
||||||
|
groupes!: Groupe[];
|
||||||
|
sessionsID!: number[];
|
||||||
|
sessions: Session[] = [];
|
||||||
|
|
||||||
|
constructor(dto:AthleteDTO);
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
constructor(dto?:AthleteDTO){
|
||||||
|
super();
|
||||||
|
this.id = dto?.id ?? 0;
|
||||||
|
this.keycloakId = dto?.id_keycloak ?? "";
|
||||||
|
this.nom = dto?.name ?? "";
|
||||||
|
this.prenom = dto?.prenom ?? "" ;
|
||||||
|
this.email = ""; //TODO
|
||||||
|
this.groupes = dto?.groupes ?? [];
|
||||||
|
this.sessionsID = dto?.sessionIds ?? [];
|
||||||
|
this.sessions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
toDTO():AthleteDTO{
|
||||||
|
const dto:AthleteDTO = {
|
||||||
|
id: this.id,
|
||||||
|
id_keycloak: this.keycloakId,
|
||||||
|
name: this.nom,
|
||||||
|
prenom: this.prenom,
|
||||||
|
categorie: "",
|
||||||
|
niveau: "",
|
||||||
|
groupes: this.groupes,
|
||||||
|
sessionIds: this.sessionsID,
|
||||||
|
};
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Coach extends User{
|
||||||
|
sessionsID!: number[];
|
||||||
|
sessions: Session[] = [];
|
||||||
|
|
||||||
|
constructor(dto:CoachDTO);
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
constructor(dto?:CoachDTO){
|
||||||
|
super();
|
||||||
|
this.id = dto?.id ?? 0;
|
||||||
|
this.keycloakId = dto?.id_keycloak ?? "";
|
||||||
|
this.nom = dto?.name ?? "";
|
||||||
|
this.prenom = dto?.prenom ?? "";
|
||||||
|
this.email = ""; //TODO
|
||||||
|
this.sessionsID = dto?.sessionIds ?? [];
|
||||||
|
this.sessions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
toDTO():CoachDTO{
|
||||||
|
const dto:CoachDTO = {
|
||||||
|
id: this.id,
|
||||||
|
id_keycloak: this.keycloakId,
|
||||||
|
name: this.nom,
|
||||||
|
prenom: this.prenom,
|
||||||
|
sessionIds: this.sessionsID ,
|
||||||
|
};
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Session{
|
||||||
|
id: number|null = null;
|
||||||
|
name!: string;
|
||||||
|
activitesID: number[] = [];
|
||||||
|
activites: Activite[] = [];
|
||||||
|
isRecurrent! : boolean;
|
||||||
|
creneau!: Date;
|
||||||
|
coach!: Coach;
|
||||||
|
athletesID!: number[]
|
||||||
|
athletes!: Athlete[]
|
||||||
|
duree! : number;
|
||||||
|
groupe! : Groupe;
|
||||||
|
ligne! : Ligne[];
|
||||||
|
|
||||||
|
constructor(dto:SessionDTO);
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
constructor(dto?:SessionDTO){
|
||||||
|
this.id = dto?.id?? 0;
|
||||||
|
this.name = dto?.name?? "";
|
||||||
|
this.activitesID = dto?.activiteIds?? [];
|
||||||
|
this.activites = [];
|
||||||
|
this.isRecurrent = dto?.isRecurrent?? false;
|
||||||
|
this.creneau = dto? new Date(dto.creneau) : new Date();
|
||||||
|
//this.coach = new Coach(); //dto.coachId; //TODO
|
||||||
|
this.athletesID = dto?.athleteIds ?? [];
|
||||||
|
this.athletes = [];
|
||||||
|
this.duree = dto?.duree ?? 0;
|
||||||
|
this.groupe = dto?.groupe ?? "";
|
||||||
|
this.ligne = []; //TODO
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
toDTO():SessionDTO{
|
||||||
|
const dto:SessionDTO = {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
isRecurrent: this.isRecurrent,
|
||||||
|
creneau: this.creneau.toISOString(),
|
||||||
|
duree: this.duree,
|
||||||
|
groupe: "", //TODO
|
||||||
|
coachId: this.coach?.id ?? null,
|
||||||
|
athleteIds: [],
|
||||||
|
activiteIds: []
|
||||||
|
};
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Activite{
|
||||||
|
id: number|null = null;
|
||||||
|
nom!: string;
|
||||||
|
session!: Session;
|
||||||
|
theme!: string;
|
||||||
|
data!: Map<string,string>;
|
||||||
|
duree!: number;
|
||||||
|
|
||||||
|
constructor(dto:ActiviteDTO);
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
constructor(dto?:ActiviteDTO){
|
||||||
|
this.id = dto?.id ?? 0;
|
||||||
|
this.nom = dto?.name ?? "";
|
||||||
|
//this.session = dto.sessionId; //TODO
|
||||||
|
this.theme = dto?.theme ?? "";
|
||||||
|
//this.data = //TODO
|
||||||
|
this.duree = dto?.duree ?? 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
toDTO():ActiviteDTO{
|
||||||
|
const dto:ActiviteDTO = {
|
||||||
|
id: this.id,
|
||||||
|
name: this.nom,
|
||||||
|
duree: this.duree,
|
||||||
|
dataActivite: [],
|
||||||
|
sessionId: this.session.id,
|
||||||
|
theme: this.theme
|
||||||
|
};
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
export function getUserTest():User{
|
||||||
|
const user = new Athlete();
|
||||||
|
const s1 = new Session();
|
||||||
|
const s2 = new Session();
|
||||||
|
const s3 = new Session();
|
||||||
|
const athlete1 = new Athlete();
|
||||||
|
athlete1.id = 1;
|
||||||
|
athlete1.nom = "Alice Dupont";
|
||||||
|
athlete1.email = "alice@nootnoot.yee";
|
||||||
|
athlete1.groupes = ["Entrainement"];
|
||||||
|
|
||||||
|
const athlete2 = new Athlete();
|
||||||
|
athlete2.id = 2;
|
||||||
|
athlete2.nom = "Bob Martin";
|
||||||
|
athlete2.groupes = ["Competition"];
|
||||||
|
|
||||||
|
const athlete3 = new Athlete();
|
||||||
|
athlete3.id = 3;
|
||||||
|
athlete3.nom = "Clara Lopez";
|
||||||
|
athlete3.groupes = ["Loisir"];
|
||||||
|
|
||||||
|
const ligne1 = new Ligne();
|
||||||
|
ligne1.id = 1;
|
||||||
|
ligne1.nom = "Ligne A";
|
||||||
|
ligne1.composition = [athlete1, athlete2]; // Alice + Bob
|
||||||
|
ligne1.tempsDeJeu = 45;
|
||||||
|
|
||||||
|
const ligne2 = new Ligne();
|
||||||
|
ligne2.id = 2;
|
||||||
|
ligne2.nom = "Ligne B";
|
||||||
|
ligne2.composition = [athlete2, athlete3]; // Bob + Clara
|
||||||
|
ligne2.tempsDeJeu = 40;
|
||||||
|
|
||||||
|
const ligne3 = new Ligne();
|
||||||
|
ligne3.id = 3;
|
||||||
|
ligne3.nom = "Ligne C";
|
||||||
|
ligne3.composition = [athlete1, athlete3]; // Alice + Clara
|
||||||
|
ligne3.tempsDeJeu = 50;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
user.id = 0;
|
||||||
|
user.nom = "Emilien-Yee NootNoot";
|
||||||
|
user.email = "emilien@nootnoot.yee";
|
||||||
|
s1.creneau = new Date();
|
||||||
|
s1.id = 1;
|
||||||
|
s1.name = "Entrainement Frisbee"
|
||||||
|
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;
|
||||||
|
s2.id = 2;
|
||||||
|
s2.isRecurrent = false;
|
||||||
|
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;
|
||||||
|
s3.athletes = [athlete2, 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);
|
||||||
|
s2.activites.push(act5);
|
||||||
|
s2.activites.push(act6);
|
||||||
|
|
||||||
|
user.sessions.push(s1);
|
||||||
|
user.sessions.push(s2);
|
||||||
|
user.sessions.push(s3);
|
||||||
|
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
*/
|
||||||
50
front_end/src/classesDTO.tsx
Normal file
50
front_end/src/classesDTO.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Groupe } from "./classes";
|
||||||
|
|
||||||
|
export type ActiviteDTO = {
|
||||||
|
id: number|null;
|
||||||
|
name: string;
|
||||||
|
theme: string;
|
||||||
|
duree: number;
|
||||||
|
dataActivite: string[];
|
||||||
|
sessionId: number|null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AdminDTO = {
|
||||||
|
id: number|null;
|
||||||
|
id_keycloak: string;
|
||||||
|
name: string;
|
||||||
|
prenom: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AthleteDTO = {
|
||||||
|
id:number|null;
|
||||||
|
id_keycloak: string;
|
||||||
|
name: string;
|
||||||
|
prenom: string;
|
||||||
|
categorie: string;
|
||||||
|
niveau: string;
|
||||||
|
groupes: Groupe[];
|
||||||
|
sessionIds: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CoachDTO = {
|
||||||
|
id: number|null;
|
||||||
|
id_keycloak: string;
|
||||||
|
name: string;
|
||||||
|
prenom: string;
|
||||||
|
sessionIds: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SessionDTO = {
|
||||||
|
id: number|null;
|
||||||
|
name: string;
|
||||||
|
isRecurrent: boolean;
|
||||||
|
creneau: string; // LocalDateTime → ISO string
|
||||||
|
duree: number;
|
||||||
|
groupe: Groupe;
|
||||||
|
|
||||||
|
coachId: number|null;
|
||||||
|
athleteIds: number[];
|
||||||
|
activiteIds: number[];
|
||||||
|
};
|
||||||
|
|
||||||
34
front_end/src/components/Modal.tsx
Normal file
34
front_end/src/components/Modal.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {useEffect } from "react"
|
||||||
|
|
||||||
|
type ModalProps = {
|
||||||
|
isOpen: boolean
|
||||||
|
onClose: () => void
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
78
front_end/src/components/StatsAthlete.tsx
Normal file
78
front_end/src/components/StatsAthlete.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Athlete, Session } from "../classes";
|
||||||
|
import { calculStatsAthlete, niveauAlerte, StatsAthlete } from "../utils/athleteUtils"
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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(athlete.sessions, athlete, dateDebut, dateFin);
|
||||||
|
setStats(statsCalculees);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="creneau-stats">
|
||||||
|
<div>Nb Session : {athlete.sessions.length};</div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Début :</th>
|
||||||
|
<th><input type="datetime-local" value={dateToDatetimeLocal(dateDebut)} onChange={e => setDateDebut(new Date(e.target.value))}/></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Fin :</th>
|
||||||
|
<th><input type="datetime-local" value={dateToDatetimeLocal(dateFin)} onChange={e => setDateFin(new Date(e.target.value))}/></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Seuil critique :</th>
|
||||||
|
<th><input type="number" value={seuilCritique} min={1} onChange={e => setSeuilCritique(Number(e.target.value))}/></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Seuil max :</th>
|
||||||
|
<th><input type="number" value={seuilMax} min={1} onChange={e => setSeuilMax(Number(e.target.value))}/></th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<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;
|
||||||
37
front_end/src/components/SwitchThemeColor.tsx
Normal file
37
front_end/src/components/SwitchThemeColor.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { useState,useEffect } from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
const SwitchThemeColor = () => {
|
||||||
|
const [theme, setTheme] = useState<'light' | 'dark'>('dark');
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Inverse le thème actuel
|
||||||
|
*/
|
||||||
|
function switchTheme(){
|
||||||
|
const currentTheme = document.documentElement.getAttribute('data-theme');
|
||||||
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light'
|
||||||
|
//const newTheme = window.matchMedia("(prefers-color-scheme: dark)").matches? "dark": "light";
|
||||||
|
setTheme(newTheme)
|
||||||
|
document.documentElement.setAttribute('data-theme', newTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
détecte automatiquement le thème du navigateur au démarrage
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const newTheme = window.matchMedia("(prefers-color-scheme: dark)").matches? "dark": "light";
|
||||||
|
setTheme(newTheme)
|
||||||
|
document.documentElement.setAttribute('data-theme', newTheme);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button className="ButtonTheme" onClick={switchTheme}>
|
||||||
|
{theme === 'light' ? '𖤓' : '☾'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SwitchThemeColor;
|
||||||
77
front_end/src/components/createActivite.tsx
Normal file
77
front_end/src/components/createActivite.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { Session, User, Coach, Activite, Groupe } from "../classes";
|
||||||
|
import { Modal } from "./Modal";
|
||||||
|
|
||||||
|
type CreateActiciteProps = {
|
||||||
|
returnActivite: (activite:Activite|null) => void
|
||||||
|
session: Session;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function CreateActivite({ returnActivite,session }: CreateActiciteProps){
|
||||||
|
|
||||||
|
const [activities, setActivities] = useState<Activite[]>([]);
|
||||||
|
const [activiteNom, setActiviteNom] = useState("");
|
||||||
|
const [activiteTheme, setActiviteTheme] = useState("");
|
||||||
|
const [activiteDuree, setActiviteDuree] = useState(0);
|
||||||
|
|
||||||
|
function addAcitivte(){
|
||||||
|
if (!activiteNom) return;
|
||||||
|
|
||||||
|
const newActivite = new Activite();
|
||||||
|
newActivite.nom= activiteNom;
|
||||||
|
newActivite.theme=activiteTheme;
|
||||||
|
newActivite.duree= activiteDuree;
|
||||||
|
newActivite.data= new Map<string,string>();
|
||||||
|
setActivities([...activities, newActivite]);
|
||||||
|
|
||||||
|
setActiviteNom("");
|
||||||
|
setActiviteTheme("");
|
||||||
|
setActiviteDuree(0);
|
||||||
|
|
||||||
|
returnActivite(newActivite);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel(){
|
||||||
|
returnActivite(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} onClose={() => cancel()}>
|
||||||
|
<div className="create_activite_modal">
|
||||||
|
<h2>Nouvelle Activité :</h2>
|
||||||
|
<div>Session : {session.name}</div>
|
||||||
|
<div>
|
||||||
|
Nom de l'activité:
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="text" value={activiteNom} onChange={e => setActiviteNom(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Theme:
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="text" value={activiteTheme} onChange={e => setActiviteTheme(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Duree (minutes):
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="number" value={activiteDuree} onChange={e => setActiviteDuree(Number(e.target.value))} />
|
||||||
|
</div>
|
||||||
|
<button type="button" onClick={()=>addAcitivte()}>Ajouter l'activite</button>
|
||||||
|
<button type="button" onClick={()=>cancel()}>Annuler</button>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{activities.map((act, idx) => (
|
||||||
|
<li key={idx}>{act.nom} - {act.theme} ({act.duree} min)</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateActivite;
|
||||||
123
front_end/src/components/createSession.tsx
Normal file
123
front_end/src/components/createSession.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { Session, User, Coach, Activite, Groupe } from "../classes";
|
||||||
|
import { useLocalData } from "../context/useLocalData";
|
||||||
|
import { activiteService, sessionService } from "../api";
|
||||||
|
import { createSessionAPI, postSession } from "../requetes";
|
||||||
|
import './style/createSession.css';
|
||||||
|
|
||||||
|
export const CreateSession = () => {
|
||||||
|
const {user} = useLocalData();
|
||||||
|
const [session,setSession] = useState<Session>(new Session());
|
||||||
|
const [activities, setActivities] = useState<Activite[]>([]);
|
||||||
|
const [name,setName] = useState("");
|
||||||
|
const [groupe, setGroupe] = useState<Groupe>("");
|
||||||
|
const [creneau, setCreneau] = useState<Date>(new Date());
|
||||||
|
const [duree, setDuree] = useState<number>(0);
|
||||||
|
const [activiteNom, setActiviteNom] = useState("");
|
||||||
|
const [activiteTheme, setActiviteTheme] = useState("");
|
||||||
|
const [activiteDuree, setActiviteDuree] = useState(0);
|
||||||
|
const [isRecurent, setIsRecurent] = useState(false);
|
||||||
|
|
||||||
|
async function addAcitivte(){
|
||||||
|
if (!activiteNom) return;
|
||||||
|
|
||||||
|
const newActivite = new Activite();
|
||||||
|
newActivite.nom= activiteNom;
|
||||||
|
newActivite.theme=activiteTheme;
|
||||||
|
newActivite.duree= activiteDuree;
|
||||||
|
newActivite.data= new Map<string,string>();
|
||||||
|
|
||||||
|
setActivities([...activities, newActivite]);
|
||||||
|
session.activites.push(newActivite);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCreateSession() {
|
||||||
|
if(user instanceof Coach){
|
||||||
|
session.name = name;
|
||||||
|
session.groupe = groupe;
|
||||||
|
session.creneau = creneau;
|
||||||
|
session.duree = duree;
|
||||||
|
session.isRecurrent = isRecurent;
|
||||||
|
session.activites = activities;
|
||||||
|
session.coach = user;
|
||||||
|
|
||||||
|
const newSession = await createSessionAPI(session);
|
||||||
|
if(newSession!==null){
|
||||||
|
console.log("Session créée");
|
||||||
|
}
|
||||||
|
else console.error("Erreur lors de la création de la session");
|
||||||
|
|
||||||
|
// reset
|
||||||
|
setName("");
|
||||||
|
setGroupe("");
|
||||||
|
setCreneau(new Date());
|
||||||
|
setDuree(0);
|
||||||
|
setIsRecurent(false);
|
||||||
|
setActivities([]);
|
||||||
|
|
||||||
|
setSession(new Session());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="composant-container">
|
||||||
|
<h2>Créer une session</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Nom:</th>
|
||||||
|
<th><input type="text" value={name} onChange={e => setName(e.target.value)} /></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Groupe:</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Creneau:</th>
|
||||||
|
<th><input type="datetime-local" value={creneau.toISOString().slice(0, 16)} onChange={e => setCreneau(new Date(e.target.value))} /></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Duree (minutes):</th>
|
||||||
|
<th><input type="number" value={duree} onChange={e => setDuree(Number(e.target.value))} /></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Recurrent:</th>
|
||||||
|
<th><input type="checkbox" checked={isRecurent} onChange={e => setIsRecurent(e.target.checked)} /></th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div className="createActivite">
|
||||||
|
<h3>Ajouter une activité : </h3>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Nom de l'activitée:</th>
|
||||||
|
<th><input type="text" value={activiteNom} onChange={e => setActiviteNom(e.target.value)} /></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Theme:</th>
|
||||||
|
<th><input type="text" value={activiteTheme} onChange={e => setActiviteTheme(e.target.value)} /></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Duree (minutes):</th>
|
||||||
|
<th><input type="number" value={activiteDuree} onChange={e => setActiviteDuree(Number(e.target.value))} /></th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<button type="button" onClick={addAcitivte}>Ajouter</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{activities.map((act, idx) => (
|
||||||
|
<li key={idx}>{act.nom} - {act.theme} ({act.duree} min)</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<button type="button" onClick={handleCreateSession}>Create Session</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateSession;
|
||||||
161
front_end/src/components/edt.tsx
Normal file
161
front_end/src/components/edt.tsx
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { Athlete, Coach, Session} from "../classes"
|
||||||
|
import { useLocalData } from "../context/useLocalData"
|
||||||
|
import './style/edt.css';
|
||||||
|
import {getSessionsOfUserAPI } 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" : "";
|
||||||
|
const mm_prefix = date.getMonth()+1<10 ? "0" : "";
|
||||||
|
const dd:String = dd_prefix+date.getDate().toString();
|
||||||
|
const mm:String = mm_prefix+(date.getMonth()+1).toString();
|
||||||
|
const yyyy:String = date.getFullYear().toString();
|
||||||
|
return dd+"/"+mm+"/"+yyyy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hoursToString(date:Date){
|
||||||
|
const hh_prefix = date.getHours()<10 ? "0" : "";
|
||||||
|
const mm_prefix = date.getMinutes()+1<10 ? "0" : "";
|
||||||
|
const hh:String = hh_prefix+date.getHours().toString();
|
||||||
|
const mm:String = mm_prefix+date.getMinutes().toString();
|
||||||
|
return hh+"h"+mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)){
|
||||||
|
newWeek.push(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
newWeek.sort((a, b) =>a.creneau.getHours() * 60 + a.creneau.getMinutes() -(b.creneau.getHours() * 60 + b.creneau.getMinutes()));
|
||||||
|
setSessions(newWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeWeek(date:Date){
|
||||||
|
setWeek(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameDay(date1:Date,date2:Date){
|
||||||
|
return (
|
||||||
|
date1.getDay()===date2.getDay() &&
|
||||||
|
date1.getMonth()===date2.getMonth() &&
|
||||||
|
date1.getFullYear()===date2.getFullYear());
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
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(week:Date){
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
setLoadedWeek(week);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handlePrev(): void {
|
||||||
|
changeWeek(getNextDay(week,-7));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNext(): void {
|
||||||
|
changeWeek(getNextDay(week,7));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFirstDay(date:Date):Date{
|
||||||
|
const numWeek = date.getDay();
|
||||||
|
var firstDate:Date;
|
||||||
|
if(numWeek == 0){
|
||||||
|
firstDate = getNextDay(date,-6);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
firstDate = getNextDay(date,-numWeek+1);
|
||||||
|
}
|
||||||
|
return firstDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextDay(date:Date,nb:number):Date{
|
||||||
|
var newDate = new Date(date);
|
||||||
|
newDate.setDate(newDate.getDate() + nb);
|
||||||
|
return newDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameDay(d1:Date,d2:Date):boolean{
|
||||||
|
return (
|
||||||
|
d1.getDate() === d2.getDate() &&
|
||||||
|
d1.getMonth() === d2.getMonth() &&
|
||||||
|
d1.getFullYear() === d2.getFullYear()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
loadSessions(week)
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className="composant-container">
|
||||||
|
<div className="edt_header">
|
||||||
|
<button className="edt_button_week_select" onClick={() =>handlePrev()}>Prev</button>
|
||||||
|
<button onClick={()=>refresh()}>Actualiser</button>
|
||||||
|
<button className="edt_button_week_select" onClick={() => handleNext()}>Next</button>
|
||||||
|
</div>
|
||||||
|
<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_day_header ${sameDay(getNextDay(week, index), new Date()) ? "today" : ""}`}>
|
||||||
|
<div> {week_days[index]} </div>
|
||||||
|
<div className="edt_date"> {dateToString(getNextDay(week,index))} </div>
|
||||||
|
</div>
|
||||||
|
<div className="edt_day_content">
|
||||||
|
{sessions.map((session,index2)=>(
|
||||||
|
session.creneau.getDay()===num &&
|
||||||
|
<EdtSession session={session}/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EDT
|
||||||
50
front_end/src/components/edt_coach.tsx
Normal file
50
front_end/src/components/edt_coach.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { coachService } from "../api";
|
||||||
|
|
||||||
|
export default function EdtCoach() {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [statusCode, setStatusCode] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const handleCreate = async () => {
|
||||||
|
try {
|
||||||
|
const response = await coachService.create({ name });
|
||||||
|
console.log("Success:", response.status, response.data);
|
||||||
|
alert(`Coach created! Status: ${response.status}`);
|
||||||
|
setError("");
|
||||||
|
setStatusCode(response.status);
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.response) {
|
||||||
|
// This is the HTTP response from the server
|
||||||
|
console.error("HTTP status:", err.response.status);
|
||||||
|
console.error("Response data:", err.response.data);
|
||||||
|
setError(`Failed to create coach: ${err.response.data}`);
|
||||||
|
setStatusCode(err.response.status);
|
||||||
|
} else if (err.request) {
|
||||||
|
console.error("No response received", err.request);
|
||||||
|
setError("No response from server!");
|
||||||
|
setStatusCode(null);
|
||||||
|
} else {
|
||||||
|
console.error("Error", err.message);
|
||||||
|
setError(err.message);
|
||||||
|
setStatusCode(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Create Coach</h2>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Coach name"
|
||||||
|
/>
|
||||||
|
<button onClick={handleCreate}>Create</button>
|
||||||
|
|
||||||
|
{error && <p style={{ color: "red" }}>{error}</p>}
|
||||||
|
{statusCode && <p>HTTP Status: {statusCode}</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
front_end/src/components/edt_session.tsx
Normal file
41
front_end/src/components/edt_session.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Activite, Session } from '../classes';
|
||||||
|
import { dateToString, hoursToString } from './edt';
|
||||||
|
import './style/edt.css';
|
||||||
|
import { Modal } from './Modal';
|
||||||
|
import Loading from './loading';
|
||||||
|
import {delay} from "../requetes";
|
||||||
|
import CreateActivite from './createActivite';
|
||||||
|
import DetailSession from './object/detailSession';
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
session:Session;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EdtSession({session}:Props){
|
||||||
|
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
|
function handleOpen(): void {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sDate = session.creneau;
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<div className="edt_session" onClick={() => handleOpen()}>
|
||||||
|
<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>
|
||||||
|
<DetailSession session={session} open={open} setOpen={setOpen}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EdtSession
|
||||||
10
front_end/src/components/loading.tsx
Normal file
10
front_end/src/components/loading.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
function Loading(){
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<img className="loading" src="/loading.png"></img>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Loading
|
||||||
91
front_end/src/components/login.tsx
Normal file
91
front_end/src/components/login.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { useKeycloak } from '@react-keycloak/web'
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Admin, Athlete, Coach, User } from '../classes';
|
||||||
|
import { useLocalData } from '../context/useLocalData';
|
||||||
|
import { loginOrRegister, postAthlete } from '../requetes';
|
||||||
|
import { clearAuthToken, setAuthToken } from '../api';
|
||||||
|
import { AthleteDTO } from '../classesDTO';
|
||||||
|
import { Modal } from './Modal';
|
||||||
|
import './style/topBar.css';
|
||||||
|
|
||||||
|
export const Login =() =>{
|
||||||
|
const {user,setUser} = useLocalData()
|
||||||
|
const { keycloak } = useKeycloak();
|
||||||
|
const [open,setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
|
async function loginUser(){
|
||||||
|
if(keycloak.authenticated){
|
||||||
|
setAuthToken(keycloak.token);
|
||||||
|
|
||||||
|
const logedUser = await loginOrRegister(keycloak);
|
||||||
|
console.log(logedUser);
|
||||||
|
if(logedUser!==null){
|
||||||
|
setUser(logedUser);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert("Erreur de connexion " + keycloak.tokenParsed?.sub + " " + keycloak.tokenParsed?.realm_access?.roles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loginUser()
|
||||||
|
},[keycloak.authenticated])
|
||||||
|
|
||||||
|
async function handleLogin() {
|
||||||
|
await keycloak.login();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogout(): void {
|
||||||
|
keycloak.logout()
|
||||||
|
setUser(new User());
|
||||||
|
clearAuthToken();
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpen(): void {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(keycloak.authenticated){
|
||||||
|
return(
|
||||||
|
<div className='loginContainer'>
|
||||||
|
<button className="loginButton" onClick={()=>handleOpen()}>{user.prenom}</button>
|
||||||
|
{open &&
|
||||||
|
<div className='login'>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
Prenom : { user.prenom}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Nom : { user.nom}
|
||||||
|
</div>
|
||||||
|
{/* <div>Keycloak ID : { keycloak.tokenParsed?.sub}</div> */}
|
||||||
|
{user instanceof Athlete && <div>Role : Athlete</div>}
|
||||||
|
{user instanceof Coach && <div>Role : Coach</div>}
|
||||||
|
{user instanceof Admin && <div>Role : Admin</div>}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onClick={() => handleLogout()}>
|
||||||
|
Se déconnecter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<button onClick={() => handleLogin()}>
|
||||||
|
Se connecter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login
|
||||||
34
front_end/src/components/object/activite.tsx
Normal file
34
front_end/src/components/object/activite.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Activite } from '../../classes';
|
||||||
|
import '../style/objectList.css';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
activite: Activite
|
||||||
|
}
|
||||||
|
|
||||||
|
function ObjectActivite({activite}: Props) {
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
function handleOpen(): void {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="object" onClick={() => handleOpen()}>
|
||||||
|
<div>{activite.nom}</div>
|
||||||
|
<div>{activite.theme ? activite.theme : "Pas de thème défini pour l'activité"}</div>
|
||||||
|
<div>{activite.duree} min</div>
|
||||||
|
</div>
|
||||||
|
{open && activite.data && activite.data.size > 0 && (
|
||||||
|
<div className="object_details">
|
||||||
|
{Array.from(activite.data.entries()).map(([key, value]) => (
|
||||||
|
<div key={key}><strong>{key}:</strong> {value}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ObjectActivite
|
||||||
135
front_end/src/components/object/detailSession.tsx
Normal file
135
front_end/src/components/object/detailSession.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Activite, Admin, Athlete, Coach, Session } from "../../classes";
|
||||||
|
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 { useLocalData } from "../../context/useLocalData";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
session:Session;
|
||||||
|
open:boolean;
|
||||||
|
setOpen:(b:boolean)=>void
|
||||||
|
}
|
||||||
|
|
||||||
|
function DetailSession({session,open,setOpen}:Props){
|
||||||
|
|
||||||
|
const {user,setUser} = useLocalData()
|
||||||
|
|
||||||
|
const [activites,setActivites] = useState<Activite[]>([]);
|
||||||
|
const [open2, setOpen2] = useState<boolean>(false);
|
||||||
|
const [loading,setLoading] = useState<boolean>(false);
|
||||||
|
const [join,setJoin] = useState<boolean>(user instanceof Athlete && user.sessions.includes(session));
|
||||||
|
|
||||||
|
//Vérification pour l'ajout et la suppression des activités
|
||||||
|
const canEdit = user instanceof Admin || user instanceof Coach;
|
||||||
|
const sDate = session.creneau;
|
||||||
|
|
||||||
|
function handleDeleteActivite(activite:Activite): void {
|
||||||
|
session.activites.splice(session.activites.indexOf(activite), 1);
|
||||||
|
deletActiviteFromSession(activite);
|
||||||
|
setActivites([...session.activites])
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAddActivite(): void {
|
||||||
|
setOpen2(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateActivites(){
|
||||||
|
const newActivites = await getSessionOfActivite(session);
|
||||||
|
if(newActivites!=null){
|
||||||
|
session.activites=newActivites;
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(open){
|
||||||
|
setLoading(true);
|
||||||
|
updateActivites()
|
||||||
|
}
|
||||||
|
},[open])
|
||||||
|
|
||||||
|
async function subscribeSession(){
|
||||||
|
if(user instanceof Athlete){
|
||||||
|
await subscribeSessionAPI(user,session);
|
||||||
|
user.sessions.push(session);
|
||||||
|
setJoin(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unsubscribeSession(){
|
||||||
|
if(user instanceof Athlete){
|
||||||
|
await unsubscribeSessionAPI(user,session);
|
||||||
|
user.sessions = user.sessions.filter(s => s !== session);
|
||||||
|
setJoin(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function returnActivite(activite: Activite|null){
|
||||||
|
if(activite!==null){
|
||||||
|
activite.session = session;
|
||||||
|
const newActivite = await createActivityAPI(activite);
|
||||||
|
await addActiviteToSession(session,newActivite);
|
||||||
|
if(newActivite!=null){
|
||||||
|
session.activites.push(newActivite);
|
||||||
|
setActivites([...session.activites])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
setOpen2(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!loading){
|
||||||
|
setActivites([...session.activites])
|
||||||
|
}
|
||||||
|
},[loading])
|
||||||
|
|
||||||
|
if(!open2){
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Modal isOpen={open} onClose={() => setOpen(false)}>
|
||||||
|
<div className="object_modal">
|
||||||
|
<h2>{session.name}</h2>
|
||||||
|
<div>{hoursToString(sDate)}</div>
|
||||||
|
<div>{dateToString(sDate)}</div>
|
||||||
|
{user instanceof Athlete &&
|
||||||
|
<div>
|
||||||
|
{user.sessions.includes(session) ? <button onClick={()=>unsubscribeSession()}>quitter</button>
|
||||||
|
:<button onClick={()=>subscribeSession()}>rejoindre</button>}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Activités :
|
||||||
|
<div className="session_modal_activite_list">
|
||||||
|
{activites.map((activite,index)=>(
|
||||||
|
<div className="activiteList">
|
||||||
|
- {activite.nom}
|
||||||
|
{canEdit && (
|
||||||
|
<button className="deleteButton" onClick={() => handleDeleteActivite(activite)}>x</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{canEdit && (
|
||||||
|
<button className="addButton" onClick={() => handleAddActivite()}>+</button>
|
||||||
|
)}
|
||||||
|
{loading && <div className='top_left_loading'><Loading/></div>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
return(
|
||||||
|
<CreateActivite returnActivite={(activite) => returnActivite(activite)} session={session}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailSession;
|
||||||
23
front_end/src/components/object/lignes.tsx
Normal file
23
front_end/src/components/object/lignes.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Activite, Ligne } from '../../classes';
|
||||||
|
import '../style/objectList.css';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ligne: Ligne
|
||||||
|
}
|
||||||
|
|
||||||
|
function ObjectLigne({ligne}: Props) {
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
function handleOpen(): void {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* TODO */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ObjectLigne
|
||||||
47
front_end/src/components/object/session.tsx
Normal file
47
front_end/src/components/object/session.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Activite, Session } from '../../classes';
|
||||||
|
import { dateToString, hoursToString } from '../edt';
|
||||||
|
import '../style/objectList.css';
|
||||||
|
import { Modal } from '../Modal';
|
||||||
|
import Loading from '../loading';
|
||||||
|
import {delay} from "../../requetes";
|
||||||
|
import CreateActivite from '../createActivite';
|
||||||
|
import DetailSession from './detailSession';
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
session:Session;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ObjectSession({session}:Props){
|
||||||
|
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
function handleOpen(): void {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sDate = session.creneau;
|
||||||
|
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<div className="object" onClick={() => handleOpen()}>
|
||||||
|
<div className="object_header">
|
||||||
|
{session.isRecurrent ?
|
||||||
|
<div className="object_small"> Recurrent</div> :
|
||||||
|
<div className="object_small"> {dateToString(session.creneau)}</div>
|
||||||
|
}
|
||||||
|
<div className="object_small">{hoursToString(sDate)}</div>
|
||||||
|
</div>
|
||||||
|
<div>{session.name}</div>
|
||||||
|
<div>{session.groupe}</div>
|
||||||
|
<div>{session.coach ? session.coach.nom : "Pas de coach sur la séance"}</div>
|
||||||
|
{session.ligne ? session.ligne.map(ligne => ligne.nom).join(", ") : "Pas de ligne sur la séance"}
|
||||||
|
</div>
|
||||||
|
<DetailSession session={session} open={open} setOpen={setOpen}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ObjectSession
|
||||||
130
front_end/src/components/object/user.tsx
Normal file
130
front_end/src/components/object/user.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Activite, Admin, Athlete, Coach, Session, User } from '../../classes';
|
||||||
|
import { dateToString, hoursToString } from '../edt';
|
||||||
|
import '../style/objectList.css';
|
||||||
|
import { Modal } from '../Modal';
|
||||||
|
import Loading from '../loading';
|
||||||
|
import {delay, getSessionsOfUserAPI} from "../../requetes";
|
||||||
|
import CreateActivite from '../createActivite';
|
||||||
|
import { useLocalData } from '../../context/useLocalData';
|
||||||
|
import ObjectSession from './session';
|
||||||
|
import StatAthlete from '../StatsAthlete';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
user:User;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function ObjectUser({user}:Props){
|
||||||
|
|
||||||
|
//const {user,setUser} = useLocalData());
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const [open2, setOpen2] = useState<boolean>(false);
|
||||||
|
const [loading,setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// Initialisation sécurisée des sessions
|
||||||
|
function getInitialSessions(): Session[] {
|
||||||
|
if (user instanceof Athlete || user instanceof Coach) return [...(user.sessions || [])];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [sessions, setSessions] = useState<Session[]>(getInitialSessions());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function handleOpen(): void {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteSession(session:Session): void {
|
||||||
|
if(user instanceof Coach || user instanceof Athlete){
|
||||||
|
user.sessions.splice(user.sessions.indexOf(session), 1);
|
||||||
|
setSessions([...user.sessions])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAddSession(): void {
|
||||||
|
if(user instanceof Athlete){
|
||||||
|
setOpen2(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSession(){
|
||||||
|
if(user instanceof Athlete || user instanceof Coach){
|
||||||
|
user.sessions = await getSessionsOfUserAPI(user);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(open){
|
||||||
|
setLoading(true);
|
||||||
|
updateSession()
|
||||||
|
}
|
||||||
|
},[open])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!loading){
|
||||||
|
if(user instanceof Athlete){
|
||||||
|
setSessions([...user.sessions])
|
||||||
|
}
|
||||||
|
if(user instanceof Coach){
|
||||||
|
setSessions([...user.sessions])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},[loading])
|
||||||
|
|
||||||
|
|
||||||
|
function returnSession(session: Session|null){
|
||||||
|
if(session!==null){
|
||||||
|
if(user instanceof Athlete){
|
||||||
|
user.sessions.push(session);
|
||||||
|
setSessions([...user.sessions])
|
||||||
|
}
|
||||||
|
if(user instanceof Coach){
|
||||||
|
user.sessions.push(session);
|
||||||
|
setSessions([...user.sessions])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOpen2(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<div className="object" onClick={() => handleOpen()}>
|
||||||
|
<div>{user.prenom} {user.nom}</div>
|
||||||
|
|
||||||
|
{/* <div>{user2.role}</div> */}
|
||||||
|
</div>
|
||||||
|
{open &&
|
||||||
|
<Modal isOpen={open} onClose={() => setOpen(false)}>
|
||||||
|
<div className="object_modal">
|
||||||
|
<div>{user.prenom}</div>
|
||||||
|
<div>{user.nom}</div>
|
||||||
|
{(user instanceof Athlete || user instanceof Coach) &&
|
||||||
|
<div className='padding'>
|
||||||
|
<div className='list_object_modal'>
|
||||||
|
<div>Sessions :</div>
|
||||||
|
{user.sessions.map((session,index)=>(
|
||||||
|
<ObjectSession session={session}></ObjectSession>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{user instanceof Athlete && (
|
||||||
|
<div className="stats-container">
|
||||||
|
<StatAthlete athlete={user}/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ObjectUser
|
||||||
151
front_end/src/components/ressourceList.tsx
Normal file
151
front_end/src/components/ressourceList.tsx
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Athlete, Activite, Coach, Session, Ligne } from "../classes";
|
||||||
|
import ObjectSession from "./object/session";
|
||||||
|
import {calculStatsAthlete, niveauAlerte} from "../utils/athleteUtils";
|
||||||
|
import {calculTempsDeJeuParLigne} from "../utils/ligneUtils";
|
||||||
|
import ObjectActivite from "./object/activite";
|
||||||
|
type AthleteListProps = { athletes: Athlete[], sessions: Session[]};
|
||||||
|
type ActiviteListProps = { activites: Activite[] };
|
||||||
|
type CoachListProps = { coachs: Coach[] };
|
||||||
|
type SessionListProps = { sessions: Session[]};
|
||||||
|
type LigneListProps = { lignes: Ligne[], tempsDeJeuParLigne: Map<number, number> };
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="AthleteList">
|
||||||
|
{athletes.map(a => {
|
||||||
|
const stats = calculStatsAthlete(sessions, a, dateDebut, dateFin);
|
||||||
|
const alerte = niveauAlerte(stats, seuilCritique, seuilMax);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={a.id}>
|
||||||
|
<div><strong>Nom:</strong> {a.nom}</div>
|
||||||
|
<div><strong>Groupe:</strong> {a.groupes}</div>
|
||||||
|
<div><strong>Nombre de sessions:</strong> {stats.nbSessions}</div>
|
||||||
|
<div><strong>Sessions/semaine:</strong> {stats.nbSessionsPerWeek.toFixed(2)}</div>
|
||||||
|
<div><strong>Alerte:</strong> {alerte}</div>
|
||||||
|
<div><strong>Distribuion des activités:</strong> {stats.distributions}</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ActiviteList({ activites }: ActiviteListProps) {
|
||||||
|
return (
|
||||||
|
<div className="list_object">
|
||||||
|
{activites.map((activite) => (
|
||||||
|
<ObjectActivite activite={activite}/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CoachList({ coachs }: CoachListProps) {
|
||||||
|
return (
|
||||||
|
<ul className="CoachList">
|
||||||
|
{coachs.map((coachs) => (
|
||||||
|
<li key={coachs.id}>
|
||||||
|
<div>
|
||||||
|
<strong>Nom:</strong> {coachs.nom}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SessionList({ sessions }: SessionListProps) {
|
||||||
|
return (
|
||||||
|
<div className="list_object">
|
||||||
|
{sessions.map((session) => (
|
||||||
|
<ObjectSession session={session}/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LigneList({ lignes, tempsDeJeuParLigne }: LigneListProps) {
|
||||||
|
return (
|
||||||
|
<ul className="LigneList">
|
||||||
|
{lignes.map((lignes) => (
|
||||||
|
<li key={lignes.id}>
|
||||||
|
<div>
|
||||||
|
<strong>Nom:</strong> {lignes.nom}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<strong>Composition :</strong>
|
||||||
|
<ul>
|
||||||
|
{lignes.composition.map((athlete) => (
|
||||||
|
<li key={athlete.id}>
|
||||||
|
{athlete.nom}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Temps de jeu total :</strong>{" "}
|
||||||
|
{/* {tempsDeJeuParLigne.get(lignes.id) ?? 0} min */}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { AthleteList, ActiviteList, CoachList , SessionList, LigneList };
|
||||||
129
front_end/src/components/ressourcePanel.tsx
Normal file
129
front_end/src/components/ressourcePanel.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useLocalData } from "../context/useLocalData";
|
||||||
|
import { Activite, Athlete, Coach , Session, Ligne, Admin } from "../classes";
|
||||||
|
import {calculTempsDeJeuParLigne} from "../utils/ligneUtils";
|
||||||
|
import { keyboard } from "@testing-library/user-event/dist/keyboard";
|
||||||
|
import ObjectSession from "./object/session";
|
||||||
|
import ObjectActivite from "./object/activite";
|
||||||
|
import ObjectUser from "./object/user";
|
||||||
|
import { getAllActiviteAPI, getAllAthlete, getAllCoach, getAllSessionsAPI } from "../requetes";
|
||||||
|
import { useKeycloak } from "@react-keycloak/web";
|
||||||
|
import ObjectLigne from "./object/lignes";
|
||||||
|
|
||||||
|
|
||||||
|
export type keyWord = "athletes" | "activites" | "coachs" | "sessions"| "lignes";
|
||||||
|
|
||||||
|
export default function RessourcePanel() {
|
||||||
|
const { keycloak } = useKeycloak();
|
||||||
|
const { user } = useLocalData();
|
||||||
|
//const user = getUserTest(); //TODO
|
||||||
|
const [value,setValue] = useState<keyWord>("sessions");
|
||||||
|
|
||||||
|
const[allAthletes,setAllAthletes] = useState<Athlete[]>([]);
|
||||||
|
const[allActivites,setAllActivites] = useState<Activite[]>([]);
|
||||||
|
const[allCoachs,setAllCoachs] = useState<Coach[]>([]);
|
||||||
|
const[allSessions,setAllSessions] = useState<Session[]>([]);
|
||||||
|
const[allLignes,setAllLignes] = useState<Ligne[]>([]);
|
||||||
|
|
||||||
|
|
||||||
|
async function updateAthletes() {
|
||||||
|
const athletes:Athlete[] = await getAllAthlete();
|
||||||
|
setAllAthletes(athletes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function updateCoachs() {
|
||||||
|
const coachs:Coach[] = await getAllCoach();
|
||||||
|
setAllCoachs(coachs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateActivites() {
|
||||||
|
const activites:Activite[] = await getAllActiviteAPI();
|
||||||
|
setAllActivites(activites);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSessions() {
|
||||||
|
const sessions:Session[] = await getAllSessionsAPI();
|
||||||
|
setAllSessions(sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateLignes() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
update();
|
||||||
|
},[user,value])
|
||||||
|
|
||||||
|
function update(){
|
||||||
|
if(keycloak.authenticated){
|
||||||
|
if(value=="sessions") updateSessions();
|
||||||
|
if (user instanceof Admin || user instanceof Coach){
|
||||||
|
if(value=="athletes") updateAthletes();
|
||||||
|
if(value=="coachs") updateCoachs();
|
||||||
|
if(value=="lignes") updateLignes();
|
||||||
|
if(value=="activites") updateActivites();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="composant-container">
|
||||||
|
|
||||||
|
{(user instanceof Admin || user instanceof Coach) &&
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
Sélectionner une ressource:
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = (e.target as HTMLSelectElement).value;
|
||||||
|
setValue(v as keyWord)
|
||||||
|
}}>
|
||||||
|
<option value="sessions"> Sessions</option>
|
||||||
|
{(user instanceof Admin || user instanceof Coach) &&<option value="athletes">Athlètes</option>}
|
||||||
|
{(user instanceof Admin || user instanceof Coach) &&<option value="activites">Activités</option>}
|
||||||
|
{(user instanceof Admin || user instanceof Coach) && <option value="coachs"> Coachs</option>}
|
||||||
|
{(user instanceof Admin || user instanceof Coach) &&<option value="lignes"> Lignes</option>}
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className="edt_sessions_panel">
|
||||||
|
<h3>Liste des {value}</h3>
|
||||||
|
<button onClick={()=>update()}> Actualiser </button>
|
||||||
|
<div className="list_object">
|
||||||
|
{value==="athletes" && (
|
||||||
|
allAthletes.map((athlete) => (
|
||||||
|
<ObjectUser user={athlete}/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
{value==="activites" && (
|
||||||
|
allActivites.map(activite => (
|
||||||
|
<ObjectActivite activite={activite}/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
{value==="coachs" && (
|
||||||
|
allCoachs.map((coach) => (
|
||||||
|
<ObjectUser user={coach}/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
{value==="sessions" && (
|
||||||
|
allSessions.map((session) => (
|
||||||
|
<ObjectSession session={session}/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
{value==="lignes" && (
|
||||||
|
allLignes.map((ligne) => (
|
||||||
|
<ObjectLigne ligne={ligne}/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
13
front_end/src/components/style/createSession.css
Normal file
13
front_end/src/components/style/createSession.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.createActivite {
|
||||||
|
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint3) 100%);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
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);
|
||||||
|
}
|
||||||
197
front_end/src/components/style/edt.css
Normal file
197
front_end/src/components/style/edt.css
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
.edt {
|
||||||
|
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint3) 100%);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt:hover {
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
box-shadow: 0 12px 40px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_header {
|
||||||
|
justify-content: center;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 0.5fr);
|
||||||
|
padding-bottom: 16px;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_colonnes {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
align-items: flex-start;
|
||||||
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
color: var(--text);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_loading {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_colonne {
|
||||||
|
background: linear-gradient(135deg, var(--tint3) 0%, var(--tint4) 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.edt_day_header {
|
||||||
|
font-size: clamp(5px, 1vw, 18px);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
height: fit-content;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1em;
|
||||||
|
background: linear-gradient(135deg, var(--green-primary) 0%, var(--green-secondary) 100%);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: large;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.today {
|
||||||
|
background: linear-gradient(135deg, var(--green-primary) 0%, var(--cyan-accent) 100%);
|
||||||
|
color: #FFFFFF;
|
||||||
|
box-shadow: 0 6px 20px var(--green-A-primary);
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_day_content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 20px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_session {
|
||||||
|
font-size: clamp(1px, 8cqi, 18px);
|
||||||
|
gap: 8px;
|
||||||
|
background: linear-gradient(135deg, var(--tint4) 0%, var(--tint5) 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
border-left: 4px solid var(--green-primary);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_session:hover {
|
||||||
|
background: linear-gradient(135deg, var(--green-A-primary), var(--green-A-secondary));
|
||||||
|
border-left-width: 6px;
|
||||||
|
transform: translateX(4px);
|
||||||
|
box-shadow: 0 8px 16px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_session:active {
|
||||||
|
transform: translateX(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_session_header {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_date {
|
||||||
|
font-size: 0.75em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_button_week_select {
|
||||||
|
background: linear-gradient(135deg, var(--green-primary), var(--green-secondary));
|
||||||
|
color: white;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: none;
|
||||||
|
padding: 0 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 4px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_button_week_select:hover {
|
||||||
|
background: linear-gradient(135deg, var(--green-secondary), var(--green-dark));
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_button_week_select:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 1px 2px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_session_modal {
|
||||||
|
background-color: var(--tint2);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid var(--green-primary);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1), 0 0 0 1px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ent_activite_list {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--tint3);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_activite_modal {
|
||||||
|
background-color: var(--tint3);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid #10b981;
|
||||||
|
box-shadow: 0 4px 12px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Classes bonus pour d'autres boutons */
|
||||||
|
.edt_button_secondary {
|
||||||
|
background-color: var(--tint3);
|
||||||
|
color: var(--text);
|
||||||
|
border: 2px solid var(--green-primary);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_button_secondary:hover {
|
||||||
|
background-color: var(--green-A-primary);
|
||||||
|
border-color: var(--green-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_button_outline {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--green-primary);
|
||||||
|
border: 2px solid var(--green-primary);
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edt_button_outline:hover {
|
||||||
|
background-color: var(--green-primary);
|
||||||
|
color: white;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
85
front_end/src/components/style/objectList.css
Normal file
85
front_end/src/components/style/objectList.css
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
.list_object{
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint3) 100%);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list_object_modal{
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint3) 100%);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
max-height: 280px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.object {
|
||||||
|
font-size: clamp(1px, 8cqi, 18px);
|
||||||
|
gap: 8px;
|
||||||
|
background: linear-gradient(135deg, var(--tint3) 0%, var(--tint4) 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.object:hover {
|
||||||
|
background: linear-gradient(135deg, var(--green-A-primary), var(--green-A-secondary));
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 16px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.object:active {
|
||||||
|
background: linear-gradient(135deg, var(--tint4) 0%, var(--tint5) 100%);
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.object_header{
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object_small{
|
||||||
|
font-size: 0.75em;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object_modal{
|
||||||
|
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint3) 100%);
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
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;
|
||||||
|
padding: 12px;
|
||||||
|
background: linear-gradient(135deg, var(--tint3) 0%, var(--tint4) 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
gap: 8px;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activiteList{
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
104
front_end/src/components/style/topBar.css
Normal file
104
front_end/src/components/style/topBar.css
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
.topBar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 24px;
|
||||||
|
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint3) 100%);
|
||||||
|
border-radius: 20px;
|
||||||
|
height: auto;
|
||||||
|
min-height: 70px;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar:hover {
|
||||||
|
border-color: var(--green-primary);
|
||||||
|
box-shadow: 0 8px 32px var(--green-A-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toBarLeft {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toBarLeft h2 {
|
||||||
|
background: linear-gradient(135deg, var(--green-primary) 0%, var(--cyan-accent) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.topBarRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginContainer {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
width: fit-content;
|
||||||
|
white-space: nowrap;
|
||||||
|
align-items: left;
|
||||||
|
background: linear-gradient(135deg, var(--tint2) 0%, var(--tint3) 100%);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
z-index: 1000;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginButton {
|
||||||
|
width: 120px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ButtonTheme {
|
||||||
|
height: 44px;
|
||||||
|
width: 44px;
|
||||||
|
color: var(--text);
|
||||||
|
background: linear-gradient(135deg, var(--tint3) 0%, var(--tint4) 100%);
|
||||||
|
border: 1px solid var(--tint4);
|
||||||
|
border-radius: 12px;
|
||||||
|
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);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 170, 255, 0.3);
|
||||||
|
transform: scale(1.05) rotateZ(-180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 40px;
|
||||||
|
filter: drop-shadow(0 2px 8px rgba(16, 185, 129, 0.3));
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo:hover {
|
||||||
|
transform: scale(1.1) rotateY(10deg);
|
||||||
|
}
|
||||||
29
front_end/src/components/test_api.tsx
Normal file
29
front_end/src/components/test_api.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
25
front_end/src/components/topBar.tsx
Normal file
25
front_end/src/components/topBar.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Login from "./login"
|
||||||
|
import SwitchThemeColor from "./SwitchThemeColor"
|
||||||
|
|
||||||
|
function TopBar(){
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className="topBar">
|
||||||
|
<div className="toBarLeft">
|
||||||
|
<img className="logo" src="/Frisbyee_logo.png"/>
|
||||||
|
<h2>Frisbyee</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="topBarRight">
|
||||||
|
<SwitchThemeColor/>
|
||||||
|
<Login/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TopBar
|
||||||
14
front_end/src/context/LocalDataContext.tsx
Normal file
14
front_end/src/context/LocalDataContext.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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[]>>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LocalDataContext = createContext<LocalDataContextType | undefined>(undefined)
|
||||||
10
front_end/src/context/useLocalData.tsx
Normal file
10
front_end/src/context/useLocalData.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useContext } from 'react'
|
||||||
|
import { LocalDataContext } from './LocalDataContext'
|
||||||
|
|
||||||
|
export const useLocalData = () => {
|
||||||
|
const context = useContext(LocalDataContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useLocalData must be used within LocalDataProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/* Reset global */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles de base */
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
@@ -5,6 +15,9 @@ body {
|
|||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
background-color: var(--tint0);
|
||||||
|
color: var(--text);
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
|||||||
5
front_end/src/keycloak.js
Normal file
5
front_end/src/keycloak.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import Keycloak from 'keycloak-js'
|
||||||
|
|
||||||
|
const keycloak = new Keycloak("/keycloak.json")
|
||||||
|
|
||||||
|
export default keycloak
|
||||||
20
front_end/src/provider/LocalDataProvider.tsx
Normal file
20
front_end/src/provider/LocalDataProvider.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
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[]>([])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LocalDataContext.Provider
|
||||||
|
value={{ user, setUser, sessions, setSessions, users, setUsers }}>
|
||||||
|
{children}
|
||||||
|
</LocalDataContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
363
front_end/src/requetes.tsx
Normal file
363
front_end/src/requetes.tsx
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
import api, { activiteService, 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";
|
||||||
|
import { useLocalData } from "./context/useLocalData";
|
||||||
|
|
||||||
|
//debug:
|
||||||
|
export function delay(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
//UPDATE /////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//COACH / ATHLETE
|
||||||
|
|
||||||
|
/*
|
||||||
|
retourne l'utilisateur lié à l'identifiant keyloack
|
||||||
|
*/
|
||||||
|
export async function loginOrRegister(keycloak:Keycloak): Promise<User|null>{
|
||||||
|
try {
|
||||||
|
if(keycloak.tokenParsed!=null){
|
||||||
|
const roles = keycloak.tokenParsed?.realm_access?.roles
|
||||||
|
if(roles!=null){
|
||||||
|
if(roles.includes("admin")){
|
||||||
|
const newAdmin: Admin = new Admin();
|
||||||
|
newAdmin.keycloakId = keycloak.tokenParsed.sub || "";
|
||||||
|
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 admin = new Admin(response.data);
|
||||||
|
return admin;
|
||||||
|
}
|
||||||
|
else if(roles.includes("coach")){
|
||||||
|
const newCoach: Coach = new Coach();
|
||||||
|
newCoach.keycloakId = keycloak.tokenParsed.sub || "";
|
||||||
|
newCoach.email = keycloak.tokenParsed.email || "";
|
||||||
|
newCoach.nom = keycloak.tokenParsed.family_name || "";
|
||||||
|
newCoach.prenom = keycloak.tokenParsed.given_name || "";
|
||||||
|
const response = await coachService.create(newCoach.toDTO());
|
||||||
|
const coach = new Coach(response.data);
|
||||||
|
return coach;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(roles.includes("athlete")){
|
||||||
|
const newAthlete: Athlete = new Athlete();
|
||||||
|
newAthlete.keycloakId = keycloak.tokenParsed.sub || "";
|
||||||
|
newAthlete.email = keycloak.tokenParsed.email || "";
|
||||||
|
newAthlete.nom = keycloak.tokenParsed.family_name || "";
|
||||||
|
newAthlete.prenom = keycloak.tokenParsed.given_name || "";
|
||||||
|
const response = await athleteService.create(newAthlete.toDTO());
|
||||||
|
const athlete = new Athlete(response.data);
|
||||||
|
return athlete;
|
||||||
|
}
|
||||||
|
else console.error("Error : role inconnu");
|
||||||
|
}
|
||||||
|
else console.error("Error : role null");
|
||||||
|
}
|
||||||
|
else console.error("Error : token Keylcoak null");
|
||||||
|
console.error("Error : pendant la récupération de l'User");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error("Error fetching user:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSessionOfActivite(session:Session):Promise<Activite[]|null>{
|
||||||
|
try {
|
||||||
|
if(session.id !=null){
|
||||||
|
const activites:Activite[] = [];
|
||||||
|
const response = await sessionService.getActivities(session.id!);
|
||||||
|
response.data.forEach(activiteDTO => {
|
||||||
|
activites.push(new Activite(activiteDTO));
|
||||||
|
});
|
||||||
|
return activites;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting session:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function deletActiviteFromSession(activite:Activite){
|
||||||
|
try {
|
||||||
|
if(activite.id !=null){
|
||||||
|
activiteService.delete(activite.id);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting session:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addActiviteToSession(session:Session,activite:Activite):Promise<Boolean>{
|
||||||
|
try {
|
||||||
|
if(activite.id !=null && session.id !=null){
|
||||||
|
await sessionService.addActivity(session.id,activite.id)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting session:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateActivitiesOfSessionAPI(session:Session){
|
||||||
|
try {
|
||||||
|
session.activites.forEach(activite => {
|
||||||
|
if(activite.id!=null){
|
||||||
|
activiteService.update(activite.id, activite.toDTO());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// To refresh the activities in the session object
|
||||||
|
//const response = await sessionService.getActivities(session_id!);
|
||||||
|
//session.activites = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching activities for session:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function subscribeSessionAPI(user:User, session:Session):Promise<boolean>{
|
||||||
|
try {
|
||||||
|
const session_id =session.id
|
||||||
|
const user_id = user.id
|
||||||
|
const response = await sessionService.subscribe(session_id!, user_id!);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error subscribing to session:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unsubscribeSessionAPI(user:Athlete, session:Session):Promise<boolean>{
|
||||||
|
try {
|
||||||
|
const session_id =session.id
|
||||||
|
const user_id = user.id
|
||||||
|
const response = await sessionService.unsubscribe(session_id!, user_id!);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error unsubscribing from session:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// POST /////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// COACH ADMIN
|
||||||
|
export async function createSessionAPI(session: Session): Promise<Session> {
|
||||||
|
async function postActivite(activite:Activite,sessionRes:Session){
|
||||||
|
activite.session = sessionRes;
|
||||||
|
const activite2 = await createActivityAPI(activite);
|
||||||
|
activite2.session = sessionRes;
|
||||||
|
sessionRes.activites.push(activite2);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await api.post<SessionDTO>("/session/create", session.toDTO());
|
||||||
|
const sessionRes:Session = new Session(response.data);
|
||||||
|
await Promise.all(
|
||||||
|
session.activites.map(activite =>
|
||||||
|
postActivite(activite, sessionRes)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
console.log(sessionRes.activites);
|
||||||
|
updateActivitiesOfSessionAPI(sessionRes);
|
||||||
|
|
||||||
|
return sessionRes;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating session:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function createActivityAPI(activity: Activite):Promise<Activite>{
|
||||||
|
try {
|
||||||
|
const response = await activiteService.create(activity.toDTO())
|
||||||
|
return new Activite(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating activity:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllActiviteAPI(): Promise<Activite[]> {
|
||||||
|
try {
|
||||||
|
const response = await activiteService.getAll();
|
||||||
|
const activites:Activite[] = []
|
||||||
|
response.data.forEach(dto => {
|
||||||
|
activites.push(new Activite(dto));
|
||||||
|
});
|
||||||
|
return activites;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching users:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function postAthlete(athlete: Athlete):Promise<Athlete>{
|
||||||
|
try {
|
||||||
|
const response = await api.post<Athlete>("/athlete/create/",athlete.toDTO);
|
||||||
|
console.log(response);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching coachs:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
// console.log("Session créée");
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error post Session:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET /////////////////////////////////////////////////////////
|
||||||
|
export async function createAdminAPI(athlete: Admin):Promise<Admin>{
|
||||||
|
try {
|
||||||
|
const response = await api.post<Admin>("/admin/create/",athlete);
|
||||||
|
console.log(response);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching coachs:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//GET /////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//USER
|
||||||
|
export async function getAllUserAPI(): Promise<User[]> {
|
||||||
|
try{
|
||||||
|
const response = await api.get<User[]>("/users/all");
|
||||||
|
return response.data;
|
||||||
|
}catch (error) {
|
||||||
|
console.error("Error fetching users:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCoachByIdAPI(id:number|null): Promise<Coach|null> {
|
||||||
|
try{
|
||||||
|
if(id!==null){
|
||||||
|
const response = await coachService.getById(id);
|
||||||
|
return new Coach(response.data);
|
||||||
|
}
|
||||||
|
console.error("Error fetching coach by id : id null");
|
||||||
|
return null;
|
||||||
|
}catch (error) {
|
||||||
|
console.error("Error fetching coach by id:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//SESSION
|
||||||
|
export async function getSessionsOfUserAPI(user:User): Promise<Session[]>{
|
||||||
|
try {
|
||||||
|
var sessionsDTO:SessionDTO[] = []
|
||||||
|
if (user instanceof Coach) {
|
||||||
|
const response = await coachService.getSessionsForCoach(user.id); //TODO
|
||||||
|
sessionsDTO = response.data;
|
||||||
|
}else if (user instanceof Athlete) {
|
||||||
|
const response = await athleteService.getSessionsForAthlete(user.id); //TODO
|
||||||
|
sessionsDTO = response.data;
|
||||||
|
}
|
||||||
|
const sessions:Session[] = [];
|
||||||
|
sessionsDTO.forEach(sessionDTO => {
|
||||||
|
sessions.push(new Session(sessionDTO));
|
||||||
|
});
|
||||||
|
return sessions;
|
||||||
|
|
||||||
|
}catch (error) {
|
||||||
|
console.error("Error fetching sessions for user:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllSessionsAPI():Promise<Session[]>{
|
||||||
|
try {
|
||||||
|
const response = await sessionService.getAll();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//COACH
|
||||||
|
export async function getAllCoach(): Promise<Coach[]> {
|
||||||
|
try {
|
||||||
|
const response = await coachService.getAll();
|
||||||
|
const coachs:Coach[] = []
|
||||||
|
response.data.forEach(dto => {
|
||||||
|
coachs.push(new Coach(dto));
|
||||||
|
});
|
||||||
|
return coachs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching coachs:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//ATHLETE
|
||||||
|
export async function getAllAthlete(): Promise<Athlete[]> {
|
||||||
|
try {
|
||||||
|
const response = await athleteService.getAll();
|
||||||
|
const athletes:Athlete[] = []
|
||||||
|
response.data.forEach(dto => {
|
||||||
|
athletes.push(new Athlete(dto));
|
||||||
|
});
|
||||||
|
return athletes;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching users:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
50
front_end/src/utils/athleteUtils.tsx
Normal file
50
front_end/src/utils/athleteUtils.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Athlete, Session , Activite} from '../classes';
|
||||||
|
|
||||||
|
export interface StatsAthlete {
|
||||||
|
nbSessions: number;
|
||||||
|
nbSessionsPerWeek: number;
|
||||||
|
isAlerte: boolean;
|
||||||
|
distributions: Map<String, number>; //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<string, number> = 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;
|
||||||
|
//distribution des activités
|
||||||
|
session.activites.forEach(activite => {
|
||||||
|
const currentCount = distributions.get(activite.nom) || 0;
|
||||||
|
distributions.set(activite.nom, currentCount + increment);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const nbSessionsPerWeek = nb_sessions / nb_semaine;
|
||||||
|
const isAlerte = nbSessionsPerWeek > 8;
|
||||||
|
|
||||||
|
return {
|
||||||
|
nbSessions: nb_sessions,
|
||||||
|
nbSessionsPerWeek: nbSessionsPerWeek,
|
||||||
|
isAlerte: isAlerte,
|
||||||
|
distributions: distributions
|
||||||
|
};
|
||||||
|
}
|
||||||
16
front_end/src/utils/ligneUtils.tsx
Normal file
16
front_end/src/utils/ligneUtils.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import {Ligne, Session} from '../classes';
|
||||||
|
|
||||||
|
//Temps de jeu cumulé par ligne
|
||||||
|
|
||||||
|
export function calculTempsDeJeuParLigne(sessions: Session[], ligne : Ligne): number {
|
||||||
|
let tempsDeJeuTotal = 0;
|
||||||
|
|
||||||
|
sessions.forEach(session => {
|
||||||
|
// Vérifier si la ligne est présente dans la session
|
||||||
|
if (session.ligne && session.ligne.some(l => l.id === ligne.id)) {
|
||||||
|
tempsDeJeuTotal += session.duree;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tempsDeJeuTotal;
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "ES2020",
|
||||||
"lib": [
|
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
||||||
"dom",
|
"module": "ESNext",
|
||||||
"dom.iterable",
|
"moduleResolution": "bundler",
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
@@ -13,14 +12,10 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"]
|
||||||
"src"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
255
keycloak/themes/frisbyee/login/resources/css/styles.css
Normal file
255
keycloak/themes/frisbyee/login/resources/css/styles.css
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
/* Personnalisation de la page de login Keycloak */
|
||||||
|
|
||||||
|
.login-pf body {
|
||||||
|
background: linear-gradient(135deg, #10b981 0%, #059669 50%, #047857 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#kc-header-wrapper {
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kc-header-wrapper h1,
|
||||||
|
#kc-header-wrapper .kc-logo-text {
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card principale */
|
||||||
|
.card-pf {
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2),
|
||||||
|
0 5px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 2px solid rgba(16, 185, 129, 0.3);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background-color: rgba(255, 255, 255, 0.98);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-pf:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25),
|
||||||
|
0 8px 20px rgba(16, 185, 129, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header de la card */
|
||||||
|
#kc-form-login .card-pf h1,
|
||||||
|
.login-pf-page h1 {
|
||||||
|
color: #047857;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 3px solid #10b981;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Champs de formulaire */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kc-form-login input[type="text"],
|
||||||
|
#kc-form-login input[type="password"],
|
||||||
|
#kc-form-login input[type="email"],
|
||||||
|
.pf-c-form-control {
|
||||||
|
border: 2px solid #d1d5db;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kc-form-login input[type="text"]:focus,
|
||||||
|
#kc-form-login input[type="password"]:focus,
|
||||||
|
#kc-form-login input[type="email"]:focus,
|
||||||
|
.pf-c-form-control:focus {
|
||||||
|
border-color: #10b981;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Labels */
|
||||||
|
label,
|
||||||
|
.pf-c-form__label {
|
||||||
|
color: #374151;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton principal */
|
||||||
|
.btn-primary,
|
||||||
|
#kc-login,
|
||||||
|
.pf-c-button.pf-m-primary {
|
||||||
|
background: linear-gradient(135deg, #10b981, #059669);
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 6px rgba(16, 185, 129, 0.3);
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover,
|
||||||
|
#kc-login:hover,
|
||||||
|
.pf-c-button.pf-m-primary:hover {
|
||||||
|
background: linear-gradient(135deg, #059669, #047857);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 12px rgba(16, 185, 129, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:active,
|
||||||
|
#kc-login:active,
|
||||||
|
.pf-c-button.pf-m-primary:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 4px rgba(16, 185, 129, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Boutons secondaires */
|
||||||
|
.btn-secondary,
|
||||||
|
.btn-default,
|
||||||
|
.pf-c-button.pf-m-secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #10b981;
|
||||||
|
border: 2px solid #10b981;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover,
|
||||||
|
.btn-default:hover,
|
||||||
|
.pf-c-button.pf-m-secondary:hover {
|
||||||
|
background-color: #10b981;
|
||||||
|
color: white;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Liens */
|
||||||
|
#kc-form-options a,
|
||||||
|
#kc-registration a,
|
||||||
|
.login-pf-page a {
|
||||||
|
color: #10b981;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kc-form-options a:hover,
|
||||||
|
#kc-registration a:hover,
|
||||||
|
.login-pf-page a:hover {
|
||||||
|
color: #059669;
|
||||||
|
border-bottom-color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox "Se souvenir de moi" */
|
||||||
|
.checkbox label {
|
||||||
|
color: #4b5563;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
accent-color: #10b981;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Messages d'alerte */
|
||||||
|
.alert-error,
|
||||||
|
.pf-c-alert.pf-m-danger {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
border-left: 4px solid #ef4444;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning,
|
||||||
|
.pf-c-alert.pf-m-warning {
|
||||||
|
background-color: #fffbeb;
|
||||||
|
border-left: 4px solid #f59e0b;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success,
|
||||||
|
.pf-c-alert.pf-m-success {
|
||||||
|
background-color: #f0fdf4;
|
||||||
|
border-left: 4px solid #10b981;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info,
|
||||||
|
.pf-c-alert.pf-m-info {
|
||||||
|
background-color: #eff6ff;
|
||||||
|
border-left: 4px solid #3b82f6;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Social login buttons */
|
||||||
|
#kc-social-providers .btn {
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kc-social-providers .btn:hover {
|
||||||
|
transform: translateX(5px);
|
||||||
|
border-color: #10b981;
|
||||||
|
box-shadow: -3px 3px 10px rgba(16, 185, 129, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
#kc-registration {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation au chargement */
|
||||||
|
.card-pf {
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.card-pf {
|
||||||
|
border-radius: 15px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary,
|
||||||
|
#kc-login {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
keycloak/themes/frisbyee/login/theme.properties
Normal file
3
keycloak/themes/frisbyee/login/theme.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
parent=keycloak
|
||||||
|
import=common/keycloak
|
||||||
|
styles=css/styles.css
|
||||||
1
keycloak/themes/frisbyee/theme.properties
Normal file
1
keycloak/themes/frisbyee/theme.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name=frisbyee
|
||||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "hackathon",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user