2024-08-26 14:46:28 +02:00
|
|
|
{%- extends 'sco_page.j2' -%}
|
|
|
|
|
|
|
|
{% block styles %}
|
|
|
|
{{super()}}
|
2023-07-22 22:40:09 +02:00
|
|
|
<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>
|
2024-08-26 14:46:28 +02:00
|
|
|
{% endblock %}
|
|
|
|
|
2023-07-22 22:40:09 +02:00
|
|
|
|
2024-08-26 14:46:28 +02:00
|
|
|
{% block app_content %}
|
2023-07-22 22:40:09 +02:00
|
|
|
<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>
|
|
|
|
|
2024-08-26 14:46:28 +02:00
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
{% block scripts %}
|
|
|
|
{{ super() }}
|
|
|
|
|
2023-11-14 23:24:29 +01:00
|
|
|
<script src="{{scu.STATIC_DIR}}/libjs/xlsx-populate-1.21.0.min.js"></script>
|
2023-07-22 22:40:09 +02:00
|
|
|
<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,
|
2023-07-24 21:24:44 +02:00
|
|
|
places: savedData.parcours?.[group.id].places || 28,
|
2023-07-22 22:40:09 +02:00
|
|
|
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 => {
|
2023-07-24 21:24:44 +02:00
|
|
|
saved = savedData.etudiants?.['id' + etudiant.id] || {};
|
2023-07-22 22:40:09 +02:00
|
|
|
etudiants['id' + etudiant.id] = {
|
2023-07-24 21:24:44 +02:00
|
|
|
actif: saved.actif == false ? false : true,
|
2023-07-22 22:40:09 +02:00
|
|
|
nom: etudiant.nom + " " + etudiant.prenom,
|
|
|
|
criteres: saved.criteres || {},
|
|
|
|
voeux: saved.voeux || {}
|
|
|
|
};
|
2023-07-24 21:24:44 +02:00
|
|
|
|
|
|
|
if (Object.keys(saved).length == 0)
|
|
|
|
Object.keys(parcours).forEach((idParcours, index) => {
|
|
|
|
etudiants['id' + etudiant.id].criteres[idParcours] = 10;
|
|
|
|
etudiants['id' + etudiant.id].voeux[idParcours] = index + 1;
|
|
|
|
})
|
2023-07-22 22:40:09 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
/* 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(
|
2024-07-17 14:58:49 +02:00
|
|
|
"/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre + "/groups_save_auto_assignment",
|
2023-07-22 22:40:09 +02:00
|
|
|
{
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
},
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getFromScodoc() {
|
2024-07-17 14:58:49 +02:00
|
|
|
let dataRaw = await fetch("/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre + "/groups_get_auto_assignment");
|
2023-07-22 22:40:09 +02:00
|
|
|
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);
|
|
|
|
|
2023-07-24 21:34:35 +02:00
|
|
|
saveFile("Donnees groupes - " + formsemestre, workbook);
|
2023-07-22 22:40:09 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
})
|
|
|
|
|
2023-07-24 21:34:35 +02:00
|
|
|
saveFile("Resultats groupes - " + formsemestre, workbook);
|
2023-07-22 22:40:09 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-11-14 23:24:29 +01:00
|
|
|
</script>
|
2024-08-26 14:46:28 +02:00
|
|
|
{% endblock %}
|