/** * * Ensemble des fonctions liées à la gestion des assiduités * Créé par : HARTMANN Matthias (Iziram) * */ /** * <== OUTILS ==> */ /** * Ajout d'une fonction `capitalize` sur tous les strings * "alice".capitalize() -> "Alice" */ Object.defineProperty(String.prototype, "capitalize", { value: function () { return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase(); }, enumerable: false, }); /** * 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 */ 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 */ async function async_post(path, data, success, errors) { let response; try { response = await fetch(path, { 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."); } } } catch (error) { console.error(error); if (errors) errors(error); } return response; } /** * 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} Un objet contenant les étudiants, indexés par leur identifiant. */ async function recupEtuds(groupIds) { const etuds = new Map(); if (groupIds == null || groupIds.length == 0) return etuds; // 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); return etuds; } /** * Récupère l'assiduité des étudiants pour une date donnée * @param {Map} etuds * @param {Date} date */ async function recupAssiduites(etuds, date) { const etudIds = [...etuds.keys()].join(","); 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/group/query?date_debut=${date_debut}` + `&date_fin=${date_fin}&etudids=${etudIds}&with_justifs`; await fetch(url) .then((res) => { if (!res.ok) { throw new Error("Network response was not ok"); } return res.json(); }) .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 ) ); } /** * Génération ligne étudiante */ function creerLigneEtudiant(etud, index) { let currentAssiduite = { etat: "", type: "creation", assiduite_id: -1, date_debut: null, date_fin: null, }; /**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) ); }); } // Pas de conflit en readonly const conflits = readOnly ? [] : recupConflitsAssiduites(etud.assiduites); // 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"; } // Création de la ligne étudiante en DOM /* exemple de ligne étudiante
1
13h
*/ 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.textContent = index; ligneEtud.appendChild(indexDiv); // div name_field const nameField = document.createElement("div"); nameField.classList.add("name_field"); 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); const nameSet = document.createElement("a"); nameSet.classList.add("name_set"); nameSet.href = `bilan_etud?etudid=${etud.id}`; const nom = document.createElement("h4"); nom.classList.add("nom"); nom.textContent = etud.nom; const prenom = document.createElement("h5"); prenom.classList.add("prenom"); prenom.textContent = etud.prenom; nameSet.appendChild(nom); nameSet.appendChild(prenom); nameField.appendChild(nameSet); ligneEtud.appendChild(nameField); // div assiduites_bar const assiduitesBar = document.createElement("div"); assiduitesBar.classList.add("assiduites_bar"); const prevDateAssi = document.createElement("div"); prevDateAssi.id = "prevDateAssi"; function recupDerniereAssiduite(assiduites) { const period = { deb: $("#date").datepicker("getDate").add(-1, "days"), fin: $("#date").datepicker("getDate"), }; const lastAssiduite = 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) ); }) .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; } }); }); // Action au clic btn.addEventListener("click", (e) => { actionAssiduite( etud, btn.value, currentAssiduite.type, currentAssiduite.type == "edition" ? currentAssiduite : null ); e.preventDefault(); }); 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"), }); const update = () => { MiseAJourLigneEtud(etud); }; solveur.callbacks = { delete: update, edit: update, split: update, }; btn.addEventListener("click", () => { solveur.open(); btn.checked = false; }); btnsField.appendChild(btn); } ligneEtud.appendChild(btnsField); return ligneEtud; } /** * Génération de toutes les lignes étudiantes */ 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 une version lisible du moduleimpl * @param {Object} assiduite * @returns {String} */ async function getModuleImpl(assiduite) { if (assiduite == null) return "Module non spécifié"; 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 == "Autre" ? "Autre module (pas dans la liste)" : assiduite.external_data.module; } else { 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 + ""; } } /** * Récupère les etudid de tous les étudiants inscrits au module * @param {String} moduleimpl_id * @returns {Array} */ 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, []); }); } return inscriptionsModules.get(moduleimpl_id); } // 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`; 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 ); }); } 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 ); etudRow.replaceWith(ligneEtud); } // 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 } = getPeriodAsDate(); // génération d'un objet assiduité basique qui sera complété let assiduiteObjet = assiduite ?? { date_debut: deb, date_fin: fin, etudid: etud.id, }; 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); } ); } 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); } ); } } // 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 = `

Attention, le module doit obligatoirement être renseigné.

Cela vient de la configuration du semestre ou plus largement du département.

Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.

`; const content = document.createElement("div"); content.innerHTML = HTML; 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 = `

Attention, l'étudiant n'est pas inscrit à ce module.

Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.

`; const content = document.createElement("div"); content.innerHTML = HTML; openAlertModal("Sélection du module", 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(); const assiduiteObjet = { date_debut: deb, date_fin: fin, 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 comprisent // 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 couvertent 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 }, { deb: date_debut, fin: date_fin } ) ) { // 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 = ` `; let html = `

Les assiduités des étudiants suivants n'ont pas été supprimées car elles ne sont pas incluses dans la plage de suppression :

${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); } ); 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); }); } // 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) présent(e)"; break; case "RETARD": etatAffiche = "%etud% a été noté(e) en retard"; break; case "ABSENT": etatAffiche = "%etud% a été noté(e) absent(e)"; break; case "REMOVE": etatAffiche = "L'assiduité de %etud% a été retirée."; } const nom_prenom = `${etud.nom.toUpperCase()} ${etud.prenom.capitalize()}`; const span = document.createElement("span"); span.innerHTML = etatAffiche.replace("%etud%", nom_prenom); 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 ${etatAffiche .capitalize() .trim()}`; } else { span.innerHTML = `Aucun étudiant n'a été mis ${etatAffiche .capitalize() .trim()}`; } break; } 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); } // 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()); // Ajout d'un lien pour plus d'informations const infos = document.createElement("a"); infos.className = ""; infos.textContent = `ℹ️`; infos.title = "Détails / Modifier"; 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); 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); // 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}`; } bubble.appendChild(periodDiv); const motifDiv = document.createElement("div"); motifDiv.className = "assiduite-why"; const motif = ["", null, undefined].includes(assiduite.desc) ? "Non spécifié" : assiduite.desc.capitalize(); 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; }