forked from ScoDoc/DocScoDoc
Merge pull request 'Partition editor V1 beta' (#455) from lehmann/ScoDoc-Front:master into master
Reviewed-on: https://scodoc.org/git/ScoDoc/ScoDoc/pulls/455
This commit is contained in:
commit
d87efd99b7
@ -1 +1,208 @@
|
||||
// Editeur de partitions
|
||||
.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 {
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
main h2 {
|
||||
border-bottom: 4px solid #09c;
|
||||
}
|
||||
|
||||
main h2,
|
||||
main h3 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/*****************************/
|
||||
/* Zone Choix */
|
||||
/*****************************/
|
||||
.filtres>div {
|
||||
background: #ddd;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.filtres>div>div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
row-gap: 2px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.filtres>div>div>div {
|
||||
background: #09c;
|
||||
color: #FFF;
|
||||
border-radius: 4px;
|
||||
padding: 8px 32px;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.25);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filtres>div>div>div:hover {
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.filtres>div>div>div:active {
|
||||
box-shadow: 0 0 0 #000;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
.filtres>div>div>.unselect {
|
||||
background: rgba(0, 153, 204, 0.5);
|
||||
}
|
||||
|
||||
#zoneChoix .etudiants>div {
|
||||
background: #FFF;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
margin: 4px 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#zoneChoix .etudiants .nom {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#zoneChoix small {
|
||||
color: #444;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#zoneChoix .etudiants .partition {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#zoneChoix label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#zoneChoix .etudiants .partition>div,
|
||||
#zoneChoix .etudiants .partition span {
|
||||
display: block;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#zoneChoix .etudiants .partition input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#zoneChoix .etudiants .partition input:checked:not([value=aucun])+span {
|
||||
background: #c09;
|
||||
border-color: #c09;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#zoneChoix .etudiants .partition>:nth-child(1) {
|
||||
background: #09c;
|
||||
border-color: #09c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.saved+span {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.saving+span {
|
||||
outline: 2px solid orange;
|
||||
}
|
||||
|
||||
.saved+span::before {
|
||||
content: '✔️';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/*****************************/
|
||||
/* Zone Groupes */
|
||||
/*****************************/
|
||||
#zoneGroupes {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#zoneGroupes .groupes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
#zoneGroupes .partition {
|
||||
background: #ddd;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#zoneGroupes .groupe {
|
||||
background: #FFF;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#zoneGroupes .groupe>div {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
#zoneGroupes .groupe>div:nth-child(1) {
|
||||
color: #09c;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
#zoneGroupes .etudiants {
|
||||
counter-reset: cpt;
|
||||
}
|
||||
|
||||
#zoneGroupes .etudiants>*::before {
|
||||
counter-increment: cpt;
|
||||
content: counter(cpt) " - ";
|
||||
}
|
||||
|
||||
#zoneGroupes [data-idgroupe=aucun] {
|
||||
background: #b5c2c3 !important;
|
||||
}
|
@ -1,6 +1,266 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
<h2>{% if not read_only %}Édition des p{% else %}P{%endif%}artitions</h2>
|
||||
<h1>{% if not read_only %}Édition des p{% else %}P{%endif%}artitions</h1>
|
||||
|
||||
<div>Une spécialité Mulhousaine vous sera prochainement servie ici</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() {
|
||||
let params = (new URL(document.location)).searchParams;
|
||||
let formsemestre_id = params.get('formsemestre_id');
|
||||
|
||||
let partitions = await fetchData("/ScoDoc/api/formsemestre/" + formsemestre_id + "/partitions");
|
||||
let etudiants = await fetchData("/ScoDoc/api/formsemestre/" + formsemestre_id + "/resultats");
|
||||
|
||||
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 = "";
|
||||
let outputMasques = "";
|
||||
let outputGroupes = "";
|
||||
Object.entries(partitions).forEach(([idPartition, partition]) => {
|
||||
// Filtres
|
||||
outputPartitions += `<div data-idpartition="${idPartition}">${partition.partition_name}</div>`;
|
||||
outputMasques += `<span style="flex-basis: 100%"></span><div data-idpartition="${idPartition}" data-idgroupe=aucun>${partition.partition_name} N/A</div>`;
|
||||
|
||||
// Groupes
|
||||
outputGroupes += `
|
||||
<div class=partition data-idpartition="${idPartition}">
|
||||
<h3>${partition.partition_name}</h3>
|
||||
<div class=groupe data-idgroupe=aucun>
|
||||
<div>Non affecté(s)</div>
|
||||
<div class=etudiants></div>
|
||||
</div>
|
||||
${(() => {
|
||||
let output = "";
|
||||
Object.entries(partition.groups).forEach(([idGroupe, titreGroupe]) => {
|
||||
/***************/
|
||||
outputMasques += `<div data-idgroupe="${idGroupe}">${titreGroupe.name}</div>`;
|
||||
/***************/
|
||||
output += `
|
||||
<div class=groupe data-idgroupe="${idGroupe}">
|
||||
<div>${titreGroupe.name}</div>
|
||||
<div class=etudiants></div>
|
||||
</div>`;
|
||||
})
|
||||
return output;
|
||||
})()}
|
||||
</div>`;
|
||||
})
|
||||
document.querySelector(".filtres>.partitions>div").innerHTML = outputPartitions;
|
||||
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>";
|
||||
Object.entries(partitions).forEach(([idPartition, dataPartition]) => {
|
||||
output += `
|
||||
<div class=partition data-idpartition="${idPartition}">
|
||||
<div>${dataPartition.partition_name}</div>
|
||||
${(() => {
|
||||
let output = "";
|
||||
let affected = false;
|
||||
Object.entries(dataPartition.groups).forEach(([idGroupe, titreGroupe]) => {
|
||||
output += `
|
||||
<label><input type=radio name="${etudiant.etudid}-${idPartition}" value="${idGroupe}" ${(etudiant.partitions[idPartition] == idGroupe) ? "checked" : ""}><span>${titreGroupe.name}</span></label>`;
|
||||
|
||||
if (etudiant.partitions[idPartition] == idGroupe) {
|
||||
affected = true;
|
||||
document.querySelector(`#zoneGroupes [data-idgroupe="${idGroupe}"]>.etudiants`).innerHTML += templateEtudiantGroupes(etudiant);
|
||||
}
|
||||
})
|
||||
if (!affected) {
|
||||
document.querySelector(`#zoneGroupes [data-idpartition="${idPartition}"]>[data-idgroupe="aucun"]>.etudiants`).innerHTML += templateEtudiantGroupes(etudiant);
|
||||
}
|
||||
return `<label><input type=radio name="${etudiant.etudid}-${idPartition}" value="aucun" ${(!affected) ? "checked" : ""}><span>N/A</span></label>` + output;
|
||||
})()}
|
||||
</div>`;
|
||||
})
|
||||
return output + "</div>";
|
||||
})()}
|
||||
</div>`;
|
||||
})
|
||||
document.querySelector("#zoneChoix>.etudiants").innerHTML = output;
|
||||
|
||||
}
|
||||
|
||||
function templateEtudiantGroupes(etudiant) {
|
||||
return `<div data-etudid="${etudiant.etudid}" data-nom="${etudiant.nom_disp}" data-prenom="${etudiant.prenom}">${etudiant.nom_disp} ${etudiant.prenom}</div>`
|
||||
}
|
||||
|
||||
function processEvents() {
|
||||
document.querySelectorAll(".filtres>div>div>div").forEach(btn => {
|
||||
btn.addEventListener("click", filtre);
|
||||
btn.addEventListener("mousedown", (event) => { event.preventDefault() }) // Eviter de sélectionner le texte si on clique plusieurs fois frénétiquement
|
||||
});
|
||||
document.querySelectorAll("#zoneChoix label").forEach(btn => { btn.addEventListener("mousedown", (event) => { event.preventDefault() }) });
|
||||
|
||||
document.querySelectorAll(".etudiants input").forEach(input => {
|
||||
input.addEventListener("input", assignment);
|
||||
})
|
||||
}
|
||||
|
||||
function filtre() {
|
||||
|
||||
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");
|
||||
})
|
||||
}
|
||||
|
||||
let groupesSelected = [];
|
||||
this.parentElement.querySelectorAll(":not(.unselect)").forEach(e => {
|
||||
groupesSelected.push(
|
||||
{
|
||||
idpartition: e.dataset.idpartition,
|
||||
idgroupe: e.dataset.idgroupe
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
if (!this.dataset.idgroupe) {
|
||||
document.querySelectorAll(`
|
||||
.etudiants .partition[data-idpartition],
|
||||
#zoneGroupes [data-idpartition]
|
||||
`).forEach(e => {
|
||||
let found = false;
|
||||
for (let i = 0; i < groupesSelected.length; i++) {
|
||||
if (groupesSelected[i].idpartition == e.dataset.idpartition) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
e.classList.remove("hide")
|
||||
} else {
|
||||
e.classList.add("hide")
|
||||
}
|
||||
})
|
||||
} else {
|
||||
document.querySelectorAll("#zoneChoix .etudiants>div").forEach(e => {
|
||||
let found = false;
|
||||
for (let i = 0; i < groupesSelected.length; i++) {
|
||||
let partition = "";
|
||||
if (groupesSelected[i].idpartition) {
|
||||
partition = `[data-idpartition="${groupesSelected[i].idpartition}"] `;
|
||||
}
|
||||
if (e.querySelector(`${partition}input[value="${groupesSelected[i].idgroupe}"]:checked`)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
e.classList.remove("hide")
|
||||
} else {
|
||||
e.classList.add("hide")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 += templateEtudiantGroupes(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");
|
||||
fetch(`/ScoDoc/api/group/${idGroupe}/set_etudiant/${etudid}`, { method: "POST" })
|
||||
.then(r => { return r.json() })
|
||||
.then(r => {
|
||||
if (r.group_id == idGroupe && 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>";
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user