/** * * 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) { // console.log("async_post " + path); 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, }; 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) ); }); } const conflits = readOnly ? [] : recupConflitsAssiduites(etud.assiduites); 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"; } 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); [...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); } 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); 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); } 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(); let assiduiteObjet = assiduite ?? { date_debut: deb, date_fin: fin, etudid: etud.id, }; assiduiteObjet.etat = etat; assiduiteObjet.moduleimpl_id = modimpl_id; 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); } ); } } 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); } } 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 } const assiduites_id = lignesEtuds .filter((e) => e.getAttribute("type") == "edition") .map((e) => Number(e.getAttribute("assiduite_id"))); 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); }, (error) => { console.error("Erreur lors de la suppression de l'assiduité", error); } ); return; } // Création / édition des assiduités const assiduitesACreer = lignesEtuds .filter((e) => e.getAttribute("type") == "creation") .map((e) => Number(e.getAttribute("etudid"))); const assiduitesAEditer = lignesEtuds .filter((e) => e.getAttribute("type") == "edition") .map((e) => Number(e.getAttribute("assiduite_id"))); // 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); } ); const promiseEdit = async_post( `../../api/assiduites/edit`, assiduitesAEditer.map((assiduite_id) => { return { ...assiduiteObjet, assiduite_id }; }), async (data) => { if (data.errors.length > 0) { console.error(data.errors); } }, (error) => { console.error("Erreur lors de l'édition de l'assiduité", error); } ); // Affiche un loader afficheLoader(); Promise.all([promiseCreate, promiseEdit]).then(async () => { retirerLoader(); await recupAssiduites(etuds, $("#date").datepicker("getDate")); creerTousLesEtudiants(etuds); envoiToastTous(etat, assiduitesACreer.length + assiduitesAEditer.length); }); } 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); } function retirerLoader() { document.getElementById("loader").remove(); } 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)); } // TODO commenter toutes les fonctions js 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)); } function estJourTravail(jour, nonWorkdays) { const d = Intl.DateTimeFormat("fr-FR", { timeZone: SCO_TIMEZONE, weekday: "short", }) .format(jour) .replace(".", ""); return !nonWorkdays.includes(d); } 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; } function dateCouranteEstTravaillee() { const date = $("#date").datepicker("getDate"); if (!estJourTravail(date, nonWorkDays)) { const nouvelleDate = retourJourTravail(date); $("#date").datepicker("setDate", nouvelleDate); 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") )}.` ) ); openAlertModal("Attention", div, "", "#eec660"); return false; } return true; } 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 = "Cliquez pour plus d'informations"; infos.target = "_blank"; infos.href = `tableau_assiduite_actions?type=assiduite&action=details&obj_id=${assiduite.assiduite_id}`; // Ajout d'un lien pour modifier l'assiduité const modifs = document.createElement("a"); modifs.className = ""; modifs.textContent = `📝`; modifs.title = "Cliquez pour modifier l'assiduité"; modifs.target = "_blank"; modifs.href = `tableau_assiduite_actions?type=assiduite&action=modifier&obj_id=${assiduite.assiduite_id}`; const actionsDiv = document.createElement("div"); actionsDiv.className = "assiduite-actions"; actionsDiv.appendChild(modifs); 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; }