Compare commits

...

11 Commits
v0 ... 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
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
Theal0
7569934ce8 Release v1
Optimisations finales
Ajout de la gestion d'absences (Ajout, suppression, justification)
2021-05-18 17:21:19 +02:00
25 changed files with 3809 additions and 1554 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,36 +2,25 @@
## Description
Version mobile de l'application web ScoDoc (v0)
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
- Affichage des absences
- 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.
Pour éviter des erreurs 404 liées à l'arborescence dynamique de React:
```
<Directory ...>
...
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ index.html [L]
</Directory>
```
`npm run build` > Génère un dossier `build` avec le contenu du site en prod.
## Arborescence

2971
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,18 @@
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^12.8.3",
"antd": "^4.15.2",
"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",
@ -24,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,45 +1,50 @@
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);
this.state = {
// Liste des départements disponibles pour l'utilisateur
depts: [],
};
}
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 =>
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() {
return (
<div className="wrapper">
<h1 id="pageTitle">Choix du département</h1>
{this.state.depts.map((dept, index) => {
return (
<div id="wrapDept">
<Link to={`/ScoDoc/static/mobile/${dept}/Scolarite`}>
Département {dept}
</Link>
</div>
)
},)}
<div className="container">
<div className="row">
{this.state.depts.map((dept, index) => {
return (
<div className="col-sm" key={index} id="wrapDept">
<Link to={`/${dept}/Scolarite`}>
Département {dept}
</Link>
</div>
)
},)}
</div>
</div>
</div>
);
}

View File

@ -1,60 +1,48 @@
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);
this.state = {
// Données de l'étudiant
etud: {},
semestres: [],
// Formation actuelle de l'étudiant
formation: [],
// Semestres correspondant a la formation de l'étudiant
semestres: [],
loaded: false
};
}
componentWillMount() {
let dept = window.location.href.split('/')[6]
let etudid = window.location.href.split('/')[9]
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 =>
response.json().then(data => ({
data: data,
status: response.status
})
).then(res => {
// Utilisation de '???' en cas de données vides pour le moment}
/*for (let propName in res.data) {
if (res.data[propName] === null || res.data[propName] === undefined || res.data[propName] === "") {
res.data[propName] = "???";
}
}*/
this.setState({ etud: res.data })
this.setState({ formation: res.data.insemestre })
this.getData()
}
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',
/**
* 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
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 => ({
data: data,
status: response.status
}))
).then(res => {
var joined = this.state.semestres.concat(res.data[0]);
this.setState({ semestres: joined, loaded: true })
})
})
})
);
})
}
render() {
@ -66,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"
@ -77,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>
@ -119,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,30 +16,38 @@ class GestionSemestre extends Component {
selectOptions: [],
id: "",
name: '',
defaulttab: "Accueil",
defaultsel: "",
loading: true
}
}
componentWillMount() {
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 =>
response.json().then(data => ({
data: data,
})
).then(res => {
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){
@ -48,13 +58,22 @@ class GestionSemestre extends Component {
return (
<div>
<ScoNavBar/>
<div id="wrapDept">
Choix de l'étudiant
<Select className="mySelect" options={this.state.selectOptions} onChange={this.handleSelectChange.bind(this)}/>
<div className="container">
<div className="row justify-content-center">
{/* Selection de l'étudiant pour les sous-composants */}
<div className="col-sm" id="wrapDept">
Choix de l'étudiant
<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="Acceuil" >
<Tabs fill defaultActiveKey={this.state.defaulttab} id="controlled-tab-example" variant="pills">
<Tab eventKey="Accueil" title="Accueil">
<Accueil />
</Tab>
<Tab eventKey="Absences" title="Absences">
@ -63,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,63 +1,158 @@
import React, {Component} from "react";
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)
this.state = {
abs: []
// Gestion des fenetres modales
// Ajout d'absences
isOpen: false,
// Suppression
isDelOpen: false,
// Justification
isJustOpen: false,
// Données de la liste des absences
abs: [],
absjust: [],
// Données d'une absence selectionnée
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();
}
}
// Recuperation des données lors du chargement de la page si un étudiant est selectionné
componentDidMount() {
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})
}, 500))
if (data) {this.setState({data: data})}
}
/**
* Recupère les données d'absences depuis l'API
*/
getData() {
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&etudid=" + this.props.id, {
method: 'GET',
verify: false,
credentials: 'include',
})
.then(response =>
response.json().then(data => ({
data: data,
})
).then(res => {
this.setState({abs: 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}));
}
}
render() {
return (
<div className="wrapper">
{this.props.id !== "" &&
// Gestion du modal de saisie
<SaisieAbs open={this.state.isOpen} etudid={this.props.id}/>
} {this.props.id !== "" &&
// Gestion du modal de suppression
<SupprAbs open={this.state.isDelOpen} etudid={this.props.id} data={this.state.data}/>
} {this.props.id !== "" &&
// Gestion du modal de justification
<JustAbs open={this.state.isJustOpen} etudid={this.props.id} data={this.state.data}/>
}
<h1 id="pageTitle">Gestion des absences</h1>
{this.props.name !== "" &&
<div className="col-sm" id="wrapDept">
<h4>Absences de {this.props.name}</h4>
{(this.state.abs.length === 0 && this.props.name !== "") &&
<h6>Aucune absence de l'élève</h6>
<h4>Absences de {this.props.name + " "}
<Button variant="primary" size="sm" style={{"margin-right": "2px"}}
onClick={() => this.openModal('isOpen', null)}>
<span>+</span>
</Button>
<Button variant="secondary" size="sm" style={{"margin-left": "2px"}}
onClick={() => this.getData()}>
<span>🗘</span>
</Button>
</h4>
{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">
<h6>{abs.datedmy} | {abs.matin}</h6>
{abs.motif !== "" &&
<span>Motif: {abs.motif}</span>
} {abs.exams !== "" &&
<span>Exam a rattraper: {abs.exams}</span>
}
<Col>
<h5>{abs.datedmy} | {abs.matin}</h5>
{abs.motif !== "" &&
<span>Motif: {abs.motif}</span>
} {abs.exams !== "" &&
<span>Exam a rattraper: {abs.exams}</span>
}
</Col>
<Col>
{abs.motif === "" &&
<Button variant="primary" size="sm" style={{"margin-right": "2px"}}
onClick={() => this.openModal('isJustOpen', {
date: abs.datedmy,
demijournee: abs.ampm
})}>
Justifier
</Button>
}
<Button variant="danger" size="sm" style={{"margin-left": "2px"}}
onClick={() => this.openModal('isDelOpen', {
date: abs.datedmy,
demijournee: abs.ampm
})}>
Supprimer
</Button>
</Col>
</div>
)
})}
{this.state.absjust.map((abs) => {
return (
<div className="col-sm" id="wrapDept">
<Col>
<h5>{abs.datedmy} | {abs.matin}</h5>
{abs.motif !== "" &&
<span>Motif: {abs.motif}</span>
} {abs.exams !== "" &&
<span>Exam a rattraper: {abs.exams}</span>
}
</Col>
<Col>
<Button variant="danger" size="sm" style={{"margin-left": "2px"}}
onClick={() => this.openModal('isDelOpen', {
date: abs.datedmy,
demijournee: abs.ampm
})}>
Supprimer
</Button>
</Col>
</div>
)
})}

View File

@ -0,0 +1,119 @@
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)
this.state = {
isOpen: false,
etudid: "",
date: ""
}
}
openModal = () => this.setState({ isOpen: true });
closeModal = () => this.setState({ isOpen: false });
componentDidUpdate(prevProps) {
if (prevProps.open !== this.props.open) {
this.setState({etudid: this.props.etudid})
if (this.props.open === true) {
this.setState({isOpen: true})
}
// Recuperation et conversion de la date par defaut de l'absence (Format ISO demandé par les form Bootstrap)
let date = this.props.data.date.split("/")
date = new Date(date[2] + "-" + date[1] + "-" + date[0])
date = date.toISOString().substr(0,10);
this.setState({date: date})
}
}
/**
* 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('/')[7]
let BASE_URL = window.$api_url
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
e.preventDefault()
// Recuperation des valeurs
const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())
let reqstr = "etudid=" + this.state.etudid + "&datedebut=" + this.props.data.date
if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== "") {
let dateFin = formDataObj['dateFin'].split("-")
dateFin = dateFin[2] + "/" + dateFin[1] + "/" + dateFin[0]
reqstr += "&datefin=" + dateFin
} else {
reqstr += "&datefin=" + this.props.data.date
} if (formDataObj.hasOwnProperty('duree')) {
reqstr += "&demijournee=" + formDataObj['duree']
} else {
reqstr += "&demijournee=" + this.props.data.demijournee
} if (formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== "") {
reqstr += "&description=" + formDataObj['motif']
}
this.postData(reqstr)
}
render() {
return (
<>
<Modal show={this.state.isOpen} onHide={this.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Suppression d'absence</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={this.onFormSubmit}>
<Form.Row>
<Form.Group as={Col} ControlId="dateDebut">
<Form.Label>Date début</Form.Label>
<Form.Control type="date" name="dateDebut" defaultValue={this.state.date} readOnly/>
</Form.Group>
<Form.Group as={Col} ControlId="dateFin">
<Form.Label>Date fin (Optionnel)</Form.Label>
<Form.Control type="date" name="dateFin" defaultValue={this.state.date}/>
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} ControlId="duree">
<Form.Check inline label="Journée" name="duree" type="radio" value="2"/>
<Form.Check inline label="Demie-journée" name="duree" type="radio" defaultValue={this.props.data.demijournee} checked/>
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} ControlId="motif">
<Form.Label>Motif</Form.Label>
<Form.Control as="textarea" rows={3} name="motif"/>
</Form.Group>
</Form.Row>
<Form.Row>
<Button type="submit" variant="primary">Sauvegarder</Button>
</Form.Row>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => {this.closeModal()}}>Fermer</Button>
</Modal.Footer>
</Modal>
</>
)
}
}
export default JustAbs

View File

@ -0,0 +1,137 @@
import React, {Component} from "react";
import {Button, Col, Form, Modal} from "react-bootstrap";
import {post} from "../../Request";
/** Module de saisie des absences */
class SaisieAbs extends Component {
constructor(props){
super(props)
this.state = {
isOpen: false,
form: {},
error: false,
etudid: ""
}
}
openModal = () => this.setState({ isOpen: true });
closeModal = () => this.setState({ isOpen: false });
componentDidUpdate(prevProps) {
if (prevProps.open !== this.props.open) {
this.setState({etudid: this.props.etudid})
if (this.props.open === true) {
this.setState({isOpen: true})
}
}
}
/**
* Gestion des données du formulaire
* @param e {Event}
*/
onFormSubmit = e => {
// Traitement du formulaire
// Empeche le bouton de rediriger ou actualiser la page
e.preventDefault()
// Recuperation des valeurs
const formData = new FormData(e.target), formDataObj = Object.fromEntries(formData.entries())
let reqstr = "etudid=" + this.state.etudid + "&datedebut="
if (formDataObj.hasOwnProperty('dateDebut') && formDataObj['dateDebut'] !== "") {
let dateDebut = formDataObj['dateDebut'].split("-")
dateDebut = dateDebut[2] + "/" + dateDebut[1] + "/" + dateDebut[0]
reqstr += dateDebut
if (formDataObj.hasOwnProperty('dateFin') && formDataObj['dateFin'] !== "") {
let dateFin = formDataObj['dateFin'].split("-")
dateFin = dateFin[2] + "/" + dateFin[1] + "/" + dateFin[0]
reqstr += "&datefin=" + dateFin
} else {
reqstr += "&datefin=" + dateDebut
}
if (formDataObj.hasOwnProperty('duree')) {
reqstr += "&demijournee=" + formDataObj['duree']
}
if (formDataObj.hasOwnProperty('estjust') && formDataObj.hasOwnProperty('motif') && formDataObj['motif'] !== "") {
reqstr += "&estjust=True&description=" + formDataObj['motif']
}
this.postData(reqstr)
} else {
this.setState({error: true})
}
}
/**
* 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('/')[7]
let BASE_URL = window.$api_url
post(BASE_URL + dept + "/Scolarite/Absences/doSignaleAbsence", data)
.then(response => {
if (response.status === 200) {
// Fermeture du modal
this.closeModal()
}
});
}
render() {
return (
<>
<Modal show={this.state.isOpen} onHide={this.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Saisie d'absence</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.state.error &&
<span>Erreur: La date de début ne doit pas être vide</span>
}
<Form onSubmit={this.onFormSubmit}>
<Form.Row>
<Form.Group as={Col} ControlId="dateDebut">
<Form.Label>Date début</Form.Label>
<Form.Control type="date" name="dateDebut"/>
</Form.Group>
<Form.Group as={Col} ControlId="dateFin">
<Form.Label>Date fin (Optionnel)</Form.Label>
<Form.Control type="date" name="dateFin"/>
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} ControlId="duree">
<Form.Check inline label="Journée(s)" name="duree" type="radio" value="2" />
<Form.Check inline label="Matin(s)" name="duree" type="radio" value="1" />
<Form.Check inline label="Après-midi" name="duree" type="radio" value="0" />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} ControlId="estjust">
<Form.Check label="Justifiée" name="estjust" type="checkbox" id="estjust" />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} ControlId="motif">
<Form.Label>Motif</Form.Label>
<Form.Control as="textarea" rows={3} name="motif"/>
</Form.Group>
</Form.Row>
<Form.Row>
<Button type="submit" variant="primary">Sauvegarder</Button>
</Form.Row>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => {this.closeModal()}}>Fermer</Button>
</Modal.Footer>
</Modal>
</>
)
}
}
export default SaisieAbs

View File

@ -0,0 +1,61 @@
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)
this.state = {
isOpen: false,
etudid: "",
}
}
openModal = () => this.setState({ isOpen: true });
closeModal = () => this.setState({ isOpen: false });
componentDidUpdate(prevProps) {
if (prevProps.open !== this.props.open) {
this.setState({etudid: this.props.etudid})
if (this.props.open === true) {
this.setState({isOpen: true})
}
}
}
/**
* 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('/')[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
post(BASE_URL + dept + "/Scolarite/Absences/doAnnuleAbsence", data)
// Fermeture du modal
this.setState({isOpen: false})
}
render() {
return (
<>
<Modal show={this.state.isOpen} onHide={this.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Suppression d'absence</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Etes-vous sûr.e de vouloir supprimer cette absence ?</p>
</Modal.Body>
<Modal.Footer>
<Button variant="danger" onClick={() => {this.postData()}}>Supprimer</Button>
<Button variant="secondary" onClick={() => {this.closeModal()}}>Fermer</Button>
</Modal.Footer>
</Modal>
</>
)
}
}
export default SupprAbs

View File

@ -1,40 +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: {},
students: [],
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,87 +1,72 @@
import React, {Component} from "react";
import Select from "react-select";
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);
this.getJsonData = this.getJsonData.bind(this);
}
getJsonData () {
this.setState({bltn: require('..\\..\\json\\bltn.json')}, () => {
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}, () => {
this.setState({loaded: true})
})
})
}
/**
* 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 => {
this.setState({ bltn: res.data }, () => {
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}, () => {
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})
})
})
);
})
}
/**
* 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'étudiant.e)
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.setState({loading: true})
this.getData();
}
}
// Recuperation des données lors du chargement de la page si un étudiant est selectionné
componentDidMount() {
if (this.props.id !== "") {this.getData()}
}
@ -92,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">
@ -111,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">
@ -131,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>
@ -149,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,55 +0,0 @@
import React, {Component} from "react";
import {Row, Col} from "react-bootstrap"
import '../Style.css'
import {Link} from "react-router-dom";
class Eleves extends Component {
constructor(props) {
super(props);
this.state = {
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 =>
response.json().then(data => ({
data: data,
})
).then(res => {
this.setState({ students: res.data});
}));
}
render() {
return (
<div className="wrapper">
<h1 id="pageTitle">Liste des élèves</h1>
<div className="container">
<div className="row">
{this.state.students.map((student, index) => {
return (
<div className="col-sm" key={index} id="wrapDept">
<Link to={`/ScoDoc/static/mobile/${window.location.href.split('/')[6]}/Scolarite/Etudiant/${student.etudid}`}>
{student.nom_disp} {student.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

@ -1,10 +1,11 @@
import React, {Component, useState} from "react";
import React, {Component} from "react";
import { isMobile } from 'react-device-detect';
import {Modal, Button} from 'react-bootstrap'
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);
@ -26,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();
@ -34,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,12 +3,11 @@ 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);
this.state = {
login: {},
semestre: {},
logout: false
};
}
@ -38,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,38 +16,25 @@ class Scolarite extends Component {
toast: false
};
this.dismissToast = this.dismissToast.bind(this);
this.getData = this.getData.bind(this);
}
/* getJsonData () {
return require('..\\json\\sems.json');
} */
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 (
@ -64,38 +53,44 @@ class Scolarite extends Component {
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
{this.state.semestres.map((sem, index) => {
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`}>
<h3>{sem.titre} [{sem.modalite}]</h3>
<p>Semestre {sem.semestre_id} - Année {sem.anneescolaire} [{sem.date_debut} - {sem.date_fin}]</p>
</Link>
</div>
)
})}
<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={`/${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>
</Card.Body>
</Accordion.Collapse>
</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,14 +1,16 @@
import React, {Component} from "react";
import {Link, Redirect} from "react-router-dom";
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);
this.state = {
students: [],
// Status possibles:
// 0: Vide - 1: Pas de resultat - 2: Plusieurs resultats
// 0: Vide - 1: Pas de resultat - 2: Un ou plusieurs resultats
search_status: 0,
};
this.handleChangeSearch = this.handleChangeSearch.bind(this)
@ -20,25 +22,18 @@ 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 });
console.log(this.state.students)
}))
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 if (this.state.students.length === 1) {
return <Redirect to="/" />
} else {
this.setState({search_status: 2, toast: false});
}
@ -46,24 +41,28 @@ class SearchStudent extends Component {
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>
)
@ -76,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,3 @@
/* BASIC */
body {
font-family: "Poppins", sans-serif;
height: 100vh;

View File

@ -1,626 +0,0 @@
{
"rang": {
"ninscrits": 52,
"value": "1"
},
"etape_apo2": "",
"etape_apo3": "",
"etape_apo4": "",
"etudiant": {
"nom": "BOLANO",
"prenom": "Roberto",
"sexe": "M.",
"code_ine": "",
"etudid": "EID9860",
"code_nip": "123456789",
"email": "roberto@santateresa.mx",
"photo_url": "\/ScoDoc\/static\/photos\/F68\/RT_EID29960.h90.jpg"
},
"bonus_sport_culture": {
"value": 0
},
"absences": {
"nbabsjust": 0,
"nbabs": 1
},
"decision": {
"etat": "I",
"code": "ADM",
"compense_formsemestre_id" : "SEM12345"
},
"note": {
"max": "15.51",
"moy": "10.80",
"value": "15.51",
"min": "07.29"
},
"etudid": "EID9860",
"decision_ue": [
{
"acronyme": "UE11",
"code": "ADM",
"ects": "16.0",
"titre": "D\u00e9couverte m\u00e9tiers",
"numero": "11",
"ue_id": "UE21456"
},
{
"acronyme": "UE12",
"code": "ADM",
"ects": "14.0",
"titre": "Mise \u00e0 niveau des comp\u00e9tences transversales et scientifiques",
"numero": "12",
"ue_id": "UE21478"
}
],
"ue_capitalisee": [
],
"publie": 1,
"autorisation_inscription": [
{
"semestre_id": 2
}
],
"appreciation": [
],
"note_max": {
"value": 20
},
"date": "2014-07-12T17:38:47.693262",
"rang_group": [
{
"ninscrits": 26,
"value": "1",
"group_type": "TD",
"group_name": "B"
},
{
"ninscrits": 13,
"value": "1",
"group_type": "TP",
"group_name": "B1"
},
{
"ninscrits": 4,
"value": "1",
"group_type": "G",
"group_name": "G4"
},
{
"ninscrits": "",
"value": "",
"group_type": "tutorat",
"group_name": ""
},
{
"ninscrits": "",
"value": "",
"group_type": "App",
"group_name": ""
},
{
"ninscrits": "",
"value": "",
"group_type": "sport",
"group_name": ""
}
],
"formsemestre_id": "SEM12345",
"etape_apo": "V1RT",
"ue": [
{
"acronyme": "UE11",
"rang": "1",
"code_apogee": "VRTU11",
"ects": "16",
"numero": "11",
"note": {
"max": "16.17",
"value": "16.17",
"min": "06.56"
},
"module": [
{
"coefficient": 3,
"rang": {
"value": "1"
},
"code": "M1101",
"code_apogee": "VRT1101",
"numero": 1101,
"note": {
"moy": "08.94",
"nb_notes": 51,
"nb_missing": 0,
"max": "19.18",
"min": "03.70",
"nb_valid_evals": 3,
"value": "19.18"
},
"abbrev": "R\u00e9seaux d&apos;entreprises",
"effectif": {
"value": 51
},
"titre": "Initiation aux r\u00e9seaux d&apos;entreprises",
"evaluation": [
],
"id": "MIP27427"
},
{
"coefficient": 2,
"rang": {
"value": "2"
},
"code": "M1102",
"code_apogee": "VRT1102",
"numero": 1102,
"note": {
"moy": "12.58",
"nb_notes": 50,
"nb_missing": 1,
"max": "16.79",
"min": "02.50",
"nb_valid_evals": 2,
"value": "16.50"
},
"abbrev": "Initiation \u00e0 la t\u00e9l\u00e9phonie",
"effectif": {
"value": 51
},
"titre": "Initiation \u00e0 la t\u00e9l\u00e9phonie d&apos;entreprise",
"evaluation": [
],
"id": "MIP27437"
},
{
"coefficient": 1.5,
"rang": {
"value": "1"
},
"code": "M1103",
"code_apogee": "VRT1103",
"numero": 1103,
"note": {
"moy": "08.26",
"nb_notes": 51,
"nb_missing": 0,
"max": "13.41",
"min": "00.94",
"nb_valid_evals": 2,
"value": "13.41"
},
"abbrev": "Architecture des \u00e9quipements informatiques",
"effectif": {
"value": 51
},
"titre": "Architecture des \u00e9quipements informatiques",
"evaluation": [
],
"id": "MIP27451"
},
{
"coefficient": 2,
"rang": {
"value": "1"
},
"code": "M1104",
"code_apogee": "VRT1104",
"numero": 1104,
"note": {
"moy": "10.77",
"nb_notes": 51,
"nb_missing": 0,
"max": "17.90",
"min": "04.63",
"nb_valid_evals": 3,
"value": "17.90"
},
"abbrev": "Principe et architecture des r\u00e9seaux",
"effectif": {
"value": 51
},
"titre": "Principe et architecture des r\u00e9seaux",
"evaluation": [
],
"id": "MIP27431"
},
{
"coefficient": 2,
"rang": {
"value": "1"
},
"code": "M1105",
"code_apogee": "VRT1105",
"numero": 1105,
"note": {
"moy": "11.00",
"nb_notes": 51,
"nb_missing": 0,
"max": "17.83",
"min": "04.98",
"nb_valid_evals": 2,
"value": "17.83"
},
"abbrev": "Bases des syst\u00e8mes d&apos;exploitation",
"effectif": {
"value": 51
},
"titre": "Bases des syst\u00e8mes d&apos;exploitation",
"evaluation": [
],
"id": "MIP27433"
},
{
"coefficient": 1.5,
"rang": {
"value": "6"
},
"code": "M1106",
"code_apogee": "VRT1106",
"numero": 1106,
"note": {
"moy": "13.05",
"nb_notes": 51,
"nb_missing": 0,
"max": "17.79",
"min": "07.08",
"nb_valid_evals": 1,
"value": "16.25"
},
"abbrev": "Initiation au d\u00e9veloppement Web",
"effectif": {
"value": 51
},
"titre": "Initiation au d\u00e9veloppement Web",
"evaluation": [
],
"id": "MIP27449"
},
{
"coefficient": 1.5,
"rang": {
"value": "9"
},
"code": "M1107",
"code_apogee": "VRT1107",
"numero": 1107,
"note": {
"moy": "09.36",
"nb_notes": 51,
"nb_missing": 0,
"max": "14.21",
"min": "04.17",
"nb_valid_evals": 3,
"value": "11.66"
},
"abbrev": "Initiation \u00e0 la mesure du signal",
"effectif": {
"value": 51
},
"titre": "Initiation \u00e0 la mesure du signal",
"evaluation": [
],
"id": "MIP27440"
},
{
"coefficient": 1.5,
"rang": {
"value": "7"
},
"code": "M1108",
"code_apogee": "VRT1108",
"numero": 1108,
"note": {
"moy": "10.49",
"nb_notes": 51,
"nb_missing": 0,
"max": "16.31",
"min": "05.39",
"nb_valid_evals": 4,
"value": "13.22"
},
"abbrev": "Acquisition et codage de l&apos;information",
"effectif": {
"value": 51
},
"titre": "Acquisition et codage de l&apos;information",
"evaluation": [
],
"id": "MIP27453"
},
{
"coefficient": 1,
"rang": {
"value": "4 ex"
},
"code": "M1109",
"code_apogee": "VRT1109",
"numero": 1109,
"note": {
"moy": "12.46",
"nb_notes": 51,
"nb_missing": 0,
"max": "16.00",
"min": "09.00",
"nb_valid_evals": 1,
"value": "15.00"
},
"abbrev": "PT : Recherche documentaire",
"effectif": {
"value": 51
},
"titre": "PT : Recherche documentaire",
"evaluation": [
],
"id": "MIP27444"
}
],
"effectif": "51",
"titre": "D\u00e9couverte m\u00e9tiers",
"id": "UE21456"
},
{
"acronyme": "UE12",
"rang": "5",
"code_apogee": "VRTU12",
"ects": "14",
"numero": "12",
"note": {
"max": "15.20",
"value": "14.63",
"min": "07.94"
},
"module": [
{
"coefficient": 2,
"rang": {
"value": "3 ex"
},
"code": "M1201",
"code_apogee": "VRT1201",
"numero": 10,
"note": {
"moy": "13.02",
"nb_notes": 51,
"nb_missing": 0,
"max": "16.00",
"min": "10.00",
"nb_valid_evals": 1,
"value": "14.00"
},
"abbrev": "Anglais",
"effectif": {
"value": 51
},
"titre": "Anglais g\u00e9n\u00e9ral de communication et initiation au vocabulaire technique",
"evaluation": [
],
"id": "MIP27430"
},
{
"coefficient": 2,
"rang": {
"value": "16 ex"
},
"code": "M1202",
"code_apogee": "VRT1202",
"numero": 20,
"note": {
"moy": "12.74",
"nb_notes": 51,
"nb_missing": 0,
"max": "17.75",
"min": "04.00",
"nb_valid_evals": 2,
"value": "14.00"
},
"abbrev": "Expression",
"effectif": {
"value": 51
},
"titre": "EC: \u00c9l\u00e9ments fondamentaux de la communication",
"evaluation": [
],
"id": "MIP27439"
},
{
"coefficient": 1,
"rang": {
"value": "1 ex"
},
"code": "M1203",
"code_apogee": "VRT1203",
"numero": 30,
"note": {
"moy": "NA",
"nb_notes": 0,
"nb_missing": 51,
"max": "-",
"min": "-",
"nb_valid_evals": 0,
"value": "-"
},
"abbrev": "PPP: Connaitre son champ d&apos;activit\u00e9",
"effectif": {
"value": 51
},
"titre": "PPP: Connaitre son champ d&apos;activit\u00e9",
"evaluation": [
],
"id": "MIP27436"
},
{
"coefficient": 2,
"rang": {
"value": "6"
},
"code": "M1204",
"code_apogee": "VRT1204",
"numero": 40,
"note": {
"moy": "10.66",
"nb_notes": 51,
"nb_missing": 0,
"max": "16.35",
"min": "05.73",
"nb_valid_evals": 8,
"value": "14.09"
},
"abbrev": "Mise \u00e0 niveau en num\u00e9ration et calculs",
"effectif": {
"value": 51
},
"titre": "Mise \u00e0 niveau en num\u00e9ration et calculs",
"evaluation": [
],
"id": "MIP27454"
},
{
"coefficient": 2,
"rang": {
"value": "23"
},
"code": "M1205",
"code_apogee": "VRT1205",
"numero": 50,
"note": {
"moy": "10.37",
"nb_notes": 51,
"nb_missing": 0,
"max": "18.69",
"min": "05.01",
"nb_valid_evals": 5,
"value": "10.58"
},
"abbrev": "Connaissances et Outils pour le signal",
"effectif": {
"value": 51
},
"titre": "Harmonisation des connaissances et des outils pour le signal",
"evaluation": [
],
"id": "MIP27432"
},
{
"coefficient": 2,
"rang": {
"value": "3 ex"
},
"code": "M1206",
"code_apogee": "VRT1206",
"numero": 60,
"note": {
"moy": "11.22",
"nb_notes": 51,
"nb_missing": 0,
"max": "16.76",
"min": "03.91",
"nb_valid_evals": 6,
"value": "15.12"
},
"abbrev": "Circuits \u00e9lectroniques : mise \u00e0 niveau",
"effectif": {
"value": 51
},
"titre": "Circuits \u00e9lectroniques : mise \u00e0 niveau",
"evaluation": [
],
"id": "MIP27435"
},
{
"coefficient": 2,
"rang": {
"value": "1 ex"
},
"code": "M1207",
"code_apogee": "VRT1207",
"numero": 70,
"note": {
"moy": "08.39",
"nb_notes": 51,
"nb_missing": 0,
"max": "20.00",
"min": "00.00",
"nb_valid_evals": 1,
"value": "20.00"
},
"abbrev": "Programmation 1",
"effectif": {
"value": 51
},
"titre": "Bases de la programmation",
"evaluation": [
],
"id": "MIP27445"
},
{
"coefficient": 1,
"rang": {
"value": "1 ex"
},
"code": "M1208",
"code_apogee": "VRT1208",
"numero": 80,
"note": {
"moy": "NA",
"nb_notes": 0,
"nb_missing": 51,
"max": "-",
"min": "-",
"nb_valid_evals": 0,
"value": "-"
},
"abbrev": "M\u00e9thodologie Universitaire 1",
"effectif": {
"value": 51
},
"titre": "Adaptation et m\u00e9thodologie pour la r\u00e9ussite Universitaire",
"evaluation": [
],
"id": "MIP27434"
}
],
"effectif": "51",
"titre": "Mise \u00e0 niveau des comp\u00e9tences transversales et scientifiques",
"id": "UE21478"
},
{
"acronyme": "UE 1S",
"rang": "1 ex",
"code_apogee": "",
"ects": "0",
"numero": "13",
"note": {
"max": "00.00",
"value": "00.00",
"min": "00.00"
},
"module": [
],
"effectif": "51",
"titre": "Sport &amp;amp; Culture",
"id": "UE23716"
}
],
"situation": "Inscrit le 02\/09\/2015. D\u00e9cision jury: Valid\u00e9. UE acquises: UE11, UE12. Autoris\u00e9 \u00e0 s&apos;inscrire en S2."
}

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>
);
}