Compare commits

..

No commits in common. "master" and "v0" have entirely different histories.
master ... v0

25 changed files with 1555 additions and 3810 deletions

View File

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

View File

@ -2,25 +2,36 @@
## Description
Version mobile de l'application web ScoDoc (v1.7)
Version mobile de l'application web ScoDoc (v0)
### Fonctionnalités:
- Login
- Choix de département / formation
- Affichage des profils étudiants
- 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.
- Recherche d'élèves
- Affichage des absences
## Usage
`npm run build` > Génère un dossier `build` avec le contenu du site en prod.
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>
```
## Arborescence

2969
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,18 +7,14 @@
"@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",
@ -28,8 +24,7 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"docs": "jsdoc2md ./src > docs.md"
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,50 +1,45 @@
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
getJson(BASE_URL + 'list_depts?format=json')
.then(res => {
this.setState({ depts: res.data })
});
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 })
}));
}
render() {
return (
<div className="wrapper">
<h1 id="pageTitle">Choix du département</h1>
<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>
{this.state.depts.map((dept, index) => {
return (
<div id="wrapDept">
<Link to={`/ScoDoc/static/mobile/${dept}/Scolarite`}>
Département {dept}
</Link>
</div>
)
},)}
</div>
);
}

View File

@ -1,48 +1,60 @@
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: {},
// Formation actuelle de l'étudiant
formation: [],
// Semestres correspondant a la formation de l'étudiant
semestres: [],
formation: [],
loaded: false
};
}
componentWillMount() {
this.getData()
}
/**
* Recupère les données de l'étudiant depuis l'API
*/
getData() {
let dept = window.location.href.split('/')[7]
let etudid = window.location.href.split('/')[10]
let dept = window.location.href.split('/')[6]
let etudid = window.location.href.split('/')[9]
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 })
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 })
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',
})
.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() {
@ -54,7 +66,7 @@ class Etudiant extends Component {
<h1>{this.state.etud.nomprenom}</h1>
<img
alt={`${this.state.etud.nomprenom}`}
src={`/ScoDoc/${window.location.href.split('/')[7]}/Scolarite/Notes/${this.state.etud.photo_url}`}
src={`/ScoDoc/${window.location.href.split('/')[6]}/Scolarite/Notes/${this.state.etud.photo_url}`}
width="102"
height="128"
className="d-inline-block align-top"
@ -65,10 +77,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>
@ -107,33 +119,22 @@ class Etudiant extends Component {
return (
<div>
<b>{sem.titreannee}</b><br/>
{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>
{sem.date_debut} - {sem.date_fin}
</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,14 +1,12 @@
import React, {Component} from "react";
import {Tabs, Tab, Nav, NavItem, NavLink} from "react-bootstrap"
import {Tabs, Tab} from "react-bootstrap"
import Accueil from "./GestionSemestre/Accueil";
import Absences from "./GestionSemestre/Absences";
import Etudiants from "./GestionSemestre/Etudiants";
import Eleves from "./GestionSemestre/Eleves";
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)
@ -16,38 +14,30 @@ class GestionSemestre extends Component {
selectOptions: [],
id: "",
name: '',
defaulttab: "Accueil",
defaultsel: "",
loading: true
}
}
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.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 dept = window.location.href.split('/')[6]
let sem = window.location.href.split('/')[8]
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});
this.setState({selectOptions: joined, loading: false})
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})
})
})
})
);
}
handleSelectChange(e){
@ -58,22 +48,13 @@ class GestionSemestre extends Component {
return (
<div>
<ScoNavBar/>
<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 id="wrapDept">
Choix de l'étudiant
<Select className="mySelect" options={this.state.selectOptions} onChange={this.handleSelectChange.bind(this)}/>
</div>
<div>
<Tabs fill defaultActiveKey={this.state.defaulttab} id="controlled-tab-example" variant="pills">
<Tab eventKey="Accueil" title="Accueil">
<Tabs defaultActiveKey="Accueil" id="controlled-tab-example">
<Tab eventKey="Accueil" title="Acceuil" >
<Accueil />
</Tab>
<Tab eventKey="Absences" title="Absences">
@ -82,8 +63,8 @@ class GestionSemestre extends Component {
<Tab eventKey="Bulletin" title="Bulletins">
<Bulletin id={this.state.id} name={this.state.name}/>
</Tab>
<Tab eventKey="Etud" title="Etudiants">
<Etudiants students={this.state.students}/>
<Tab eventKey="Eleves" title="Eleves">
<Eleves />
</Tab>
</Tabs>
</div>

View File

@ -1,158 +1,63 @@
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 = {
// 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
abs: []
}
}
// 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('/')[7]
let dept = window.location.href.split('/')[6]
let BASE_URL = window.$api_url
if (this.state.id !== "") {
// 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}));
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})
})
);
}
}
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 + " "}
<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"/>
<h4>Absences de {this.props.name}</h4>
{(this.state.abs.length === 0 && this.props.name !== "") &&
<h6>Aucune absence de l'élève</h6>
}
{(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) => {
{this.state.abs.map((abs, index) => {
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>
{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>
<h6>{abs.datedmy} | {abs.matin}</h6>
{abs.motif !== "" &&
<span>Motif: {abs.motif}</span>
} {abs.exams !== "" &&
<span>Exam a rattraper: {abs.exams}</span>
}
</div>
)
})}

View File

@ -1,119 +0,0 @@
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

@ -1,137 +0,0 @@
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

@ -1,61 +0,0 @@
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,66 +1,40 @@
import React, {Component} from "react";
import '../Style.css'
import {getJson} from "../Request";
import {Spinner} from "react-bootstrap";
/** Page d'accueil de la gestion du semestre */
class Accueil extends Component {
constructor(props) {
super(props);
this.state = {
semestre: {},
resp: [],
loading: true
students: [],
};
}
componentWillMount() {
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 dept = window.location.href.split('/')[6]
let sem = window.location.href.split('/')[8]
let BASE_URL = window.$api_url
// 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})
})
)
})
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]});
}));
}
render() {
return (
<div className="wrapper">
{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"/>
}
<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>
</div>
)
}

View File

@ -1,72 +1,87 @@
import React, {Component} from "react";
import {Table, Button, Dropdown, Spinner} from "react-bootstrap"
import Select from "react-select";
import {Table, Button, Dropdown} 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,
loading: false
loaded: 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('/')[7]
let sem = window.location.href.split('/')[9]
let dept = window.location.href.split('/')[6]
let sem = window.location.href.split('/')[8]
let BASE_URL = window.$api_url
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})
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})
})
})
})
})
);
}
/**
* 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('/')[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 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 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()}
}
@ -77,9 +92,6 @@ 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">
@ -99,15 +111,13 @@ 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>
{this.state.bltn.hasOwnProperty('rang') &&
<Dropdown.Item href="#">Classement: {this.state.bltn.rang.value}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
}
<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) => {
{this.state.bltn.ue.map((ue, index) => {
return (
<tbody>
<tr className="ueRow">
@ -121,14 +131,12 @@ class Bulletin extends Component {
<Dropdown.Menu>
<Dropdown.Item href="#">Min: {ue.note.min}</Dropdown.Item>
<Dropdown.Item href="#">Max: {ue.note.max}</Dropdown.Item>
{ue.hasOwnProperty('rang') &&
<Dropdown.Item href="#">Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
}
<Dropdown.Item href="#">Classement: {ue.rang}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</td>
</tr>
{ue.module.map((mod) => {
{ue.module.map((mod, index) => {
return (
<tr>
<td colSpan="3">{mod.titre.replace("&apos;", "'")}</td>
@ -141,9 +149,7 @@ class Bulletin extends Component {
<Dropdown.Menu>
<Dropdown.Item href="#">Min: {mod.note.min}</Dropdown.Item>
<Dropdown.Item href="#">Max: {mod.note.max}</Dropdown.Item>
{mod.hasOwnProperty('rang') &&
<Dropdown.Item href="#">Classement: {mod.rang.value}/{this.state.bltn.rang.ninscrits}</Dropdown.Item>
}
<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

@ -0,0 +1,55 @@
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

@ -1,170 +0,0 @@
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,11 +1,10 @@
import React, {Component} from "react";
import React, {Component, useState} 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);
@ -27,10 +26,6 @@ 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();
@ -39,7 +34,15 @@ class Login extends Component {
let BASE_URL = window.$api_url
getLogin(BASE_URL, login, pass)
fetch(BASE_URL, {
method: 'GET',
verify: false,
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa(login + ":" + pass)
},
})
.then(res => {
this.setState({ status: res["status"] });
})

View File

@ -1,67 +0,0 @@
/**
* 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,11 +3,12 @@ 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
};
}
@ -37,21 +38,13 @@ class ScoNavBar extends Component {
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
<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.Link href="/ScoDoc">Version Desktop</Nav.Link>
<Button variant="primary" onClick={() => {this.logout()}}>Déconnexion</Button>
</Nav>
</Navbar.Collapse>
</Container>
{this.state.logout === true &&
<Redirect push to="/"/>
<Redirect push to={window.$api_url + 'static/mobile/'}/>
}
</Navbar>
)

View File

@ -4,9 +4,7 @@ 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);
@ -16,25 +14,38 @@ 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() {
this.getData()
}
/**
* Recupère la liste des semestres depuis l'API
*/
getData () {
let dept = window.location.href.split('/')[7]
let dept = window.location.href.split('/')[6]
let BASE_URL = window.$api_url
getJson(BASE_URL + dept + '/Scolarite/Notes/formsemestre_list?format=json')
.then(res => {
this.setState({ semestres: res.data });
})
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 });
}));
}
dismissToast = () => this.setState({toast: false})
getData () {
return this.state.semestres
}
dismissToast() {
this.setState({toast: false})
}
render() {
return (
@ -53,44 +64,38 @@ class Scolarite extends Component {
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
<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>
{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>
)
})}
</Card.Body>
</Accordion.Collapse>
</Card>
<Card>
<Card.Header>
<Accordion.Toggle as={Button} variant="link" eventKey="1">
<Accordion.Toggle as={Button} variant="link" eventKey="0">
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={`/${window.location.href.split('/')[7]}/Scolarite/${sem.formsemestre_id}/GestionSem`}>
<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>
)
}
})}
</Card.Body>
</Accordion.Collapse>

View File

@ -1,16 +1,14 @@
import React, {Component} from "react";
import {Link} from "react-router-dom";
import {Link, Redirect} 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: Un ou plusieurs resultats
// 0: Vide - 1: Pas de resultat - 2: Plusieurs resultats
search_status: 0,
};
this.handleChangeSearch = this.handleChangeSearch.bind(this)
@ -22,18 +20,25 @@ class SearchStudent extends Component {
this.setState({ search: e.target.value });
}
/**
* Lance une recherche de l'étudiant depuis l'API
* @param search {String} - Texte recherché
*/
searchStudent(search) {
let dept = window.location.href.split('/')[7]
searchStudent() {
let dept = window.location.href.split('/')[6]
let BASE_URL = window.$api_url
getJson(BASE_URL + dept + '/Scolarite/Notes/search_etud_by_name?term=' + search +'&format=json')
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)
}))
.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});
}
@ -41,28 +46,24 @@ 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 étudiant trouvé
Aucun élève trouvé
</div>
)
} else if (this.state.search_status === 2) {
return (
<Col>
{this.state.students.map((student) => {
{this.state.students.map((student, index) => {
return (
<Row id="wrapDept">
<Link to={`/${window.location.href.split('/')[7]}/Scolarite/Etudiant/${student.value}`}>
<Link to={`/ScoDoc/static/mobile/${window.location.href.split('/')[6]}/Scolarite/Etudiant/${student.value}`}>
<span>{student.label}</span>
</Link>
</Row>
)
);
})}
</Col>
)
@ -75,7 +76,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(this.state.search)}}>
<button type="button" className="btn waves-effect waves-light btn-primary" onClick={() => {this.searchStudent()}}>
Rechercher
</button>
</div>

View File

@ -1,3 +1,5 @@
/* BASIC */
body {
font-family: "Poppins", sans-serif;
height: 100vh;

626
src/json/bltn.json Normal file
View File

@ -0,0 +1,626 @@
{
"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 {HashRouter, Route} from 'react-router-dom';
import {Switch, Route} from 'react-router-dom';
import Scolarite from './ScoDoc/Scolarite.js'
import Login from './ScoDoc/Login'
import GestionSemestre from "./ScoDoc/GestionSemestre";
@ -8,18 +8,12 @@ import Etudiant from "./ScoDoc/Etudiant";
const Main = () => {
return (
/*<HashRouter>
<Switch>
<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}/>
</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>
</Switch>
);
}