/** * * 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<Object>} 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 <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; 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.id = `etudid-${etud.id}`; 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); // Attache les infos de l'étudiant (bulle etud_info) try { attach_etud_info(nameSet); attach_etud_info(indexDiv); } catch {} 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 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); } } /** * 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 } = 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, }; 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 = ` <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> `; 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 = ` <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); } ); 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) <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."; } 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 <u><strong>${etatAffiche .capitalize() .trim()}</strong></u>`; } else { span.innerHTML = `Aucun étudiant n'a été mis <u><strong>${etatAffiche .capitalize() .trim()}</strong></u>`; } 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; }