{# -*- mode: jinja-html -*- #} <h1>{% if not read_only %}Édition des p{% else %}P{%endif%}artitions</h1> <div> <label class="edition"> <input type="checkbox"> 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>