forked from ScoDoc/ScoDoc
1875 lines
49 KiB
JavaScript
1875 lines
49 KiB
JavaScript
// <=== CONSTANTS and GLOBALS ===>
|
|
|
|
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,
|
|
});
|
|
|
|
/**
|
|
* 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 && !readOnly) {
|
|
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);
|
|
return;
|
|
}
|
|
|
|
getAssiduitesFromEtuds(true);
|
|
|
|
document.querySelector(".selectors").disabled = true;
|
|
$("#tl_date").datepicker("option", "disabled", true);
|
|
generateMassAssiduites();
|
|
generateAllEtudRow();
|
|
btn.remove();
|
|
onlyAbs();
|
|
};
|
|
|
|
if (!verifyDateInSemester()) {
|
|
const HTML = `
|
|
<p>Attention, la date sélectionnée n'est pas comprise dans le semestre.</p>
|
|
<p>Cette page permet l'affichage et la modification des assiduités uniquement pour le semestre sélectionné.</p>
|
|
<p>Vous n'aurez donc pas accès aux assiduités.</p>
|
|
<p>Appuyer sur "Valider" uniquement si vous souhaitez poursuivre sans modifier la date.</p>
|
|
`;
|
|
|
|
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) {
|
|
//TODO Optimiser : rendre asynchrone + sans jquery
|
|
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
|
|
*/
|
|
async function async_get(path, success, errors) {
|
|
console.log("async_get " + path);
|
|
let response;
|
|
try {
|
|
response = await fetch(path);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
success(data);
|
|
} 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 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) {
|
|
//TODO Optimiser : rendre asynchrone + sans jquery
|
|
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
|
|
*/
|
|
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 {
|
|
throw new Error("Network response was not ok.");
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
if (errors) errors(error);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
// <<== 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.toFakeIso(),
|
|
date_fin: tlTimes.fin.toFakeIso(),
|
|
};
|
|
|
|
assiduite = setModuleImplId(assiduite);
|
|
if (!hasModuleImpl(assiduite) && window.forceModule) {
|
|
const html = `
|
|
<h3>Aucun module n'a été spécifié</h3>
|
|
`;
|
|
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 = `
|
|
<h3>Aucun module n'a été spécifié</h3>
|
|
`;
|
|
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 <u><strong>${etat
|
|
.capitalize()
|
|
.trim()}</strong></u>`;
|
|
} else {
|
|
span.innerHTML = `Aucun étudiant n'a été mis <u><strong>${etat
|
|
.capitalize()
|
|
.trim()}</strong></u>`;
|
|
}
|
|
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 = `
|
|
<span>Mettre tout le monde :</span>
|
|
<fieldset class="btns_field mass">
|
|
<input type="checkbox" value="present" name="mass_btn_assiduites" id="mass_rbtn_present"
|
|
class="rbtn present">
|
|
<input type="checkbox" value="retard" name="mass_btn_assiduites" id="mass_rbtn_retard" class="rbtn retard">
|
|
<input type="checkbox" value="absent" name="mass_btn_assiduites" id="mass_rbtn_absent" class="rbtn absent">
|
|
<input type="checkbox" value="remove" name="mass_btn_assiduites" id="mass_rbtn_aucun" class="rbtn aucun">
|
|
</fieldset>`;
|
|
|
|
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", {
|
|
...{ timeZone: SCO_TIMEZONE },
|
|
...styles,
|
|
}).format(date);
|
|
}
|
|
|
|
/**
|
|
* Met à jour la date visible sur la page en la formatant
|
|
*/
|
|
function updateDate() {
|
|
const dateInput = document.querySelector("#tl_date");
|
|
let date = $(dateInput).datepicker("getDate");
|
|
if (date == null) {
|
|
date = new Date(Date.fromFRA(dateInput.value));
|
|
}
|
|
const intlOptions = {
|
|
dateStyle: "full",
|
|
timeZone: SCO_TIMEZONE,
|
|
};
|
|
let dateStr = "";
|
|
|
|
if (!isNonWorkDay(date, nonWorkDays)) {
|
|
dateStr = formatDate(date, intlOptions).capitalize();
|
|
} else {
|
|
// On se rend au dernier jour travaillé disponible
|
|
const lastWorkDay = getNearestWorkDay(date);
|
|
const att = document.createTextNode(
|
|
`Le jour sélectionné (${formatDate(
|
|
date,
|
|
intlOptions
|
|
)}) 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,
|
|
intlOptions
|
|
)}.`
|
|
)
|
|
);
|
|
openAlertModal("Attention", div, "", "#eec660");
|
|
|
|
$(dateInput).datepicker("setDate", date_fra);
|
|
dateInput.value = date_fra;
|
|
date = lastWorkDay;
|
|
|
|
dateStr = formatDate(lastWorkDay, {
|
|
dateStyle: "full",
|
|
timeZone: SCO_TIMEZONE,
|
|
}).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 (isNonWorkDay(day, 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 (!document.querySelector(".selectors").disabled) {
|
|
try {
|
|
document.querySelector(".infos .ui-datepicker-trigger").click();
|
|
} catch {}
|
|
}
|
|
});
|
|
|
|
if (onchange != null) {
|
|
$(input).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 isNonWorkDay(day, nonWorkdays) {
|
|
const d = Intl.DateTimeFormat("fr-FR", {
|
|
timeZone: SCO_TIMEZONE,
|
|
weekday: "short",
|
|
})
|
|
.format(day)
|
|
.replace(".", "");
|
|
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 = getDate().format("YYYY-MM-DD");
|
|
|
|
//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(Date.removeUTC(conflict.date_debut)),
|
|
fin: new Date(Date.removeUTC(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 =
|
|
$("#tl_date").datepicker("getDate") ??
|
|
new Date(Date.fromFRA(document.querySelector("#tl_date").value));
|
|
return date.startOf("day");
|
|
}
|
|
|
|
/**
|
|
* Retourne un objet date représentant le jour suivant
|
|
* @returns {Date} le jour suivant
|
|
*/
|
|
function getNextDate() {
|
|
const date = getDate();
|
|
return date.clone().add(1, "days");
|
|
}
|
|
/**
|
|
* Retourne un objet date représentant le jour précédent
|
|
* @returns {Date} le jour précédent
|
|
*/
|
|
function getPrevDate() {
|
|
const date = getDate();
|
|
return date.clone().add(-1, "days");
|
|
}
|
|
|
|
/**
|
|
* Transformation d'un objet Date en chaîne ISO
|
|
* @param {Date} date
|
|
* @returns {string} la date iso avec le timezone
|
|
*/
|
|
|
|
/**
|
|
* 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 = getDate().format("YYYY-MM-DD");
|
|
|
|
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 {<etudid:str> : [<assiduite>,]}
|
|
*/
|
|
function getAssiduitesFromEtuds(clear, deb, fin) {
|
|
const etudIds = Object.keys(etuds).join(",");
|
|
|
|
const date_debut = deb ? deb : getPrevDate().toFakeIso();
|
|
const date_fin = fin ? fin : getNextDate().toFakeIso();
|
|
|
|
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.toFakeIso(),
|
|
date_fin: tlTimes.fin.toFakeIso(),
|
|
etat: etat,
|
|
};
|
|
|
|
assiduite = setModuleImplId(assiduite);
|
|
|
|
if (!hasModuleImpl(assiduite) && window.forceModule) {
|
|
const html = `
|
|
<h3>Aucun module n'a été spécifié</h3>
|
|
`;
|
|
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 = `
|
|
<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 (
|
|
data.errors["0"].message == "L'étudiant n'est pas inscrit au module"
|
|
) {
|
|
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);
|
|
}
|
|
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 = `
|
|
<h3>Aucune assiduité n'a pû être éditée</h3>
|
|
`;
|
|
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 = `
|
|
<h3>Aucun module n'a été spécifié</h3>
|
|
`;
|
|
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 = `
|
|
<h3>L'étudiant n'est pas inscrit à ce module</h3>
|
|
`;
|
|
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 = `
|
|
<h3>Un module doit être spécifié</h3>
|
|
`;
|
|
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 timeline
|
|
* @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(Date.removeUTC(assi.date_debut)),
|
|
fin: new Date(Date.removeUTC(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(Date.removeUTC(assi.date_debut)),
|
|
fin: new Date(Date.removeUTC(assi.date_fin)),
|
|
};
|
|
|
|
return hasTimeConflict(period, interval);
|
|
})
|
|
.sort((a, b) => {
|
|
const a_fin = new Date(Date.removeUTC(a.date_fin));
|
|
const b_fin = new Date(Date.removeUTC(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 = getPrevDate().toFakeIso();
|
|
const date_fin = getNextDate().toFakeIso();
|
|
|
|
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" : "")
|
|
: ""
|
|
}`;
|
|
//TODO Utiliser async_get au lieu de jquery
|
|
$.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) <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 = `${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 += `<input checked type="checkbox" value="${abs}" name="btn_assiduites_${index}" id="rbtn_${abs}" class="rbtn ${abs}" title="${abs}">`;
|
|
} else {
|
|
assi += `<input type="checkbox" value="${abs}" name="btn_assiduites_${index}" id="rbtn_${abs}" class="rbtn ${abs}" title="${abs}">`;
|
|
}
|
|
});
|
|
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 = `<div class="etud_row ${conflit} ${defdem}" id="etud_row_${
|
|
etud.id
|
|
}">
|
|
|
|
<div class="index">${index}</div>
|
|
<div class="name_field">
|
|
|
|
<img class="pdp" src="${pdp_url}">
|
|
|
|
<a class="name_set" href="BilanEtud?etudid=${etud.id}">
|
|
<h4 class="nom">${etud.nom}</h4>
|
|
<h5 class="prenom">${etud.prenom}</h5>
|
|
</a>
|
|
|
|
</div>
|
|
<div class="assiduites_bar">
|
|
<div id="prevDateAssi" class="${assiduite.prevAssiduites?.etat?.toLowerCase()}">
|
|
</div>
|
|
</div>
|
|
<fieldset class="btns_field single" etudid="${etud.id}" assiduite_id="${
|
|
assiduite.id
|
|
}" type="${assiduite.type}">
|
|
|
|
${assi}
|
|
|
|
</fieldset>
|
|
|
|
|
|
</div>`;
|
|
|
|
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 = Date.removeUTC(conflict[0].date_debut);
|
|
assiduite.date_fin = Date.removeUTC(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")
|
|
.toFakeIso()}&date_fin=${date.fin.add(-1, "seconds").toFakeIso()}`,
|
|
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(Date.removeUTC(assiduite.date_debut)),
|
|
fin: new Date(Date.removeUTC(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(Date.removeUTC(assiduite.date_debut)).toFakeIso(),
|
|
date_fin: new Date(Date.removeUTC(assiduite.date_fin)).toFakeIso(),
|
|
raison: raison,
|
|
etat: etat,
|
|
};
|
|
|
|
createJustificatif(justif);
|
|
|
|
generateAllEtudRow();
|
|
try {
|
|
loadAll();
|
|
} catch {}
|
|
};
|
|
|
|
const content = document.createElement("fieldset");
|
|
|
|
const htmlPrompt = `<legend>Entrez l'état du justificatif :</legend>
|
|
<select name="promptSelect" id="promptSelect" required>
|
|
<option value="valide">Valide</option>
|
|
<option value="attente">En Attente de validation</option>
|
|
<option value="non_valide">Non Valide</option>
|
|
<option value="modifie">Modifié</option>
|
|
</select>
|
|
<legend>Raison:</legend>
|
|
<textarea type="text" placeholder="Explication du justificatif (non obligatoire)" id="promptText" style="width:100%;"></textarea>
|
|
`;
|
|
|
|
content.innerHTML = htmlPrompt;
|
|
|
|
openPromptModal(
|
|
"Nouveau justificatif (Rapide)",
|
|
content,
|
|
success,
|
|
() => {},
|
|
"var(--color-primary)"
|
|
);
|
|
};
|
|
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" : "") : ""
|
|
}`;
|
|
|
|
//TODO Utiliser async_get au lieu de jquery
|
|
$.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 = `
|
|
<h4>Il peut s'agir d'un problème de droits, ou d'une modification survenue sur le serveur.</h4>
|
|
<p>Si le problème persiste, demandez de l'aide sur le Discord d'assistance de ScoDoc</p>
|
|
|
|
`;
|
|
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;
|
|
}
|