ScoDoc/app/templates/scolar/partition_editor.html

711 lines
25 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>
<main>
<div class="wait"></div>
<section id="zonePartitions">
<h2>Partitions et groupes</h2>
<div>
<label class="edition">
<input type="checkbox" autocomplete="off">
Modifier les partitions et groupes - tout s'enregistre automatiquement dès qu'il y a modification
</label>
<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>
</section>
<section id="zoneChoix">
<h2>Etudiants</h2>
<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
if (partition.groups_editable) {
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>`;
} else {
outputPartitions += `<div data-idpartition="${partition.id}"><span>${partition.partition_name}</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) => {
/***************/
if (partition.groups_editable) {
outputMasques += `<div data-idgroupe="${groupe.id}"><span class="editing move">||</span><span>${groupe.group_name}</span><span class="editing modif"></span><span class="editing suppr"></span></div>`;
} else {
outputMasques += `<div data-idgroupe="${groupe.id}"><span>${groupe.group_name}</span></div>`;
}
/***************/
output += templateGroupe_zoneGroupes(groupe.id, groupe.group_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.group_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 setEditMode() {
let editing = document.querySelector("[contentEditable=true]");
if (!editing) {
document.querySelector("body").classList.toggle("editionActivated");
return;
}
this.checked = true;
if(!editing.classList.contains("highlight")) {
editing.classList.add("highlight");
setTimeout(()=>{editing.classList.remove("highlight")}, 1000);
}
}
function processEvents() {
/*--------------------*/
/* Edition partitions */
/*--------------------*/
document.querySelector(".edition>input").addEventListener("input", setEditMode);
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("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) {
// Partitions
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 {
// Groupes
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);
div.querySelector(".modif").click();
// 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("#zonePartitions .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() {
let e = this.previousElementSibling;
e.classList.add("editingText");
e.setAttribute("contenteditable", "true");
e.addEventListener("keydown", writing);
// On sélectionne la zone
const range = document.createRange();
const selection = window.getSelection();
selection.removeAllRanges();
range.selectNodeContents(e);
selection.addRange(range);
}
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.parentElement.parentElement.parentElement.className == "partitions") {
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(`#zonePartitions .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>