ScoDoc/app/static/js/assiduites.js

1125 lines
33 KiB
JavaScript
Raw Normal View History

/**
*
* Ensemble des fonctions liées à la gestion des assiduités
* Créé par : HARTMANN Matthias (Iziram)
*
*/
2023-04-17 15:44:55 +02:00
/**
* <== OUTILS ==>
*/
2023-04-17 15:44:55 +02:00
/**
* Ajout d'une fonction `capitalize` sur tous les strings
* "alice".capitalize() -> "Alice"
2023-04-17 15:44:55 +02:00
*/
Object.defineProperty(String.prototype, "capitalize", {
value: function () {
return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
},
enumerable: false,
});
2023-11-03 16:55:26 +01:00
/**
* Fait une requête GET de façon asynchrone
* @param {String} path adresse distante
* @param {CallableFunction} success fonction à effectuer en cas de succès
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/
2023-11-15 14:11:47 +01:00
async function async_get(path, success, errors) {
const response = fetch(path);
response
.then((response) => {
if (response.ok) {
response.json().then((data) => {
success(data, "success");
});
} else {
throw new Error("Network response was not ok.");
}
})
.catch((error) => {
console.error(error);
if (errors) errors(error);
});
return response;
}
/**
* Fait une requête POST de façon asynchrone
* @param {String} path adresse distante
* @param {object} data données à envoyer (objet js)
* @param {CallableFunction} success fonction à effectuer en cas de succès
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/
2023-11-15 14:11:47 +01:00
async function async_post(path, data, success, errors) {
let response;
2023-11-15 14:11:47 +01:00
try {
response = await fetch(path, {
2023-11-15 14:11:47 +01:00
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
const responseData = await response.json();
success(responseData);
} else {
if (response.status == 404) {
response.json().then((data) => {
if (errors) errors(data);
});
} else {
throw new Error("Network response was not ok.");
}
2023-11-15 14:11:47 +01:00
}
} catch (error) {
2023-11-15 17:34:13 +01:00
console.error(error);
if (errors) errors(error);
2023-11-15 14:11:47 +01:00
}
return response;
}
2023-11-15 14:11:47 +01:00
2023-04-17 15:44:55 +02:00
/**
* Récupère les étudiants en fonction des groupes sélectionnés
* @param {Array} groupIds - Les identifiants des groupes pour lesquels récupérer les étudiants.
* @returns {Promise<Object>} Un objet contenant les étudiants, indexés par leur identifiant.
2023-04-17 15:44:55 +02:00
*/
async function recupEtuds(groupIds) {
const etuds = new Map();
if (groupIds == null || groupIds.length == 0) return etuds;
2023-04-17 15:44:55 +02:00
// Créer un tableau de promesses pour chaque requête GET asynchrone.
let requests = groupIds.map((groupId) =>
fetch(`../../api/group/${groupId}/etudiants`)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => {
data.forEach((etud) => {
etuds.set(etud.id, etud);
});
})
.catch((error) =>
console.error(
"There has been a problem with your fetch operation:",
error
)
)
);
// Attendre que toutes les promesses dans le tableau `requests` soient résolues.
await Promise.all(requests);
2023-04-17 15:44:55 +02:00
return etuds;
2023-04-17 15:44:55 +02:00
}
/**
* Récupère l'assiduité des étudiants pour une date donnée
* @param {Map} etuds
2023-04-17 15:44:55 +02:00
* @param {Date} date
*/
async function recupAssiduites(etuds, date) {
const etudIds = [...etuds.keys()].join(",");
2023-04-17 15:44:55 +02:00
const date_debut = date.add(-1, "days").format("YYYY-MM-DDTHH:mm");
const date_fin = date.add(2, "days").format("YYYY-MM-DDTHH:mm");
2023-04-17 15:44:55 +02:00
url =
`../../api/assiduites/group/query?date_debut=${date_debut}` +
`&date_fin=${date_fin}&etudids=${etudIds}&with_justifs`;
2023-04-17 15:44:55 +02:00
await fetch(url)
.then((res) => {
if (!res.ok) {
throw new Error("Network response was not ok");
}
return res.json();
2023-04-17 15:44:55 +02:00
})
.then((data) => {
Object.keys(data).forEach((etudid) => {
const etud = etuds.get(Number(etudid));
const assiduites = data[etudid];
etud.assiduites = assiduites;
});
})
.catch((error) =>
console.error(
"There has been a problem with your fetch operation:",
error
)
);
}
2023-04-17 15:44:55 +02:00
/**
* Génération ligne étudiante
2023-04-17 15:44:55 +02:00
*/
function creerLigneEtudiant(etud, index) {
let currentAssiduite = {
etat: "",
type: "creation",
assiduite_id: -1,
date_debut: null,
date_fin: null,
};
2023-04-17 15:44:55 +02:00
/**Retourne une liste d'assiduité en conflit avec la période actuelle
* @param {Array} assiduites - Les assiduités de l'étudiant
* @returns {Array} Les assiduités en conflit
*/
function recupConflitsAssiduites(assiduites) {
const period = getPeriodAsDate();
return assiduites.filter((assi) => {
const interval = {
deb: new Date(Date.removeUTC(assi.date_debut)),
fin: new Date(Date.removeUTC(assi.date_fin)),
};
return (
period.deb.isBefore(interval.fin) && period.fin.isAfter(interval.deb)
);
});
2023-04-17 15:44:55 +02:00
}
// Pas de conflit en readonly
const conflits = readOnly ? [] : recupConflitsAssiduites(etud.assiduites);
2023-04-17 15:44:55 +02:00
// Si il y a des conflits, on prend le premier pour l'afficher
// si les dates de début et de fin sont les mêmes, c'est une édition
// sinon c'est un conflit
if (conflits.length > 0) {
currentAssiduite = conflits[0];
const conflitsPeriode = {
deb: new Date(Date.removeUTC(currentAssiduite.date_debut)),
fin: new Date(Date.removeUTC(currentAssiduite.date_fin)),
};
const period = getPeriodAsDate();
currentAssiduite.type =
period.deb.isSame(conflitsPeriode.deb) &&
period.fin.isSame(conflitsPeriode.fin)
? "edition"
: "conflit";
2023-04-17 15:44:55 +02:00
}
// Création de la ligne étudiante en DOM
/* exemple de ligne étudiante
<div class="etud_row" id="etud_row_497">
<div class="index">1</div>
<div class="name_field"><img src="../../api/etudiant/etudid/497/photo?size=small" alt="Baudin Joseph" class="pdp"><a
class="name_set" href="bilan_etud?etudid=497">
<h4 class="nom">Baudin</h4>
<h5 class="prenom">Joseph</h5>
</a></div>
<div class="assiduites_bar">
<div id="prevDateAssi" class="vide"></div>
<div class="mini-timeline"><span class="mini_tick" style="left: 47.5%;">13h</span>
<div class="mini-timeline-block creneau" style="left: 20%; width: 17.5%;"></div>
</div>
</div>
<fieldset class="btns_field single" etudid="497" type="creation" assiduite_id="-1">
<input
type="checkbox"
value="present"
name="btn_assiduites_1"
id="rbtn_present"
class="rbtn present"
title="present"
>
<input
type="checkbox"
value="retard"
name="btn_assiduites_1"
id="rbtn_retard"
class="rbtn retard"
title="retard"
>
<input
type="checkbox"
value="absent"
name="btn_assiduites_1"
id="rbtn_absent"
class="rbtn absent"
title="absent"
>
</fieldset>
</div>
*/
const ligneEtud = document.createElement("div");
ligneEtud.classList.add("etud_row");
if (Object.keys(etudsDefDem).includes(etud.id)) {
ligneEtud.classList.add(etudsDefDem[etud.id] == "D" ? "dem" : "def");
}
ligneEtud.id = `etud_row_${etud.id}`;
if (currentAssiduite.type === "conflit" && !readOnly)
ligneEtud.classList.add("conflit");
// div index avec l'index
const indexDiv = document.createElement("div");
indexDiv.classList.add("index");
indexDiv.id = `etudid-${etud.id}`;
indexDiv.textContent = index;
2023-11-08 23:19:58 +01:00
ligneEtud.appendChild(indexDiv);
2023-11-08 23:19:58 +01:00
// div name_field
2023-11-08 23:19:58 +01:00
const nameField = document.createElement("div");
nameField.classList.add("name_field");
2023-11-08 23:19:58 +01:00
const pdp = document.createElement("img");
pdp.src = `../../api/etudiant/etudid/${etud.id}/photo?size=small`;
pdp.alt = `${etud.nom} ${etud.prenom}`;
pdp.classList.add("pdp");
nameField.appendChild(pdp);
2023-11-08 23:19:58 +01:00
const nameSet = document.createElement("a");
nameSet.classList.add("name_set");
nameSet.id = `etudid-${etud.id}`;
nameSet.href = `bilan_etud?etudid=${etud.id}`;
2023-11-08 23:19:58 +01:00
const nom = document.createElement("h4");
nom.classList.add("nom");
nom.textContent = etud.nom;
2023-11-08 23:19:58 +01:00
const prenom = document.createElement("h5");
prenom.classList.add("prenom");
prenom.textContent = etud.prenom;
2023-11-08 23:19:58 +01:00
nameSet.appendChild(nom);
nameSet.appendChild(prenom);
2023-04-17 15:44:55 +02:00
nameField.appendChild(nameSet);
ligneEtud.appendChild(nameField);
2023-04-17 15:44:55 +02:00
// div assiduites_bar
2023-04-17 15:44:55 +02:00
const assiduitesBar = document.createElement("div");
assiduitesBar.classList.add("assiduites_bar");
2023-04-17 15:44:55 +02:00
const prevDateAssi = document.createElement("div");
prevDateAssi.id = "prevDateAssi";
2023-04-17 15:44:55 +02:00
function recupDerniereAssiduite(assiduites) {
const period = {
deb: $("#date").datepicker("getDate").add(-1, "days"),
fin: $("#date").datepicker("getDate"),
};
2023-04-17 15:44:55 +02:00
const lastAssiduite = assiduites
.filter((assi) => {
const interval = {
deb: new Date(Date.removeUTC(assi.date_debut)),
fin: new Date(Date.removeUTC(assi.date_fin)),
};
2023-04-17 15:44:55 +02:00
return (
period.deb.isBefore(interval.fin) && period.fin.isAfter(interval.deb)
);
})
.sort((a, b) => {
return (
new Date(Date.removeUTC(b.date_debut)) -
new Date(Date.removeUTC(a.date_debut))
);
})
.pop();
return lastAssiduite ?? null;
}
const lastAssiduite = recupDerniereAssiduite(etud.assiduites);
prevDateAssi.classList.add(lastAssiduite?.etat.toLowerCase() ?? "vide");
setupAssiduiteBubble(prevDateAssi, lastAssiduite);
assiduitesBar.appendChild(prevDateAssi);
// div minitimeline
assiduitesBar.appendChild(createMiniTimeline(etud.assiduites));
ligneEtud.appendChild(assiduitesBar);
// fieldset btns_field single
const btnsField = document.createElement("fieldset");
btnsField.classList.add("btns_field", "single");
btnsField.setAttribute("etudid", etud.id);
btnsField.setAttribute("type", currentAssiduite.type);
btnsField.setAttribute("assiduite_id", currentAssiduite.assiduite_id);
// Création des boutons d'assiduités
if (readOnly) {
} else if (currentAssiduite.type != "conflit") {
const etats = ["retard", "absent"];
if (!window.nonPresent) {
etats.splice(0, 0, "present");
}
etats.forEach((abs) => {
const btn = document.createElement("input");
btn.type = "checkbox";
btn.value = abs;
btn.name = `btn_assiduites_${index}`;
btn.id = `rbtn_${abs}`;
btn.classList.add("rbtn", abs);
btn.title = abs;
btn.checked = abs === currentAssiduite?.etat.toLowerCase();
// Une seule checkbox à la fois
btn.addEventListener("click", () => {
Array.from(btn.parentElement.children).forEach((chbox) => {
if (chbox.checked && chbox.value !== btn.value) {
chbox.checked = false;
}
});
});
2023-04-17 15:44:55 +02:00
// Action au clic
btn.addEventListener("click", (e) => {
actionAssiduite(
etud,
btn.value,
currentAssiduite.type,
currentAssiduite.type == "edition" ? currentAssiduite : null
);
e.preventDefault();
});
2023-04-17 15:44:55 +02:00
btnsField.appendChild(btn);
});
} else {
const btn = document.createElement("input");
btn.type = "checkbox";
btn.value = "conflit";
btn.name = `btn_assiduites_${index}`;
btn.id = `rbtn_conflit`;
btn.classList.add("rbtn", "conflit");
btn.title = "conflit";
// TODO : Ouvrir solveur
const solveur = new ConflitResolver(etud.assiduites, getPeriodAsDate(), {
deb: $("#date").datepicker("getDate"),
fin: $("#date").datepicker("getDate").add(1, "days"),
});
2023-04-17 15:44:55 +02:00
const update = () => {
MiseAJourLigneEtud(etud);
};
solveur.callbacks = {
delete: update,
edit: update,
split: update,
};
2023-04-17 15:44:55 +02:00
btn.addEventListener("click", () => {
solveur.open();
btn.checked = false;
});
btnsField.appendChild(btn);
}
2023-04-17 15:44:55 +02:00
ligneEtud.appendChild(btnsField);
2023-04-17 15:44:55 +02:00
// Attache les infos de l'étudiant (bulle etud_info)
try {
attach_etud_info(nameSet);
attach_etud_info(indexDiv);
} catch {}
return ligneEtud;
}
2023-04-17 15:44:55 +02:00
/**
* Génération de toutes les lignes étudiantes
2023-04-17 15:44:55 +02:00
*/
async function creerTousLesEtudiants(etuds) {
const etudsDiv = document.querySelector(".etud_holder");
etudsDiv.innerHTML = "";
const moduleImplId = readOnly ? null : $("#moduleimpl_select").val();
const inscriptions = await getInscriptionModule(moduleImplId);
// on trie les étudiants par ordre alphabétique
// et on garde ceux qui sont inscrits au module
// puis pour chaque étudiant on crée une ligne
[...etuds.values()]
.sort((a, b) => {
return a.sort_key > b.sort_key ? 1 : -1;
})
.filter((etud) => {
return inscriptions == null || inscriptions.includes(etud.id);
})
.forEach((etud, index) => {
etudsDiv.appendChild(creerLigneEtudiant(etud, index + 1));
});
// Récupère l'offset timezone serveur pour la date sélectionnée
const date_iso = getSelectedDateIso();
try {
const res = await fetch(`../../api/assiduite/date_time_offset/${date_iso}`);
if (!res.ok) {
throw new Error("Network response was not ok");
}
const text = await res.text();
SERVER_TIMEZONE_OFFSET = text;
} catch (error) {
console.error('Error:', error);
}
2023-04-17 15:44:55 +02:00
}
/**
* Récupère une version lisible du moduleimpl
* @param {Object} assiduite
* @returns {String}
2023-04-17 15:44:55 +02:00
*/
async function getModuleImpl(assiduite) {
2024-05-24 16:51:44 +02:00
if (assiduite == null) return "Module non spécifié";
const id = assiduite.moduleimpl_id;
2023-04-17 15:44:55 +02:00
if (id == null || id == undefined) {
if (
assiduite.hasOwnProperty("external_data") &&
assiduite.external_data != null &&
assiduite.external_data.hasOwnProperty("module")
) {
return assiduite.external_data.module == "Autre"
? "Autre module (pas dans la liste)"
: assiduite.external_data.module;
} else {
2024-05-24 16:51:44 +02:00
return "Module non spécifié";
}
}
if (id in moduleimpls) {
return moduleimpls[id];
}
const url_api = `../../api/moduleimpl/${id}`;
return await fetch(url_api)
.then((res) => {
if (!res.ok) {
throw new Error("Network response was not ok");
}
return res.json();
})
.then((data) => {
moduleimpls[id] = `${data.module.code} ${data.module.abbrev || ""}`;
return moduleimpls[id];
})
.catch((_) => {
moduleimpls[id] = "Pas de module";
return moduleimpls[id];
});
}
/**
* Renvoie le moduleimpl_id de l'assiduité
* ou l'external_data.module si le moduleimpl_id n'est pas défini
* "" si aucun module n'est défini
* @param {Object} assiduite
* @returns {String}
*/
function getModuleImplId(assiduite) {
const id = assiduite.moduleimpl_id;
if (id == null || id == undefined) {
if (
assiduite.hasOwnProperty("external_data") &&
assiduite.external_data != null &&
assiduite.external_data.hasOwnProperty("module")
) {
return assiduite.external_data.module.toLowerCase();
} else {
return "";
}
} else {
return id + "";
}
}
2023-04-17 15:44:55 +02:00
/**
* Récupère les etudid de tous les étudiants inscrits au module
* @param {String} moduleimpl_id
* @returns {Array}
2023-04-17 15:44:55 +02:00
*/
async function getInscriptionModule(moduleimpl_id) {
if ([null, "", "autre"].includes(moduleimpl_id)) return null;
if (!inscriptionsModules.has(moduleimpl_id)) {
const path = `../../api/moduleimpl/${moduleimpl_id}/inscriptions`;
await fetch(path)
.then((res) => {
if (!res.ok) {
throw new Error("Network response was not ok");
}
return res.json();
})
.then((data) => {
inscriptionsModules.set(
moduleimpl_id,
data.map((i) => i.etudid)
);
})
.catch((_) => {
inscriptionsModules.set(moduleimpl_id, []);
});
}
2023-04-17 15:44:55 +02:00
return inscriptionsModules.get(moduleimpl_id);
2023-04-17 15:44:55 +02:00
}
// Mise à jour de la ligne étudiant
async function MiseAJourLigneEtud(etud) {
//Récupérer ses assiduités
function RecupAssiduitesEtudiant(etudid) {
const date = $("#date").datepicker("getDate");
const date_debut = date.add(-1, "days").format("YYYY-MM-DDTHH:mm");
const date_fin = date.add(2, "days").format("YYYY-MM-DDTHH:mm");
url =
`../../api/assiduites/${etudid}/query?date_debut=${date_debut}` +
`&date_fin=${date_fin}&with_justifs`;
2023-04-17 15:44:55 +02:00
return fetch(url)
.then((res) => {
if (!res.ok) {
throw new Error("Network response was not ok");
}
return res.json();
})
.then((data) => {
etud.assiduites = data;
})
.catch((error) => {
console.error(
"There has been a problem with your fetch operation:",
error
);
});
2023-04-17 15:44:55 +02:00
}
await RecupAssiduitesEtudiant(etud.id);
// Une fois les assiduités récupérées, on met à jour la ligne étudiant
// on replace l'ancienne ligne par la nouvellement générée
const etudRow = document.getElementById(`etud_row_${etud.id}`);
if (etudRow == null) return;
const ligneEtud = creerLigneEtudiant(
etud,
document.querySelector(`#etud_row_${etud.id}`).querySelector(".index")
.textContent
2023-04-17 15:44:55 +02:00
);
etudRow.replaceWith(ligneEtud);
}
2023-04-17 15:44:55 +02:00
// Action appelée lors d'un clic sur un bouton d'assiduité
// Création, édition ou suppression d'une assiduité
async function actionAssiduite(etud, etat, type, assiduite = null) {
const modimpl_id = $("#moduleimpl_select").val();
if (assiduite && assiduite.etat.toLowerCase() === etat) type = "suppression";
const { deb, fin } = getPeriodAsISO(); // chaines sans timezone pour l'API
// génération d'un objet assiduité basique qui sera complété
let assiduiteObjet = assiduite ?? {
date_debut: deb,
date_fin: fin,
etudid: etud.id,
};
2023-04-17 15:44:55 +02:00
assiduiteObjet.etat = etat;
assiduiteObjet.moduleimpl_id = modimpl_id;
// En fonction du type d'action on appelle la bonne route
// avec les bonnes valeurs
if (type === "creation") {
await async_post(
`../../api/assiduite/${etud.id}/create`,
[assiduiteObjet],
(data) => {
if (data.success.length > 0) {
MiseAJourLigneEtud(etud);
envoiToastEtudiant(etat, etud);
} else {
console.error(data.errors["0"].message);
erreurModuleImpl(data.errors["0"].message);
}
},
(error) => {
console.error("Erreur lors de la création de l'assiduité", error);
2023-04-17 15:44:55 +02:00
}
);
} else if (type === "edition") {
await async_post(
`../../api/assiduite/${assiduite.assiduite_id}/edit`,
{
etat: assiduiteObjet.etat,
moduleimpl_id: assiduiteObjet.moduleimpl_id,
},
(data) => {
MiseAJourLigneEtud(etud);
envoiToastEtudiant(etat, etud);
},
(error) => {
console.error("Erreur lors de la modification de l'assiduité", error);
}
);
} else if (type === "suppression") {
await async_post(
`../../api/assiduite/delete`,
[assiduite.assiduite_id],
(data) => {
if (data.success.length > 0) {
MiseAJourLigneEtud(etud);
envoiToastEtudiant("remove", etud);
} else {
console.error(data.errors["0"].message);
erreurModuleImpl(data.errors["0"].message);
}
},
(error) => {
console.error("Erreur lors de la suppression de l'assiduité", error);
}
);
2023-04-17 15:44:55 +02:00
}
}
// Fonction pour afficher un message d'erreur si le module n'est pas renseigné
// ou si l'étudiant n'est pas inscrit au module.
// On donne le message d'erreur d'une requête api et cela affiche le message correspondant
function erreurModuleImpl(message) {
if (message == "Module non renseigné") {
const HTML = `
<p>Attention, le module doit obligatoirement être renseigné.</p>
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
`;
2023-04-17 15:44:55 +02:00
const content = document.createElement("div");
content.innerHTML = HTML;
2023-04-17 15:44:55 +02:00
openAlertModal("Sélection du module", content);
}
if (
message == "L'étudiant n'est pas inscrit au module" ||
message == "param 'moduleimpl_id': etud non inscrit"
) {
const HTML = `
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
`;
const content = document.createElement("div");
content.innerHTML = HTML;
openAlertModal("Sélection du module", content);
}
if (
message == "La date de début n'est pas un jour travaillé"
) {
const HTML = `
<p>Attention, la date de début n'est pas un jour travaillé.</p>
`;
const content = document.createElement("div");
content.innerHTML = HTML;
openAlertModal("Date de début", content);
}
}
// Fonction pour ajouter en lot une assiduité à tous les étudiants
// Fonctionne uniquement pour créer ou supprimer des assiduités
// Pas d'édition possible
function mettreToutLeMonde(etat, el = null) {
const lignesEtuds = [...document.querySelectorAll("fieldset.btns_field")];
const { deb, fin } = getPeriodAsDate(true); // tz server
const period_iso = getPeriodAsISO(); // chaines sans timezone pour l'API
const deb_iso = period_iso.deb;
const fin_iso = period_iso.fin;
const assiduiteObjet = {
date_debut: deb_iso,
date_fin: fin_iso,
etat: etat,
moduleimpl_id: $("#moduleimpl_select").val(),
};
if (el != null) el.checked = false;
// Suppression des assiduités
if (etat == "vide") {
if (!confirm("Effacer tout les évènements correspondant à cette plage ?")) {
return; // annulation
}
// On récupère les lignes avec une seule assiduité
let assiduites_id = lignesEtuds
.filter((e) => e.getAttribute("type") == "edition")
.map((e) => Number(e.getAttribute("assiduite_id")));
// On récupère les assiduités conflictuelles mais qui sont comprises
// dans la plage de suppression
const unDeleted = {};
lignesEtuds
.filter((e) => e.getAttribute("type") == "conflit")
.forEach((e) => {
const etud = etuds.get(Number(e.getAttribute("etudid")));
// On récupère les assiduités couvertes par la plage de suppression
etud.assiduites.forEach((a) => {
const date_debut = new Date(a.date_debut);
const date_fin = new Date(a.date_fin);
// On prend en compte uniquement les assiduités conflictuelles
// (qui intersectent la plage de suppression)
if (
Date.intersect(
{ deb: deb, fin: fin }, // la plage, en Date avec timezone serveur
{ deb: date_debut, fin: date_fin } // dates de l'assiduité avec leur timezone
)
) {
// Si l'assiduité est couverte par la plage de suppression
// On l'ajoute à la liste des assiduités à supprimer.
if (
date_debut.isBetween(deb, fin, "[]") &&
date_fin.isBetween(deb, fin, "[]")
) {
assiduites_id.push(a.assiduite_id);
}
// Sinon on ajoute l'étudiant à la liste des étudiants non gérés
else {
unDeleted[a.etudid] = true;
}
}
});
});
afficheLoader();
async_post(
`../../api/assiduite/delete`,
assiduites_id,
async (data) => {
retirerLoader();
if (data.errors.length == 0) {
await recupAssiduites(etuds, $("#date").datepicker("getDate"));
creerTousLesEtudiants(etuds);
} else {
console.error(data.errors);
}
envoiToastTous("remove", assiduites_id.length);
if (Object.keys(unDeleted).length == 0) return;
// CAS : des assiduités d'étudiants n'ont pas pu être supprimés
let unDeletedEtuds = `
<ul>
${Object.keys(unDeleted)
.map((etudid) => {
const etud = etuds.get(Number(etudid));
return `<li>${etud.civilite}. ${etud.nom.toUpperCase()} ${
etud.prenom
}</li>`;
})
.join("")}
</ul>
`;
let html = `
<p>Les assiduités des étudiants suivants n'ont pas été supprimées car elles ne sont pas incluses dans la plage de suppression :</p>
${unDeletedEtuds}
`;
const div = document.createElement("div");
div.innerHTML = html;
openAlertModal("Assiduité non supprimée", div);
},
(error) => {
console.error("Erreur lors de la suppression de l'assiduité", error);
}
);
2023-04-17 15:44:55 +02:00
return;
}
// Création
const assiduitesACreer = lignesEtuds
.filter((e) => e.getAttribute("type") == "creation")
.map((e) => Number(e.getAttribute("etudid")));
// création
const promiseCreate = async_post(
`../../api/assiduites/create`,
assiduitesACreer.map((etudid) => {
return { ...assiduiteObjet, etudid };
}),
async (data) => {
if (data.errors.length > 0) {
console.error(data.errors);
}
},
(error) => {
console.error("Erreur lors de la création de l'assiduité", error);
}
);
// Affiche un loader
afficheLoader();
Promise.all([promiseCreate]).then(async () => {
retirerLoader();
await recupAssiduites(etuds, $("#date").datepicker("getDate"));
creerTousLesEtudiants(etuds);
envoiToastTous(etat, assiduitesACreer.length);
2023-06-20 15:50:56 +02:00
});
2023-04-17 15:44:55 +02:00
}
// Affichage d'un loader (animation jeu pong)
function afficheLoader() {
const loaderDiv = document.createElement("div");
loaderDiv.id = "loader";
const span = document.createElement("span");
span.textContent = "Chargement en cours";
loaderDiv.appendChild(span);
const loader = document.createElement("div");
loader.classList.add("loader");
loaderDiv.appendChild(loader);
document.body.appendChild(loaderDiv);
}
// Retrait du loader (animation jeu pong)
function retirerLoader() {
document.getElementById("loader").remove();
}
// Simplification de l'envoie de toast pour un étudiant
// affiche le nom, le prénom et l'état de l'assiduité avec une couleur spécifique
function envoiToastEtudiant(etat, etud) {
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.";
}
2023-07-26 16:43:49 +02:00
const nom_prenom = `${etud.nom.toUpperCase()} ${etud.prenom.capitalize()}`;
const span = document.createElement("span");
span.innerHTML = etatAffiche.replace("%etud%", nom_prenom);
2023-07-26 16:43:49 +02:00
pushToast(generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5));
}
// Fonction pour simplifier l'envoie de toast avec le bouton "mettre tout le monde"
// On donne un etat et un compte et cela affichera le message associé.
// ex : 12 assiduités ont été supprimées
// ex : 15 étudiants ont été mis Absent.
function envoiToastTous(etat, count) {
const span = document.createElement("span");
let etatAffiche = etat;
switch (etat) {
case "remove":
if (count > 0) {
span.innerHTML = `${count} assiduités ont été supprimées.`;
} else {
span.innerHTML = `Aucune assiduité n'a été supprimée.`;
}
break;
case "retard":
etatAffiche = "En retard";
default:
if (count > 0) {
span.innerHTML = `${count} étudiants ont été mis <u><strong>${etatAffiche
.capitalize()
.trim()}</strong></u>`;
} else {
span.innerHTML = `Aucun étudiant n'a été mis <u><strong>${etatAffiche
.capitalize()
.trim()}</strong></u>`;
}
break;
2023-07-26 16:43:49 +02:00
}
pushToast(generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5));
}
// Permet de savoir si un jour est travaillé ou pas
// jour : Date
// nonWorkdays : Array[str] => ["mar", "sam", "dim"]
function estJourTravail(jour, nonWorkdays) {
const d = Intl.DateTimeFormat("fr-FR", {
timeZone: SCO_TIMEZONE,
weekday: "short",
})
.format(jour)
.replace(".", "");
return !nonWorkdays.includes(d);
2023-07-26 16:43:49 +02:00
}
// Renvoie le dernier jour travaillé disponible.
// par défaut va en arrière (dans le passé)
// si anti == False => va dans le futur
function retourJourTravail(date, anti = true) {
const jourMiliSecondes = 86400000; // 24 * 3600 * 1000 | H * s * ms
let jour = date;
let compte = 0;
while (!estJourTravail(jour, nonWorkDays) && compte++ < 7) {
let temps = anti
? jour - jourMiliSecondes
: jour.valueOf() + jourMiliSecondes;
jour = new Date(temps);
}
return jour;
}
// Vérifie si la date courante est travaillée
// Si ce n'est pas le cas, on change la date pour le dernier jour travaillé (passé)
// et on affiche une alerte
// (utilise le datepicker #date)
function dateCouranteEstTravaillee() {
const date = $("#date").datepicker("getDate");
if (!estJourTravail(date, nonWorkDays)) {
// récupération du jour travaillé le plus proche
const nouvelleDate = retourJourTravail(date);
$("#date").datepicker("setDate", nouvelleDate);
// Création du message d'alerte
let msg = "Le jour sélectionné";
if (new Date().format("YYYY-MM-DD") == date.format("YYYY-MM-DD")) {
msg = "Aujourd'hui";
}
const att = document.createTextNode(
`${msg} (${Date.toFRA(
date.format("YYYY-MM-DD")
)}) n'est pas un jour travaillé.`
);
const div = document.createElement("div");
div.appendChild(att);
div.appendChild(document.createElement("br"));
div.appendChild(
document.createTextNode(
`Le dernier jour travaillé disponible a été sélectionné : ${Date.toFRA(
nouvelleDate.format("YYYY-MM-DD")
)}.`
)
);
// Affichage de l'alerte
openAlertModal("Attention", div, "", "#eec660");
return false;
}
return true;
}
// Fonction pour passer au jour suivant
// anti : bool => si true, on va dans le passé
function jourSuivant(anti = false) {
let date = $("#date").datepicker("getDate");
date = anti ? date.add(-1, "days") : date.add(1, "days");
const nouvelleDate = retourJourTravail(date, anti);
$("#date").datepicker("setDate", nouvelleDate);
creerTousLesEtudiants(etuds);
}
/**
* Ajout de la visualisation des assiduités de la mini timeline
* @param {HTMLElement} el l'élément survollé
* @param {Assiduité} assiduite l'assiduité représentée par l'élément
*/
function setupAssiduiteBubble(el, assiduite) {
function formatDateModal(dateStr) {
const date = new Date(Date.removeUTC(dateStr));
return date.format("DD/MM/Y HH:mm");
}
if (!assiduite) return;
const bubble = document.createElement("div");
bubble.className = "assiduite-bubble";
bubble.classList.add(assiduite.etat.toLowerCase());
2024-03-15 15:56:33 +01:00
// Ajout d'un lien pour plus d'informations
const infos = document.createElement("a");
infos.className = "";
2024-03-15 15:56:33 +01:00
infos.textContent = ``;
infos.title = "Détails / Modifier";
2024-03-15 15:56:33 +01:00
infos.target = "_blank";
infos.href = `edit_assiduite_etud/${assiduite.assiduite_id}`;
const actionsDiv = document.createElement("div");
actionsDiv.className = "assiduite-actions";
actionsDiv.appendChild(infos);
bubble.appendChild(actionsDiv);
2024-03-15 15:56:33 +01:00
2024-05-24 16:51:44 +02:00
const stateDiv = document.createElement("div");
stateDiv.className = "assiduite-state";
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
bubble.appendChild(stateDiv);
const idDiv = document.createElement("div");
idDiv.className = "assiduite-id";
getModuleImpl(assiduite).then((modImpl) => {
idDiv.textContent = `${modImpl}`;
});
bubble.appendChild(idDiv);
2024-05-24 16:51:44 +02:00
// Affichage des dates
// si les jours sont les mêmes, on affiche "jour hh:mm - hh:mm"
// sinon on affiche "jour hh:mm - jour hh:mm"
const periodDiv = document.createElement("div");
periodDiv.className = "assiduite-period";
const dateDeb = new Date(Date.removeUTC(assiduite.date_debut));
const dateFin = new Date(Date.removeUTC(assiduite.date_fin));
if (dateDeb.isSame(dateFin, "day")) {
const jour = dateDeb.format("DD/MM/YYYY");
const deb = dateDeb.format("HH:mm");
const fin = dateFin.format("HH:mm");
periodDiv.textContent = `${jour} de ${deb} à ${fin}`;
} else {
const jourDeb = dateDeb.format("DD/MM/YYYY");
const jourFin = dateFin.format("DD/MM/YYYY");
periodDiv.textContent = `du ${jourDeb} au ${jourFin}`;
}
2024-05-24 16:51:44 +02:00
bubble.appendChild(periodDiv);
const motifDiv = document.createElement("div");
2024-05-24 16:51:44 +02:00
motifDiv.className = "assiduite-why";
const motif = ["", null, undefined].includes(assiduite.desc)
2024-05-24 16:51:44 +02:00
? "Non spécifié"
: assiduite.desc.capitalize();
2024-05-24 16:51:44 +02:00
motifDiv.textContent = `Motif: ${motif}`;
bubble.appendChild(motifDiv);
const userIdDiv = document.createElement("div");
userIdDiv.className = "assiduite-user_id";
userIdDiv.textContent = `saisie le ${formatDateModal(
assiduite.entry_date,
" à "
)}`;
if (assiduite.user_id != null) {
userIdDiv.textContent += `\npar ${assiduite.user_nom_complet}`;
}
bubble.appendChild(userIdDiv);
el.appendChild(bubble);
}
/**
* Permet d'afficher ou non les photos des étudiants
* @param {boolean} checked
*/
function afficherPDP(checked) {
if (checked) {
gtrcontent.setAttribute("data-pdp", "true");
} else {
gtrcontent.removeAttribute("data-pdp");
}
// On sauvegarde le choix dans le localStorage
localStorage.setItem("scodoc-etud-pdp", `${checked}`);
pdp.checked = checked;
}