Compare commits

...

10 Commits
v1 ... master

Author SHA1 Message Date
Theal0 58756dcc22 Release 1.7
Ajout d'un bouton "Retour au choix de semestre" dans la navbar
2021-06-28 22:33:53 +02:00
Theal0 5e00bd068c Release 1.6
Améliorations mineures de l'interface
Fix: "Absences de undefined" + Erreur
2021-06-11 12:53:42 +02:00
Theal0 ef3c3ce7b8 Release 1.5
Ajout de filtrage par groupe de TD/TP sur la page des étudiants inscrits au semestre
2021-06-11 02:02:21 +02:00
Theal0 3725d508ab Release 1.4
Erreur de la page blanche liée a une valeur de rang inexistant résolu
2021-06-10 12:47:28 +02:00
Theal0 8a5b2fc0e7 Actualisation du README 2021-06-04 12:03:45 +02:00
Theal0 05656acd0c Merge remote-tracking branch 'origin/master' 2021-06-04 12:03:04 +02:00
Theal0 6e41809da7 Release v1.3
Ajout des noms et logins sur la page d'acceuil de la gestion du semestre
2021-06-04 12:02:54 +02:00
Théo Alric b8551c73d7 Actualisation du README 2021-05-31 14:20:51 +02:00
Theal0 d50beeb4d7 Release v1.2
Ajout de la documentation des fonctions avec JsDoc (En Markdown)
Ajout de spinners de chargement
Ajout de liens vers les absences et le bulletin depuis le profil étudiant
Affichage des groupes de TD sur le profil étudiant
Reduction/Optimisation du nombre de requetes
Optimisations mineures

(TODO: Changements des logins en nom/prénom sur l'acceuil de la gestion du semestre)
2021-05-31 14:19:43 +02:00
Theal0 65cb895a1c Release v1.1
Optimisations mineures
Documentation, réorganisation et nettoyage du code
2021-05-25 12:55:30 +02:00
23 changed files with 3407 additions and 399 deletions

405
Documentation.md Normal file
View File

@ -0,0 +1,405 @@
# Classes
## `/`
<dl>
<dt><a href="#ChoixDept">ChoixDept</a></dt>
<dd><p>Page de choix du département</p>
</dd>
<dt><a href="#Etudiant">Etudiant</a></dt>
<dd><p>Page d&#39;information d&#39;un étudiant&#39;</p>
</dd>
<dt><a href="#GestionSemestre">GestionSemestre</a></dt>
<dd><p>Page de gestion du semestre</p>
</dd>
<dt><a href="#Login">Login</a></dt>
<dd><p>Page de Login</p>
</dd>
<dt><a href="#Scolarite">Scolarite</a></dt>
<dd><p>Page de choix du semestre</p>
</dd>
<dt><a href="#ScoNavBar">ScoNavBar</a></dt>
<dd><p>Barre de navigation</p>
</dd>
<dt><a href="#SearchStudent">SearchStudent</a></dt>
<dd><p>Module de recherche d&#39;étudiant</p>
</dd>
</dl>
## `/GestionSemestre`
<dl>
<dt><a href="#Absences">Absences</a></dt>
<dd><p>Page de gestion des absences</p>
</dd>
<dt><a href="#Accueil">Accueil</a></dt>
<dd><p>Page d&#39;accueil de la gestion du semestre</p>
</dd>
<dt><a href="#Bulletin">Bulletin</a></dt>
<dd><p>Page de présentation des bulletins étudiants</p>
</dd>
<dt><a href="#Etudiants">Etudiants</a></dt>
<dd><p>Page de présentation des étudiants inscrits au semestre</p>
</dd>
</dl>
## `/GestionSemestre/Absences`
<dl>
<dt><a href="#JustAbs">JustAbs</a></dt>
<dd><p>Module de justification des absences</p>
</dd>
<dt><a href="#SaisieAbs">SaisieAbs</a></dt>
<dd><p>Module de saisie des absences</p>
</dd>
<dt><a href="#SupprAbs">SupprAbs</a></dt>
<dd><p>Module de suppression des absences</p>
</dd>
</dl>
<br>
---
# Functions
<dl>
<dt><a href="#get">get(url)</a><code>Promise.&lt;Response&gt;</code></dt>
<dd><p>Lance une requête GET a l&#39;URL donnée en paramètre et retourne une Promise.</p>
</dd>
<dt><a href="#getLogin">getLogin(url, login, pass)</a><code>Promise.&lt;Response&gt;</code></dt>
<dd><p>Lance une requête GET a l&#39;URL donnée en paramètre et retourne une Promise.
Dans ce cas particulier, on ajoute un header d&#39;authentification.</p>
</dd>
<dt><a href="#getJson">getJson(url)</a><code>Promise.&lt;{data: any}&gt;</code></dt>
<dd><p>Lance une requête GET a l&#39;URL donnée en paramètre et retourne les données JSON d&#39;une Promise.</p>
</dd>
<dt><a href="#post">post(url, data)</a><code>Promise.&lt;Response&gt;</code></dt>
<dd><p>Lance une requête POST a l&#39;URL donnée en paramètre et retourne une Promise.</p>
</dd>
</dl>
<br>
---
## `/`
<a name="ChoixDept"></a>
## ChoixDept
Page de choix du département
**Kind**: global class
<a name="ChoixDept+getData"></a>
### choixDept.getData()
Recupère la liste des départements depuis l'API
**Kind**: instance method of [<code>ChoixDept</code>](#ChoixDept)
<a name="Etudiant"></a>
## Etudiant
Page d'information d'un étudiant'
**Kind**: global class
<a name="Etudiant+getData"></a>
### etudiant.getData()
Recupère les données de l'étudiant depuis l'API
**Kind**: instance method of [<code>Etudiant</code>](#Etudiant)
<a name="GestionSemestre"></a>
## GestionSemestre
Page de gestion du semestre
**Kind**: global class
<a name="GestionSemestre+getData"></a>
### gestionSemestre.getData()
Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API
**Kind**: instance method of [<code>GestionSemestre</code>](#GestionSemestre)
<a name="Login"></a>
## Login
Page de Login
**Kind**: global class
<a name="Login+checkCredentials"></a>
### login.checkCredentials(e)
Verifie la validité des identifiants depuis l'API
**Kind**: instance method of [<code>Login</code>](#Login)
| Param | Type |
| --- | --- |
| e | <code>event</code> |
<a name="Scolarite"></a>
## Scolarite
Page de choix du semestre
**Kind**: global class
<a name="Scolarite+getData"></a>
### scolarite.getData()
Recupère la liste des semestres depuis l'API
**Kind**: instance method of [<code>Scolarite</code>](#Scolarite)
<a name="ScoNavBar"></a>
## ScoNavBar
Barre de navigation
**Kind**: global class
<a name="SearchStudent"></a>
## SearchStudent
Module de recherche d'étudiant
**Kind**: global class
* [SearchStudent](#SearchStudent)
* [.searchStudent(search)](#SearchStudent+searchStudent)
* [.result()](#SearchStudent+result) ⇒ <code>JSX.Element</code>
<a name="SearchStudent+searchStudent"></a>
### searchStudent.searchStudent(search)
Lance une recherche de l'étudiant depuis l'API
**Kind**: instance method of [<code>SearchStudent</code>](#SearchStudent)
| Param | Type | Description |
| --- | --- | --- |
| search | <code>String</code> | Texte recherché |
<a name="SearchStudent+result"></a>
### searchStudent.result() ⇒ <code>JSX.Element</code>
Presentation du résultat
**Kind**: instance method of [<code>SearchStudent</code>](#SearchStudent)
**Returns**: <code>JSX.Element</code> - - Resultat au format JSX
<br>
---
## `/GestionSemestre`
<a name="Absences"></a>
## Absences
Page de gestion des absences
**Kind**: global class
* [Absences](#Absences)
* [.openModal(key, data)](#Absences+openModal)
* [.getData()](#Absences+getData)
<a name="Absences+openModal"></a>
### absences.openModal(key, data)
Gère l'ouverture des Modal
**Kind**: instance method of [<code>Absences</code>](#Absences)
| Param | Type | Description |
| --- | --- | --- |
| key | <code>String</code> | Correspond au type de modal [isOpen, isDelOpen, isJustOpen] |
| data | <code>Object</code> | Objet contenant les données à transmettre |
<a name="Absences+getData"></a>
### absences.getData()
Recupère les données d'absences depuis l'API
**Kind**: instance method of [<code>Absences</code>](#Absences)
<a name="Accueil"></a>
## Accueil
Page d'accueil de la gestion du semestre
**Kind**: global class
<a name="Accueil+getData"></a>
### accueil.getData()
Recupère les données du semestre selectionné depuis l'API
**Kind**: instance method of [<code>Accueil</code>](#Accueil)
<a name="Bulletin"></a>
## Bulletin
Page de présentation des bulletins étudiants
**Kind**: global class
* [Bulletin](#Bulletin)
* [.getData()](#Bulletin+getData)
* [.getPdf()](#Bulletin+getPdf)
<a name="Bulletin+getData"></a>
### bulletin.getData()
Recupère les données de bulletin depuis l'API
**Kind**: instance method of [<code>Bulletin</code>](#Bulletin)
<a name="Bulletin+getPdf"></a>
### bulletin.getPdf()
Recupère les données de bulletin en tant que "blob" pour un PDF depuis l'API
**Kind**: instance method of [<code>Bulletin</code>](#Bulletin)
<a name="Etudiants"></a>
## Etudiants
Page de présentation des étudiants inscrits au semestre
**Kind**: global class
<a name="Etudiants+getData"></a>
### etudiants.getData()
Recupère la liste des étudiants inscrits au semestre depuis l'API
**Kind**: instance method of [<code>Etudiants</code>](#Etudiants)
<br>
---
## `/GestionSemestre/Absences`
<a name="JustAbs"></a>
## JustAbs
Module de justification des absences
**Kind**: global class
* [JustAbs](#JustAbs)
* [.onFormSubmit](#JustAbs+onFormSubmit)
* [.postData(data)](#JustAbs+postData)
<a name="JustAbs+onFormSubmit"></a>
### justAbs.onFormSubmit
Gestion des données du formulaire
**Kind**: instance property of [<code>JustAbs</code>](#JustAbs)
| Param | Type |
| --- | --- |
| e | <code>Event</code> |
<a name="JustAbs+postData"></a>
### justAbs.postData(data)
Envoie une requête POST a l'API
**Kind**: instance method of [<code>JustAbs</code>](#JustAbs)
| Param | Type | Description |
| --- | --- | --- |
| data | <code>String</code> | Données à envoyer sous la forme param1=val1&param2=val2... |
<a name="SaisieAbs"></a>
## SaisieAbs
Module de saisie des absences
**Kind**: global class
* [SaisieAbs](#SaisieAbs)
* [.onFormSubmit](#SaisieAbs+onFormSubmit)
* [.postData(data)](#SaisieAbs+postData)
<a name="SaisieAbs+onFormSubmit"></a>
### saisieAbs.onFormSubmit
Gestion des données du formulaire
**Kind**: instance property of [<code>SaisieAbs</code>](#SaisieAbs)
| Param | Type |
| --- | --- |
| e | <code>Event</code> |
<a name="SaisieAbs+postData"></a>
### saisieAbs.postData(data)
Envoie une requête POST a l'API
**Kind**: instance method of [<code>SaisieAbs</code>](#SaisieAbs)
| Param | Type | Description |
| --- | --- | --- |
| data | <code>String</code> | Données à envoyer sous la forme param1=val1&param2=val2... |
<a name="SupprAbs"></a>
## SupprAbs
Module de suppression des absences
**Kind**: global class
<a name="SupprAbs+postData"></a>
### supprAbs.postData(data)
Envoie une requête POST a l'API
**Kind**: instance method of [<code>SupprAbs</code>](#SupprAbs)
| Param | Type | Description |
| --- | --- | --- |
| data | <code>String</code> | Données à envoyer sous la forme param1=val1&param2=val2... |
<br>
---
## Global
<a name="get"></a>
## get(url) ⇒ <code>Promise.&lt;Response&gt;</code>
Lance une requête GET a l'URL donnée en paramètre et retourne une Promise.
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| url | <code>String</code> | URL de la requête |
<a name="getLogin"></a>
## getLogin(url, login, pass) ⇒ <code>Promise.&lt;Response&gt;</code>
Lance une requête GET a l'URL donnée en paramètre et retourne une Promise. Dans ce cas particulier, on ajoute un header d'authentification.
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| url | <code>String</code> | URL de la requête |
| login | <code>String</code> | Identifiant |
| pass | <code>String</code> | Mot de passe |
<a name="getJson"></a>
## getJson(url) ⇒ <code>Promise.&lt;{data: any}&gt;</code>
Lance une requête GET a l'URL donnée en paramètre et retourne les données JSON d'une Promise.
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| url | <code>String</code> | URL de la requête |
<a name="post"></a>
## post(url, data) ⇒ <code>Promise.&lt;Response&gt;</code>
Lance une requête POST a l'URL donnée en paramètre et retourne une Promise.
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| url | <code>String</code> | URL de la requête |
| data | <code>String</code> | Données de la requête au format "param1=val1&param2=val2..." |

View File

@ -2,25 +2,25 @@
## Description
Version mobile de l'application web ScoDoc (v1)
Version mobile de l'application web ScoDoc (v1.7)
### Fonctionnalités:
- Login
- Choix de département / formation
- Affichage des profils étudiants
- Recherche d'élèves
- Recherche d'élèves | Filtrage par groupe de TD/TP
- Affichage des bulletins de notes
- Gestion des absences
### Installation
Le contenu du dossier `build` devrait se situer dans `Scodoc\static\mobile`.
Le dossier `build` est disponible dans les [releases](https://scodoc.org/git/theal/ScoDocMobile/releases) du projet ou à générer depuis les sources.
## Usage
Modifier le fichier index.js (ligne 8) afin de mettre l'endpoint de l'API ScoDoc
`npm install` > Crée un dossier `build` avec le contenu du site en prod
### Dans le cadre d'un serveur web Apache
Le contenu du dossier `build` doit etre la racine du site web.
`npm run build` > Génère un dossier `build` avec le contenu du site en prod.
## Arborescence

2424
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,17 @@
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^12.8.3",
"bootstrap": "^4.6.0",
"i": "^0.3.6",
"js-cookie": "^2.2.1",
"js-cookies": "^1.0.4",
"jsdoc": "^3.6.7",
"jsdoc-to-markdown": "^7.0.1",
"postcss": "^8.2.15",
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
"react-device-detect": "^1.17.0",
"react-dom": "^17.0.2",
"react-lazy-load-image-component": "^1.5.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"react-select": "^4.3.0",
@ -23,7 +28,8 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"docs": "jsdoc2md ./src > docs.md"
},
"eslintConfig": {
"extends": [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,7 +1,9 @@
import React, {Component} from "react";
import {Link} from "react-router-dom";
import './Style.css'
import {getJson} from './Request'
/** Page de choix du département */
class ChoixDept extends Component {
constructor(props) {
super(props);
@ -12,21 +14,18 @@ class ChoixDept extends Component {
}
componentWillMount() {
this.getData()
}
/**
* Recupère la liste des départements depuis l'API
*/
getData() {
let BASE_URL = window.$api_url
fetch(BASE_URL + 'list_depts?format=json', {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
// Traitement des données JSON
response.json().then(data => ({
data: data,
status: response.status
})
).then(res => {
this.setState({ depts: res.data })
}));
getJson(BASE_URL + 'list_depts?format=json')
.then(res => {
this.setState({ depts: res.data })
});
}
render() {
@ -38,7 +37,7 @@ class ChoixDept extends Component {
{this.state.depts.map((dept, index) => {
return (
<div className="col-sm" key={index} id="wrapDept">
<Link to={`/ScoDoc/static/mobile/${dept}/Scolarite`}>
<Link to={`/${dept}/Scolarite`}>
Département {dept}
</Link>
</div>

View File

@ -1,7 +1,11 @@
import React, {Component} from "react";
import {Link} from "react-router-dom";
import './Style.css'
import ScoNavBar from "./ScoNavBar";
import {getJson} from "./Request";
import {Button} from "react-bootstrap";
/** Page d'information d'un étudiant' */
class Etudiant extends Component {
constructor(props) {
super(props);
@ -17,43 +21,28 @@ class Etudiant extends Component {
}
componentWillMount() {
let dept = window.location.href.split('/')[6]
let etudid = window.location.href.split('/')[9]
this.getData()
}
/**
* Recupère les données de l'étudiant depuis l'API
*/
getData() {
let dept = window.location.href.split('/')[7]
let etudid = window.location.href.split('/')[10]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept + '/Scolarite/Notes/etud_info?format=json&etudid=' + etudid, {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
// Traitement des données JSON
response.json().then(data => ({
data: data,
status: response.status
})
).then(res => {
this.setState({ etud: res.data })
this.setState({ formation: res.data.insemestre })
// Recuperation des données de semestres pour la formation d'un étudiant
res.data.insemestre.map((sem, index) => {
fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem.formsemestre_id, {
method: 'GET',
verify: false,
credentials: 'include',
getJson(BASE_URL + dept + '/Scolarite/Notes/etud_info?format=json&etudid=' + etudid)
.then(res => {
this.setState({ etud: res.data, formation: res.data.insemestre })
// Recuperation des données de semestres pour la formation d'un étudiant
res.data.insemestre.map((sem) => {
getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem.formsemestre_id)
.then(res => {
let joined = this.state.semestres.concat(res.data[0]);
this.setState({ semestres: joined, loaded: true })
})
.then(response =>
response.json().then(data => ({
// Traitement des données JSON
data: data,
status: response.status
}))
).then(res => {
let joined = this.state.semestres.concat(res.data[0]);
this.setState({ semestres: joined, loaded: true })
})
})
})
);
})
}
render() {
@ -65,7 +54,7 @@ class Etudiant extends Component {
<h1>{this.state.etud.nomprenom}</h1>
<img
alt={`${this.state.etud.nomprenom}`}
src={`/ScoDoc/${window.location.href.split('/')[6]}/Scolarite/Notes/${this.state.etud.photo_url}`}
src={`/ScoDoc/${window.location.href.split('/')[7]}/Scolarite/Notes/${this.state.etud.photo_url}`}
width="102"
height="128"
className="d-inline-block align-top"
@ -76,10 +65,10 @@ class Etudiant extends Component {
this.state.etud.email !== "" || this.state.etud.emailperso !== "" ?
<div className="col-sm">
<h4>Contact</h4>
{this.state.etud.telephone !== "" && <a href={'tel:' + this.state.etud.telephone}>Téléphone: {this.state.etud.telephone}</a>}<br/>
{this.state.etud.telephonemobile !== "" && <a href={'tel:' + this.state.etud.telephonemobile}>Mobile: {this.state.etud.telephonemobile}</a>}<br/>
{this.state.etud.email !== "" && <a href={'mailto:' + this.state.etud.email}>Mail étudiant: {this.state.etud.email}</a>}<br/>
{this.state.etud.emailperso !== "" && <a href={'mailto:' + this.state.etud.emailperso}>Mail personnel: {this.state.etud.emailperso}</a>}<br/>
{this.state.etud.telephone !== "" && <><a href={'tel:' + this.state.etud.telephone}>Téléphone: {this.state.etud.telephone}</a><br/></>}
{this.state.etud.telephonemobile !== "" && <><a href={'tel:' + this.state.etud.telephonemobile}>Mobile: {this.state.etud.telephonemobile}</a><br/></>}
{this.state.etud.email !== "" && <><a href={'mailto:' + this.state.etud.email}>Mail étudiant: {this.state.etud.email}</a><br/></>}
{this.state.etud.emailperso !== "" && <><a href={'mailto:' + this.state.etud.emailperso}>Mail personnel: {this.state.etud.emailperso}</a><br/></>}
</div>
:
<div className="col-sm">Aucun contact disponible</div>
@ -118,22 +107,33 @@ class Etudiant extends Component {
return (
<div>
<b>{sem.titreannee}</b><br/>
{sem.date_debut} - {sem.date_fin}
{sem.date_debut} - {sem.date_fin}<br/>
{this.state.etud.insemestre[index].groupes !== "" && this.state.etud.insemestre[index].groupes &&
"Groupes: " + this.state.etud.insemestre[index].groupes
}
<h5>Liens</h5>
<Link to={{
pathname: `/${window.location.href.split('/')[7]}/Scolarite/${sem.formsemestre_id}/GestionSem`,
tab: "Absences",
etudid: window.location.href.split('/')[10],
name: this.state.etud.nomprenom
}}>
<Button variant="primary" style={{"margin-right": "2px", "margin-bottom": "2px"}}>Vers Absences</Button>
</Link>
<Link to={{
pathname: `/${window.location.href.split('/')[7]}/Scolarite/${sem.formsemestre_id}/GestionSem`,
tab: "Bulletin",
etudid: window.location.href.split('/')[10],
name: this.state.etud.nomprenom
}}>
<Button variant="primary" style={{"margin-left": "2px", "margin-bottom": "2px"}}>Vers bulletin</Button>
</Link>
</div>
)
})}
</div>
}
</div>
{/* TODO: Lien vers la gestion des absences
<div id="wrapDept" className="col-sm">
<Link to="">
<div className="col-sm">
Gestion des absences
</div>
</Link>
</div>
*/}
</div>
</div>
</div>

View File

@ -1,12 +1,14 @@
import React, {Component} from "react";
import {Tabs, Tab} from "react-bootstrap"
import {Tabs, Tab, Nav, NavItem, NavLink} from "react-bootstrap"
import Accueil from "./GestionSemestre/Accueil";
import Absences from "./GestionSemestre/Absences";
import Eleves from "./GestionSemestre/Eleves";
import Etudiants from "./GestionSemestre/Etudiants";
import ScoNavBar from "./ScoNavBar";
import Bulletin from "./GestionSemestre/Bulletin";
import Select from "react-select";
import {getJson} from "./Request";
/** Page de gestion du semestre */
class GestionSemestre extends Component {
constructor(props){
super(props)
@ -14,33 +16,38 @@ class GestionSemestre extends Component {
selectOptions: [],
id: "",
name: '',
defaulttab: "Accueil",
defaultsel: "",
loading: true
}
}
componentWillMount() {
// Recuperation de la liste des semestres
let dept = window.location.href.split('/')[6]
let sem = window.location.href.split('/')[8]
this.getData()
if (this.props.location.tab) {
this.setState({defaulttab: this.props.location.tab, defaultsel: this.props.location.etudid,
id: this.props.location.etudid, name: this.props.location.name
})
}
}
/**
* Recupère la liste des étudiants inscrits au semestre pour le Select depuis l'API
*/
getData() {
let dept = window.location.href.split('/')[7]
let sem = window.location.href.split('/')[9]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept +
'/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
// Traitement des données JSON
response.json().then(data => ({
data: data,
})
).then(res => {
// Création d'une liste pour le select
res.data.map((student, index) => {
let joined = this.state.selectOptions.concat({label: student.nom_disp + " " + student.prenom, value: student.etudid});
this.setState({selectOptions: joined})
})
getJson(BASE_URL + dept + '/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem)
.then(res => {
this.setState({students: res.data})
// Création d'une liste pour le select
res.data.map((student) => {
let joined = this.state.selectOptions.concat({label: student.nom_disp + " " + student.prenom, value: student.etudid});
this.setState({selectOptions: joined, loading: false})
})
);
})
}
handleSelectChange(e){
@ -56,13 +63,17 @@ class GestionSemestre extends Component {
{/* Selection de l'étudiant pour les sous-composants */}
<div className="col-sm" id="wrapDept">
Choix de l'étudiant
<Select className="mySelect" options={this.state.selectOptions} onChange={this.handleSelectChange.bind(this)}/>
<Select className="mySelect"
isLoading={this.state.loading}
options={this.state.selectOptions}
onChange={this.handleSelectChange.bind(this)}
value={this.state.selectOptions.find(option => option.value === this.state.defaultsel)} />
</div>
</div>
</div>
<div>
<Tabs defaultActiveKey="Accueil" id="controlled-tab-example">
<Tab eventKey="Accueil" title="Accueil" >
<Tabs fill defaultActiveKey={this.state.defaulttab} id="controlled-tab-example" variant="pills">
<Tab eventKey="Accueil" title="Accueil">
<Accueil />
</Tab>
<Tab eventKey="Absences" title="Absences">
@ -71,8 +82,8 @@ class GestionSemestre extends Component {
<Tab eventKey="Bulletin" title="Bulletins">
<Bulletin id={this.state.id} name={this.state.name}/>
</Tab>
<Tab eventKey="Eleves" title="Eleves">
<Eleves />
<Tab eventKey="Etud" title="Etudiants">
<Etudiants students={this.state.students}/>
</Tab>
</Tabs>
</div>

View File

@ -1,10 +1,12 @@
import React, {Component} from "react";
import {Button, Col} from 'react-bootstrap'
import {Button, Spinner, Col} from 'react-bootstrap'
import '../Style.css'
import SaisieAbs from "./Absences/SaisieAbs";
import SupprAbs from "./Absences/SupprAbs";
import JustAbs from "./Absences/JustAbs";
import {getJson} from "../Request";
/** Page de gestion des absences */
class Absences extends Component {
constructor(props){
super(props)
@ -20,13 +22,16 @@ class Absences extends Component {
abs: [],
absjust: [],
// Données d'une absence selectionnée
data: {}
data: {},
// En cours de recuperation de données
loading: false
}
}
// Recuperation des données en cas de changement de props (dans notre cas, changement d'élève)
// Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.setState({loading: true})
this.getData();
}
}
@ -36,6 +41,11 @@ class Absences extends Component {
if (this.props.id !== "") {this.getData()}
}
/**
* Gère l'ouverture des Modal
* @param key {String} - Correspond au type de modal [isOpen, isDelOpen, isJustOpen]
* @param data {Object} - Objet contenant les données à transmettre
*/
openModal(key, data) {
this.setState({[key]: true}, () => setTimeout(() => {
this.setState({[key]: false})
@ -43,52 +53,19 @@ class Absences extends Component {
if (data) {this.setState({data: data})}
}
/**
* Recupère les données d'absences depuis l'API
*/
getData() {
this.getAbs()
this.getAbsJust()
}
getAbs() {
let dept = window.location.href.split('/')[6]
let dept = window.location.href.split('/')[7]
let BASE_URL = window.$api_url
if (this.state.id !== "") {
fetch(BASE_URL + dept + "/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=" + this.props.id, {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
// Traitement des données JSON
response.json().then(data => ({
data: data,
})
).then(res => {
// Recuperation de la liste des absences
this.setState({abs: res.data})
})
);
}
}
getAbsJust() {
let dept = window.location.href.split('/')[6]
let BASE_URL = window.$api_url
if (this.state.id !== "") {
fetch(BASE_URL + dept + "/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=" + this.props.id, {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
// Traitement des données JSON
response.json().then(data => ({
data: data,
})
).then(res => {
// Recuperation de la liste des absences
this.setState({absjust: res.data})
})
);
// Recuperation des absences non-justifiées
getJson(BASE_URL + dept + "/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=0&etudid=" + this.props.id)
.then(res => this.setState({abs: res.data}));
// Recuperation des absences justifiées
getJson(BASE_URL + dept + "/Scolarite/Absences/ListeAbsEtud?format=json&absjust_only=1&etudid=" + this.props.id)
.then(res => this.setState({absjust: res.data, loading: false}));
}
}
@ -118,10 +95,13 @@ class Absences extends Component {
<span>🗘</span>
</Button>
</h4>
{(this.state.abs.length === 0 && this.state.absjust.length === 0 && this.props.name !== "") &&
<h6>Aucune absence de l'élève</h6>
{this.state.loading === true &&
<Spinner animation="border"/>
}
{this.state.abs.map((abs, index) => {
{(this.state.abs.length + this.state.absjust.length === 0 && this.props.name !== "" && this.state.loading === false) &&
<h6>Aucune absence de l'étudiant.e</h6>
}
{this.state.abs.map((abs) => {
return (
<div className="col-sm" id="wrapDept">
<Col>
@ -153,7 +133,7 @@ class Absences extends Component {
</div>
)
})}
{this.state.absjust.map((abs, index) => {
{this.state.absjust.map((abs) => {
return (
<div className="col-sm" id="wrapDept">
<Col>

View File

@ -1,6 +1,8 @@
import React, {Component} from "react";
import {Button, Col, Form, Modal} from "react-bootstrap";
import {post} from "../../Request";
/** Module de justification des absences */
class JustAbs extends Component {
constructor(props){
super(props)
@ -28,20 +30,22 @@ class JustAbs extends Component {
}
}
/**
* Envoie une requête POST a l'API
* @param data {String} - Données à envoyer sous la forme param1=val1&param2=val2...
*/
postData(data) {
let dept = window.location.href.split('/')[6]
let dept = window.location.href.split('/')[7]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept + "/Scolarite/Absences/doJustifAbsence", {
method: 'POST',
verify: false,
credentials: 'include',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: data
})
post(BASE_URL + dept + "/Scolarite/Absences/doJustifAbsence", data)
// Fermeture du modal
this.setState({isOpen: false})
}
/**
* Gestion des données du formulaire
* @param e {Event}
*/
onFormSubmit = e => {
// Traitement du formulaire
// Empeche le bouton de rediriger ou actualiser la page

View File

@ -1,7 +1,8 @@
import React, {Component} from "react";
import {Button, Col, Form, Modal} from "react-bootstrap";
import Absences from '../Absences'
import {post} from "../../Request";
/** Module de saisie des absences */
class SaisieAbs extends Component {
constructor(props){
super(props)
@ -25,6 +26,10 @@ class SaisieAbs extends Component {
}
}
/**
* Gestion des données du formulaire
* @param e {Event}
*/
onFormSubmit = e => {
// Traitement du formulaire
// Empeche le bouton de rediriger ou actualiser la page
@ -57,16 +62,14 @@ class SaisieAbs extends Component {
}
}
/**
* Envoie une requête POST a l'API
* @param data {String} - Données à envoyer sous la forme param1=val1&param2=val2...
*/
postData(data) {
let dept = window.location.href.split('/')[6]
let dept = window.location.href.split('/')[7]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept + "/Scolarite/Absences/doSignaleAbsence", {
method: 'POST',
verify: false,
credentials: 'include',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: data
})
post(BASE_URL + dept + "/Scolarite/Absences/doSignaleAbsence", data)
.then(response => {
if (response.status === 200) {
// Fermeture du modal

View File

@ -1,6 +1,8 @@
import React, {Component} from "react";
import {Button, Modal} from "react-bootstrap";
import {post} from "../../Request";
/** Module de suppression des absences */
class SupprAbs extends Component {
constructor(props){
super(props)
@ -22,18 +24,16 @@ class SupprAbs extends Component {
}
}
/**
* Envoie une requête POST a l'API
* @param data {String} - Données à envoyer sous la forme param1=val1&param2=val2...
*/
postData() {
let dept = window.location.href.split('/')[6]
let dept = window.location.href.split('/')[7]
let BASE_URL = window.$api_url
let data = "datedebut=" + this.props.data.date + "&datefin=" + this.props.data.date +
"&demijournee=" + this.props.data.demijournee + "&etudid=" + this.state.etudid
fetch(BASE_URL + dept + "/Scolarite/Absences/doAnnuleAbsence", {
method: 'POST',
verify: false,
credentials: 'include',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: data
})
post(BASE_URL + dept + "/Scolarite/Absences/doAnnuleAbsence", data)
// Fermeture du modal
this.setState({isOpen: false})
}

View File

@ -1,39 +1,66 @@
import React, {Component} from "react";
import '../Style.css'
import {getJson} from "../Request";
import {Spinner} from "react-bootstrap";
/** Page d'accueil de la gestion du semestre */
class Accueil extends Component {
constructor(props) {
super(props);
this.state = {
semestre: {},
resp: [],
loading: true
};
}
componentWillMount() {
let dept = window.location.href.split('/')[6]
let sem = window.location.href.split('/')[8]
this.setState({loading: true})
this.getData()
}
/**
* Recupère les données du semestre selectionné depuis l'API
*/
getData() {
let dept = window.location.href.split('/')[7]
let sem = window.location.href.split('/')[9]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept +
'/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem, {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
response.json().then(data => ({
data: data,
})
).then(res => {
this.setState({ semestre: res.data[0]});
}));
// Recuperation des infos de semestre
getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)
.then(res => {
this.setState({ semestre: res.data[0], resp_l: res.data[0].responsables});
// Recuperation des noms complets des responsables
res.data[0].responsables.map((resp) =>
getJson(BASE_URL + dept + '/Scolarite/Users/user_info?format=json&user_name=' + resp)
.then(res => {
let joined = this.state.resp.concat(res.data.nomplogin)
this.setState({resp: joined, loading: false})
})
.catch(error => {
this.setState({resp: this.state.resp_l, loading: false})
})
)
})
}
render() {
return (
<div className="wrapper">
<h1 id="pageTitle">{this.state.semestre.titre}<br/>
Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}<br/>
(Responsable: {this.state.semestre.responsables})</h1>
{this.state.loading === false ?
<h1 id="pageTitle">{this.state.semestre.titre}<br/>
Semestre {this.state.semestre.semestre_id} en {this.state.semestre.modalite}<br/>
{this.state.resp.length === 1 ? "Responsable: (" : "Responsables ("}
{this.state.resp.map((resp, index) => {
if (index !== this.state.resp.length-1) {return (resp + ", ")}
else {return (resp + ")")}
})}
</h1>
:
// En cas de chargement
<Spinner animation="border"/>
}
</div>
)
}

View File

@ -1,72 +1,67 @@
import React, {Component} from "react";
import {Table, Button, Dropdown} from "react-bootstrap"
import {Table, Button, Dropdown, Spinner} from "react-bootstrap"
import '../Style.css'
import {get, getJson} from "../Request";
/** Page de présentation des bulletins étudiants */
class Bulletin extends Component {
constructor(props) {
super(props);
this.state = {
bltn: {},
datue: {},
loaded: false
loaded: false,
loading: false
};
this.getData = this.getData.bind(this);
}
/**
* Recupère les données de bulletin depuis l'API
*/
getData() {
let dept = window.location.href.split('/')[6]
let sem = window.location.href.split('/')[8]
let dept = window.location.href.split('/')[7]
let sem = window.location.href.split('/')[9]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' +
sem +'&etudid=' + this.props.id +'&format=json', {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
response.json().then(data => ({
data: data,
status: response.status
})
).then(res => {
// Recuperation des données du bulletin
this.setState({ bltn: res.data }, () => {
// Recuperation d'un tableau CodeUE | NomUE
let ls = {}
for (let elm in this.state.bltn.decision_ue) {
elm = this.state.bltn.decision_ue[elm]
ls[elm.acronyme] = elm.titre
}
this.setState({datue: ls}, () => {
// Marquage du bulletin comme "chargé"
this.setState({loaded: true})
})
getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=' + sem +'&etudid=' +
this.props.id +'&format=json')
.then(res => {
// Recuperation des données du bulletin
this.setState({ bltn: res.data }, () => {
// Recuperation d'un tableau CodeUE | NomUE
let ls = {}
for (let elm in this.state.bltn.decision_ue) {
elm = this.state.bltn.decision_ue[elm]
ls[elm.acronyme] = elm.titre
}
this.setState({datue: ls}, () => {
// Marquage du bulletin comme "chargé"
this.setState({loaded: true, loading: false})
})
})
);
})
}
// Recuperation du bulletin au format PDF
/**
* Recupère les données de bulletin en tant que "blob" pour un PDF depuis l'API
*/
getPdf() {
let BASE_URL = window.$api_url
let dept = window.location.href.split('/')[6]
let sem = window.location.href.split('/')[8]
fetch( BASE_URL + dept + "/Scolarite/Notes/formsemestre_bulletinetud?" +
"formsemestre_id=" + sem + "&etudid=" + this.props.id + "&format=pdf&version=selectedevals", {
method: 'GET',
verify: false,
credentials: 'include',
})
.then( res => res.blob() )
.then( blob => {
let dept = window.location.href.split('/')[7]
let sem = window.location.href.split('/')[9]
get(BASE_URL + dept + "/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=" + sem +
"&etudid=" + this.props.id + "&format=pdf&version=selectedevals")
.then(res => res.blob())
.then(blob => {
let file = window.URL.createObjectURL(blob);
window.location.assign(file);
});
}
// Recuperation des données en cas de changement de props (dans notre cas, changement d'élève)
// Recuperation des données en cas de changement de props (dans notre cas, changement d'étudiant.e)
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.setState({loading: true})
this.getData();
}
}
@ -82,6 +77,9 @@ class Bulletin extends Component {
<div style={{"margin-bottom": "20px"}}>
<h1 id="pageTitle">Bulletins de notes</h1>
</div>
{this.state.loading === true && this.state.loaded === false &&
<Spinner animation="border"/>
}
{this.state.loaded === true &&
<div>
<Table responsive="sm">
@ -101,13 +99,15 @@ class Bulletin extends Component {
<Dropdown.Menu>
<Dropdown.Item href="#">Min: {this.state.bltn.note.min}</Dropdown.Item>
<Dropdown.Item href="#">Max: {this.state.bltn.note.max}</Dropdown.Item>
<Dropdown.Item href="#">Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
{this.state.bltn.hasOwnProperty('rang') &&
<Dropdown.Item href="#">Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
}
</Dropdown.Menu>
</Dropdown>
</th>
</tr>
</thead>
{this.state.bltn.ue.map((ue, index) => {
{this.state.bltn.ue.map((ue) => {
return (
<tbody>
<tr className="ueRow">
@ -121,12 +121,14 @@ class Bulletin extends Component {
<Dropdown.Menu>
<Dropdown.Item href="#">Min: {ue.note.min}</Dropdown.Item>
<Dropdown.Item href="#">Max: {ue.note.max}</Dropdown.Item>
<Dropdown.Item href="#">Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
{ue.hasOwnProperty('rang') &&
<Dropdown.Item href="#">Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
}
</Dropdown.Menu>
</Dropdown>
</td>
</tr>
{ue.module.map((mod, index) => {
{ue.module.map((mod) => {
return (
<tr>
<td colSpan="3">{mod.titre.replace("&apos;", "'")}</td>
@ -139,7 +141,9 @@ class Bulletin extends Component {
<Dropdown.Menu>
<Dropdown.Item href="#">Min: {mod.note.min}</Dropdown.Item>
<Dropdown.Item href="#">Max: {mod.note.max}</Dropdown.Item>
<Dropdown.Item href="#">Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
{mod.hasOwnProperty('rang') &&
<Dropdown.Item href="#">Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
}
<Dropdown.Item href="#">Coefficient: {mod.coefficient}</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>

View File

@ -1,73 +0,0 @@
import React, {Component} from "react";
import '../Style.css'
import {Link} from "react-router-dom";
class Eleves extends Component {
constructor(props) {
super(props);
this.state = {
// Liste des élèves inscrits au semestre
students: [],
};
}
componentWillMount() {
let dept = window.location.href.split('/')[6]
let sem = window.location.href.split('/')[8]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept +
'/Scolarite/Notes/groups_view?with_codes=1&format=json&formsemestre_id=' + sem, {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
// Traitement des données JSON
response.json().then(data => ({
data: data,
})
).then(res => {
// Gestion des données sous forme de tableau a deux colonnes
const dat = res.data.map((x,i) => {
return i % 2 === 0 ? res.data.slice(i, i+2) : null;
}).filter(x => x != null);
this.setState({ students: dat});
}));
}
render() {
return (
<div className="wrapper">
<h1 id="pageTitle">Liste des élèves</h1>
<div className="container">
{this.state.students.map((students, index) => {
// Creation du tableau de deux colonnes
return (
<div className="row justify-content-center">
{students.map((etud, index) => {
return (
<div className="col" key={index} id="wrapDept">
<Link to={`/ScoDoc/static/mobile/${window.location.href.split('/')[6]}/Scolarite/Etudiant/${etud.etudid}`}>
{/* Recuperation de la photo de l'etudiant */}
<img
alt={`${etud.nom_disp} ${etud.prenom}`}
src={`/ScoDoc/RT/Scolarite/Notes/get_photo_image?etudid=${etud.etudid}`}
width="102"
height="128"
className="d-inline-block align-top"
/>{' '}<br/>
{etud.nom_disp} {etud.prenom}
</Link>
</div>
)
})}
</div>
)
})}
</div>
</div>
)
}
}
export default Eleves

View File

@ -0,0 +1,170 @@
import React, {Component} from "react";
import {LazyLoadImage} from 'react-lazy-load-image-component';
import '../Style.css'
import {Link} from "react-router-dom";
import {getJson} from "../Request";
import {Spinner} from "react-bootstrap";
import Select from "react-select";
// CONSTANTES DE STYLE SELECT GROUP
const groupStyles = {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
};
const groupBadgeStyles = {
backgroundColor: '#EBECF0',
borderRadius: '2em',
color: '#172B4D',
display: 'inline-block',
fontSize: 12,
fontWeight: 'normal',
lineHeight: '1',
minWidth: 1,
padding: '0.16666666666667em 0.5em',
textAlign: 'center',
};
const formatGroupLabel = data => (
<div style={groupStyles}>
<span>{data.label}</span>
<span style={groupBadgeStyles}>{data.options.length}</span>
</div>
);
/** Page de présentation des étudiants inscrits au semestre */
class Etudiants extends Component {
constructor(props) {
super(props);
this.state = {
// Liste des étudiants inscrits au semestre
students: [],
// Gestion du select
selectOptions: [{label: "Groupe: Aucun", value: "Default"}],
id: "",
name: '',
};
}
componentWillMount() {
this.getData()
}
componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
if (this.props.students.length && this.state.students.length === 0) {
const dat = this.props.students.map((x,i) => {
return i % 2 === 0 ? this.props.students.slice(i, i+2) : null;
}).filter(x => x != null);
this.setState({ students: dat});
}
}
}
/**
* Recupère la liste des groupes du semestre depuis l'API
*/
getData() {
let dept = window.location.href.split('/')[7]
let sem = window.location.href.split('/')[9]
let BASE_URL = window.$api_url
getJson(BASE_URL + dept + '/Scolarite/formsemestre_partition_list?format=json&formsemestre_id=' + sem)
.then(res => {
// eslint-disable-next-line array-callback-return
res.data.map((part) => {
// Ajout de la catégorie
let new_part = {label: part.partition_name, options: []}
// Ajout des groupes
// eslint-disable-next-line array-callback-return
part.group.map((group) => {
new_part.options.push({label: group.group_name, value: group.group_id})
})
// Ajout au state
let joined = this.state.selectOptions.concat(new_part);
this.setState({ selectOptions: joined})
})
})
}
/**
* Recupère la liste des étudiants dans un groupe depuis l'API
*/
getStudents() {
let dept = window.location.href.split('/')[7]
let group = this.state.id
let BASE_URL = window.$api_url
getJson(BASE_URL + dept + '/Scolarite/groups_view?with_codes=1&format=json&group_ids=' + group)
.then(res => {
const dat = res.data.map((x,i) => {
return i % 2 === 0 ? res.data.slice(i, i+2) : null;
}).filter(x => x != null);
this.setState({students: dat});
})
}
/**
* Gestion des données du Select
*/
handleSelectChange(e){
this.setState({id:e.value, name:e.label}, () => {
if (this.state.id !== "Default") {this.getStudents()}
else {
if (this.props.students.length) {
const dat = this.props.students.map((x,i) => {
return i % 2 === 0 ? this.props.students.slice(i, i+2) : null;
}).filter(x => x != null);
this.setState({ students: dat});
}
}
})
}
render() {
return (
<div className="wrapper">
<h1 id="pageTitle">Liste des étudiants</h1>
<div className="container">
<Select
defaultValue={this.state.selectOptions[0]}
options={this.state.selectOptions}
formatGroupLabel={formatGroupLabel}
onChange={this.handleSelectChange.bind(this)}
/>
{this.state.students.length !== 0 ?
this.state.students.map((students) => {
// Creation du tableau de deux colonnes
return (
<div className="row justify-content-center">
{students.map((etud, index) => {
return (
<div className="col" key={index} id="wrapDept">
<Link to={`/${window.location.href.split('/')[7]}/Scolarite/Etudiant/${etud.etudid}`}>
{/* Recuperation de la photo de l'etudiant */}
<LazyLoadImage
alt={`${etud.nom_disp} ${etud.prenom}`}
src={`/ScoDoc/${window.location.href.split('/')[7]}/Scolarite/Notes/get_photo_image?etudid=${etud.etudid}`}
width="102"
height="128"
className="d-inline-block align-top"
/>{' '}<br/>
{etud.nom_disp} {etud.prenom}
</Link>
</div>
)
})}
</div>
)
})
:
<div className="row justify-content-center">
<Spinner animation="border"/>
</div>
}
</div>
</div>
)
}
}
export default Etudiants

View File

@ -3,7 +3,9 @@ import { isMobile } from 'react-device-detect';
import './Style.css'
import ChoixDept from "./ChoixDept";
import ScoNavBar from "./ScoNavBar";
import {getLogin} from "./Request";
/** Page de Login */
class Login extends Component {
constructor(props) {
super(props);
@ -25,6 +27,10 @@ class Login extends Component {
this.setState({ pass: e.target.value });
}
/**
* Verifie la validité des identifiants depuis l'API
* @param e {event}
*/
checkCredentials(e) {
e.preventDefault();
@ -33,15 +39,7 @@ class Login extends Component {
let BASE_URL = window.$api_url
fetch(BASE_URL, {
method: 'GET',
verify: false,
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa(login + ":" + pass)
},
})
getLogin(BASE_URL, login, pass)
.then(res => {
this.setState({ status: res["status"] });
})

67
src/ScoDoc/Request.js Normal file
View File

@ -0,0 +1,67 @@
/**
* Lance une requête GET a l'URL donnée en paramètre et retourne une Promise.
* @param url {String} - URL de la requête
* @returns {Promise<Response>}
*/
export function get(url) {
return (
fetch(url, {
method: 'GET',
verify: false,
credentials: 'include',
})
)
}
/**
* Lance une requête GET a l'URL donnée en paramètre et retourne une Promise.
* Dans ce cas particulier, on ajoute un header d'authentification.
* @param url {String} - URL de la requête
* @param login {String} - Identifiant
* @param pass {String} - Mot de passe
* @returns {Promise<Response>}
*/
export function getLogin(url, login, pass) {
return (
fetch(url, {
method: 'GET',
verify: false,
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa(login + ":" + pass)
},
})
)
}
/**
* Lance une requête GET a l'URL donnée en paramètre et retourne les données JSON d'une Promise.
* @param url {String} - URL de la requête
* @returns {Promise<{data: any}>}
*/
export function getJson(url) {
return get(url)
.then(response => response.json()
.then(data => ({data: data}))
.then(res => {return res})
);
}
/**
* Lance une requête POST a l'URL donnée en paramètre et retourne une Promise.
* @param url {String} - URL de la requête
* @param data {String} - Données de la requête au format "param1=val1&param2=val2..."
* @returns {Promise<Response>}
*/
export function post(url, data) {
return (
fetch(url, {
method: 'POST',
verify: false,
credentials: 'include',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: data
})
)
}

View File

@ -3,6 +3,7 @@ import {Nav, Navbar, Button, Container} from 'react-bootstrap'
import { Redirect } from 'react-router-dom';
import './Style.css'
/** Barre de navigation */
class ScoNavBar extends Component {
constructor(props) {
super(props);
@ -36,13 +37,21 @@ class ScoNavBar extends Component {
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
<Nav.Link href="/ScoDoc">Version Desktop</Nav.Link>
<Button variant="primary" onClick={() => {this.logout()}}>Déconnexion</Button>
<Button variant="outline-primary" href="/ScoDoc" style={{"margin": "1px"}}>Version Desktop</Button>
{window.location.href.split('/').length > 9 &&
<Button
variant="outline-primary"
href={"/ScoDoc/static/mobile/#/" + window.location.href.split('/')[7] + "/Scolarite"}
style={{"margin": "1px"}}>
Retour au choix de semestre
</Button>
}
<Button variant="primary" style={{"margin": "1px"}} onClick={() => {this.logout()}}>Déconnexion</Button>
</Nav>
</Navbar.Collapse>
</Container>
{this.state.logout === true &&
<Redirect push to={window.$api_url + 'static/mobile/'}/>
<Redirect push to="/"/>
}
</Navbar>
)

View File

@ -4,7 +4,9 @@ import './Style.css'
import ScoNavBar from "./ScoNavBar";
import SearchStudent from './SearchStudent'
import {Accordion, Card, Button} from 'react-bootstrap'
import {getJson} from "./Request";
/** Page de choix du semestre */
class Scolarite extends Component {
constructor(props) {
super(props);
@ -14,34 +16,25 @@ class Scolarite extends Component {
toast: false
};
this.dismissToast = this.dismissToast.bind(this);
this.getData = this.getData.bind(this);
}
componentWillMount() {
let dept = window.location.href.split('/')[6]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json', {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
response.json().then(data => ({
data: data,
status: response.status
})
).then(res => {
this.setState({ semestres: res.data });
}));
this.getData()
}
/**
* Recupère la liste des semestres depuis l'API
*/
getData () {
return this.state.semestres
let dept = window.location.href.split('/')[7]
let BASE_URL = window.$api_url
getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')
.then(res => {
this.setState({ semestres: res.data });
})
}
dismissToast() {
this.setState({toast: false})
}
dismissToast = () => this.setState({toast: false})
render() {
return (
@ -63,15 +56,16 @@ class Scolarite extends Component {
<div className="container">
<div className="row">
{this.state.semestres.map((sem, index) => {
if(sem.etat === "1")
return (
<div className="col-sm" key={index} id="wrapDept">
<Link to={`/ScoDoc/static/mobile/${window.location.href.split('/')[6]}/Scolarite/${sem.formsemestre_id}/GestionSem`}>
<h4>{sem.titre} [{sem.modalite}]</h4>
<p>Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]</p>
</Link>
</div>
)
if (sem.etat === "1") {
return (
<div className="col-sm" key={index} id="wrapDept">
<Link to={`/${window.location.href.split('/')[7]}/Scolarite/${sem.formsemestre_id}/GestionSem`}>
<h4>{sem.titre} [{sem.modalite}]</h4>
<p>Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]</p>
</Link>
</div>
)
}
})}
</div>
</div>
@ -80,22 +74,23 @@ class Scolarite extends Component {
</Card>
<Card>
<Card.Header>
<Accordion.Toggle as={Button} variant="link" eventKey="0">
<Accordion.Toggle as={Button} variant="link" eventKey="1">
Semestres passés
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="1">
<Card.Body>
{this.state.semestres.map((sem, index) => {
if(sem.etat !== "1")
if (sem.etat !== "1") {
return (
<div className="col-12" key={index} id="wrapDept">
<Link to={`/ScoDoc/static/mobile/${window.location.href.split('/')[6]}/Scolarite/${sem.formsemestre_id}/GestionSem`}>
<Link to={`/${window.location.href.split('/')[7]}/Scolarite/${sem.formsemestre_id}/GestionSem`}>
<h3>{sem.titre} [{sem.modalite}]</h3>
<p>Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]</p>
</Link>
</div>
)
}
})}
</Card.Body>
</Accordion.Collapse>

View File

@ -1,7 +1,9 @@
import React, {Component} from "react";
import {Link} from "react-router-dom";
import {Row, Col} from "react-bootstrap"
import {getJson} from "./Request";
/** Module de recherche d'étudiant */
class SearchStudent extends Component {
constructor(props) {
super(props);
@ -20,46 +22,47 @@ class SearchStudent extends Component {
this.setState({ search: e.target.value });
}
searchStudent() {
let dept = window.location.href.split('/')[6]
/**
* Lance une recherche de l'étudiant depuis l'API
* @param search {String} - Texte recherché
*/
searchStudent(search) {
let dept = window.location.href.split('/')[7]
let BASE_URL = window.$api_url
fetch(BASE_URL + dept +
'/Scolarite/Notes/search_etud_by_name?term=' + this.state.search +'&format=json', {
method: "GET",
credentials: "include",
})
.then(response =>
response.json().then(data => ({data: data}))
.then(res => {
this.setState({ students: res.data });
if (this.state.students.length === 0) {
this.setState({search_status: 1, toast: true});
} else {
this.setState({search_status: 2, toast: false});
}
}))
getJson(BASE_URL + dept + '/Scolarite/Notes/search_etud_by_name?term=' + search +'&format=json')
.then(res => {
this.setState({ students: res.data });
if (this.state.students.length === 0) {
this.setState({search_status: 1, toast: true});
} else {
this.setState({search_status: 2, toast: false});
}
})
this.setState({searched: true})
}
/**
* Presentation du résultat
* @returns {JSX.Element} - Resultat au format JSX
*/
result() {
if (this.state.toast === true) {
return (
<div id="wrapDept">
Aucun élève trouvé
Aucun étudiant trouvé
</div>
)
} else if (this.state.search_status === 2) {
return (
<Col>
{this.state.students.map((student, index) => {
{this.state.students.map((student) => {
return (
<Row id="wrapDept">
<Link to={`/ScoDoc/static/mobile/${window.location.href.split('/')[6]}/Scolarite/Etudiant/${student.value}`}>
<Link to={`/${window.location.href.split('/')[7]}/Scolarite/Etudiant/${student.value}`}>
<span>{student.label}</span>
</Link>
</Row>
);
)
})}
</Col>
)
@ -72,7 +75,7 @@ class SearchStudent extends Component {
<div className="input-group">
<input type="text" id="search" className="form-control" onChange={this.handleChangeSearch}/>
<div className="input-group-append">
<button type="button" className="btn waves-effect waves-light btn-primary" onClick={() => {this.searchStudent()}}>
<button type="button" className="btn waves-effect waves-light btn-primary" onClick={() => {this.searchStudent(this.state.search)}}>
Rechercher
</button>
</div>

View File

@ -1,5 +1,5 @@
import React from 'react';
import {Switch, Route} from 'react-router-dom';
import {HashRouter, Route} from 'react-router-dom';
import Scolarite from './ScoDoc/Scolarite.js'
import Login from './ScoDoc/Login'
import GestionSemestre from "./ScoDoc/GestionSemestre";
@ -8,12 +8,18 @@ import Etudiant from "./ScoDoc/Etudiant";
const Main = () => {
return (
<Switch>
/*<HashRouter>
<Route exact path='/ScoDoc/static/mobile' component={Login}/>
<Route exact path='/ScoDoc/static/mobile/:DEPT/Scolarite' component={Scolarite}/>
<Route exact path='/ScoDoc/static/mobile/:DEPT/Scolarite/Etudiant/:EtudId' component={Etudiant}/>
<Route exact path='/ScoDoc/static/mobile/:DEPT/Scolarite/:SEM/GestionSem' component={GestionSemestre}/>
</Switch>
</HashRouter>*/
<HashRouter>
<Route exact path='/' component={Login}/>
<Route exact path='/:DEPT/Scolarite' component={Scolarite}/>
<Route exact path='/:DEPT/Scolarite/Etudiant/:EtudId' component={Etudiant}/>
<Route exact path='/:DEPT/Scolarite/:SEM/GestionSem' component={GestionSemestre}/>
</HashRouter>
);
}