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)
This commit is contained in:
Theal0 2021-05-31 14:19:43 +02:00
parent 65cb895a1c
commit d50beeb4d7
7 changed files with 510 additions and 33 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

@ -1,7 +1,9 @@
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 {
@ -63,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>
@ -101,11 +103,29 @@ class Etudiant extends Component {
{this.state.loaded === true &&
<div className="col-sm">
<h4>Formation actuelle</h4>
{this.state.semestres.map((sem) => {
{this.state.semestres.map((sem, index) => {
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]
}}>
<Button variant="primary" style={{"margin-right": "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]
}}>
<Button variant="primary" style={{"margin-left": "2px"}}>Vers bulletin</Button>
</Link>
</div>
)
})}

View File

@ -16,11 +16,19 @@ class GestionSemestre extends Component {
selectOptions: [],
id: "",
name: '',
defaulttab: "Accueil",
defaultsel: ""
}
}
componentWillMount() {
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.state.selectOptions.find(option => option.value === this.state.id)
})
}
}
/**
@ -32,6 +40,7 @@ class GestionSemestre extends Component {
let BASE_URL = window.$api_url
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});
@ -53,23 +62,24 @@ 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" 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">
<Tabs defaultActiveKey={this.state.defaulttab} id="controlled-tab-example">
<Tab eventKey="Accueil" title="Accueil" >
<Accueil />
</Tab>
<Tab eventKey="Absences" title="Absences">
<Absences id={this.state.id} name={this.state.name}/>
</Tab>
<Tab eventKey="Bulletin" title="Bulletins">
<Tab eventKey="Bulletin" title="Bulletin">
<Bulletin id={this.state.id} name={this.state.name}/>
</Tab>
<Tab eventKey="Etud" title="Etudiants">
<Etudiants />
<Etudiants students={this.state.students}/>
</Tab>
</Tabs>
</div>

View File

@ -1,5 +1,5 @@
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";
@ -22,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'étudiant.e)
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.setState({loading: true})
this.getData();
}
}
@ -62,7 +65,7 @@ class Absences extends Component {
.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}));
.then(res => this.setState({absjust: res.data, loading: false}));
}
}
@ -92,7 +95,10 @@ class Absences extends Component {
<span>🗘</span>
</Button>
</h4>
{(this.state.abs.length === 0 && this.state.absjust.length === 0 && this.props.name !== "") &&
{this.state.loading === true &&
<Spinner animation="border"/>
}
{(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) => {

View File

@ -1,6 +1,7 @@
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 {
@ -8,10 +9,13 @@ class Accueil extends Component {
super(props);
this.state = {
semestre: {},
resp: [],
loading: true
};
}
componentWillMount() {
this.setState({loading: true})
this.getData()
}
@ -24,16 +28,25 @@ class Accueil extends Component {
let BASE_URL = window.$api_url
getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json&formsemestre_id=' + sem)
.then(res => {
this.setState({ semestre: res.data[0]});
this.setState({ semestre: res.data[0], resp: res.data[0].responsables, 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>
:
<Spinner animation="border"/>
}
</div>
)
}

View File

@ -1,5 +1,5 @@
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";
@ -10,7 +10,8 @@ class Bulletin extends Component {
this.state = {
bltn: {},
datue: {},
loaded: false
loaded: false,
loading: false
};
this.getData = this.getData.bind(this);
}
@ -35,7 +36,7 @@ class Bulletin extends Component {
}
this.setState({datue: ls}, () => {
// Marquage du bulletin comme "chargé"
this.setState({loaded: true})
this.setState({loaded: true, loading: false})
})
})
})
@ -46,8 +47,8 @@ class Bulletin extends Component {
*/
getPdf() {
let BASE_URL = window.$api_url
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]
get(BASE_URL + dept + "/Scolarite/Notes/formsemestre_bulletinetud?formsemestre_id=" + sem +
"&etudid=" + this.props.id + "&format=pdf&version=selectedevals")
.then(res => res.blob())
@ -60,6 +61,7 @@ class Bulletin extends Component {
// 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();
}
}
@ -75,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">

View File

@ -3,6 +3,7 @@ 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";
/** Page de présentation des étudiants inscrits au semestre */
class Etudiants extends Component {
@ -15,7 +16,18 @@ class Etudiants extends Component {
}
componentWillMount() {
this.getData()
// this.getData()
}
componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
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});
}
}
}
/**
@ -40,30 +52,36 @@ class Etudiants extends Component {
<div className="wrapper">
<h1 id="pageTitle">Liste des étudiants</h1>
<div className="container">
{this.state.students.map((students) => {
// Creation du tableau de deux colonnes
return (
<div className="row justify-content-center">
{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
<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}
/>{' '}<br/>
{etud.nom_disp} {etud.prenom}
</Link>
</div>
)
})}
</div>
)
})}
)
})
:
<div className="row justify-content-center">
<Spinner animation="border"/>
</div>
}
</div>
</div>
)