// <=== CONSTANTS and GLOBALS ===> const TIMEZONE = "Europe/Paris"; let url; function getUrl() { if (!url) { url = SCO_URL.substring(0, SCO_URL.lastIndexOf("/")); } return url; } //Les valeurs par défaut de la timeline (8h -> 18h) let currentValues = [8.0, 10.0]; //Objet stockant les étudiants et les assiduités let etuds = {}; let assiduites = {}; let justificatifs = {}; // Variable qui définit si le processus d'action de masse est lancé let currentMassAction = false; let currentMassActionEtat = undefined; /** * 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, }); const DatePrecisions = [ "year", "month", "day", "hour", "minute", "second", "millisecond", ]; // <<== Outils ==>> Object.defineProperty(Array.prototype, "reversed", { value: function () { return [...this].map(this.pop, this); }, enumerable: false, }); // <= Gestion des dates => Object.defineProperty(Date.prototype, "toPrecision", { /** * Ajuste la date à la précision donnée. * @param {String} precision - La précision désirée (année, mois, jour, etc.) */ value: function (precision) { const newDate = new Date(this.getTime()); // Trouve l'indice de la précision spécifiée dans le tableau des précisions const precisionsIndex = precision != undefined ? DatePrecisions.indexOf(precision) : DatePrecisions.length - 1; // Réinitialise toutes les parties de la date moins significatives que la précision spécifiée for (let i = precisionsIndex + 1; i < DatePrecisions.length; i++) { const p = DatePrecisions[i]; switch (p) { case "month": // Les mois en JavaScript sont indexés à partir de 0, donc on met 0 pour janvier newDate.setMonth(0); break; case "day": // Les jours en JavaScript commencent à 1, donc on met 1 pour le premier jour du mois newDate.setDate(1); break; case "hour": newDate.setHours(0); break; case "minute": newDate.setMinutes(0); break; case "second": newDate.setSeconds(0); break; case "millisecond": newDate.setMilliseconds(0); break; } } return newDate; }, }); Object.defineProperty(Date.prototype, "isBefore", { value: function (date) { return this.valueOf() < date.valueOf(); }, }); Object.defineProperty(Date.prototype, "isAfter", { value: function (date) { return this.valueOf() > date.valueOf(); }, }); Object.defineProperty(Date.prototype, "isSame", { /** * Retourne vrai si les dates sont les mêmes * @param {Date} date * @returns boolean */ value: function (date) { return this.valueOf() == date.valueOf(); }, }); Object.defineProperty(Date.prototype, "isBetween", { /** * Vérifie si la date est comprise dans une période avec une précision et une inclusivité optionnelles * @param {Date} deb - La date de début de la période * @param {Date} fin - La date de fin de la période * @param {String} precision - La précision pour la comparaison (année, mois, jour, etc.) * @param {String} bornes - L'inclusivité/exclusivité de la comparaison ("[]", "()", "[)", "(]") */ value: function (deb, fin, precision, bornes = "[]") { // Ajuste la date actuelle, la date de début et la date de fin à la précision spécifiée const thisPrecision = this.toPrecision(precision); const debPrecision = deb.toPrecision(precision); const finPrecision = fin.toPrecision(precision); // Vérifie les bornes en fonction de l'inclusivité/exclusivité spécifiée dans 'bornes' const check_deb = bornes[0] === "(" ? thisPrecision > debPrecision : thisPrecision >= debPrecision; const check_fin = bornes[1] === ")" ? finPrecision > thisPrecision : finPrecision >= thisPrecision; return check_deb && check_fin; }, }); Object.defineProperty(Date.prototype, "toIsoUtcString", { /** * @returns date au format iso utc (yyyy-mm-ddThh:MM±oo:oo:oo) */ value: function () { const date = this; var tzo = -date.getTimezoneOffset(), dif = tzo >= 0 ? "+" : "-", pad = function (num) { return (num < 10 ? "0" : "") + num; }; return ( date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + "T" + pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds()) + dif + pad(Math.floor(Math.abs(tzo) / 60)) + ":" + pad(Math.abs(tzo) % 60) ); }, }); Object.defineProperty(Date.prototype, "clone", { /** * @returns Retourne une copie de la date (copie non liée) */ value: function () { return structuredClone(this); }, }); Object.defineProperty(Date.prototype, "format", { value: function (formatString) { switch (formatString) { case "DD/MM/Y HH:mm": return this.toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", year: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }); case "DD/MM/YYYY HH:mm": return this.toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", hour12: false, }); case "YYYY-MM-DDTHH:mm": let iso = this.toIsoUtcString(); // slice : YYYY-MM-DDTHH // slice + 3 : YYYY-MM-DDTHH:mm return iso.slice(0, iso.indexOf(":") + 3); default: return this.toIsoUtcString(); } }, }); Object.defineProperty(Date.prototype, "add", { /** * Ajoute une valeur spécifiée à un élément de la date. * @param {number} value - La valeur à ajouter. * @param {string} type - Le type de la valeur (year, month, day, hours, minutes, seconds). */ value: function (value, type) { switch (type) { case "years": this.setFullYear(this.getFullYear() + value); break; case "months": this.setMonth(this.getMonth() + value); break; case "days": this.setDate(this.getDate() + value); break; case "hours": this.setHours(this.getHours() + value); break; case "minutes": this.setMinutes(this.getMinutes() + value); break; case "seconds": this.setSeconds(this.getSeconds() + value); break; default: throw new Error( `Invalid type for adding to date | type : ${type} value : ${value}` ); } return this; // Return the modified date }, }); Object.defineProperty(Date.prototype, "startOf", { /** * Ajuste la date à la plus petite valeur pour la précision donnée. * @param {string} precision - La précision souhaitée (year, month, day, hours, minutes, seconds, milliseconds). * @returns {Date} - Une nouvelle date ajustée. */ value: function (precision) { const newDate = this.clone(); switch (precision) { case "year": newDate.setMonth(0); case "month": newDate.setDate(1); case "day": newDate.setHours(0); case "hours": newDate.setMinutes(0); case "minutes": newDate.setSeconds(0); case "seconds": newDate.setMilliseconds(0); break; case "milliseconds": break; default: throw new Error("Invalid precision for startOf function"); } return newDate; }, }); Object.defineProperty(Date.prototype, "endOf", { /** * Ajuste la date à la plus grande valeur pour la précision donnée. * @param {string} precision - La précision souhaitée (year, month, day, hours, minutes, seconds, milliseconds). * @returns {Date} - Une nouvelle date ajustée. */ value: function (precision) { const newDate = this.clone(); switch (precision) { case "year": newDate.setMonth(11); // Décembre est le 11ème mois (0-indexé) case "month": newDate.setDate(0); // Le jour 0 du mois suivant est le dernier jour du mois courant newDate.setMonth(newDate.getMonth() + 1); case "day": newDate.setHours(23); // 23 heures est la dernière heure de la journée case "hours": newDate.setMinutes(59); // 59 minutes est la dernière minute de l'heure case "minutes": newDate.setSeconds(59); // 59 secondes est la dernière seconde de la minute case "seconds": newDate.setMilliseconds(999); // 999 millisecondes est la dernière milliseconde de la seconde break; case "milliseconds": // Rien à faire pour les millisecondes break; default: throw new Error("Invalid precision for endOf function"); } return newDate; }, }); class Duration { /** * Constructeur de la classe Duration. * @param {Date} start - La date de début de la période. * @param {Date} end - La date de fin de la période. */ constructor(start, end) { this.start = start; // Stocke la date de début. this.end = end; // Stocke la date de fin. this.duration = end - start; // Calcule la durée en millisecondes entre les deux dates. } /** * Calcule le nombre d'années entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre d'années arrondi à quatre décimales. */ get years() { const startYear = this.start.getFullYear(); // Obtient l'année de la date de début. const endYear = this.end.getFullYear(); // Obtient l'année de la date de fin. // Calcule la différence en années et arrondit à quatre décimales. return parseFloat((endYear - startYear).toFixed(4)); } /** * Calcule le nombre de mois entre les deux dates, en tenant compte des années et des jours, et arrondit le résultat à quatre décimales. * @return {number} Le nombre de mois arrondi à quatre décimales. */ get months() { const years = this.years; // Nombre d'années complètes. // Calcule la différence en mois, en ajoutant la différence en jours divisée par 30 pour une approximation. const months = years * 12 + (this.end.getMonth() - this.start.getMonth()) + (this.end.getDate() - this.start.getDate()) / 30; // Arrondit à quatre décimales. return parseFloat(months.toFixed(4)); } /** * Calcule le nombre de jours entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre de jours arrondi à quatre décimales. */ get days() { // Convertit la durée en millisecondes en jours et arrondit à quatre décimales. return parseFloat((this.duration / (24 * 60 * 60 * 1000)).toFixed(4)); } /** * Calcule le nombre d'heures entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre d'heures arrondi à quatre décimales. */ get hours() { // Convertit la durée en millisecondes en heures et arrondit à quatre décimales. return parseFloat((this.duration / (60 * 60 * 1000)).toFixed(4)); } /** * Calcule le nombre de minutes entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre de minutes arrondi à quatre décimales. */ get minutes() { // Convertit la durée en millisecondes en minutes et arrondit à quatre décimales. return parseFloat((this.duration / (60 * 1000)).toFixed(4)); } /** * Calcule le nombre de secondes entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre de secondes arrondi à quatre décimales. */ get seconds() { // Convertit la durée en millisecondes en secondes et arrondit à quatre décimales. return parseFloat((this.duration / 1000).toFixed(4)); } /** * Obtient le nombre de millisecondes entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre de millisecondes arrondi à quatre décimales. */ get milliseconds() { // Arrondit la durée totale en millisecondes à quatre décimales. return parseFloat(this.duration.toFixed(4)); } } /** * Ajout des évents sur les boutons d'assiduité * @param {Document | HTMLFieldSetElement} parent par défaut le document, un field sinon */ function setupCheckBox(parent = document) { const checkboxes = Array.from(parent.querySelectorAll(".rbtn")); checkboxes.forEach((box) => { box.addEventListener("click", (event) => { if (!uniqueCheckBox(box)) { event.preventDefault(); } if (!box.parentElement.classList.contains("mass")) { assiduiteAction(box); } }); }); } /** * Validation préalable puis désactivation des chammps : * - Groupe * - Module impl * - Date */ function validateSelectors(btn) { const action = () => { const group_ids = getGroupIds(); etuds = {}; group_ids.forEach((group_id) => { sync_get( getUrl() + `/api/group/${group_id}/etudiants`, (data, status) => { if (status === "success") { data.forEach((etud) => { if (!(etud.id in etuds)) { etuds[etud.id] = etud; } }); } } ); }); if (getModuleImplId() == null && window.forceModule) { 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); return; } getAssiduitesFromEtuds(true); document.querySelector(".selectors").disabled = true; generateMassAssiduites(); generateAllEtudRow(); btn.remove(); onlyAbs(); }; if (!verifyDateInSemester()) { const HTML = `

Attention, la date sélectionnée n'est pas comprise dans le semestre.

Cette page permet l'affichage et la modification des assiduités uniquement pour le semestre sélectionné.

Vous n'aurez donc pas accès aux assiduités.

Appuyer sur "Valider" uniquement si vous souhaitez poursuivre sans modifier la date.

`; const content = document.createElement("div"); content.innerHTML = HTML; openPromptModal("Vérification de la date", content, action); return; } action(); } function onlyAbs() { if (getDate() > Date.now()) { document .querySelectorAll(".rbtn.present, .rbtn.retard") .forEach((el) => el.remove()); } } /** * Limite le nombre de checkbox marquée * Vérifie aussi si le cliqué est fait sur des assiduités conflictuelles * @param {HTMLInputElement} box la checkbox utilisée * @returns {boolean} Faux si il y a un conflit d'assiduité, Vrai sinon */ function uniqueCheckBox(box) { const type = box.parentElement.getAttribute("type") === "conflit"; if (!type) { const checkboxs = Array.from(box.parentElement.children); checkboxs.forEach((chbox) => { if (chbox.checked && chbox.value !== box.value) { chbox.checked = false; } }); return true; } return false; } /** * Fait une requête GET de façon synchrone * @param {String} path adresse distante * @param {CallableFunction} success fonction à effectuer en cas de succès * @param {CallableFunction} errors fonction à effectuer en cas d'échec */ function sync_get(path, success, errors) { console.log("sync_get " + path); $.ajax({ async: false, type: "GET", url: path, success: success, error: errors, }); } /** * 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 */ function async_get(path, success, errors) { console.log("async_get " + path); $.ajax({ async: true, type: "GET", url: path, success: success, error: errors, }); } /** * Fait une requête POST de façon synchrone * @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 */ function sync_post(path, data, success, errors) { console.log("sync_post " + path); $.ajax({ async: false, type: "POST", url: path, data: JSON.stringify(data), success: success, error: errors, }); } /** * 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 */ function async_post(path, data, success, errors) { console.log("sync_post " + path); return $.ajax({ async: true, type: "POST", url: path, data: JSON.stringify(data), success: success, error: errors, }); } // <<== Gestion des actions de masse ==>> const massActionQueue = new Map(); /** * Cette fonction remet à zero la gestion des actions de masse */ function resetMassActionQueue() { massActionQueue.set("supprimer", []); massActionQueue.set("editer", []); massActionQueue.set("creer", []); } /** * Fonction pour alimenter la queue des actions de masse * @param {String} type Le type de queue ("creer", "supprimer", "editer") * @param {*} obj L'objet qui sera utilisé par les API */ function addToMassActionQueue(type, obj) { massActionQueue.get(type)?.push(obj); } /** * Fonction pour exécuter les actions de masse */ function executeMassActionQueue() { if (!currentMassAction) return; //Récupération des queues const toCreate = massActionQueue.get("creer"); const toEdit = massActionQueue.get("editer"); const toDelete = massActionQueue.get("supprimer"); //Fonction qui créé les assidutiés de la queue "creer" const create = () => { /** * Création du template de l'assiduité * * { * date_debut: #debut_timeline, * date_fin: #fin_timeline, * moduleimpl_id ?: <> * } */ const tlTimes = getTimeLineTimes(); let assiduite = { date_debut: tlTimes.deb.toIsoUtcString(), date_fin: tlTimes.fin.toIsoUtcString(), }; assiduite = setModuleImplId(assiduite); if (!hasModuleImpl(assiduite) && window.forceModule) { const html = `

Aucun module n'a été spécifié

`; const div = document.createElement("div"); div.innerHTML = html; openAlertModal("Erreur Module", div); return 0; } const createQueue = []; //liste des assiduités qui seront créées. /** * Pour chaque état de la queue 'creer' on génère une * assiduitée précise depuis le template */ toCreate.forEach((obj) => { const curAssiduite = structuredClone(assiduite); curAssiduite.etudid = obj.etudid; curAssiduite.etat = obj.etat; createQueue.push(curAssiduite); }); /** * On envoie les données à l'API */ const path = getUrl() + `/api/assiduites/create`; sync_post( path, createQueue, (data, status) => { //success }, (data, status) => { //error console.error(data, status); errorAlert(); } ); return createQueue.length; }; //Fonction qui modifie les assiduités de la queue 'edition' const edit = () => { //On ajoute le moduleimpl (s'il existe) aux assiduités à modifier const editQueue = toEdit.map((assiduite) => { assiduite = setModuleImplId(assiduite); return assiduite; }); if (getModuleImplId() == null && window.forceModule) { const html = `

Aucun module n'a été spécifié

`; const div = document.createElement("div"); div.innerHTML = html; openAlertModal("Erreur Module", div); return 0; } const path = getUrl() + `/api/assiduites/edit`; sync_post( path, editQueue, (data, status) => { //success }, (data, status) => { //error console.error(data, status); errorAlert(); } ); return editQueue.length; }; //Fonction qui supprime les assiduités de la queue 'supprimer' const supprimer = () => { const path = getUrl() + `/api/assiduite/delete`; sync_post( path, toDelete, (data, status) => { //success }, (data, status) => { //error console.error(data, status); errorAlert(); } ); return toDelete.length; }; //On exécute les fonctions de queue let count = 0; if (currentMassActionEtat == "remove") { count += supprimer(); const span = document.createElement("span"); if (count > 0) { span.innerHTML = `${count} assiduités ont été supprimées.`; } else { span.innerHTML = `Aucune assiduité n'a été supprimée.`; } pushToast( generateToast( span, getToastColorFromEtat(currentMassActionEtat.toUpperCase()), 5 ) ); } else { count += create(); count += edit(); const etat = currentMassActionEtat.toUpperCase() == "RETARD" ? "En retard" : currentMassActionEtat; const span = document.createElement("span"); if (count > 0) { span.innerHTML = `${count} étudiants ont été mis ${etat .capitalize() .trim()}`; } else { span.innerHTML = `Aucun étudiant n'a été mis ${etat .capitalize() .trim()}`; } pushToast( generateToast( span, getToastColorFromEtat(currentMassActionEtat.toUpperCase()), 5 ) ); } //On récupère les assiduités puis on regénère les lignes d'étudiants getAssiduitesFromEtuds(true); generateAllEtudRow(); } /** * Processus de peuplement des queues * puis d'exécution */ function massAction() { //On récupère tous les boutons d'assiduités const fields = Array.from(document.querySelectorAll(".btns_field.single")); //On récupère l'état de l'action de masse currentMassActionEtat = getAssiduiteValue( document.querySelector(".btns_field.mass") ); //On remet à 0 les queues resetMassActionQueue(); //on met à vrai la variable pour la suite currentMassAction = true; //On affiche le "loader" le temps du processus showLoader(); //On timeout 0 pour le mettre à la fin de l'event queue de JS setTimeout(() => { const conflicts = []; /** * Pour chaque étudiant : * On vérifie s'il y a un conflit -> on place l'étudiant dans l'array conflicts * Sinon -> on fait comme si l'utilisateur cliquait sur le bouton d'assiduité */ fields.forEach((field) => { if (field.getAttribute("type") != "conflit") { if (currentMassActionEtat != "remove") { field.querySelector(`.rbtn.${currentMassActionEtat}`).click(); } else { field.querySelector(".rbtn.absent").click(); } } else { const etudid = field.getAttribute("etudid"); conflicts.push(etuds[parseInt(etudid)]); } }); //on exécute les queues puis on cache le loader executeMassActionQueue(); hideLoader(); //Fin du processus, on remet à false currentMassAction = false; currentMassActionEtat = undefined; //On remet à zero les boutons d'assiduité de masse const boxes = Array.from( document.querySelector(".btns_field.mass").querySelectorAll(".rbtn") ); boxes.forEach((box) => { box.checked = false; }); //Si il y a des conflits d'assiduité, on affiche la liste dans une alert if (conflicts.length > 0) { const div = document.createElement("div"); const sub = document.createElement("p"); sub.textContent = "L'assiduité des étudiants suivant n'a pas pu être modifiée"; div.appendChild(sub); const ul = document.createElement("ul"); conflicts.forEach((etu) => { const li = document.createElement("li"); li.textContent = `${etu.nom} ${etu.prenom.capitalize()}`; ul.appendChild(li); }); div.appendChild(ul); openAlertModal("Conflits d'assiduités", div, ""); } }, 0); } /** * On génère les boutons d'assiduités de masse * puis on ajoute les événements associés */ function generateMassAssiduites() { const content = document.getElementById("content"); const mass = document.createElement("div"); mass.className = "mass-selection"; mass.innerHTML = ` Mettre tout le monde :
`; content.insertBefore(mass, content.querySelector(".etud_holder")); const mass_btn = Array.from(mass.querySelectorAll(".rbtn")); mass_btn.forEach((btn) => { btn.addEventListener("click", () => { massAction(); }); }); if (!verifyDateInSemester() || readOnly) { content.querySelector(".btns_field.mass").setAttribute("disabled", "true"); } } /** * Affichage du loader */ function showLoader() { document.getElementById("loaderContainer").style.display = "block"; } /** * Dissimulation du loader */ function hideLoader() { document.getElementById("loaderContainer").style.display = "none"; } // <<== Gestion du temps ==>> /** * Transforme un temps numérique en string * 8.75 -> 08h45 * @param {number} time Le temps (float) * @returns {string} le temps (string) */ function toTime(time) { let heure = Math.floor(time); let minutes = Math.round((time - heure) * 60); if (minutes < 10) { minutes = `0${minutes}`; } if (heure < 10) { heure = `0${heure}`; } return `${heure}h${minutes}`; } /** * Transforme une date iso en une date lisible: * new Date('2023-03-03') -> "vendredi 3 mars 2023" * @param {Date} date * @param {object} styles * @returns */ function formatDate(date, styles = { dateStyle: "full" }) { return new Intl.DateTimeFormat("fr-FR", styles).format(date); } /** * Met à jour la date visible sur la page en la formatant */ function updateDate() { const dateInput = document.querySelector("#tl_date"); const date = dateInput.valueAsDate ?? new Date(); let dateStr = ""; if (!verifyNonWorkDays(date.getDay(), nonWorkDays)) { dateStr = formatDate(date).capitalize(); } else { // On se rend au dernier jour travaillé disponible const lastWorkDay = getNearestWorkDay(date); const att = document.createTextNode( `Le jour sélectionné (${formatDate(date)}) 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é : ${formatDate( lastWorkDay )}.` ) ); openAlertModal("Attention", div, "", "#eec660"); dateInput.value = lastWorkDay.toISOString().split("T")[0]; dateStr = formatDate(lastWorkDay).capitalize(); } document.querySelector("#datestr").textContent = dateStr; return true; } function getNearestWorkDay(date) { const aDay = 86400000; // 24 * 3600 * 1000 | H * s * ms let day = date; let count = 0; while (verifyNonWorkDays(day.getDay(), nonWorkDays) && count++ < 7) { day = new Date(day - aDay); } return day; } function verifyDateInSemester() { const date = getDate(); const periodSemester = getFormSemestreDates(); return date.isBetween(periodSemester.deb, periodSemester.fin, "[]"); } /** * Ajoute la possibilité d'ouvrir le calendrier * lorsqu'on clique sur la date */ function setupDate(onchange = null) { const datestr = document.querySelector("#datestr"); const input = document.querySelector("#tl_date"); datestr.addEventListener("click", () => { if (!input.disabled) { try { input.showPicker(); } catch {} } }); if (onchange != null) { input.addEventListener("change", onchange); } } /** * GetAssiduitesOnDateChange * (Utilisé uniquement avec étudiant unique) */ function getAssiduitesOnDateChange() { if (!isSingleEtud()) return; actualizeEtud(etudid); } /** * Transforme une date iso en date intelligible * @param {String} str date iso * @param {String} separator le séparateur de la date intelligible (01/01/2000 {separtor} 10:00) * @returns {String} la date intelligible */ function formatDateModal(str, separator = " ") { return new Date(str).format("DD/MM/Y HH:mm").replace(" ", separator); } /** * Vérifie si la date sélectionnée n'est pas un jour non travaillé * Renvoie Vrai si le jour est non travaillé */ function verifyNonWorkDays(day, nonWorkdays) { let d = ""; switch (day) { case 0: d = "dim"; break; case 1: d = "lun"; break; case 2: d = "mar"; break; case 3: d = "mer"; break; case 4: d = "jeu"; break; case 5: d = "ven"; break; case 6: d = "sam"; break; } return nonWorkdays.indexOf(d) != -1; } /** * Fonction qui vérifie si une période est dans un interval * Objet période / interval * { * deb: Date, * fin: Date, * } * @param {object} period * @param {object} interval * @returns {boolean} Vrai si la période est dans l'interval */ function hasTimeConflict(period, interval) { return period.deb.isBefore(interval.fin) && period.fin.isAfter(interval.deb); } /** * On récupère la période de la timeline * @returns {deb : Date, fin: Date)} */ function getTimeLineTimes() { //getPeriodValues() -> retourne la position de la timeline [a,b] avec a et b des number let values = getPeriodValues(); //On récupère la date const dateiso = document.querySelector("#tl_date").value; //On génère des objets temps values = values.map((el) => { el = toTime(el).replace("h", ":"); el = `${dateiso}T${el}`; return new Date(el); }); return { deb: values[0], fin: values[1] }; } /** * Vérification de l'égalité entre un conflit et la période de la timeline * @param {object} conflict * @returns {boolean} Renvoie Vrai si la période de la timeline est égal au conflit */ function isConflictSameAsPeriod(conflict, period = undefined) { const tlTimes = period == undefined ? getTimeLineTimes() : period; const clTimes = { deb: new Date(conflict.date_debut), fin: new Date(conflict.date_fin), }; return tlTimes.deb.isSame(clTimes.deb) && tlTimes.fin.isSame(clTimes.fin); } /** * Retourne un objet Date de la date sélectionnée * @returns {Date} la date sélectionnée */ function getDate() { const date = new Date(document.querySelector("#tl_date").value); date.setHours(0, 0, 0, 0); return date; } /** * Retourne un objet date représentant le jour suivant * @returns {Date} le jour suivant */ function getNextDate() { const date = getDate(); const next = new Date(date.valueOf()); next.setDate(date.getDate() + 1); next.setHours(0, 0, 0, 0); return next; } /** * Retourne un objet date représentant le jour précédent * @returns {Date} le jour précédent */ function getPrevDate() { const date = getDate(); const next = new Date(date.valueOf()); next.setDate(date.getDate() - 1); next.setHours(0, 0, 0, 0); return next; } /** * Transformation d'un objet Date en chaîne ISO * @param {Date} date * @returns {string} la date iso avec le timezone */ function toIsoString(date) { var tzo = -date.getTimezoneOffset(), dif = tzo >= 0 ? "+" : "-", pad = function (num) { return (num < 10 ? "0" : "") + num; }; return ( date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + "T" + pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds()) + dif + pad(Math.floor(Math.abs(tzo) / 60)) + ":" + pad(Math.abs(tzo) % 60) ); } /** * Transforme un temps numérique en une date * @param {number} nb * @returns {Date} Une date formée du temps donné et de la date courante */ function numberTimeToDate(nb) { time = toTime(nb).replace("h", ":"); date = document.querySelector("#tl_date").value; datetime = `${date}T${time}`; return new Date(datetime); } // <<== Gestion des assiduités ==>> /** * Récupère les assiduités des étudiants * en fonction de : * - du semestre * - de la date courant et du jour précédent. * @param {boolean} clear vidage de l'objet "assiduites" ou non * @returns {object} l'objet Assiduités { : [,]} */ function getAssiduitesFromEtuds(clear, deb, fin) { const etudIds = Object.keys(etuds).join(","); const date_debut = deb ? deb : toIsoString(getPrevDate()); const date_fin = fin ? fin : toIsoString(getNextDate()); if (clear) { assiduites = {}; } const url_api = getUrl() + `/api/assiduites/group/query?date_debut=${date_debut}&date_fin=${date_fin}&etudids=${etudIds}`; sync_get(url_api, (data, status) => { if (status === "success") { const dataKeys = Object.keys(data); dataKeys.forEach((key) => { if (clear || !(key in assiduites)) { assiduites[key] = data[key]; } else { assiduites[key] = assiduites[key].concat(data[key]); } let assi_ids = []; assiduites[key] = assiduites[key].reversed().filter((value) => { if (assi_ids.indexOf(value.assiduite_id) == -1) { assi_ids.push(value.assiduite_id); return true; } return false; }); }); } }); return assiduites; } /** * Création d'une assiduité pour un étudiant * @param {String} etat l'état de l'étudiant * @param {Number | String} etudid l'identifiant de l'étudiant * * TODO : Rendre asynchrone */ function createAssiduite(etat, etudid) { const tlTimes = getTimeLineTimes(); let assiduite = { date_debut: tlTimes.deb.toIsoUtcString(), date_fin: tlTimes.fin.toIsoUtcString(), etat: etat, }; assiduite = setModuleImplId(assiduite); if (!hasModuleImpl(assiduite) && window.forceModule) { const html = `

Aucun module n'a été spécifié

`; const div = document.createElement("div"); div.innerHTML = html; openAlertModal("Erreur Module", div); return false; } const path = getUrl() + `/api/assiduite/${etudid}/create`; let with_errors = false; sync_post( path, [assiduite], (data, status) => { //success if (data.success.length > 0) { let obj = data.success["0"].message.assiduite_id; } if (data.errors.length > 0) { console.error(data.errors["0"].message); if (data.errors["0"].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); } with_errors = true; } }, (data, status) => { //error console.error(data, status); errorAlert(); with_errors = true; } ); return !with_errors; } /** * Suppression d'une assiduité * @param {String | Number} assiduite_id l'identifiant de l'assiduité * TODO : Rendre asynchrone */ function deleteAssiduite(assiduite_id) { const path = getUrl() + `/api/assiduite/delete`; sync_post( path, [assiduite_id], (data, status) => { //success if (data.success.length > 0) { let obj = data.success["0"].message.assiduite_id; } }, (data, status) => { //error console.error(data, status); errorAlert(); } ); return true; } function hasModuleImpl(assiduite) { if (assiduite.moduleimpl_id != null) return true; return ( assiduite.hasOwnProperty("external_data") && assiduite.external_data != null && assiduite.external_data.hasOwnProperty("module") ); } /** * * @param {String | Number} assiduite_id l'identifiant d'une assiduité * @param {String} etat l'état à modifier * @returns {boolean} si l'édition a fonctionné * TODO : Rendre asynchrone */ function editAssiduite(assiduite_id, etat, assi) { if (assi.length != 1 || !assi[0].hasOwnProperty("assiduite_id")) { const html = `

Aucune assiduité n'a pû être éditée

`; const div = document.createElement("div"); div.innerHTML = html; openAlertModal("Erreur", div); return; } let assiduite = { etat: etat, external_data: assi ? assi.external_data : null, }; assiduite = setModuleImplId(assiduite); if (!hasModuleImpl(assiduite) && window.forceModule) { const html = `

Aucun module n'a été spécifié

`; const div = document.createElement("div"); div.innerHTML = html; openAlertModal("Erreur Module", div); return; } const path = getUrl() + `/api/assiduite/${assiduite_id}/edit`; let bool = false; sync_post( path, assiduite, (data, status) => { bool = true; }, (data, status) => { //error console.error(data, status); try { errorJson = data.responseJSON; if (errorJson.message == "param 'moduleimpl_id': etud non inscrit") { const html = `

L'étudiant n'est pas inscrit à ce module

`; const div = document.createElement("div"); div.innerHTML = html; openAlertModal("Erreur Module", div); return; } if ( errorJson.message == "param 'moduleimpl_id' : le moduleimpl_id ne peut pas être nul" ) { const html = `

Un module doit être spécifié

`; const div = document.createElement("div"); div.innerHTML = html; openAlertModal("Erreur Module", div); return; } } catch (e) { console.error(e); //errorAlert(); } } ); return bool; } /** * Récupération des assiduités conflictuelles avec la période de la time line * @param {String | Number} etudid identifiant de l'étudiant * @returns {Array[Assiduité]} un tableau d'assiduité */ function getAssiduitesConflict(etudid, periode) { const etudAssiduites = assiduites[etudid]; if (!etudAssiduites) { return []; } if (!periode) { periode = getTimeLineTimes(); } return etudAssiduites.filter((assi) => { const interval = { deb: new Date(assi.date_debut), fin: new Date(assi.date_fin), }; const test = hasTimeConflict(periode, interval); return test; }); } /** * Récupération de la dernière assiduité du jour précédent * @param {String | Number} etudid l'identifiant de l'étudiant * @returns {Assiduité} la dernière assiduité du jour précédent */ function getLastAssiduiteOfPrevDate(etudid) { const etudAssiduites = assiduites[etudid]; if (!etudAssiduites) { return ""; } const period = { deb: getPrevDate(), fin: getDate(), }; const prevAssiduites = etudAssiduites .filter((assi) => { const interval = { deb: new Date(assi.date_debut), fin: new Date(assi.date_fin), }; return hasTimeConflict(period, interval); }) .sort((a, b) => { const a_fin = new Date(a.date_fin); const b_fin = new Date(b.date_fin); return b_fin < a_fin; }); if (prevAssiduites.length < 1) { return null; } return prevAssiduites.pop(); } /** * Récupération de l'état appointé * @param {HTMLFieldSetElement} field le conteneur des boutons d'assiduité d'une ligne étudiant * @returns {String} l'état appointé : ('present','absent','retard', 'remove') * * état = 'remove' si le clic désélectionne une assiduité appointée */ function getAssiduiteValue(field) { const checkboxs = Array.from(field.children); let value = "remove"; checkboxs.forEach((chbox) => { if (chbox.checked) { value = chbox.value; } }); return value; } /** * Mise à jour des assiduités d'un étudiant * @param {String | Number} etudid identifiant de l'étudiant */ function actualizeEtudAssiduite(etudid) { const date_debut = toIsoString(getPrevDate()); const date_fin = toIsoString(getNextDate()); const url_api = getUrl() + `/api/assiduites/${etudid}/query?date_debut=${date_debut}&date_fin=${date_fin}`; sync_get(url_api, (data, status) => { if (status === "success") { assiduites[etudid] = data; } }); } function getAllAssiduitesFromEtud( etudid, action, order = false, justifs = false, courant = false ) { const url_api = getUrl() + `/api/assiduites/${etudid}${ order ? "/query?order%°" .replace("%", justifs ? "&with_justifs" : "") .replace("°", courant ? "&courant" : "") : "" }`; $.ajax({ async: true, type: "GET", url: url_api, success: (data, status) => { if (status === "success") { assiduites[etudid] = data; action(data); } }, error: () => {}, }); } /** * Déclenchement d'une action après appuie sur un bouton d'assiduité * @param {HTMLInputElement} element Bouton d'assiduité appuyé */ function assiduiteAction(element) { const field = element.parentElement; const type = field.getAttribute("type"); const etudid = parseInt(field.getAttribute("etudid")); const assiduite_id = parseInt(field.getAttribute("assiduite_id")); const etat = getAssiduiteValue(field); // Cas de l'action de masse -> peuplement des queues if (currentMassAction) { if (currentMassActionEtat != "remove") { switch (type) { case "création": addToMassActionQueue("creer", { etat: etat, etudid: etudid }); break; case "édition": if (etat != "remove") { addToMassActionQueue("editer", { etat: etat, assiduite_id: assiduite_id, }); } break; } } else if (type == "édition") { addToMassActionQueue("supprimer", assiduite_id); } } else { // Cas normal -> mise à jour en base let done = false; switch (type) { case "création": done = createAssiduite(etat, etudid); break; case "édition": if (etat === "remove") { done = deleteAssiduite(assiduite_id); } else { done = editAssiduite( assiduite_id, etat, assiduites[etudid].filter((a) => a.assiduite_id == assiduite_id) ); } break; case "conflit": const conflitResolver = new ConflitResolver( assiduites[etudid], getTimeLineTimes(), { deb: getDate(), fin: getNextDate(), } ); const update = (assi) => { actualizeEtud(assi.etudid); }; conflitResolver.callbacks = { delete: update, edit: update, split: update, }; conflitResolver.open(); return; } if (type != "conflit" && done) { 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 = `${etuds[etudid].nom.toUpperCase()} ${etuds[ etudid ].prenom.capitalize()}`; const span = document.createElement("span"); span.innerHTML = etatAffiche.replace("%etud%", nom_prenom); pushToast( generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5) ); } actualizeEtud(etudid, !isSingleEtud); } } // <<== Gestion de l'affichage des barres étudiant ==>> /** * Génère l'HTML lié à la barre d'un étudiant * @param {Etudiant} etud représentation objet d'un étudiant * @param {Number} index l'index de l'étudiant dans la liste * @param {AssiduitéMod} assiduite Objet représentant l'état de l'étudiant pour la période de la timeline * @returns {String} l'HTML généré */ function generateEtudRow( etud, index, assiduite = { etatAssiduite: "", type: "création", id: -1, date_debut: null, date_fin: null, prevAssiduites: "", } ) { // Génération des boutons du choix de l'assiduité let assi = ""; ["present", "retard", "absent"].forEach((abs) => { if (abs.toLowerCase() === assiduite.etatAssiduite.toLowerCase()) { assi += ``; } else { assi += ``; } }); const conflit = assiduite.type == "conflit" ? "conflit" : ""; const pdp_url = `${getUrl()}/api/etudiant/etudid/${etud.id}/photo?size=small`; let defdem = ""; try { if (etud.id in etudsDefDem) { defdem = etudsDefDem[etud.id] == "D" ? "dem" : "def"; } } catch (_) {} const HTML = ``; return HTML; } /** * Insertion de la ligne étudiant * @param {Etudiant} etud l'objet représentant un étudiant * @param {Number} index le n° de l'étudiant dans la liste des étudiants * @param {boolean} output ajout automatique dans la page ou non (default : Non) * @returns {String} HTML si output sinon rien */ function insertEtudRow(etud, index, output = false) { const etudHolder = document.querySelector(".etud_holder"); const conflict = getAssiduitesConflict(etud.id); const prevAssiduite = getLastAssiduiteOfPrevDate(etud.id); let assiduite = { etatAssiduite: "", type: "création", id: -1, date_debut: null, date_fin: null, prevAssiduites: prevAssiduite, }; if (conflict.length > 0) { assiduite.etatAssiduite = conflict[0].etat; assiduite.id = conflict[0].assiduite_id; assiduite.date_debut = conflict[0].date_debut; assiduite.date_fin = conflict[0].date_fin; if (isConflictSameAsPeriod(conflict[0])) { assiduite.type = "édition"; } else { assiduite.type = "conflit"; } } let row = generateEtudRow(etud, index, assiduite); if (output) { return row; } etudHolder.insertAdjacentHTML("beforeend", row); row = document.getElementById(`etud_row_${etud.id}`); const prev = row.querySelector("#prevDateAssi"); setupAssiduiteBuble(prev, prevAssiduite); const bar = row.querySelector(".assiduites_bar"); bar.appendChild(createMiniTimeline(assiduites[etud.id])); if (!verifyDateInSemester() || readOnly) { row.querySelector(".btns_field.single").setAttribute("disabled", "true"); } } /** * Mise à jour d'une ligne étudiant * @param {String | Number} etudid l'identifiant de l'étudiant */ function actualizeEtud(etudid) { actualizeEtudAssiduite(etudid); //Actualize row const etudHolder = document.querySelector(".etud_holder"); const ancient_row = document.getElementById(`etud_row_${etudid}`); let new_row = document.createElement("div"); new_row.innerHTML = insertEtudRow( etuds[etudid], ancient_row.querySelector(".index").textContent, true ); setupCheckBox(new_row.firstElementChild); const bar = new_row.firstElementChild.querySelector(".assiduites_bar"); bar.appendChild(createMiniTimeline(assiduites[etudid])); const prev = new_row.firstElementChild.querySelector("#prevDateAssi"); if (isSingleEtud()) { prev.classList.add("single"); } setupAssiduiteBuble(prev, getLastAssiduiteOfPrevDate(etudid)); etudHolder.replaceChild(new_row.firstElementChild, ancient_row); } /** * Génération de toutes les lignes étudiant */ function generateAllEtudRow() { if (isSingleEtud()) { try { actualizeEtud(etudid); } catch (ignored) {} return; } if (!document.querySelector(".selectors")?.disabled) { return; } document.querySelector(".etud_holder").innerHTML = ""; etuds_ids = Object.keys(etuds).sort((a, b) => etuds[a].nom > etuds[b].nom ? 1 : etuds[b].nom > etuds[a].nom ? -1 : 0 ); let mod = getModuleImplId(); etuds_ids = etuds_ids.filter((i) => { return checkInscriptionModule(mod, i); }); for (let i = 0; i < etuds_ids.length; i++) { const etud = etuds[etuds_ids[i]]; insertEtudRow(etud, i + 1); } setupCheckBox(); } // <== Gestion du modal de conflit ==> // <<== Gestion de la récupération d'informations ==>> /** * Récupération des ids des groupes * @returns la liste des ids des groupes */ function getGroupIds() { const btns = document.querySelector(".multiselect-container.dropdown-menu"); const groups = Array.from(btns.querySelectorAll(".active")).map((el) => { return el.querySelector("input").value; }); return groups; } /** * Récupération du moduleimpl_id * @returns {String} l'identifiant ou null si inéxistant */ function getModuleImplId() { const val = document.querySelector("#moduleimpl_select")?.value; return ["", undefined, null].includes(val) ? null : val; } function setModuleImplId(assiduite, module = null) { const moduleimpl = module == null ? getModuleImplId() : module; if (moduleimpl === "autre") { if ( assiduite.hasOwnProperty("external_data") && assiduite.external_data != null ) { if (assiduite.external_data.hasOwnProperty("module")) { assiduite.external_data.module = "Autre"; } else { assiduite["external_data"] = { module: "Autre" }; } } else { assiduite["external_data"] = { module: "Autre" }; } assiduite.moduleimpl_id = null; } else { assiduite["moduleimpl_id"] = moduleimpl; if ( assiduite.hasOwnProperty("external_data") && assiduite.external_data != null ) { if (assiduite.external_data.hasOwnProperty("module")) { delete assiduite.external_data.module; } } } return assiduite; } /** * Récupération de l'id du formsemestre * @returns {String} l'identifiant du formsemestre */ function getFormSemestreId() { return document.querySelector(".formsemestre_id").textContent; } /** * Récupère la période du semestre * @returns {object} période {deb,fin} */ function getFormSemestreDates() { const dateDeb = document.getElementById( "formsemestre_date_debut" ).textContent; const dateFin = document.getElementById("formsemestre_date_fin").textContent; return { deb: new Date(dateDeb), fin: new Date(dateFin), }; } /** * Récupère un objet étudiant à partir de son id * @param {Number} etudid */ function getSingleEtud(etudid) { sync_get(getUrl() + `/api/etudiant/etudid/${etudid}`, (data) => { etuds[etudid] = data; }); } function isSingleEtud() { return location.href.includes("SignaleAssiduiteEtud"); } function getCurrentAssiduiteModuleImplId() { const currentAssiduites = getAssiduitesConflict(etudid); if (currentAssiduites.length > 0) { let mod = currentAssiduites[0].moduleimpl_id; if ( mod == null && currentAssiduites[0].hasOwnProperty("external_data") && currentAssiduites[0].external_data != null && currentAssiduites[0].external_data.hasOwnProperty("module") ) { mod = currentAssiduites[0].external_data.module; } return mod == null ? "" : mod; } return ""; } function getCurrentAssiduite(etudid) { const field = document.querySelector( `fieldset.btns_field.single[etudid='${etudid}']` ); if (!field) return null; const assiduite_id = parseInt(field.getAttribute("assiduite_id")); const type = field.getAttribute("type"); if (type == "édition") { let assi = null; assiduites[etudid].forEach((a) => { if (a.assiduite_id === assiduite_id) { assi = a; } }); return assi; } else { return null; } } // <<== Gestion de la justification ==>> function getJustificatifFromPeriod(date, etudid, update) { $.ajax({ async: true, type: "GET", url: getUrl() + `/api/justificatifs/${etudid}/query?date_debut=${date.deb .add(1, "seconds") .toIsoUtcString()}&date_fin=${date.fin .add(-1, "seconds") .toIsoUtcString()}`, success: (data) => { update(data); }, error: () => {}, }); } function updateJustifyBtn() { if (isSingleEtud()) { const assi = getCurrentAssiduite(etudid); const just = assi ? !assi.est_just : false; const btn = document.getElementById("justif-rapide"); if (!just) { btn.setAttribute("disabled", "true"); } else { btn.removeAttribute("disabled"); } } } function fastJustify(assiduite) { if (assiduite.etat == "PRESENT") { openAlertModal( "Attention", document.createTextNode("Une présence ne peut être justifiée.") ); return; } const period = { deb: new Date(assiduite.date_debut), fin: new Date(assiduite.date_fin), }; const action = (justifs) => { //créer un nouveau justificatif // Afficher prompt -> demander raison et état const success = () => { const raison = document.getElementById("promptText").value; const etat = document.getElementById("promptSelect").value; //créer justificatif const justif = { date_debut: new Date(assiduite.date_debut).toIsoUtcString(), date_fin: new Date(assiduite.date_fin).toIsoUtcString(), raison: raison, etat: etat, }; createJustificatif(justif); generateAllEtudRow(); try { loadAll(); } catch {} }; const content = document.createElement("fieldset"); const htmlPrompt = `Entrez l'état du justificatif : Raison: `; content.innerHTML = htmlPrompt; openPromptModal( "Nouveau justificatif (Rapide)", content, success, () => {}, "#7059FF" ); }; if (assiduite.etudid) { getJustificatifFromPeriod(period, assiduite.etudid, action); } } function justifyAssiduite(assiduite_id, justified) { const assiduite = { est_just: justified, }; const path = getUrl() + `/api/assiduite/${assiduite_id}/edit`; let bool = false; sync_post( path, assiduite, (data, status) => { bool = true; }, (data, status) => { //error console.error(data, status); errorAlert(); } ); return bool; } function createJustificatif(justif, success = () => {}) { const path = getUrl() + `/api/justificatif/${etudid}/create`; sync_post(path, [justif], success, (data, status) => { //error console.error(data, status); errorAlert(); }); } function getAllJustificatifsFromEtud( etudid, action, order = false, courant = false ) { const url_api = getUrl() + `/api/justificatifs/${etudid}${ order ? "/query?order°".replace("°", courant ? "&courant" : "") : "" }`; $.ajax({ async: true, type: "GET", url: url_api, success: (data, status) => { if (status === "success") { action(data); } }, error: () => {}, }); } function deleteJustificatif(justif_id) { const path = getUrl() + `/api/justificatif/delete`; sync_post( path, [justif_id], (data, status) => { //success if (data.success.length > 0) { } }, (data, status) => { //error console.error(data, status); errorAlert(); } ); } function errorAlert() { const html = `

Il peut s'agir d'un problème de droits, ou d'une modification survenue sur le serveur.

Si le problème persiste, demandez de l'aide sur le Discord d'assistance de ScoDoc

`; const div = document.createElement("div"); div.innerHTML = html; openAlertModal("Une erreur s'est produite", div); } const moduleimpls = {}; function getModuleImpl(assiduite) { if (assiduite == null) return "Pas de module"; 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" ? "Tout module" : assiduite.external_data.module; } else { return "Pas de module"; } } if (id in moduleimpls) { return moduleimpls[id]; } const url_api = getUrl() + `/api/moduleimpl/${id}`; sync_get( url_api, (data) => { moduleimpls[id] = `${data.module.code} ${data.module.abbrev}`; }, (data) => { moduleimpls[id] = "Pas de module"; } ); return moduleimpls[id]; } // le nom de l'utilisateur à afficher function getUser(obj) { if ( obj.hasOwnProperty("external_data") && obj.external_data != null && obj.external_data.hasOwnProperty("enseignant") ) { return obj.external_data.enseignant; } return obj.user_nom_complet || obj.user_id; } const inscriptionsModule = {}; function checkInscriptionModule(moduleimpl_id, etudid) { if ([null, "", "autre"].indexOf(moduleimpl_id) !== -1) return true; if (!inscriptionsModule.hasOwnProperty(moduleimpl_id)) { const path = getUrl() + `/api/moduleimpl/${moduleimpl_id}/inscriptions`; sync_get( path, (data, status) => { inscriptionsModule[moduleimpl_id] = data; }, (data, status) => { //error console.error(data, status); errorAlert(); } ); } const etudsInscrits = inscriptionsModule[moduleimpl_id].map((i) => i.etudid); return etudsInscrits.indexOf(Number(etudid)) !== -1; }