ScoDoc/app/templates/scolar/partition_editor.html
2022-07-27 21:58:50 +02:00

681 lines
24 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{# -*- mode: jinja-html -*- #}
<h1>{% if not read_only %}Édition des p{% else %}P{%endif%}artitions</h1>
<div>
<label class="edition">
<input type="checkbox" autocomplete="off">
Edition des partitions - tout s'enregistre automatiquement dès qu'il y a modification
</label>
</div>
<main>
<div class="wait"></div>
<section id="zoneChoix">
<h2>Choix</h2>
<div class="filtres">
<div class="partitions">
<h3>Afficher les partitions</h3>
<div></div>
</div>
<div class="masques">
<h3>
Afficher les étudiants affectés aux groupes<br>
<small>Ne s'actualise pas automatiquement lors d'une modification</small>
</h3>
<div></div>
</div>
</div>
<div class="etudiants"></div>
</section>
<section id="zoneGroupes">
<h2>Groupes</h2>
<div class="groupes"></div>
</section>
</main>
<script>
go();
async function go() {
document.querySelector('.wait').style.display = "";
let params = (new URL(document.location)).searchParams;
let formsemestre_id = params.get('formsemestre_id');
let partitions = await fetchData("/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre_id + "/partitions");
let etudiants = await fetchData("/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre_id + "/resultats");
etudiants.sort((a, b) => {
return a.nom_short.localeCompare(b.nom_short)
})
processDatas(partitions, etudiants);
processEvents();
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';
})
}
function processDatas(partitions, etudiants) {
/* Filtres et groupes */
let outputPartitions = "<div>";
let outputMasques = "";
let outputGroupes = "";
let arrayPartitions = Object.values(partitions).sort((a, b) => {
return a.numero - b.numero;
})
arrayPartitions.forEach((partition) => {
// Filtres
outputPartitions += `<div data-idpartition="${partition.id}"><span class="editing move">||</span><span>${partition.partition_name}</span><span class="editing modif">✏️</span><span class="editing suppr">❌</span></div>`;
outputMasques += `<div data-idpartition="${partition.id}"><div data-idpartition="${partition.id}" data-idgroupe=aucun>Non affectés - ${partition.partition_name}</div>`;
// Groupes
outputGroupes += `
<div class=partition data-idpartition="${partition.id}">
<h3>${partition.partition_name}</h3>
<div class=groupe data-idgroupe=aucun>
<div>Non affecté(s)</div>
<div class=etudiants></div>
</div>
${(() => {
let arrayGroups = Object.values(partition.groups).sort((a, b) => {
return a.numero - b.numero;
})
let output = "";
arrayGroups.forEach((groupe) => {
/***************/
outputMasques += `<div data-idgroupe="${groupe.id}"><span class="editing move">||</span><span>${groupe.name}</span><span class="editing modif"></span><span class="editing suppr"></span></div>`;
/***************/
output += templateGroupe_zoneGroupes(groupe.id, groupe.name);
})
return output;
})()}
</div>`;
outputMasques += `
<div class="editing ajoutGroupe">+</div>
</div>`;
})
document.querySelector(".filtres>.partitions>div").innerHTML = outputPartitions + `
<div class="editing ajoutPartition">+</div>
</div>`;
document.querySelector(".filtres>.masques>div").innerHTML = outputMasques;
document.querySelector("#zoneGroupes>.groupes").innerHTML = outputGroupes;
/* Etudiants */
output = "";
etudiants.forEach(etudiant => {
output += `
<div>
<div class=nom data-etudid="${etudiant.etudid}" data-nom="${etudiant.nom_disp}" data-prenom="${etudiant.prenom}">${etudiant.nom_disp} ${etudiant.prenom}<br><small>${etudiant.bac}</small></div>
${(() => {
let output = "<div class=grpPartitions>";
arrayPartitions.forEach((partition) => {
output += `
<div class=partition data-idpartition="${partition.id}">
<div>${partition.partition_name}</div>
${(() => {
let output = "";
let affected = false;
let arrayGroups = Object.values(partition.groups).sort((a, b) => {
return a.numero - b.numero;
})
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.name}</span></label>`;
if (etudiant.partitions[partition.id] == groupe.id) {
affected = true;
document.querySelector(`#zoneGroupes [data-idgroupe="${groupe.id}"]>.etudiants`).innerHTML += templateEtudiant_zoneGroupes(etudiant);
}
})
if (!affected) {
document.querySelector(`#zoneGroupes [data-idpartition="${partition.id}"]>[data-idgroupe="aucun"]>.etudiants`).innerHTML += templateEtudiant_zoneGroupes(etudiant);
}
return `<label title="Aucun groupe"><input type=radio name="${partition.id}-${etudiant.etudid}" value="aucun" ${(!affected) ? "checked" : ""}><span class=aucun>❌</span></label>` + output;
})()}
</div>`;
})
return output + "</div>";
})()}
</div>`;
})
document.querySelector("#zoneChoix>.etudiants").innerHTML = output;
}
function templateGroupe_zoneGroupes(idGroupe, name) {
return `<div class=groupe data-idgroupe="${idGroupe}">
<div>${name}</div>
<div class=etudiants></div>
</div>`;
}
function templateEtudiant_zoneGroupes(etudiant) {
return `<div data-etudid="${etudiant.etudid}" data-nom="${etudiant.nom_disp}" data-prenom="${etudiant.prenom}">${etudiant.nom_disp} ${etudiant.prenom}</div>`
}
/******************************/
/* Gestionnaire d'événements */
/******************************/
function input() {
document.querySelector("body").classList.toggle("editionActivated");
/*if (event.currentTarget.checked == false) {
go();
}*/
}
function processEvents() {
/*--------------------*/
/* Edition partitions */
/*--------------------*/
document.querySelector(".edition>input").addEventListener("input", input);
document.querySelectorAll(".ajoutPartition, .ajoutGroupe").forEach(btnPlus => { btnPlus.addEventListener("click", addPartition) })
document.querySelectorAll(".modif").forEach(btn => { btn.addEventListener("click", editText) })
document.querySelectorAll(".suppr").forEach(btn => { btn.addEventListener("click", suppr) })
document.querySelectorAll(".move").forEach(btn => { btn.addEventListener("mousedown", moveStart) })
/*---------*/
/* Filtres */
/*---------*/
document.querySelectorAll(".filtres>div>div>div>div:not(.editing)").forEach(btn => { btn.addEventListener("click", filtre) })
/*--------------------*/
/* Changement groupe */
/*--------------------*/
document.querySelectorAll("#zoneChoix label").forEach(btn => { btn.addEventListener("mousedown", (event) => { event.preventDefault() }) });
document.querySelectorAll(".etudiants input").forEach(input => { input.addEventListener("input", assignment) })
}
/**********************/
/* Filtrage */
/**********************/
function filtre() {
if (document.querySelector("body").classList.contains("editionActivated")) {
return;
}
let nbUnselected = this.parentElement.querySelectorAll(".unselect").length;
let nbBtn = this.parentElement.children.length;
if (nbUnselected == 0) {
Array.from(this.parentElement.children).forEach(e => {
e.classList.toggle("unselect");
})
}
this.classList.toggle("unselect");
nbUnselected = this.parentElement.querySelectorAll(".unselect").length;
if (nbUnselected == nbBtn) {
Array.from(this.parentElement.children).forEach(e => {
e.classList.toggle("unselect");
})
}
if (!this.dataset.idgroupe) {
let groupesSelected = [];
this.parentElement.querySelectorAll(":not(.unselect)").forEach(e => {
groupesSelected.push(e.dataset.idpartition);
})
document.querySelectorAll(`
.etudiants .partition[data-idpartition],
#zoneGroupes [data-idpartition]
`).forEach(e => {
if (groupesSelected.includes(e.dataset.idpartition)) {
e.classList.remove("hide")
} else {
e.classList.add("hide")
}
})
} else {
let groupesSelected = {};
this.parentElement.parentElement.querySelectorAll("[data-idgroupe]:not(.unselect)").forEach(e => {
let idpartition = e.parentElement.dataset.idpartition;
if (!groupesSelected[idpartition]) {
groupesSelected[idpartition] = [];
}
groupesSelected[idpartition].push(e.dataset.idgroupe)
})
document.querySelectorAll("#zoneChoix .etudiants>div").forEach(e => {
let found = true;
Object.entries(groupesSelected).forEach(([idpartition, tabGroupes]) => {
if (!tabGroupes.includes(
e.querySelector(`[data-idpartition="${idpartition}"] input:checked`).value
)
) {
found = false
}
})
if (found) {
e.classList.remove("hide")
} else {
e.classList.add("hide")
}
})
}
}
/****************************/
/* Affectation à un groupe */
/****************************/
function assignment() {
let groupe = this.parentElement.parentElement.parentElement.parentElement;
let nom = groupe.children[0].dataset.nom;
let prenom = groupe.children[0].dataset.prenom;
let etudid = groupe.children[0].dataset.etudid;
let idPartition = this.parentElement.parentElement.dataset.idpartition;
let idGroupe = this.value;
document.querySelector(`#zoneGroupes [data-idPartition="${idPartition}"] [data-etudid="${etudid}"]`).remove();
let etudiant = {
etudid: etudid,
nom_disp: nom,
prenom: prenom
}
let results = document.querySelector(`#zoneGroupes [data-idPartition="${idPartition}"] [data-idgroupe="${idGroupe}"]>.etudiants`);
results.innerHTML += templateEtudiant_zoneGroupes(etudiant);
/* Tri */
let results2 = [...results.children];
results2.sort((a, b) => {
return (a.dataset.nom + a.dataset.prenom).localeCompare(b.dataset.nom + b.dataset.prenom)
})
results.innerHTML = "";
results.append(...results2);
/* Save */
this.classList.add("saving");
if (idGroupe == "aucun") {
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/partition/${idPartition}/remove_etudiant/${etudid}`;
} else {
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/group/${idGroupe}/set_etudiant/${etudid}`
}
fetch(url, { method: "POST" })
.then(r => { return r.json() })
.then(r => {
if (r.etudid == etudid) {
this.classList.remove("saving");
this.classList.add("saved");
setTimeout(() => { this.classList.remove("saved") }, 800);
return;
}
throw 'Les données retournées ne sont pas valides';
})
.catch(error => {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
})
}
/****************************/
/* Ajout partition / groupe */
/****************************/
function addPartition() {
let date = new Date;
if (this.classList.contains("ajoutPartition")) {
// Partition
var name = "Nouvelle " + date.getSeconds();
let params = (new URL(document.location)).searchParams;
let formsemestre_id = params.get('formsemestre_id');
var url = "/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre_id + "/partition/create";
var payload = { partition_name: name };
} else {
// Groupe
var name = "Nouveau " + date.getSeconds();
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/partition/${this.parentElement.dataset.idpartition}/group/create`;
var payload = { group_name: name };
}
var div = document.createElement("div");
div.innerHTML = `
<span class="editing move">||</span>
<span>${name}</span>
<span class="editing modif">✏️</span>
<span class="editing suppr">❌</span>`;
div.querySelector(".modif").addEventListener("click", editText);
div.querySelector(".suppr").addEventListener("click", suppr);
div.querySelector(".move").addEventListener("mousedown", moveStart);
this.parentElement.insertBefore(div, this);
// Save
fetch(url,
{
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(r => { return r.json() })
.then(r => {
if (r.message == "invalid partition_name" || r.message == "invalid group_name") {
message("Le nom " + name + " existe déjà");
div.remove();
return;
}
if (this.classList.contains("ajoutPartition")) {
div.dataset.idpartition = r.id;
// Ajout dans la zone masques
div = document.createElement("div");
div.dataset.idpartition = r.id;
div.innerHTML = `
<div data-idpartition="${r.id}" data-idgroupe=aucun>Non affectés - ${name}</div>
<div class="editing ajoutGroupe">+</div>`;
div.querySelector("div").addEventListener("click", filtre);
div.querySelector(".ajoutGroupe").addEventListener("click", addPartition);
document.querySelector("#zoneChoix .masques>div").appendChild(div);
// Ajout de la zone pour chaque étudiant
let outputGroupes = "";
document.querySelectorAll(`#zoneChoix .grpPartitions`).forEach(e => {
let etudid = e.previousElementSibling.dataset.etudid;
// Préparation pour la section suivante
let etudiant = {
etudid: etudid,
nom_disp: e.previousElementSibling.dataset.nom,
prenom: e.previousElementSibling.dataset.prenom
}
outputGroupes += templateEtudiant_zoneGroupes(etudiant);
////////////////////////
let div = document.createElement("div");
div.className = "partition";
div.dataset.idpartition = r.id;
div.innerHTML = `
<div>${name}</div>
<label title="Aucun groupe">
<input type="radio" name="${r.id}-${etudid}" value="aucun" checked>
<span class="aucun">❌</span>
</label>
`;
div.querySelector("input").addEventListener("input", assignment);
e.appendChild(div);
});
// Ajout de la zone groupes
document.querySelector("#zoneGroupes>.groupes").innerHTML += `
<div class=partition data-idpartition="${r.id}">
<h3>${name}</h3>
<div class=groupe data-idgroupe=aucun>
<div>Non affecté(s)</div>
<div class=etudiants>${outputGroupes}</div>
</div>
</div>`;
} else {
div.dataset.idgroupe = r.id;
// Ajout du bouton pour chaque étudiant
let idpartition = this.parentElement.dataset.idpartition;
document.querySelectorAll(`#zoneChoix .etudiants [data-idpartition="${idpartition}"]`).forEach(e => {
let etudid = e.parentElement.parentElement.dataset.etudid;
let label = document.createElement("label");
label.innerHTML = `<input type=radio name="${idpartition}-${etudid}" value="${r.id}"><span>${name}</span>`;
label.querySelector("input").addEventListener("input", assignment);
e.appendChild(label);
})
// Ajout du groupe dans la zone Groupes
document.querySelector(`#zoneGroupes .partition[data-idpartition="${idpartition}"]`).innerHTML += templateGroupe_zoneGroupes(r.id, name);
}
})
.catch(error => {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
})
}
/********************/
/* Edition du texte */
/********************/
function editText() {
this.previousElementSibling.classList.add("editingText");
this.previousElementSibling.setAttribute("contenteditable", "true");
this.previousElementSibling.focus();
this.previousElementSibling.addEventListener("keydown", writing);
}
function writing(event) {
switch (event.key) {
case 'Enter':
saveEditing(this);
event.preventDefault();
break;
case 'Escape':
saveEditing(this);
event.preventDefault();
break;
}
}
function saveEditing(obj) {
obj.classList.remove("editingText");
obj.setAttribute("contenteditable", "false");
obj.removeEventListener("keydown", writing);
// Save
if (obj.parentElement.dataset.idpartition) {
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/partition/${obj.parentElement.dataset.idpartition}/edit`;
var payload = { partition_name: obj.innerText }
document.querySelector(`.masques [data-idpartition="${obj.parentElement.dataset.idpartition}"][data-idgroupe="aucun"]`).innerText = "Non affectés - " + obj.innerText;
document.querySelectorAll(`#zoneChoix .etudiants [data-idpartition="${obj.parentElement.dataset.idpartition}"]>div`).forEach(e => { e.innerText = obj.innerText });
document.querySelector(`#zoneGroupes [data-idpartition="${obj.parentElement.dataset.idpartition}"]>h3`).innerText = obj.innerText;
} else {
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/group/${obj.parentElement.dataset.idgroupe}/edit`;
var payload = { group_name: obj.innerText }
document.querySelectorAll(`#zoneChoix .etudiants [value="${obj.parentElement.dataset.idgroupe}"]+span`).forEach(e => { e.innerText = obj.innerText });
document.querySelector(`#zoneGroupes [data-idgroupe="${obj.parentElement.dataset.idgroupe}"]>div`).innerText = obj.innerText;
}
fetch(url,
{
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(r => { return r.json() })
.then(r => {
if (!r) {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
}
})
.catch(error => {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
})
}
/*********************************/
/* Suppression parcours / groupe */
/*********************************/
function suppr() {
if (this.parentElement.dataset.idpartition) {
var data = `data-idpartition="${this.parentElement.dataset.idpartition}"`;
} else {
var data = `data-idgroupe="${this.parentElement.dataset.idgroupe}"`;
}
let div = document.createElement("div");
div.className = "confirm";
div.innerHTML = `
<div>
<h1>Vous être sur le point de supprimer <span>${this.previousElementSibling.previousElementSibling.innerText}</span>,<br>cette opération est irréversible</h1>
<div>
<div class="ok" ${data}>Supprimer</div>
<div class="nok">Annuler</div>
</div>
</div>
`;
document.body.append(div);
document.querySelector(".ok").addEventListener("click", supprConfirmed);
document.querySelector(".nok").addEventListener("click", closeConfirm);
}
function supprConfirmed() {
closeConfirm();
/* Suppression des éléments dans la page */
if (this.dataset.idpartition) {
document.querySelectorAll(`[data-idpartition="${this.dataset.idpartition}"]`).forEach(e => { e.remove() })
var url = "/ScoDoc/{{formsemestre.departement.acronym}}/api/partition/" + this.dataset.idpartition + "/delete";
} else {
document.querySelectorAll(`[value="${this.dataset.idgroupe}"]`).forEach(e => {
if (e.checked == true) {
e.parentElement.parentElement.querySelector("label").click()
}
e.parentElement.remove()
})
document.querySelectorAll(`[data-idgroupe="${this.dataset.idgroupe}"]`).forEach(e => { e.remove() })
var url = "/ScoDoc/{{formsemestre.departement.acronym}}/api/group/" + this.dataset.idgroupe + "/delete";
}
//Save
fetch(url, { method: "POST" })
.then(r => { return r.json() })
.then(r => {
if (r.OK != true) {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
}
})
}
function closeConfirm() {
document.querySelector(".confirm").remove();
}
/*************************/
/* Changement de l'ordre */
/*************************/
let moveData = {};
function moveStart(event) {
moveData.x = event.pageX;
moveData.y = event.pageY;
moveData.element = this.parentElement;
moveData.element.classList.add("moving");
moveData.element.parentElement.classList.add('grabbing');
document.body.addEventListener("mousemove", move);
moveData.element.parentElement.querySelectorAll("div:not([data-idgroupe=aucun])").forEach(e => {
e.addEventListener("mouseup", newPosition)
})
document.body.addEventListener("mouseup", moveEnd);
}
function move(event) {
event.preventDefault();
moveData.element.style.transform = `translate(${event.pageX - moveData.x}px, ${event.pageY - moveData.y}px)`
}
function moveEnd() {
document.body.removeEventListener("mousemove", move);
document.body.removeEventListener("mouseup", moveEnd);
moveData.element.parentElement.classList.remove('grabbing');
moveData.element.style.transform = "";
moveData.element.classList.remove("moving");
moveData.element.parentElement.querySelectorAll("div:not([data-idgroupe=aucun])").forEach(e => {
e.removeEventListener("mouseup", newPosition)
})
moveData = {};
}
function newPosition() {
moveData.element.parentElement.insertBefore(moveData.element, this);
let positions = [];
Array.from(moveData.element.parentElement.children).forEach(e => {
if ((e.dataset.idpartition && e.dataset.idgroupe != "aucun") || (e.dataset.idgroupe && e.dataset.idgroupe != "aucun")) {
positions.push(parseInt(e.dataset.idgroupe || e.dataset.idpartition))
}
})
// Save positions
if (this.dataset.idpartition) {
let params = (new URL(document.location)).searchParams;
let formsemestre_id = params.get('formsemestre_id');
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/${formsemestre_id}/partitions/order`;
document.querySelectorAll(`#zoneChoix .masques>div`).forEach(parent => {
positions.forEach(position => {
parent.append(parent.querySelector(`[data-idpartition="${position}"]`))
})
})
document.querySelectorAll(`#zoneChoix .grpPartitions`).forEach(parent => {
positions.forEach(position => {
parent.append(parent.querySelector(`[data-idpartition="${position}"]`))
})
})
document.querySelectorAll(`#zoneGroupes>.groupes`).forEach(parent => {
positions.forEach(position => {
parent.append(parent.querySelector(`[data-idpartition="${position}"]`))
})
})
} else {
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/partition/${this.parentElement.dataset.idpartition}/groups/order`;
document.querySelectorAll(`#zoneChoix .etudiants .partition[data-idpartition="${this.parentElement.dataset.idpartition}"]`).forEach(partition => {
positions.forEach(position => {
partition.append(partition.querySelector(`[value="${position}"]`).parentElement)
})
})
document.querySelectorAll(`#zoneGroupes .partition[data-idpartition="${this.parentElement.dataset.idpartition}"]`).forEach(partition => {
positions.forEach(position => {
partition.append(partition.querySelector(`[data-idgroupe="${position}"]`))
})
})
}
fetch(url,
{
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(positions)
})
.then(r => { return r.json() })
.then(r => {
if (!r) {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
}
})
.catch(error => {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
})
}
/*************************/
/* Message */
/*************************/
function message(msg) {
var div = document.createElement("div");
div.className = "message_curtom";
div.innerHTML = msg;
document.querySelector("body").appendChild(div);
setTimeout(() => {
div.remove();
}, 3000);
}
</script>