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"
|
operation = "record"
|
||||||
db.session.add(validation)
|
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()
|
db.session.commit()
|
||||||
Scolog.logdb(
|
Scolog.logdb(
|
||||||
method="validation_rcue_record",
|
method="validation_rcue_record",
|
||||||
|
@ -307,6 +307,17 @@ class FormSemestre(db.Model):
|
|||||||
- et sont associées à l'un des parcours de ce formsemestre
|
- et sont associées à l'un des parcours de ce formsemestre
|
||||||
(ou à aucun, donc tronc commun).
|
(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
|
formation: Formation = self.formation
|
||||||
if formation.is_apc():
|
if formation.is_apc():
|
||||||
# UEs de tronc commun (sans parcours indiqué)
|
# UEs de tronc commun (sans parcours indiqué)
|
||||||
@ -326,8 +337,7 @@ class FormSemestre(db.Model):
|
|||||||
).filter(UniteEns.semestre_idx == self.semestre_id)
|
).filter(UniteEns.semestre_idx == self.semestre_id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
ues = sem_ues.values()
|
ues = sorted(sem_ues.values(), key=attrgetter("numero", "acronyme"))
|
||||||
return sorted(ues, key=attrgetter("numero", "acronyme"))
|
|
||||||
else:
|
else:
|
||||||
sem_ues = db.session.query(UniteEns).filter(
|
sem_ues = db.session.query(UniteEns).filter(
|
||||||
ModuleImpl.formsemestre_id == self.id,
|
ModuleImpl.formsemestre_id == self.id,
|
||||||
@ -336,7 +346,9 @@ class FormSemestre(db.Model):
|
|||||||
)
|
)
|
||||||
if not with_sport:
|
if not with_sport:
|
||||||
sem_ues = sem_ues.filter(UniteEns.type != codes_cursus.UE_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
|
@cached_property
|
||||||
def modimpls_sorted(self) -> list[ModuleImpl]:
|
def modimpls_sorted(self) -> list[ModuleImpl]:
|
||||||
|
@ -63,25 +63,32 @@ def dict_pvjury(
|
|||||||
Si with_parcours_decisions: ajoute infos sur code decision jury de tous les semestre du parcours
|
Si with_parcours_decisions: ajoute infos sur code decision jury de tous les semestre du parcours
|
||||||
Résultat:
|
Résultat:
|
||||||
{
|
{
|
||||||
'date' : date de la decision la plus recente,
|
'date' : str = date de la decision la plus recente, format dd/mm/yyyy,
|
||||||
'formsemestre' : sem,
|
'formsemestre' : dict = formsemestre,
|
||||||
'is_apc' : bool,
|
'is_apc' : bool,
|
||||||
'formation' : { 'acronyme' :, 'titre': ... }
|
'formation' : { 'acronyme' :, 'titre': ... }
|
||||||
'decisions' : { [ { 'identite' : {'nom' :, 'prenom':, ...,},
|
'decisions' : [
|
||||||
'etat' : I ou D ou DEF
|
{
|
||||||
'decision_sem' : {'code':, 'code_prev': },
|
'identite' : {'nom' :, 'prenom':, ...,},
|
||||||
'decisions_ue' : { ue_id : { 'code' : ADM|CMP|AJ, 'event_date' :,
|
'etat' : I ou D ou DEF
|
||||||
'acronyme', 'numero': } },
|
'decision_sem' : {'code':, 'code_prev': },
|
||||||
'autorisations' : [ { 'semestre_id' : { ... } } ],
|
'decisions_ue' : {
|
||||||
'validation_parcours' : True si parcours validé (diplome obtenu)
|
ue_id : {
|
||||||
'prev_code' : code (calculé slt si with_prev),
|
'code' : ADM|CMP|AJ,
|
||||||
'mention' : mention (en fct moy gen),
|
'ects' : float,
|
||||||
'sum_ects' : total ECTS acquis dans ce semestre (incluant les UE capitalisées)
|
'event_date' :str = "dd/mm/yyyy",
|
||||||
'sum_ects_capitalises' : somme des ECTS des UE capitalisees
|
},
|
||||||
}
|
},
|
||||||
]
|
'autorisations' : [ { 'semestre_id' : { ... } } ],
|
||||||
},
|
'validation_parcours' : True si parcours validé (diplome obtenu)
|
||||||
'decisions_dict' : { etudid : decision (comme ci-dessus) },
|
'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) },
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
@ -106,7 +106,7 @@ body:not(.editionActivated) .editing {
|
|||||||
|
|
||||||
@keyframes boing {
|
@keyframes boing {
|
||||||
100% {
|
100% {
|
||||||
transform: translateY(-20px)
|
transform: translateY(-20px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +152,7 @@ body.editionActivated .filtres>div>div>div>div {
|
|||||||
color: #000;
|
color: #000;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
outline: 4px solid #FFF;
|
outline: 4px solid #FFF;
|
||||||
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Suppression */
|
/* Suppression */
|
||||||
@ -355,7 +356,7 @@ body.editionActivated .filtres .nonEditable .move {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.groupe:has(.etudiants:empty) {
|
.groupe:has(.etudiants:empty) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .filtres .unselect {
|
/* .filtres .unselect {
|
||||||
@ -365,6 +366,23 @@ body.editionActivated .filtres .nonEditable .move {
|
|||||||
/*****************************/
|
/*****************************/
|
||||||
/* Zone Etudiants */
|
/* 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 {
|
#zoneChoix>.autoAffectation {
|
||||||
background: #c9c9c9;
|
background: #c9c9c9;
|
||||||
color: #141414;
|
color: #141414;
|
||||||
|
@ -22,6 +22,23 @@
|
|||||||
|
|
||||||
<section id="zoneChoix">
|
<section id="zoneChoix">
|
||||||
<h2>Étudiants</h2>
|
<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">
|
<div class="autoAffectation">
|
||||||
Affecter automatiquement les étudiants du groupe<br>
|
Affecter automatiquement les étudiants du groupe<br>
|
||||||
<select name="affectationFrom" id="affectationFrom"></select>
|
<select name="affectationFrom" id="affectationFrom"></select>
|
||||||
@ -38,6 +55,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx-populate/1.21.0/xlsx-populate.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
go();
|
go();
|
||||||
@ -125,7 +143,7 @@
|
|||||||
output = "";
|
output = "";
|
||||||
etudiants.forEach(etudiant => {
|
etudiants.forEach(etudiant => {
|
||||||
output += `
|
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>
|
<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>";
|
let output = "<div class=grpPartitions>";
|
||||||
@ -141,7 +159,7 @@
|
|||||||
})
|
})
|
||||||
arrayGroups.forEach((groupe) => {
|
arrayGroups.forEach((groupe) => {
|
||||||
output += `
|
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) {
|
if (etudiant.partitions[partition.id] == groupe.id) {
|
||||||
affected = true;
|
affected = true;
|
||||||
@ -192,10 +210,10 @@
|
|||||||
Configuration
|
Configuration
|
||||||
<hr>
|
<hr>
|
||||||
<label title="Calculer et afficher les rangs dans les groupes de cette partition ?">
|
<label title="Calculer et afficher les rangs dans les groupes de cette partition ?">
|
||||||
<input class=rang type=checkbox ${partition.bul_show_rank?"checked":""} data-attr=bul_show_rank> Rang bulletins
|
<input class=rang type=checkbox ${partition.bul_show_rank ? "checked" : ""} data-attr=bul_show_rank> Rang bulletins
|
||||||
</label>
|
</label>
|
||||||
<label title="Doit-on afficher les groupes de cette partition dans les listes ?">
|
<label title="Doit-on afficher les groupes de cette partition dans les listes ?">
|
||||||
<input class=groupe type=checkbox ${partition.show_in_lists?"checked":""} data-attr=show_in_lists> Afficher sur bulletins et tableaux
|
<input class=groupe type=checkbox ${partition.show_in_lists ? "checked" : ""} data-attr=show_in_lists> Afficher sur bulletins et tableaux
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<a class="stdlink" href="/ScoDoc/{{formsemestre.departement.acronym}}/Scolarite/groups_auto_repartition?partition_id=${partition.id}">Répartir les étudiants</a>
|
<a class="stdlink" href="/ScoDoc/{{formsemestre.departement.acronym}}/Scolarite/groups_auto_repartition?partition_id=${partition.id}">Répartir les étudiants</a>
|
||||||
@ -261,7 +279,7 @@
|
|||||||
|
|
||||||
document.querySelector("#affectationFrom").innerHTML = output;
|
document.querySelector("#affectationFrom").innerHTML = output;
|
||||||
document.querySelector("#affectationTo").innerHTML = output;
|
document.querySelector("#affectationTo").innerHTML = output;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************/
|
/******************************/
|
||||||
@ -353,24 +371,76 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************************/
|
||||||
|
/* 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 */
|
/* Affectation à un groupe */
|
||||||
/****************************/
|
/****************************/
|
||||||
function affectationGo(){
|
function affectationGo() {
|
||||||
let from = document.querySelector("#affectationFrom").value;
|
let from = document.querySelector("#affectationFrom").value;
|
||||||
let to = document.querySelector("#affectationTo").value;
|
let to = document.querySelector("#affectationTo").value;
|
||||||
|
|
||||||
if(!from || !to){
|
if (!from || !to) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let elements = [];
|
let elements = [];
|
||||||
|
|
||||||
if(from[0] != "n"){
|
if (from[0] != "n") {
|
||||||
elements = document.querySelectorAll(`#zoneChoix .etudiants [value="${from}"]:checked`)
|
elements = document.querySelectorAll(`#zoneChoix .etudiants [value="${from}"]:checked`)
|
||||||
} else {
|
} else {
|
||||||
document.querySelectorAll(`#zoneChoix .etudiants [data-idpartition="${from.split("-")[1]}"]`).forEach(element=>{
|
document.querySelectorAll(`#zoneChoix .etudiants [data-idpartition="${from.split("-")[1]}"]`).forEach(element => {
|
||||||
if(!element.querySelector('input:not([value="aucun"]):checked')){
|
if (!element.querySelector('input:not([value="aucun"]):checked')) {
|
||||||
elements.push(element);
|
elements.push(element);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -378,13 +448,13 @@
|
|||||||
|
|
||||||
console.log(elements);
|
console.log(elements);
|
||||||
|
|
||||||
elements.forEach(groupeSelected=>{
|
elements.forEach(groupeSelected => {
|
||||||
if(to[0] != "n"){
|
if (to[0] != "n") {
|
||||||
groupeSelected.closest(".grpPartitions").querySelector(`[value="${to}"]`).click();
|
groupeSelected.closest(".grpPartitions").querySelector(`[value="${to}"]`).click();
|
||||||
}else{
|
} else {
|
||||||
groupeSelected.closest(".grpPartitions").querySelector(`[value="aucun"]`).click();
|
groupeSelected.closest(".grpPartitions").querySelector(`[value="aucun"]`).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function assignment() {
|
function assignment() {
|
||||||
@ -609,6 +679,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveEditing(obj) {
|
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.classList.remove("editingText");
|
||||||
obj.setAttribute("contenteditable", "false");
|
obj.setAttribute("contenteditable", "false");
|
||||||
obj.removeEventListener("keydown", writing);
|
obj.removeEventListener("keydown", writing);
|
||||||
@ -829,14 +908,14 @@
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
|
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||||
},
|
},
|
||||||
body: `partition_id=${this.closest("[data-idpartition]").dataset.idpartition}&attr=${this.dataset.attr}&value=${this.checked?1:0}`
|
body: `partition_id=${this.closest("[data-idpartition]").dataset.idpartition}&attr=${this.dataset.attr}&value=${this.checked ? 1 : 0}`
|
||||||
}
|
}
|
||||||
).then(function(response) {
|
).then(function (response) {
|
||||||
return response.text();
|
return response.text();
|
||||||
})
|
})
|
||||||
.then(function(txt) {
|
.then(function (txt) {
|
||||||
sco_message(txt);
|
sco_message(txt);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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