forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into sco96
This commit is contained in:
commit
ea76dd702e
@ -282,6 +282,8 @@ def validation_rcue_record(etudid: int):
|
||||
)
|
||||
operation = "record"
|
||||
db.session.add(validation)
|
||||
# invalider bulletins (les autres résultats ne dépendent pas des RCUEs):
|
||||
sco_cache.invalidate_formsemestre_etud(etud)
|
||||
db.session.commit()
|
||||
Scolog.logdb(
|
||||
method="validation_rcue_record",
|
||||
|
@ -307,6 +307,17 @@ class FormSemestre(db.Model):
|
||||
- et sont associées à l'un des parcours de ce formsemestre
|
||||
(ou à aucun, donc tronc commun).
|
||||
"""
|
||||
# per-request caching
|
||||
key = (self.id, with_sport)
|
||||
_cache = getattr(g, "_formsemestre_get_ues_cache", None)
|
||||
if _cache:
|
||||
result = _cache.get(key, False)
|
||||
if result is not False:
|
||||
return result
|
||||
else:
|
||||
g._formsemestre_get_ues_cache = {}
|
||||
_cache = g._formsemestre_get_ues_cache
|
||||
|
||||
formation: Formation = self.formation
|
||||
if formation.is_apc():
|
||||
# UEs de tronc commun (sans parcours indiqué)
|
||||
@ -326,8 +337,7 @@ class FormSemestre(db.Model):
|
||||
).filter(UniteEns.semestre_idx == self.semestre_id)
|
||||
}
|
||||
)
|
||||
ues = sem_ues.values()
|
||||
return sorted(ues, key=attrgetter("numero", "acronyme"))
|
||||
ues = sorted(sem_ues.values(), key=attrgetter("numero", "acronyme"))
|
||||
else:
|
||||
sem_ues = db.session.query(UniteEns).filter(
|
||||
ModuleImpl.formsemestre_id == self.id,
|
||||
@ -336,7 +346,9 @@ class FormSemestre(db.Model):
|
||||
)
|
||||
if not with_sport:
|
||||
sem_ues = sem_ues.filter(UniteEns.type != codes_cursus.UE_SPORT)
|
||||
return sem_ues.order_by(UniteEns.numero).all()
|
||||
ues = sem_ues.order_by(UniteEns.numero).all()
|
||||
_cache[key] = ues
|
||||
return ues
|
||||
|
||||
@cached_property
|
||||
def modimpls_sorted(self) -> list[ModuleImpl]:
|
||||
|
@ -63,24 +63,31 @@ def dict_pvjury(
|
||||
Si with_parcours_decisions: ajoute infos sur code decision jury de tous les semestre du parcours
|
||||
Résultat:
|
||||
{
|
||||
'date' : date de la decision la plus recente,
|
||||
'formsemestre' : sem,
|
||||
'date' : str = date de la decision la plus recente, format dd/mm/yyyy,
|
||||
'formsemestre' : dict = formsemestre,
|
||||
'is_apc' : bool,
|
||||
'formation' : { 'acronyme' :, 'titre': ... }
|
||||
'decisions' : { [ { 'identite' : {'nom' :, 'prenom':, ...,},
|
||||
'decisions' : [
|
||||
{
|
||||
'identite' : {'nom' :, 'prenom':, ...,},
|
||||
'etat' : I ou D ou DEF
|
||||
'decision_sem' : {'code':, 'code_prev': },
|
||||
'decisions_ue' : { ue_id : { 'code' : ADM|CMP|AJ, 'event_date' :,
|
||||
'acronyme', 'numero': } },
|
||||
'decisions_ue' : {
|
||||
ue_id : {
|
||||
'code' : ADM|CMP|AJ,
|
||||
'ects' : float,
|
||||
'event_date' :str = "dd/mm/yyyy",
|
||||
},
|
||||
},
|
||||
'autorisations' : [ { 'semestre_id' : { ... } } ],
|
||||
'validation_parcours' : True si parcours validé (diplome obtenu)
|
||||
'prev_code' : code (calculé slt si with_prev),
|
||||
'mention' : mention (en fct moy gen),
|
||||
'sum_ects' : total ECTS acquis dans ce semestre (incluant les UE capitalisées)
|
||||
'sum_ects_capitalises' : somme des ECTS des UE capitalisees
|
||||
}
|
||||
]
|
||||
},
|
||||
...
|
||||
],
|
||||
'decisions_dict' : { etudid : decision (comme ci-dessus) },
|
||||
}
|
||||
"""
|
||||
|
@ -106,7 +106,7 @@ body:not(.editionActivated) .editing {
|
||||
|
||||
@keyframes boing {
|
||||
100% {
|
||||
transform: translateY(-20px)
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,6 +152,7 @@ body.editionActivated .filtres>div>div>div>div {
|
||||
color: #000;
|
||||
border-radius: 4px;
|
||||
outline: 4px solid #FFF;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
/* Suppression */
|
||||
@ -365,6 +366,23 @@ body.editionActivated .filtres .nonEditable .move {
|
||||
/*****************************/
|
||||
/* Zone Etudiants */
|
||||
/*****************************/
|
||||
#zoneChoix .autoAffectation>a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#zoneChoix .dropZone {
|
||||
background: #FFF;
|
||||
border-radius: 8px;
|
||||
border: 2px dashed #09C;
|
||||
margin-bottom: 4px;
|
||||
padding: 4px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.fileOver {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
#zoneChoix>.autoAffectation {
|
||||
background: #c9c9c9;
|
||||
color: #141414;
|
||||
|
@ -22,6 +22,23 @@
|
||||
|
||||
<section id="zoneChoix">
|
||||
<h2>Étudiants</h2>
|
||||
<div class="autoAffectation">
|
||||
<a href="students_groups_auto_assignment?formsemestre_id={{formsemestre.id}}"><svg
|
||||
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="#0b0b0b" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M13.8 12H3" />
|
||||
</svg> Aide à l'affectation dans les parcours</a>
|
||||
<div>Importer les résultats :
|
||||
<form class=dropZone>
|
||||
<div>
|
||||
Déposez le fichier .xlsx ou <br>
|
||||
<label>
|
||||
<input type=file accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="autoAffectation">
|
||||
Affecter automatiquement les étudiants du groupe<br>
|
||||
<select name="affectationFrom" id="affectationFrom"></select>
|
||||
@ -38,6 +55,7 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx-populate/1.21.0/xlsx-populate.min.js"></script>
|
||||
<script>
|
||||
|
||||
go();
|
||||
@ -125,7 +143,7 @@
|
||||
output = "";
|
||||
etudiants.forEach(etudiant => {
|
||||
output += `
|
||||
<div>
|
||||
<div data-etudid="${etudiant.etudid}" >
|
||||
<div class=nom data-etudid="${etudiant.etudid}" data-nom="${etudiant.nom_disp}" data-prenom="${etudiant.prenom}"><a href="ficheEtud?etudid=${etudiant.etudid}">${etudiant.nom_disp} ${etudiant.prenom}</a><div class=small>${etudiant.bac}</div></div>
|
||||
${(() => {
|
||||
let output = "<div class=grpPartitions>";
|
||||
@ -141,7 +159,7 @@
|
||||
})
|
||||
arrayGroups.forEach((groupe) => {
|
||||
output += `
|
||||
<label><input type=radio name="${partition.id}-${etudiant.etudid}" value="${groupe.id}" ${(etudiant.partitions[partition.id] == groupe.id) ? "checked" : ""}><span>${groupe.group_name}</span></label>`;
|
||||
<label><input type=radio name="${partition.id}-${etudiant.etudid}" value="${groupe.id}" ${(etudiant.partitions[partition.id] == groupe.id) ? "checked" : ""}><span data-grpname="${groupe.group_name}">${groupe.group_name}</span></label>`;
|
||||
|
||||
if (etudiant.partitions[partition.id] == groupe.id) {
|
||||
affected = true;
|
||||
@ -353,6 +371,58 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*****************************************/
|
||||
/* Import des résultats auto affectation */
|
||||
/*****************************************/
|
||||
document.querySelector(".dropZone").addEventListener("drop", dropFile);
|
||||
document.querySelector(".dropZone input").addEventListener("change", dropFile);
|
||||
document.querySelector(".dropZone").addEventListener("dragover", dragOver);
|
||||
document.querySelector(".dropZone").addEventListener("dragleave", dragLeave);
|
||||
|
||||
function dropFile(event) {
|
||||
event.preventDefault();
|
||||
this.classList.remove("fileOver");
|
||||
|
||||
if (event.target.files?.[0] || event.dataTransfer.items[0].type.match('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')) {
|
||||
let file = event.target.files?.[0] || event.dataTransfer.items[0].getAsFile();
|
||||
let reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onloadend = () => {
|
||||
manageFile(reader.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function manageFile(file) {
|
||||
XlsxPopulate.fromDataAsync(file)
|
||||
.then(function (workbook) {
|
||||
const sheet = workbook.sheet(0);
|
||||
let ligne = 1;
|
||||
let colonne = 1;
|
||||
let parcours;
|
||||
let etudid;
|
||||
|
||||
while (parcours = sheet.row(ligne).cell(colonne).value()) {
|
||||
ligne += 2;
|
||||
while (etudid = sheet.row(ligne).cell(colonne).value()) {
|
||||
console.log(parcours, etudid);
|
||||
document.querySelector(`#zoneChoix .etudiants [data-etudid="${etudid}"] [data-grpname="${parcours}"]`)?.click();
|
||||
ligne++;
|
||||
}
|
||||
ligne = 1;
|
||||
colonne += 3;
|
||||
}
|
||||
})
|
||||
}
|
||||
function dragOver(event) {
|
||||
event.preventDefault();
|
||||
this.classList.add("fileOver")
|
||||
}
|
||||
function dragLeave() {
|
||||
this.classList.remove("fileOver")
|
||||
}
|
||||
|
||||
/****************************/
|
||||
/* Affectation à un groupe */
|
||||
/****************************/
|
||||
@ -609,6 +679,15 @@
|
||||
}
|
||||
|
||||
function saveEditing(obj) {
|
||||
// Vérification que le champ est non vide
|
||||
if (obj.innerText == "") {
|
||||
event.preventDefault();
|
||||
sco_message("Ce champ ne peut rester vide.");
|
||||
obj.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fin de l'édition
|
||||
obj.classList.remove("editingText");
|
||||
obj.setAttribute("contenteditable", "false");
|
||||
obj.removeEventListener("keydown", writing);
|
||||
|
@ -1 +1,858 @@
|
||||
{# Pour Sébastien #}
|
||||
<style>
|
||||
.wait {
|
||||
position: fixed;
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
background: #424242;
|
||||
top: calc(50% - 50px);
|
||||
left: 50%;
|
||||
margin-left: -25px;
|
||||
animation: wait 0.6s ease-out alternate infinite;
|
||||
}
|
||||
|
||||
@keyframes wait {
|
||||
100% {
|
||||
transform: translateY(-30px) rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
main h2 {
|
||||
border-bottom: 4px solid #0069d9;
|
||||
grid-column: 1 / -1;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
main {
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
margin-right: 16px;
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
background: #717171;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
section {
|
||||
background: #dbccb8;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.moitemoite {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.moitemoite {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.notes {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.parcours,
|
||||
.notes {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto auto;
|
||||
align-items: center;
|
||||
grid-row-gap: 4px;
|
||||
grid-column-gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
background: #FFF;
|
||||
border: 1px solid #0069d9;
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
h3,
|
||||
.resultats small {
|
||||
grid-column: 1 / -1;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.parcours form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.parcours input {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.dropZone {
|
||||
border: 2px dashed #0069d9;
|
||||
padding: 8px 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.notes select {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.donnees,
|
||||
.resultats {
|
||||
display: inline-grid;
|
||||
}
|
||||
|
||||
.donnees {
|
||||
background: #aaa;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.donnees>* {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
.donnees>*,
|
||||
.resultats>*:not(h2, button) {
|
||||
background: #fff;
|
||||
outline: 1px solid #aaa;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.entete {
|
||||
background: #0069d9 !important;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.criteres {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.criteres:not(.criteres+.criteres),
|
||||
.voeux:not(.voeux+.voeux),
|
||||
.entete+.entete {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
small {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
padding: 2px 4px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
button {
|
||||
background: rgb(134, 179, 0);
|
||||
color: #FFF;
|
||||
font-size: 16px;
|
||||
padding: 16px 32px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.26);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(2px);
|
||||
box-shadow: initial;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
button.autre {
|
||||
background: #0c9;
|
||||
}
|
||||
|
||||
.resultats button {
|
||||
background: #90c;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
b {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.moitemoite input:invalid,
|
||||
[contenteditable]:empty {
|
||||
animation: focus .5s alternate infinite;
|
||||
}
|
||||
|
||||
@keyframes focus {
|
||||
0% {
|
||||
outline: 1px solid rgba(204, 0, 153, 0)
|
||||
}
|
||||
|
||||
100% {
|
||||
outline: 1px solid rgba(204, 0, 153, 1)
|
||||
}
|
||||
}
|
||||
|
||||
[data-actif] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-actif=false] {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
[data-highlight=true] {
|
||||
outline: 2px solid #90c !important;
|
||||
border-radius: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<main class="moitemoite">
|
||||
<div class="wait"></div>
|
||||
<section>
|
||||
<h2>Données</h2>
|
||||
<div class="parcours"></div>
|
||||
<div class="notes"></div>
|
||||
<div style="display:flex; gap: 8px;">
|
||||
<button onclick="exportData()">Exporter les données</button>
|
||||
<!--<form class="dropZone">
|
||||
<div><b>Importer les données</b></div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#0099cc" stroke-width="2" stroke-linecap="round"><path pathlength="100" d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
|
||||
Déposez une fichier ou <br>
|
||||
<label><input type="file" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"></label>
|
||||
</form>-->
|
||||
</div>
|
||||
<small style="margin-top: 4px">💡 Vous pouvez ensuite copier / coller, dans les cases blanches ci-dessous, les
|
||||
"notes" et "voeux", directement depuis le tableur.</small>
|
||||
<small>💡 Les voeux doivent être une série d'entiers en partant de 1 - exemple : 3, 1 et 2.</small>
|
||||
<small>💡 Cliquez sur un étudiant pour qu'il ne fasse pas parti du tri.</small>
|
||||
<div class="donnees"></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Résultats</h2>
|
||||
<div class=grid>
|
||||
<button class="actionTri" data-tri="notes">Trier uniquement par <b>notes</b></button>
|
||||
<button class="actionTri" data-tri="voeux">Trier par <b>voeux</b> puis par notes</button>
|
||||
</div>
|
||||
<small>
|
||||
💡 Vous pouvez exporter les résultats pour les utiliser dans l'éditeur de groupes/partitions.
|
||||
</small>
|
||||
<div class="resultats"></div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx-populate/1.21.0/xlsx-populate.min.js"></script>
|
||||
<script>
|
||||
/************************/
|
||||
/* A générer par Scodoc */
|
||||
/************************/
|
||||
let choixNotes = [
|
||||
"RCUE1",
|
||||
"RCUE2",
|
||||
"RCUE3",
|
||||
"RCUE4",
|
||||
"RCUE5"
|
||||
]
|
||||
let formsemestre;
|
||||
let parcours = {};
|
||||
let nbParcours;
|
||||
/*let parcours = {
|
||||
1448: {
|
||||
nom: "Communication",
|
||||
places: 28, // Modifiable par l'utilisateur
|
||||
etudiants: [] // Résultat du tri
|
||||
},
|
||||
1449: {
|
||||
nom: "Création numérique",
|
||||
places: 28, // Modifiable par l'utilisateur
|
||||
etudiants: [] // Résultat du tri
|
||||
},
|
||||
1450: {
|
||||
nom: "Développement Web",
|
||||
places: 28, // Modifiable par l'utilisateur
|
||||
etudiants: [] // Résultat du tri
|
||||
}
|
||||
}*/
|
||||
let etudiants = {};
|
||||
/*let etudiants = {
|
||||
123: {
|
||||
actif: true,
|
||||
nom: "Jean Bono",
|
||||
criteres: {
|
||||
1448: 10, // Modifiable par l'utilisateur
|
||||
1449: 12, // Modifiable par l'utilisateur
|
||||
1450: 8 // Modifiable par l'utilisateur
|
||||
},
|
||||
voeux: {
|
||||
1448: 1, // Modifiable par l'utilisateur
|
||||
1449: 3, // Modifiable par l'utilisateur
|
||||
1450: 2 // Modifiable par l'utilisateur
|
||||
}
|
||||
},
|
||||
124: {
|
||||
actif: true,
|
||||
nom: "Etudiant 2",
|
||||
criteres: {
|
||||
1448: 11, // Modifiable par l'utilisateur
|
||||
1449: 12.5, // Modifiable par l'utilisateur
|
||||
1450: 8 // Modifiable par l'utilisateur
|
||||
},
|
||||
voeux: {
|
||||
1448: 2, // Modifiable par l'utilisateur
|
||||
1449: 1, // Modifiable par l'utilisateur
|
||||
1450: 3 // Modifiable par l'utilisateur
|
||||
}
|
||||
},
|
||||
125: {
|
||||
actif: true,
|
||||
nom: "Etudiant 3",
|
||||
criteres: {
|
||||
1448: 10, // Modifiable par l'utilisateur
|
||||
1449: 12.5, // Modifiable par l'utilisateur
|
||||
1450: 15 // Modifiable par l'utilisateur
|
||||
},
|
||||
voeux: {
|
||||
1448: 3, // Modifiable par l'utilisateur
|
||||
1449: 2, // Modifiable par l'utilisateur
|
||||
1450: 1 // Modifiable par l'utilisateur
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/************************/
|
||||
/* Chargement des datas */
|
||||
/************************/
|
||||
go();
|
||||
async function go() {
|
||||
document.querySelector('.wait').style.display = "";
|
||||
let params = (new URL(document.location)).searchParams;
|
||||
formsemestre = params.get('formsemestre_id');
|
||||
|
||||
let parcoursRaw = await fetchData("/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre + "/partitions");
|
||||
let etudiantsRaw = await fetchData("/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre + "/etudiants");
|
||||
//let decisionsRaw = await fetchData("/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre + "/decisions_jury");
|
||||
let savedData = await getFromScodoc();
|
||||
|
||||
etudiantsRaw.sort((a, b) => {
|
||||
return (a.nom + a.prenom).localeCompare(b.nom + b.prenom)
|
||||
});
|
||||
|
||||
processParcours(parcoursRaw, savedData);
|
||||
processNotes();
|
||||
processEtudiants(etudiantsRaw, savedData);
|
||||
processEvents();
|
||||
|
||||
document.querySelector("body").classList.add("loaded");
|
||||
document.querySelector('.wait').style.display = "none";
|
||||
}
|
||||
|
||||
function fetchData(request) {
|
||||
return fetch(request)
|
||||
.then(r => { return r.json() })
|
||||
.then(data => {
|
||||
return data;
|
||||
}).catch(error => {
|
||||
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors du transfert des données.</h2>";
|
||||
throw 'Fin du script - données invalides';
|
||||
})
|
||||
}
|
||||
|
||||
/************************/
|
||||
/* Génération du front */
|
||||
/************************/
|
||||
function processParcours(parcoursRaw, savedData) {
|
||||
|
||||
Object.values(parcoursRaw).forEach(partition => {
|
||||
if (partition.partition_name == "Parcours") {
|
||||
Object.values(partition.groups).forEach(group => {
|
||||
parcours[group.id] = {
|
||||
nom: group.group_name,
|
||||
places: savedData.parcours[group.id].places || 28,
|
||||
etudiants: [] // Résultat du tri
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
nbParcours = Object.keys(parcours).length;
|
||||
/* Zones parcours */
|
||||
let output = "<h3>Places</h3>";
|
||||
Object.entries(parcours).forEach(([idParcours, dataParcours]) => {
|
||||
output += `
|
||||
<div data-idparcours=${idParcours}>${dataParcours.nom}</div>
|
||||
<div><form onsubmit="return false;"><input type=number required min=0 data-idparcours="${idParcours}" value="${dataParcours.places}"></form> places</div>
|
||||
`;
|
||||
})
|
||||
document.querySelector(".parcours").innerHTML = output;
|
||||
}
|
||||
|
||||
function processNotes() {
|
||||
/* Zones critère de notes */
|
||||
let output = "<h3>Appliquer les notes</h3>";
|
||||
Object.entries(parcours).forEach(([idParcours, dataParcours]) => {
|
||||
output += `
|
||||
<div data-idparcours=${idParcours}>${dataParcours.nom}</div>
|
||||
<div>
|
||||
<select data-idparcours=${idParcours}>
|
||||
${(
|
||||
() => {
|
||||
let output = "<option>-</option>";
|
||||
choixNotes.forEach(notes => {
|
||||
output += `<option value="${notes}">${notes}</option>`;
|
||||
})
|
||||
return output;
|
||||
}
|
||||
)()}
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
document.querySelector(".notes").innerHTML = output;
|
||||
}
|
||||
|
||||
function processEtudiants(etudiantsRaw, savedData) {
|
||||
/*id123: {
|
||||
actif: true,
|
||||
nom: "Jean Bono",
|
||||
criteres: {
|
||||
1448: 10, // Modifiable par l'utilisateur
|
||||
1449: 12, // Modifiable par l'utilisateur
|
||||
1450: 8 // Modifiable par l'utilisateur
|
||||
},
|
||||
voeux: {
|
||||
1448: 1, // Modifiable par l'utilisateur
|
||||
1449: 3, // Modifiable par l'utilisateur
|
||||
1450: 2 // Modifiable par l'utilisateur
|
||||
}
|
||||
},*/
|
||||
etudiantsRaw.forEach(etudiant => {
|
||||
saved = savedData.etudiants['id' + etudiant.id];
|
||||
etudiants['id' + etudiant.id] = {
|
||||
actif: saved.actif ? true : false,
|
||||
nom: etudiant.nom + " " + etudiant.prenom,
|
||||
criteres: saved.criteres || {},
|
||||
voeux: saved.voeux || {}
|
||||
};
|
||||
/*Object.keys(parcours).forEach((idParcours, index) => {
|
||||
etudiants['id' + etudiant.id].criteres[idParcours] = 10;
|
||||
etudiants['id' + etudiant.id].voeux[idParcours] = index + 1;
|
||||
})*/
|
||||
})
|
||||
|
||||
/* Zone étudiants */
|
||||
let output = `
|
||||
<div class=entete>Etudiants</div>
|
||||
<div class=entete style="grid-column: span ${nbParcours}">Notes</div>
|
||||
<div class=entete style="grid-column: span ${nbParcours}">Voeux</div>`;
|
||||
Object.entries(etudiants).forEach(([id, etudiant]) => {
|
||||
output += `
|
||||
<div data-actif=${etudiant.actif}>${etudiant.nom}</div>
|
||||
${display(id, etudiant, "criteres", true)}
|
||||
${display(id, etudiant, "voeux", true)}
|
||||
`;
|
||||
})
|
||||
|
||||
document.querySelector(".donnees").style.gridTemplateColumns = `repeat(${1 + nbParcours * 2}, auto)`;
|
||||
document.querySelector(".donnees").innerHTML = output;
|
||||
}
|
||||
|
||||
/**********************************/
|
||||
/* Affichage des critères / voeux */
|
||||
/**********************************/
|
||||
function display(id, datas, key, editable) {
|
||||
let output = "";
|
||||
Object.entries(datas[key]).forEach(([idParcours, data]) => {
|
||||
output += `<div class=${key} data-id=${id} data-idParcours=${idParcours} contenteditable=${editable}>${data}</div>`;
|
||||
})
|
||||
return output;
|
||||
}
|
||||
|
||||
/**********************************/
|
||||
/* Gestion des événements */
|
||||
/**********************************/
|
||||
function processEvents() {
|
||||
document.querySelectorAll(".parcours input").forEach(e => e.addEventListener("change", changeNbParcours));
|
||||
document.querySelectorAll(".notes select").forEach(e => e.addEventListener("change", changeNotes));
|
||||
document.querySelectorAll(".actionTri").forEach(e => e.addEventListener("click", processData));
|
||||
document.querySelectorAll(".donnees>.criteres, .donnees>.voeux").forEach(e => e.addEventListener("paste", pasteData));
|
||||
document.querySelectorAll("[data-actif]").forEach(e => e.addEventListener("mousedown", event => { event.preventDefault() }));
|
||||
document.querySelectorAll("[data-actif]").forEach(e => e.addEventListener("click", changeActif));
|
||||
document.querySelectorAll("[contenteditable]").forEach(e => e.addEventListener("keypress", changeListe));
|
||||
document.querySelectorAll("[contenteditable]").forEach(e => e.addEventListener("keyup", saveLocal));
|
||||
document.querySelectorAll("[contenteditable]").forEach(e => e.addEventListener("keyup", saveToScodoc));
|
||||
document.querySelectorAll("[data-idparcours]").forEach(e => e.addEventListener("mouseover", setHighlight));
|
||||
document.querySelectorAll("[data-idparcours]").forEach(e => e.addEventListener("mouseleave", resetHighlight));
|
||||
}
|
||||
|
||||
|
||||
/***************************************/
|
||||
/* Traitement des entrées utilisateurs */
|
||||
/***************************************/
|
||||
function changeNbParcours() {
|
||||
let idParcours = this.dataset.idparcours;
|
||||
let places = this.value;
|
||||
parcours[idParcours].places = places;
|
||||
saveToScodoc();
|
||||
}
|
||||
function changeNotes() {
|
||||
let idParcours = this.dataset.idparcours;
|
||||
let notes = this.value;
|
||||
/******* SCODOC - récupérer et modifier les valeurs des notes ******/
|
||||
}
|
||||
function changeActif() {
|
||||
let statut = this.dataset.actif;
|
||||
if (statut == "true") {
|
||||
statut = false;
|
||||
} else {
|
||||
statut = true;
|
||||
}
|
||||
this.dataset.actif = statut;
|
||||
etudiants[this.nextElementSibling.dataset.id].actif = statut;
|
||||
saveToScodoc();
|
||||
}
|
||||
function changeListe(event) {
|
||||
let type = this.className;
|
||||
|
||||
/* Vérification des touches */
|
||||
if (type == "criteres") {
|
||||
if (!/[0-9.]/.test(event.key)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
} else {
|
||||
if (!/[0-9]/.test(event.key)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
if (event.key == "." && /[.]/.test(this.innerText)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function saveLocal(event) {
|
||||
/* Enregistement des données */
|
||||
let type = this.className;
|
||||
let parcours = this.dataset.idparcours;
|
||||
let id = this.dataset.id;
|
||||
etudiants[id][type][parcours] = parseFloat(this.innerText);
|
||||
}
|
||||
|
||||
/*function importList(semestre) {
|
||||
console.log(parcours);
|
||||
/******* SCODOC - enregistrer la répartition des étudiants dans le parcours ******/
|
||||
/*document.querySelector(".resultats>h2").innerHTML += `<div>✔️ Groupes importés dans le ${semestre}</div>`;
|
||||
}*/
|
||||
function setHighlight() {
|
||||
document.querySelectorAll(`[data-idparcours="${this.dataset.idparcours}"]`).forEach(e => {
|
||||
e.dataset.highlight = true;
|
||||
})
|
||||
}
|
||||
function resetHighlight() {
|
||||
document.querySelectorAll(`[data-highlight=true]`).forEach(e => {
|
||||
e.dataset.highlight = false;
|
||||
})
|
||||
}
|
||||
|
||||
function saveToScodoc() {
|
||||
let data = {
|
||||
parcours: parcours,
|
||||
etudiants: etudiants
|
||||
};
|
||||
fetch(
|
||||
"/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre + "/save_groups_auto_assignment",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
async function getFromScodoc() {
|
||||
let dataRaw = await fetch("/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre + "/get_groups_auto_assignment");
|
||||
let data = await dataRaw.text();
|
||||
if (data == "") {
|
||||
return {};
|
||||
}
|
||||
return JSON.parse(data);
|
||||
}
|
||||
/***************/
|
||||
/* Algo de tri */
|
||||
/***************/
|
||||
function processData() {
|
||||
|
||||
let typeTri = this.dataset.tri;
|
||||
|
||||
// Vérifications nombre de place
|
||||
let placesTotales = 0;
|
||||
Object.values(parcours).forEach(p => { placesTotales += p.places })
|
||||
if (placesTotales < Object.keys(etudiants).length) {
|
||||
document.querySelector(".resultats").innerHTML = "<h2>⚠️ Le nombre de places est inférieur au nombre d'étudiants ⚠️</h2>";
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérification champs non remplis
|
||||
if (typeTri == "notes") {
|
||||
var recherche = ".moitemoite input:invalid";
|
||||
} else {
|
||||
var recherche = ".moitemoite input:invalid, [contenteditable]:empty";
|
||||
}
|
||||
if (document.querySelector(recherche)) {
|
||||
document.querySelector(".resultats").innerHTML = "<h2>⚠️ Veuillez remplir tous les champs ⚠️</h2>";
|
||||
return;
|
||||
}
|
||||
|
||||
// Remise à 0 des résultats
|
||||
Object.values(parcours).map(p => p.etudiants = []);
|
||||
|
||||
// Liste des étudiants à traiter
|
||||
let etudiantsID = Object.keys(etudiants);
|
||||
|
||||
/* Pour chaque niveau de voeux */
|
||||
for (let numVoeux = 1; numVoeux < nbParcours + 1; numVoeux++) {
|
||||
/* Ce qu'il restera à traiter au round suivant */
|
||||
let aTraiter = [];
|
||||
etudiantsID.forEach(id => {
|
||||
if (etudiants[id].actif == false) return;
|
||||
if (typeTri == "notes") {
|
||||
// Récupère le parcours en fonction des notes et du round
|
||||
var groupe = Object.entries(etudiants[id].criteres).sort(([, a], [, b]) => b - a)[numVoeux - 1][0];
|
||||
} else {
|
||||
// Trouve le parcours qui correspond au numéro du voeux
|
||||
var groupe = Object.keys(etudiants[id].voeux).find(key => etudiants[id].voeux[key] == numVoeux);
|
||||
}
|
||||
|
||||
if (!groupe) {
|
||||
sco_message("Il semblerait que les voeux de " + etudiants[id].nom + " ne soit pas consécutifs en partant de 1.");
|
||||
}
|
||||
|
||||
if (parcours[groupe].places == 0) {
|
||||
aTraiter.push(id);
|
||||
} else if (parcours[groupe].etudiants.length < parcours[groupe].places) {
|
||||
// Le groupe n'est pas plein, on y ajoute l'étudiant
|
||||
parcours[groupe].etudiants.push(id);
|
||||
} else {
|
||||
aTraiter.push(groupePlein(groupe, id));
|
||||
}
|
||||
})
|
||||
etudiantsID = aTraiter;
|
||||
}
|
||||
|
||||
showResults();
|
||||
}
|
||||
|
||||
function groupePlein(groupe, id) {
|
||||
// Trie des étudiants dans le parcours par rapport au critère
|
||||
parcours[groupe].etudiants = parcours[groupe].etudiants.sort((a, b) => {
|
||||
return etudiants[a].criteres[groupe] - etudiants[b].criteres[groupe];
|
||||
})
|
||||
|
||||
// Si les deux étudiants ont le même niveau : aléatoire
|
||||
let diff = etudiants[parcours[groupe].etudiants[0]].criteres[groupe] - etudiants[id].criteres[groupe];
|
||||
if (diff == 0) {
|
||||
if (Math.random() < 0.5) {
|
||||
diff = 1;
|
||||
} else {
|
||||
diff = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// On ajoute dans le parcours le meilleur et on met à traiter l'autre
|
||||
if (diff < 0) {
|
||||
parcours[groupe].etudiants.push(id);
|
||||
return parcours[groupe].etudiants.shift();
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************/
|
||||
/* Affichage des résultats */
|
||||
/***************************/
|
||||
function showResults() {
|
||||
let output = `
|
||||
<button class="autre" onclick="exportResults()">Exporter les résultats</button>`;
|
||||
/*`
|
||||
<h2>
|
||||
Importer les résultats dans Scodoc<br>
|
||||
<button onclick='importList("S1")'>S1</button>
|
||||
<button onclick='importList("S2")'>S2</button>
|
||||
<button onclick='importList("S3")'>S3</button>
|
||||
<button onclick='importList("S4")'>S4</button>
|
||||
<button onclick='importList("S5")'>S5</button>
|
||||
<button onclick='importList("S6")'>S6</button>
|
||||
</h2>
|
||||
`*/;
|
||||
|
||||
Object.values(parcours).forEach(p => {
|
||||
output += `<h2>${p.nom} - ${p.etudiants.length}</h2>
|
||||
<div class=entete>Etudiants</div>
|
||||
<div class=entete style="grid-column: span ${nbParcours}">Notes</div>
|
||||
<div class=entete style="grid-column: span ${nbParcours}">Voeux</div>`;
|
||||
p.etudiants.forEach(id => {
|
||||
output += `<div>${etudiants[id].nom}</div>
|
||||
${display(id, etudiants[id], "criteres", false)}
|
||||
${display(id, etudiants[id], "voeux", false)}
|
||||
`;
|
||||
})
|
||||
})
|
||||
|
||||
document.querySelector(".resultats").style.gridTemplateColumns = `repeat(${1 + nbParcours * 2}, auto)`;
|
||||
document.querySelector(".resultats").innerHTML = output;
|
||||
}
|
||||
|
||||
/*****************************/
|
||||
/* Copier / coller des datas */
|
||||
/*****************************/
|
||||
function pasteData(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let data = event.clipboardData.getData('Text');
|
||||
let lignes = data.split(/\r\n|\r|\n/g);
|
||||
|
||||
currentTarget = this;
|
||||
|
||||
// Calcul du décalage dans la grille
|
||||
let offset = -1;
|
||||
do {
|
||||
offset++;
|
||||
currentTarget = currentTarget.previousElementSibling;
|
||||
} while (currentTarget.className != "")
|
||||
|
||||
// Collage des données
|
||||
currentTarget = this;
|
||||
|
||||
lignes.forEach(ligne => {
|
||||
let zone = ligne.split(/\t/g);
|
||||
for (let i = 0; i < zone.length; i++) {
|
||||
if (!currentTarget) return;
|
||||
currentTarget.innerText = zone[i];
|
||||
currentTarget = currentTarget.nextElementSibling;
|
||||
if (currentTarget?.className == "") {
|
||||
for (let j = 0; j <= offset; j++) {
|
||||
currentTarget = currentTarget.nextElementSibling;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
updateStudentsDatas();
|
||||
}
|
||||
|
||||
function updateStudentsDatas() {
|
||||
document.querySelectorAll(".criteres, .voeux").forEach(e => {
|
||||
etudiants[e.dataset.id][e.className][e.dataset.idparcours] = parseFloat(e.innerText);
|
||||
})
|
||||
saveStudentsData();
|
||||
}
|
||||
|
||||
function saveStudentsData() {
|
||||
/******* SCODOC - enregistrer les données étudiantes ******/
|
||||
console.log(etudiants);
|
||||
}
|
||||
|
||||
/***************/
|
||||
/* Export xlsx */
|
||||
/***************/
|
||||
function saveFile(name, workbook) {
|
||||
workbook.outputAsync()
|
||||
.then(function (blob) {
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
var a = document.createElement("a");
|
||||
document.body.appendChild(a);
|
||||
a.href = url;
|
||||
a.download = name + ".xlsx";
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
}
|
||||
|
||||
function changeChar(char, nb) {
|
||||
return String.fromCharCode(char.charCodeAt(0) + nb);
|
||||
}
|
||||
|
||||
async function exportData() {
|
||||
XlsxPopulate.fromBlankAsync()
|
||||
.then(workbook => {
|
||||
const sheet = workbook.sheet(0);
|
||||
sheet.name("Données");
|
||||
sheet.cell("A1").value("id");
|
||||
sheet.cell("B1").value("Actif");
|
||||
sheet.cell("C1").value("Nom");
|
||||
|
||||
Object.values(parcours).forEach((p, index) => {
|
||||
sheet.cell(changeChar("D", index) + "1").value("Note " + p.nom);
|
||||
sheet.cell(changeChar("D", index + nbParcours) + "1").value("Voeu " + p.nom);
|
||||
sheet.cell(changeChar("D", index + nbParcours) + "2").value(p.places);
|
||||
})
|
||||
|
||||
Object.entries(etudiants).forEach(([id, etudiant], index) => {
|
||||
sheet.cell("A" + (index + 3)).value(id.slice(2));
|
||||
sheet.cell("B" + (index + 3)).value(etudiant.actif);
|
||||
sheet.cell("C" + (index + 3)).value(etudiant.nom);
|
||||
let offset = 0;
|
||||
Object.values(etudiant.criteres).forEach(critere => {
|
||||
sheet.cell(changeChar("D", offset) + (index + 3)).value(critere);
|
||||
offset++;
|
||||
})
|
||||
Object.values(etudiant.voeux).forEach(voeux => {
|
||||
sheet.cell(changeChar("D", offset) + (index + 3)).value(voeux);
|
||||
offset++;
|
||||
})
|
||||
})
|
||||
|
||||
sheet.column("A").width(5);
|
||||
sheet.column("B").width(5);
|
||||
sheet.column("C").width(20);
|
||||
sheet.column("D").width(20);
|
||||
sheet.column("E").width(20);
|
||||
sheet.column("F").width(20);
|
||||
sheet.column("G").width(20);
|
||||
sheet.column("H").width(20);
|
||||
sheet.column("I").width(20);
|
||||
sheet.column("J").width(20);
|
||||
sheet.column("K").width(20);
|
||||
sheet.column("L").width(20);
|
||||
|
||||
saveFile("Données groupes - " + formsemestre, workbook);
|
||||
});
|
||||
}
|
||||
|
||||
async function exportResults() {
|
||||
XlsxPopulate.fromBlankAsync()
|
||||
.then(workbook => {
|
||||
const sheet = workbook.sheet(0);
|
||||
sheet.name("Résultats");
|
||||
|
||||
let colonne = 1;
|
||||
Object.values(parcours).forEach((dataParcours, index) => {
|
||||
let ligne = 1;
|
||||
sheet.row(ligne++).cell(colonne).value(dataParcours.nom).style("bold", true);
|
||||
sheet.row(ligne).cell(colonne).value("id").style("bold", true);
|
||||
sheet.row(ligne++).cell(colonne + 1).value("Nom").style("bold", true).column().width(30);
|
||||
|
||||
dataParcours.etudiants.forEach(etudiant => {
|
||||
sheet.row(ligne).cell(colonne).value(etudiant.slice(2));
|
||||
sheet.row(ligne++).cell(colonne + 1).value(etudiants[etudiant].nom);
|
||||
})
|
||||
colonne += 3;
|
||||
})
|
||||
|
||||
saveFile("Résultats groupes - " + formsemestre, workbook);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user