ScoDoc/app/templates/assiduites/signal_assiduites_diff.j2

512 lines
15 KiB
Django/Jinja
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.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<h2>Signalement différé des assiduités {{gr |safe}}</h2>
<h3>{{sem | safe }}</h3>
<button onclick="getAndVerify()">Valider les assiduités</button>
<div id="studentTable">
<div class="thead">
<div class="tr">
<div class="th sticky" order="ASC" onclick="reOrderEtudiants()">Noms</div>
<button id="addColumn" class="floating-button">+</button>
</div>
</div>
<div class="tbody">
{% for etud in etudiants %}
<div class="tr" etudid="{{etud.etudid}}">
<div class="td sticky">{{etud.nomprenom}}</div>
</div>
{% endfor %}
</div>
</div>
{% include "assiduites/alert.j2" %}
{% include "assiduites/prompt.j2" %}
<style>
button {
margin: 10px 0;
}
.err-assi {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 15px;
}
.table-container {
overflow: auto;
position: relative;
max-width: 100%;
margin: 0 auto;
box-shadow: 0 0 1rem 0 rgba(0, 0, 0, .2);
}
.table {
border-collapse: collapse;
}
.thead .tr {
display: flex;
align-items: center;
}
.thead .tr .th {
height: 200px;
display: flex;
justify-content: center;
align-items: center;
font-size: larger;
}
.th.sticky {
z-index: 5;
cursor: pointer;
}
.th,
.td {
padding: 20px;
text-align: center;
width: 200px;
border: 1px solid #ddd;
display: inline-block;
}
.tr {
display: flex;
justify-content: flex-start;
align-items: center;
width: max-content;
}
.sticky {
position: sticky;
left: 0;
background-color: #fafafa;
border-right: 1px solid #ddd;
}
.mini-form {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.mini-form input,
.mini-form select {
display: block;
margin: 5px;
padding: 5px;
border-radius: 5px;
border: 1px solid #ddd;
}
#addColumn {
font-size: 24px;
width: 50px;
height: 50px;
border-radius: 50%;
right: -60px;
top: calc(50% - 50px /2);
background-color: #007BFF;
color: white;
border: none;
outline: none;
cursor: pointer;
transition: background-color 0.3s;
}
#addColumn:hover {
background-color: #0056b3;
}
.th {
background-color: #007BFF;
color: white;
position: relative;
}
.tbody .tr:nth-child(even) {
background-color: #f2f2f2;
}
.tbody .tr:hover {
background-color: #ddd;
}
.etat {
display: grid;
grid-template-columns: 33% 33% 33%;
grid-template-rows: 50% 50%;
font-size: small;
}
#moduleimpl_select {
max-width: 190px;
}
.btngroup {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
}
.btngroup>button {
background-color: transparent;
outline: none;
border: none;
cursor: pointer;
color: whitesmoke;
}
.close {
font-size: larger;
}
.activate {
font-size: large;
}
.th[activated] {
transition: all 0.5s;
}
.th[activated='false'] {
opacity: 0.5;
}
</style>
<script>
let verified = false;
const etatDef = "{{etat_def}}";
let forceModule = "{{ forcer_module }}"
forceModule = forceModule == "True" ? true : false
moment.tz.setDefault("Etc/UTC");
function createColumn() {
let table = document.getElementById("studentTable");
let th = document.createElement("div");
th.classList.add("th");
const col_id = `${document.querySelectorAll("[col]").length + 1}`;
th.setAttribute("col", col_id);
th.setAttribute("activated", "true");
th.innerHTML = `
<div class="mini-form">
<div class="btngroup">
<button class="activate" onclick="toggleColumn(${col_id})">Désactiver</button>
<button class="close" onclick="removeColumn(this)">x</button>
</div>
<input type="datetime-local" id="dateStart">
<input type="datetime-local" id="dateEnd">
{{moduleimpl_select|safe}}
<select name="mass_action_${col_id}" id="mass_action_${col_id}" onchange="setEtatCol(${col_id},this.value)">
<option value="">Sélectionner une assiduité</option>
<option value="0">Présent</option>
<option value="1">Retard</option>
<option value="2">Absent</option>
</select>
</div>
`;
table
.querySelector(".thead .tr")
.insertBefore(th, document.querySelector("#addColumn"));
const last = [...document.querySelectorAll("#dateStart")].pop();
defaultDate(last);
let rows = table.querySelector(".tbody").querySelectorAll(".tr");
for (let i = 0; i < rows.length; i++) {
let td = document.createElement("div");
td.setAttribute("colid", col_id)
td.classList.add("td", "etat");
const etudid = rows[i].getAttribute("etudid");
td.innerHTML = `
<input type="radio" name="etat_${col_id}_${etudid}" value="present">
<input type="radio" name="etat_${col_id}_${etudid}" value="retard">
<input type="radio" name="etat_${col_id}_${etudid}" value="absent">
<span>Present</span>
<span>Retard</span>
<span>Absent</span>
`;
rows[i].appendChild(td);
if (etatDef != "" && etatDef != "aucun") {
const inp = td.querySelector(`[value='${etatDef}']`).checked = true;
}
}
}
function reOrderEtudiants() {
const th = document.querySelector(".th.sticky");
let lines = [...document.querySelectorAll('.tr[etudid]')];
const tbody = document.querySelector('.tbody')
let order = (a, b) => { return a > b }
if (th.getAttribute("order") == "ASC") {
order = (a, b) => { return b > a }
th.setAttribute("order", "DESC")
} else {
th.setAttribute("order", "ASC")
}
lines = lines.sort((a, b) => {
const nameA = a.querySelector(".td.sticky").textContent.split(" ").pop();
const nameB = b.querySelector(".td.sticky").textContent.split(" ").pop();
return order(nameA, nameB)
})
tbody.innerHTML = "";
tbody.append(...lines)
}
function toggleColumn(colId) {
const column = document.querySelector(`[col='${colId}']`)
const btn = column.querySelector(".activate");
if (column.getAttribute('activated') == "true") {
column.setAttribute("activated", "false");
btn.textContent = "Activer"
} else {
column.setAttribute("activated", "true");
btn.textContent = "Déactiver"
}
}
function previousCol(column) {
const cols = [...document.querySelectorAll("[col]")]
let previousCol = null;
let i = 1;
while (i < cols.length) {
previousCol = cols[i - 1];
if (cols[i] == column)
break;
i++;
}
return previousCol
}
function removeColumn(element) {
const col = element.parentElement.parentElement.parentElement;
const col_id = col.getAttribute("col");
document.querySelectorAll(`[col='${col_id}'],[colid='${col_id}']`).forEach((el) => { el.remove() })
}
function defaultDate(element) {
const num = previousCol(element.parentElement.parentElement)?.getAttribute("col");
const last = document.querySelector(`[col='${num}'] #dateEnd`);
let date = undefined;
if (last == undefined) {
date = moment().tz("Europe/Paris").format("YYYY-MM-DDTHH:mm");
} else {
date = last.value;
}
element.value = date;
element.addEventListener(
"focusout",
() => {
const el = element.parentElement.querySelector("#dateEnd");
const el2 = element.parentElement.querySelector("#dateStart");
el.value = moment(el2.valueAsDate)
.add(2, "hours")
.format("YYYY-MM-DDTHH:mm");
},
{ once: true }
);
}
function setEtatCol(colId, etatId) {
const tds = [...document.querySelectorAll(`.td[colid='${colId}']`)]
tds.forEach((td) => {
let inputs = [...td.querySelectorAll("input")]
inputs.forEach((el) => { el.checked = false })
if (etatId != "") {
inputs[Number.parseInt(etatId)].checked = true;
}
})
}
function getEtatCol(colId) {
const etats = {};
const tds = [...document.querySelectorAll(`.td[colid='${colId}']`)]
tds.forEach((td) => {
const tr = td.parentElement
const etudid = tr.getAttribute("etudid");
let inputs = [...td.querySelectorAll("input")]
etatInput = inputs.filter((e) => e.checked).pop()
if (etatInput == undefined) {
etats[etudid] = "";
} else {
etats[etudid] = etatInput.value;
}
})
return etats;
}
function _createAssiduites(inputDeb, inputFin, moduleSelect, etudid, etat, colId) {
if (moduleSelect == "") {
return {
"date_debut": inputDeb,
"date_fin": inputFin,
"etudid": etudid,
"etat": etat,
"colid": colId,
}
} else {
return {
"date_debut": inputDeb,
"date_fin": inputFin,
"etudid": etudid,
"moduleimpl_id": moduleSelect,
"etat": etat,
"colid": colId,
}
}
}
function getAndVerify() {
const assiduites = [];
const cols = [...document.querySelectorAll("[col]")];
const errors = [];
cols.forEach((col) => {
const col_id = col.getAttribute("col");
if (col.getAttribute("activated") == "false") return;
toggleColumn(col_id);
const etats = getEtatCol(col_id);
const inputDeb = col.querySelector("#dateStart").value;
const inputFin = col.querySelector("#dateEnd").value;
const moduleSelect = col.querySelector("#moduleimpl_select").value;
if (inputDeb == "" || inputFin == "") {
errors.push(`La période de la colonne n°${col_id} n'est pas valide`);
return;
}
const d_debut = moment(inputDeb)
const d_fin = moment(inputFin)
const testPeriode = [
d_debut.isBefore("{{formsemestre_date_debut}}"),
d_debut.isAfter("{{formsemestre_date_fin}}"),
d_fin.isBefore("{{formsemestre_date_debut}}"),
d_fin.isAfter("{{formsemestre_date_fin}}"),
]
if (testPeriode.some((e) => e)) {
errors.push(`La période de la colonne n°${col_id} n'est pas dans le semestre`);
return;
}
if (forceModule && moduleSelect == "") {
errors.push(`Le module de la colonne n°${col_id} n'a pas été entré. (Préférence de semestre)`);
return;
}
// TODO Mettre une erreur lorsque assiduité forcé (pref)
Object.keys(etats).forEach((key) => {
const etat = etats[key];
if (etat != "") {
assiduites.push(_createAssiduites(inputDeb, inputFin, moduleSelect, key, etat, col_id))
}
})
});
if (errors.length > 0) {
const texte = document.createElement("div");
errors.map((err) => document.createTextNode(err)).forEach((err) => {
texte.appendChild(err);
texte.appendChild(document.createElement('br'));
})
openAlertModal("Erreur(s) détéctée(s)", texte)
} else {
if (assiduites.length > 0) {
createAllAssiduites(assiduites);
}
}
}
function createAllAssiduites(createQueue) {
if (createQueue.length < 0)
return;
const path = getUrl() + `/api/assiduites/create`;
sync_post(
path,
createQueue,
(data, status) => {
verified = true;
const { success, errors } = data;
const indexes = [...Object.keys(errors)];
if (indexes.length > 0) {
const incriminated = indexes.map((i) => {
return createQueue[Number.parseInt(i)];
})
const error_message = document.createElement('div');
for (let i = 0; i < incriminated.length; i++) {
const err = errors[indexes[i]];
const crimi = incriminated[i];
const nom = document.querySelector(`[etudid='${crimi.etudid}']`).firstElementChild.textContent.trim();
const col = crimi.colid;
const div = document.createElement('div');
div.classList.add("err-assi")
const span = document.createElement("span");
span.setAttribute("title", err);
span.textContent = ""
const span2 = document.createElement("span");
span2.textContent = `L'assiduité (Colonne n°${col}) de ${nom} n'a pas pu être enregistrée`;
div.appendChild(span2);
div.appendChild(span);
error_message.appendChild(div);
}
openAlertModal("Certaines assiduités non pas été enregistrées", error_message)
} else {
openAlertModal("Tous les assiduités ont bien été enregistrée", document.createTextNode(""), null, "#09AD2A")
}
},
(data, status) => {
//error
console.error(data, status);
}
);
}
document.getElementById("addColumn").addEventListener("click", () => {
createColumn();
});
const onConfirmRefresh = function (event) {
if (!verified)
return event.returnValue = "Attention, certaines données n'ont pas été enregistrées";
}
window.addEventListener("beforeunload", onConfirmRefresh, { capture: true });
createColumn();
</script>