ScoDoc/app/templates/assiduites/differee.j2

862 lines
26 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.

<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">
<span>{{etud.nomprenom}}</span>
<img class="pdp-hover" src="/ScoDoc/GEII/api/etudiant/etudid/{{etud.etudid}}/photo?size=small">
</div>
</div>
{% endfor %}
</div>
</div>
<style>
.td.sticky .pdp-hover {
display: none;
}
.td.sticky:hover .pdp-hover {
position: absolute;
display: block;
width: 90px;
height: 90px;
border-radius: 15px;
transition: all 0.5s;
left: calc(80% - 45px);
top: -25%;
}
.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: 16px;
}
.th.sticky {
z-index: 5;
cursor: pointer;
}
.th,
.td {
padding: 20px;
text-align: center;
width: 225px;
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;
width: 100%;
}
.mini-form input,
.mini-form select {
display: block;
margin: 5px;
padding: 5px;
border-radius: 5px;
border: 1px solid #ddd;
max-width: 195px;
}
#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;
}
.th.error {
background-color: crimson;
}
.tbody .tr:nth-child(even) {
background-color: #f2f2f2;
}
.tbody .tr:hover {
background-color: #ddd;
}
.td .etat {
display: grid;
grid-template-columns: 33% 33% 33%;
grid-template-rows: 50% 50%;
font-size: small;
}
.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;
}
.th .closeCol {
font-size: 16px;
}
.activate {
font-size: 14px;
}
.th[activated] {
transition: all 0.5s;
}
.th[activated='false'] {
opacity: 0.5;
}
.num {
border: 1px whitesmoke solid;
border-radius: 15px;
padding: 2px 4px;
width: 24px;
}
.assi_rbtn,
.assi_lbl {
width: 100%;
display: flex;
justify-content: space-evenly;
align-items: center;
}
.td[assiduite_id='conflit'] {
background-color: #ff0000c2;
}
input[type='radio']:disabled {
cursor: not-allowed;
}
</style>
<script>
const etatDef = "{{etat_def}}";
window.forceModule = "{{ forcer_module }}"
window.forceModule = window.forceModule == "True" ? true : false
let colCount = 1;
let currentDate = "{{date}}";
if (currentDate == "") {
currentDate = moment().tz(TIMEZONE).format("YYYY-MM-DDTHH:mm");
} else {
currentDate = moment(currentDate).tz(TIMEZONE).format("YYYY-MM-DDTHH:mm");
}
function createColumn() {
let table = document.getElementById("studentTable");
let th = document.createElement("div");
th.classList.add("th", "error");
const col_id = `${colCount++}`;
th.setAttribute("col", col_id);
th.setAttribute("activated", "true");
th.innerHTML = `
<div class="mini-form">
<div class="btngroup">
<span class="num">${col_id}</span>
<button class="closeCol" 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)" disabled>
<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);
try {
const sl = th.querySelector('.dynaSelect');
sl.id = `dynaSelect_${col_id}`;
const miniform = sl.parentElement;
const dateIso = miniform.querySelector("#dateStart").value;
if (dateIso != "") {
updateSelect("", `#${sl.id}`, dateIso)
}
miniform.querySelector("#dateStart").addEventListener('focusout', (el) => {
updateSelect("", `#${sl.id}`, el.target.value);
})
} catch {
}
th.querySelectorAll('input, select.dynaSelect, select#moduleimpl_select').forEach((el) => {
el.addEventListener("change", () => {
getAndUpdateCol(col_id);
})
})
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 = `
<div class="assi_rbtn">
<input type="radio" name="etat_${col_id}_${etudid}" value="present" disabled onchange="updateEtudAssiduite(this)">
<input type="radio" name="etat_${col_id}_${etudid}" value="retard" disabled onchange="updateEtudAssiduite(this)">
<input type="radio" name="etat_${col_id}_${etudid}" value="absent" disabled onchange="updateEtudAssiduite(this)">
</div>
<br/>
<div class="assi_lbl">
<span>Present</span>
<span>Retard</span>
<span>Absent</span>
</div>
`;
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 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 = currentDate;
} 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).tz(TIMEZONE).utc()
.add(2, "hours")
.format("YYYY-MM-DDTHH:mm");
const colid = element.parentElement.parentElement.getAttribute('col');
getAndUpdateCol(colid);
},
{ once: true }
);
}
function setEtatCol(colId, etatId) {
const tds = [...document.querySelectorAll(`.td[colid='${colId}']`)]
tds.forEach((td) => {
setEtatLine(td, etatId);
})
}
function setEtatLine(line, etatId) {
let inputs = [...line.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");
const etats = getEtatCol(col_id);
const inputDeb = col.querySelector("#dateStart").value;
const inputFin = col.querySelector("#dateEnd").value;
const moduleSelect = col.querySelector("#moduleimpl_select,.dynaSelect").value;
const d_debut = moment(inputDeb);
const d_fin = moment(inputFin);
if (inputDeb == "" || inputFin == "" || d_debut > d_fin) {
errors.push(`La période de la colonne n°${col_id} n'est pas valide`);
return;
}
{% if periode %}
const testPeriode = [
d_debut.isBefore("{{periode.deb}}"),
d_debut.isAfter("{{periode.fin}}"),
d_fin.isBefore("{{periode.deb}}"),
d_fin.isAfter("{{periode.fin}}"),
]
if (testPeriode.some((e) => e)) {
errors.push(`La période de la colonne n°${col_id} n'est pas dans le semestre`);
return;
}
{% endif %}
if (window.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) => {
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);
}
);
}
function getAssiduitesCol(col_id, get = true) {
const col = document.querySelector(`[col='${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,.dynaSelect").value;
const d_debut = moment(inputDeb).tz(TIMEZONE);
const d_fin = moment(inputFin).tz(TIMEZONE);
if (inputDeb == "" || inputFin == "" || d_debut > d_fin) {
//errors.push(`La période de la colonne n°${col_id} n'est pas valide`);
return 0x2;
}
{% if periode %}
const t_before = "{{periode.deb}}T00:00";
const t_after = "{{periode.fin}}T23:59";
const testPeriode = [
d_debut.isBefore(t_before),
d_debut.isAfter(t_after),
d_fin.isBefore(t_before),
d_fin.isAfter(t_after),
]
if (testPeriode.some((e) => e)) {
//errors.push(`La période de la colonne n°${col_id} n'est pas dans le semestre`);
return 0x3;
}
{% endif %}
if (window.forceModule && moduleSelect == "") {
//errors.push(`Le module de la colonne n°${col_id} n'a pas été entré. (Préférence de semestre)`);
return 0x4;
}
if (get) {
getAssiduitesFromEtuds(false, false, d_debut.format(), d_fin.format())
return 0x0;
}
return {
moduleimpl: moduleSelect,
deb: d_debut.format(),
fin: d_fin.format(),
}
}
function updateAssiduitesCol(col_id) {
const col = document.querySelector(`[col='${col_id}']`);
const tds = [...document.querySelectorAll(`.td[colid='${col_id}']`)]
const inputDeb = col.querySelector("#dateStart").value;
const inputFin = col.querySelector("#dateEnd").value;
const d_debut = moment(inputDeb).tz(TIMEZONE);
const d_fin = moment(inputFin).tz(TIMEZONE);
const periode = {
deb: d_debut,
fin: d_fin,
}
tds.forEach((td) => {
tds.forEach((el) => {
const inputs = [...el.querySelectorAll('input')];
inputs.forEach((i) => { i.disabled = false })
});
setEtatLine(td, "")
const etu = td.parentElement.getAttribute('etudid');
const conflits = getAssiduitesConflict(etu, periode);
if (conflits.length == 0) {
td.setAttribute('assiduite_id', "-1");
} else if (conflits.length == 1 && isConflictSameAsPeriod(conflits[0], periode)) {
const assi = conflits[0];
td.setAttribute('assiduite_id', assi.assiduite_id);
const ind = ["PRESENT", "RETARD", "ABSENT"].indexOf(assi.etat.toUpperCase());
setEtatLine(td, ind)
} else {
td.setAttribute('assiduite_id', "conflit");
const inputs = [...td.querySelectorAll('input')];
inputs.forEach((i) => {
i.disabled = true;
i.addEventListener('click', () => { /*updateEtudAssiduite(i)*/ });
})
}
})
}
function setEtuds() {
if (!isSingleEtud()) {
const etudids = [...document.querySelectorAll("[etudid]")]
.map((el) => Number.parseInt(el.getAttribute("etudid")))
.filter((value, index, array) => array.indexOf(value) === index);
etudids.forEach((etu) => {
etuds[etu] = { etudid: etu }
})
}
}
function getAndUpdateCol(colid) {
if (getAssiduitesCol(colid) == 0) {
document.querySelector(`[col='${colid}']`).classList.remove('error');
document.getElementById(`mass_action_${colid}`).removeAttribute('disabled')
updateAssiduitesCol(colid)
} else {
const column = document.querySelector(`[col='${colid}']`)
document.getElementById(`mass_action_${colid}`).setAttribute('disabled', 'true')
column.classList.add('error');
const tds = [...document.querySelectorAll(`.td[colid='${colid}']`)]
tds.forEach((el) => {
const inputs = [...el.querySelectorAll('input')];
inputs.forEach((i) => { i.disabled = true })
});
}
}
function updateAllCol() {
const colIds = [...document.querySelectorAll("[col]")].map((col) => { return col.getAttribute('col') });
colIds.forEach((colid) => {
updateAssiduitesCol(colid);
})
}
function launchToast(etudid, etat) {
let etatAffiche;
switch (etat.toUpperCase()) {
case "PRESENT":
etatAffiche =
"%etud% a été noté(e) <u><strong>présent(e)</strong></u>";
break;
case "RETARD":
etatAffiche =
"%etud% a été noté(e) <u><strong>en retard</strong></u>";
break;
case "ABSENT":
etatAffiche =
"%etud% a été noté(e) <u><strong>absent(e)</strong></u>";
break;
case "REMOVE":
etatAffiche = "L'assiduité de %etud% a été retirée.";
}
let color;
switch (etat.toUpperCase()) {
case "PRESENT":
color = "#6bdb83";
break;
case "ABSENT":
color = "#F1A69C";
break;
case "RETARD":
color = "#f0c865";
break;
default:
color = "#AAA";
break;
}
const nom_prenom = `${etuds[etudid].nom.toUpperCase()} ${etuds[
etudid
].prenom.capitalize()}`;
const span = document.createElement("span");
span.innerHTML = etatAffiche.replace("%etud%", nom_prenom);
pushToast(generateToast(span, color, 5));
}
function updateEtudAssiduite(rbtn) {
const [_, colid, etudid] = rbtn.name.split("_");
const etat = rbtn.value;
const etudLine = rbtn.parentElement.parentElement;
const assi = etudLine.getAttribute('assiduite_id');
const { moduleimpl, deb, fin } = getAssiduitesCol(colid, false);
switch (assi) {
case "-1":
// création d'une nouvelle assiduité
const assiduite = _createAssiduites(deb, fin, moduleimpl, etudid, etat, colid);
asyncCreateAssiduite(assiduite, (data) => {
if (Object.keys(data.success).length > 0) {
const assi_id = data.success['0'].assiduite_id;
etudLine.setAttribute('assiduite_id', assi_id);
assiduite["assiduite_id"] = assi_id;
assiduites[etudid].push(assiduite);
updateAllCol()
launchToast(etudid, etat);
}
})
break;
case "conflit":
// Conflit, afficher résolveur
const assiduitesList = assiduites[etudid];
const d_debut = new moment.tz(deb, TIMEZONE);
const d_fin = new moment.tz(fin, TIMEZONE);
const period = {
deb: deb,
fin: fin,
}
const conflitResolver = new ConflitResolver(
assiduites[etudid],
period,
{
deb: new moment.tz(d_debut.startOf('day'), TIMEZONE),
fin: new moment.tz(d_fin.endOf('day'), TIMEZONE),
}
);
const update = () => {
assiduites = {}
const cols = [...document.querySelectorAll("[col]")].map((col) => col.getAttribute('col'));
cols.forEach((c) => getAndUpdateCol(c));
};
conflitResolver.callbacks = {
delete: update,
edit: update,
split: update,
};
conflitResolver.open();
break;
default:
// Une assiduité est déjà connue -> modifier assiduité
const edit = {
moduleimpl_id: moduleimpl == "" ? null : moduleimpl,
etat: etat,
assiduite_id: Number.parseInt(assi),
}
asyncEditAssiduite(edit, (data) => {
const obj = getAssiduite(etudid, assi);
obj.moduleimpl = edit.moduleimpl_id;
obj.etat = edit.etat;
launchToast(etudid, etat);
})
break;
}
}
function getAssiduite(etudid, id) {
return assiduites[etudid].filter((a) => a.assiduite_id == id)
}
function asyncCreateAssiduite(assi, callback = () => { }) {
const path = getUrl() + `/api/assiduite/${assi.etudid}/create`;
async_post(
path,
[assi],
callback,
(data, status) => {
//error
console.error(data, status);
}
);
}
function asyncEditAssiduite(assi, callback = () => { }) {
const path = getUrl() + `/api/assiduite/${assi.assiduite_id}/edit`;
async_post(
path,
assi,
callback,
(data, status) => {
//error
console.error(data, status);
}
);
}
window.addEventListener('load', () => {
document.getElementById("addColumn").addEventListener("click", () => {
createColumn();
});
createColumn();
setEtuds();
}, { once: true });
</script>