forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -183,7 +183,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.etud_row.conflit {
|
.etud_row.conflit {
|
||||||
background-color: #ff000061;
|
background-color: #ff0000c2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.etud_row .assiduites_bar .absent {
|
.etud_row .assiduites_bar .absent {
|
||||||
@ -261,7 +261,7 @@
|
|||||||
|
|
||||||
/*<== Modal conflit ==>*/
|
/*<== Modal conflit ==>*/
|
||||||
.modal {
|
.modal {
|
||||||
display: none;
|
display: block;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 500;
|
z-index: 500;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -388,7 +388,6 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 1px solid #888;
|
border: 1px solid #888;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
height: 30%;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
display: none;
|
display: none;
|
||||||
@ -492,4 +491,10 @@
|
|||||||
|
|
||||||
.rouge {
|
.rouge {
|
||||||
color: crimson;
|
color: crimson;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legende {
|
||||||
|
border: 1px dashed #333;
|
||||||
|
width: 75%;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
@ -43,6 +43,12 @@ Object.defineProperty(String.prototype, "capitalize", {
|
|||||||
enumerable: false,
|
enumerable: false,
|
||||||
});
|
});
|
||||||
// <<== Outils ==>>
|
// <<== Outils ==>>
|
||||||
|
Object.defineProperty(Array.prototype, "reversed", {
|
||||||
|
value: function () {
|
||||||
|
return [...this].map(this.pop, this);
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajout des évents sur les boutons d'assiduité
|
* Ajout des évents sur les boutons d'assiduité
|
||||||
@ -88,7 +94,7 @@ function validateSelectors() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getModuleImplId() == null && forceModule) {
|
if (getModuleImplId() == null && window.forceModule) {
|
||||||
const HTML = `
|
const HTML = `
|
||||||
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
<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>Cela vient de la configuration du semestre ou plus largement du département.</p>
|
||||||
@ -181,6 +187,23 @@ function sync_post(path, data, success, errors) {
|
|||||||
error: errors,
|
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) {
|
||||||
|
$.ajax({
|
||||||
|
async: true,
|
||||||
|
type: "POST",
|
||||||
|
url: path,
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: success,
|
||||||
|
error: errors,
|
||||||
|
});
|
||||||
|
}
|
||||||
// <<== Gestion des actions de masse ==>>
|
// <<== Gestion des actions de masse ==>>
|
||||||
const massActionQueue = new Map();
|
const massActionQueue = new Map();
|
||||||
|
|
||||||
@ -489,7 +512,12 @@ function verifyDateInSemester() {
|
|||||||
|
|
||||||
const periodSemester = getFormSemestreDates();
|
const periodSemester = getFormSemestreDates();
|
||||||
|
|
||||||
return date.isBetween(periodSemester.deb, periodSemester.fin);
|
return date.isBetween(
|
||||||
|
periodSemester.deb,
|
||||||
|
periodSemester.fin,
|
||||||
|
undefined,
|
||||||
|
"[]"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -603,8 +631,8 @@ function getTimeLineTimes() {
|
|||||||
* @param {object} conflict
|
* @param {object} conflict
|
||||||
* @returns {boolean} Renvoie Vrai si la période de la timeline est égal au conflit
|
* @returns {boolean} Renvoie Vrai si la période de la timeline est égal au conflit
|
||||||
*/
|
*/
|
||||||
function isConflictSameAsTimeLine(conflict) {
|
function isConflictSameAsPeriod(conflict, period = undefined) {
|
||||||
const tlTimes = getTimeLineTimes();
|
const tlTimes = period == undefined ? getTimeLineTimes() : period;
|
||||||
const clTimes = {
|
const clTimes = {
|
||||||
deb: moment.tz(conflict.date_debut, TIMEZONE),
|
deb: moment.tz(conflict.date_debut, TIMEZONE),
|
||||||
fin: moment.tz(conflict.date_fin, TIMEZONE),
|
fin: moment.tz(conflict.date_fin, TIMEZONE),
|
||||||
@ -702,14 +730,14 @@ function numberTimeToDate(nb) {
|
|||||||
* @param {boolean} clear vidage de l'objet "assiduites" ou non
|
* @param {boolean} clear vidage de l'objet "assiduites" ou non
|
||||||
* @returns {object} l'objets Assiduités {<etudid:str> : [<assiduite>,]}
|
* @returns {object} l'objets Assiduités {<etudid:str> : [<assiduite>,]}
|
||||||
*/
|
*/
|
||||||
function getAssiduitesFromEtuds(clear, has_formsemestre = true) {
|
function getAssiduitesFromEtuds(clear, has_formsemestre = true, deb, fin) {
|
||||||
const etudIds = Object.keys(etuds).join(",");
|
const etudIds = Object.keys(etuds).join(",");
|
||||||
const formsemestre_id = has_formsemestre
|
const formsemestre_id = has_formsemestre
|
||||||
? `formsemestre_id=${getFormSemestreId()}&`
|
? `formsemestre_id=${getFormSemestreId()}&`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const date_debut = toIsoString(getPrevDate());
|
const date_debut = deb ? deb : toIsoString(getPrevDate());
|
||||||
const date_fin = toIsoString(getNextDate());
|
const date_fin = fin ? fin : toIsoString(getNextDate());
|
||||||
|
|
||||||
if (clear) {
|
if (clear) {
|
||||||
assiduites = {};
|
assiduites = {};
|
||||||
@ -717,12 +745,25 @@ function getAssiduitesFromEtuds(clear, has_formsemestre = true) {
|
|||||||
|
|
||||||
const url_api =
|
const url_api =
|
||||||
getUrl() +
|
getUrl() +
|
||||||
`/api/assiduites/group/query?date_debut=${formsemestre_id}${date_debut}&date_fin=${date_fin}&etudids=${etudIds}`;
|
`/api/assiduites/group/query?date_debut=${date_debut}&${formsemestre_id}&date_fin=${date_fin}&etudids=${etudIds}`;
|
||||||
sync_get(url_api, (data, status) => {
|
sync_get(url_api, (data, status) => {
|
||||||
if (status === "success") {
|
if (status === "success") {
|
||||||
const dataKeys = Object.keys(data);
|
const dataKeys = Object.keys(data);
|
||||||
dataKeys.forEach((key) => {
|
dataKeys.forEach((key) => {
|
||||||
assiduites[key] = data[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;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -824,18 +865,21 @@ function editAssiduite(assiduite_id, etat) {
|
|||||||
* @param {String | Number} etudid identifiant de l'étudiant
|
* @param {String | Number} etudid identifiant de l'étudiant
|
||||||
* @returns {Array[Assiduité]} un tableau d'assiduité
|
* @returns {Array[Assiduité]} un tableau d'assiduité
|
||||||
*/
|
*/
|
||||||
function getAssiduitesConflict(etudid) {
|
function getAssiduitesConflict(etudid, periode) {
|
||||||
const etudAssiduites = assiduites[etudid];
|
const etudAssiduites = assiduites[etudid];
|
||||||
if (!etudAssiduites) {
|
if (!etudAssiduites) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const period = getTimeLineTimes();
|
if (!periode) {
|
||||||
|
periode = getTimeLineTimes();
|
||||||
|
}
|
||||||
|
|
||||||
return etudAssiduites.filter((assi) => {
|
return etudAssiduites.filter((assi) => {
|
||||||
const interval = {
|
const interval = {
|
||||||
deb: moment.tz(assi.date_debut, TIMEZONE),
|
deb: moment.tz(assi.date_debut, TIMEZONE),
|
||||||
fin: moment.tz(assi.date_fin, TIMEZONE),
|
fin: moment.tz(assi.date_fin, TIMEZONE),
|
||||||
};
|
};
|
||||||
return hasTimeConflict(period, interval);
|
return hasTimeConflict(periode, interval);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -908,8 +952,8 @@ function actualizeEtudAssiduite(etudid, has_formsemestre = true) {
|
|||||||
const url_api =
|
const url_api =
|
||||||
getUrl() +
|
getUrl() +
|
||||||
`/api/assiduites/${etudid}/query?${formsemestre_id}date_debut=${date_debut}&date_fin=${date_fin}`;
|
`/api/assiduites/${etudid}/query?${formsemestre_id}date_debut=${date_debut}&date_fin=${date_fin}`;
|
||||||
|
|
||||||
sync_get(url_api, (data, status) => {
|
sync_get(url_api, (data, status) => {
|
||||||
|
console.error(data, status);
|
||||||
if (status === "success") {
|
if (status === "success") {
|
||||||
assiduites[etudid] = data;
|
assiduites[etudid] = data;
|
||||||
}
|
}
|
||||||
@ -976,8 +1020,25 @@ function assiduiteAction(element) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "conflit":
|
case "conflit":
|
||||||
openModal(assiduites[etudid]);
|
const conflitResolver = new ConflitResolver(
|
||||||
break;
|
assiduites[etudid],
|
||||||
|
getTimeLineTimes(),
|
||||||
|
{
|
||||||
|
deb: new moment.tz(getDate(), TIMEZONE),
|
||||||
|
fin: new moment.tz(getNextDate(), TIMEZONE),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const update = (assi) => {
|
||||||
|
actualizeEtud(assi.etudid);
|
||||||
|
};
|
||||||
|
conflitResolver.callbacks = {
|
||||||
|
delete: update,
|
||||||
|
edit: update,
|
||||||
|
split: update,
|
||||||
|
};
|
||||||
|
|
||||||
|
conflitResolver.open();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != "conflit") {
|
if (type != "conflit") {
|
||||||
@ -1085,7 +1146,7 @@ function insertEtudRow(etud, index, output = false) {
|
|||||||
assiduite.id = conflict[0].assiduite_id;
|
assiduite.id = conflict[0].assiduite_id;
|
||||||
assiduite.date_debut = conflict[0].date_debut;
|
assiduite.date_debut = conflict[0].date_debut;
|
||||||
assiduite.date_fin = conflict[0].date_fin;
|
assiduite.date_fin = conflict[0].date_fin;
|
||||||
if (isConflictSameAsTimeLine(conflict[0])) {
|
if (isConflictSameAsPeriod(conflict[0])) {
|
||||||
assiduite.type = "édition";
|
assiduite.type = "édition";
|
||||||
} else {
|
} else {
|
||||||
assiduite.type = "conflit";
|
assiduite.type = "conflit";
|
||||||
@ -1166,433 +1227,6 @@ function generateAllEtudRow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// <== Gestion du modal de conflit ==>
|
// <== Gestion du modal de conflit ==>
|
||||||
/**
|
|
||||||
* Mise à jour du modal de conflit
|
|
||||||
* @param {Array[Assiduité]} assiduiteList Liste des assiduités de l'étudiant
|
|
||||||
*/
|
|
||||||
function refreshModal(assiduiteList) {
|
|
||||||
const tlTime = getTimeLineTimes();
|
|
||||||
|
|
||||||
renderTimeline(assiduiteList, {
|
|
||||||
date_debut: tlTime.deb,
|
|
||||||
date_fin: tlTime.fin,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Ouverture du modal de conflit
|
|
||||||
* @param {Array[Assiduité]} assiduiteList Liste des assiduités de l'étudiant
|
|
||||||
*/
|
|
||||||
function openModal(assiduiteList) {
|
|
||||||
modal.style.display = "block";
|
|
||||||
|
|
||||||
const tlTime = getTimeLineTimes();
|
|
||||||
|
|
||||||
renderTimeline(assiduiteList, {
|
|
||||||
date_debut: tlTime.deb,
|
|
||||||
date_fin: tlTime.fin,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fermeture du modal de conflit
|
|
||||||
*/
|
|
||||||
function closeModal() {
|
|
||||||
modal.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Génération du modal
|
|
||||||
* @param {Array[Assiduité]} assiduites la liste des assiduités à afficher
|
|
||||||
* @param {Période} specialAssiduite Une assiduité représentant la période conflictuelle
|
|
||||||
*/
|
|
||||||
function renderTimeline(assiduites, specialAssiduite) {
|
|
||||||
const timeLabels = document.querySelector(".time-labels");
|
|
||||||
const assiduitesContainer = document.querySelector(".assiduites-container");
|
|
||||||
|
|
||||||
timeLabels.innerHTML = "";
|
|
||||||
assiduitesContainer.innerHTML = '<div class="assiduite-special"></div>';
|
|
||||||
|
|
||||||
// Ajout des labels d'heure sur la frise chronologique
|
|
||||||
// TODO permettre la modification des bornes (8 et 18)
|
|
||||||
for (let i = 8; i <= 18; i++) {
|
|
||||||
const timeLabel = document.createElement("div");
|
|
||||||
timeLabel.className = "time-label";
|
|
||||||
timeLabel.textContent = i < 10 ? `0${i}:00` : `${i}:00`;
|
|
||||||
timeLabels.appendChild(timeLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Placement de la période conflictuelle sur la timeline
|
|
||||||
const specialAssiduiteEl = document.querySelector(".assiduite-special");
|
|
||||||
specialAssiduiteEl.style.width = getWidth(
|
|
||||||
specialAssiduite.date_debut,
|
|
||||||
specialAssiduite.date_fin
|
|
||||||
);
|
|
||||||
specialAssiduiteEl.style.left = getLeftPosition(specialAssiduite.date_debut);
|
|
||||||
specialAssiduiteEl.style.top = "0";
|
|
||||||
specialAssiduiteEl.style.zIndex = "0"; // Place l'assiduité spéciale en arrière-plan
|
|
||||||
assiduitesContainer.appendChild(specialAssiduiteEl);
|
|
||||||
const interval = {
|
|
||||||
deb: new moment.tz(getDate(), TIMEZONE),
|
|
||||||
fin: new moment.tz(getNextDate(), TIMEZONE),
|
|
||||||
};
|
|
||||||
//Placement des assiduités sur la timeline
|
|
||||||
assiduites.forEach((assiduite) => {
|
|
||||||
const period = {
|
|
||||||
deb: new moment.tz(assiduite.date_debut, TIMEZONE),
|
|
||||||
fin: new moment.tz(assiduite.date_fin, TIMEZONE),
|
|
||||||
};
|
|
||||||
if (!hasTimeConflict(period, interval)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const el = document.createElement("div");
|
|
||||||
el.className = "assiduite";
|
|
||||||
el.style.backgroundColor = getColor(assiduite.etat);
|
|
||||||
el.style.width = getWidth(assiduite.date_debut, assiduite.date_fin);
|
|
||||||
el.style.left = getLeftPosition(assiduite.date_debut);
|
|
||||||
el.style.top = "10px";
|
|
||||||
el.setAttribute("data-id", assiduite.assiduite_id);
|
|
||||||
el.addEventListener("click", () => selectAssiduite(assiduite));
|
|
||||||
|
|
||||||
// Ajout des informations dans la visualisation d'une assiduité
|
|
||||||
const infoContainer = document.createElement("div");
|
|
||||||
infoContainer.className = "assiduite-info";
|
|
||||||
|
|
||||||
const idDiv = document.createElement("div");
|
|
||||||
idDiv.className = "assiduite-id";
|
|
||||||
idDiv.textContent = `ID: ${assiduite.assiduite_id}`;
|
|
||||||
infoContainer.appendChild(idDiv);
|
|
||||||
|
|
||||||
const periodDivDeb = document.createElement("div");
|
|
||||||
periodDivDeb.className = "assiduite-period";
|
|
||||||
periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`;
|
|
||||||
infoContainer.appendChild(periodDivDeb);
|
|
||||||
const periodDivFin = document.createElement("div");
|
|
||||||
periodDivFin.className = "assiduite-period";
|
|
||||||
periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`;
|
|
||||||
infoContainer.appendChild(periodDivFin);
|
|
||||||
|
|
||||||
const stateDiv = document.createElement("div");
|
|
||||||
stateDiv.className = "assiduite-state";
|
|
||||||
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
|
|
||||||
infoContainer.appendChild(stateDiv);
|
|
||||||
|
|
||||||
const userIdDiv = document.createElement("div");
|
|
||||||
userIdDiv.className = "assiduite-user_id";
|
|
||||||
userIdDiv.textContent = `saisi le ${formatDateModal(
|
|
||||||
assiduite.entry_date,
|
|
||||||
"à"
|
|
||||||
)} \npar ${getUserFromId(assiduite.user_id)}`;
|
|
||||||
infoContainer.appendChild(userIdDiv);
|
|
||||||
|
|
||||||
el.appendChild(infoContainer);
|
|
||||||
assiduitesContainer.appendChild(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transformation d'une date de début en position sur la timeline
|
|
||||||
* @param {String} start
|
|
||||||
* @returns {String} un déplacement par rapport à la gauche en %
|
|
||||||
*/
|
|
||||||
function getLeftPosition(start) {
|
|
||||||
const startTime = new moment.tz(start, TIMEZONE);
|
|
||||||
const startMins = (startTime.hours() - 8) * 60 + startTime.minutes();
|
|
||||||
return (startMins / (18 * 60 - 8 * 60)) * 100 + "%";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ajustement de l'espacement vertical entre les assiduités superposées
|
|
||||||
* @param {HTMLElement} container le conteneur des assiduités
|
|
||||||
* @param {String} start la date début de l'assiduité à placer
|
|
||||||
* @param {String} end la date de fin de l'assiduité à placer
|
|
||||||
* @returns {String} La position en px
|
|
||||||
*/
|
|
||||||
function getTopPosition(container, start, end) {
|
|
||||||
const overlaps = (a, b) => {
|
|
||||||
return a.start < b.end && a.end > b.start;
|
|
||||||
};
|
|
||||||
|
|
||||||
const startTime = new moment.tz(start, TIMEZONE);
|
|
||||||
const endTime = new moment.tz(end, TIMEZONE);
|
|
||||||
const assiduiteDuration = { start: startTime, end: endTime };
|
|
||||||
|
|
||||||
let position = 0;
|
|
||||||
let hasOverlap = true;
|
|
||||||
|
|
||||||
while (hasOverlap) {
|
|
||||||
hasOverlap = false;
|
|
||||||
Array.from(container.children).some((el) => {
|
|
||||||
const elStart = new moment.tz(el.getAttribute("data-start"));
|
|
||||||
const elEnd = new moment.tz(el.getAttribute("data-end"));
|
|
||||||
const elDuration = { start: elStart, end: elEnd };
|
|
||||||
|
|
||||||
if (overlaps(assiduiteDuration, elDuration)) {
|
|
||||||
position += 25; // Pour ajuster l'espacement vertical entre les assiduités superposées
|
|
||||||
hasOverlap = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return position + "px";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transformation d'un état en couleur
|
|
||||||
* @param {String} state l'état
|
|
||||||
* @returns {String} la couleur correspondant à l'état
|
|
||||||
*/
|
|
||||||
function getColor(state) {
|
|
||||||
switch (state) {
|
|
||||||
case "PRESENT":
|
|
||||||
return "#9CF1AF";
|
|
||||||
case "ABSENT":
|
|
||||||
return "#F1A69C";
|
|
||||||
case "RETARD":
|
|
||||||
return "#F1D99C";
|
|
||||||
default:
|
|
||||||
return "gray";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calcule de la largeur de l'assiduité sur la timeline
|
|
||||||
* @param {String} start date iso de début
|
|
||||||
* @param {String} end date iso de fin
|
|
||||||
* @returns {String} la taille en %
|
|
||||||
*/
|
|
||||||
function getWidth(start, end) {
|
|
||||||
const startTime = new moment.tz(start, TIMEZONE);
|
|
||||||
const endTime = new moment.tz(end, TIMEZONE);
|
|
||||||
const duration = (endTime - startTime) / 1000 / 60;
|
|
||||||
return (duration / (18 * 60 - 8 * 60)) * 100 + "%";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sélection d'une assiduité sur la timeline
|
|
||||||
* @param {Assiduité} assiduite l'assiduité sélectionnée
|
|
||||||
*/
|
|
||||||
function selectAssiduite(assiduite) {
|
|
||||||
// Désélectionner l'assiduité précédemment sélectionnée
|
|
||||||
if (selectedAssiduite) {
|
|
||||||
const prevSelectedEl = document.querySelector(
|
|
||||||
`.assiduite[data-id="${selectedAssiduite.assiduite_id}"]`
|
|
||||||
);
|
|
||||||
if (prevSelectedEl) {
|
|
||||||
prevSelectedEl.classList.remove("selected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sélectionner la nouvelle assiduité
|
|
||||||
selectedAssiduite = assiduite;
|
|
||||||
const selectedEl = document.querySelector(
|
|
||||||
`.assiduite[data-id="${assiduite.assiduite_id}"]`
|
|
||||||
);
|
|
||||||
if (selectedEl) {
|
|
||||||
selectedEl.classList.add("selected");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Mise à jour de la partie information du modal
|
|
||||||
const selectedModal = document.querySelector(".modal-assiduite-content");
|
|
||||||
|
|
||||||
selectedModal.classList.add("show");
|
|
||||||
|
|
||||||
document.getElementById("modal-assiduite-id").textContent =
|
|
||||||
assiduite.assiduite_id;
|
|
||||||
document.getElementById(
|
|
||||||
"modal-assiduite-user"
|
|
||||||
).textContent = `saisi le ${formatDateModal(
|
|
||||||
assiduite.entry_date,
|
|
||||||
"à"
|
|
||||||
)} \npar ${getUserFromId(assiduite.user_id)}`;
|
|
||||||
document.getElementById("modal-assiduite-module").textContent =
|
|
||||||
assiduite.moduleimpl_id;
|
|
||||||
document.getElementById("modal-assiduite-deb").textContent = formatDateModal(
|
|
||||||
assiduite.date_debut
|
|
||||||
);
|
|
||||||
document.getElementById("modal-assiduite-fin").textContent = formatDateModal(
|
|
||||||
assiduite.date_fin
|
|
||||||
);
|
|
||||||
document.getElementById("modal-assiduite-etat").textContent =
|
|
||||||
assiduite.etat.capitalize();
|
|
||||||
|
|
||||||
//Activation des boutons d'actions de conflit
|
|
||||||
deleteBtn.disabled = false;
|
|
||||||
splitBtn.disabled = false;
|
|
||||||
editBtn.disabled = false;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Suppression de l'assiduité sélectionnée
|
|
||||||
*/
|
|
||||||
function deleteAssiduiteModal() {
|
|
||||||
if (!selectedAssiduite) return;
|
|
||||||
deleteAssiduite(selectedAssiduite.assiduite_id);
|
|
||||||
actualizeEtud(selectedAssiduite.etudid);
|
|
||||||
refreshModal(assiduites[selectedAssiduite.etudid]);
|
|
||||||
|
|
||||||
// Désélection de l'assiduité
|
|
||||||
resetSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Division d'une assiduité
|
|
||||||
* @param {Assiduité} assiduite l'assiduité sélectionnée
|
|
||||||
*/
|
|
||||||
function splitAssiduiteModal(assiduite) {
|
|
||||||
//Préparation du prompt
|
|
||||||
const htmlPrompt = `<legend>Entrez l'heure de séparation (HH:mm) :</legend>
|
|
||||||
<input type="time" id="promptTime" name="appt"
|
|
||||||
min="08:00" max="18:00" required>`;
|
|
||||||
|
|
||||||
const fieldSet = document.createElement("fieldset");
|
|
||||||
fieldSet.classList.add("fieldsplit");
|
|
||||||
fieldSet.innerHTML = htmlPrompt;
|
|
||||||
|
|
||||||
//Callback de division
|
|
||||||
const success = () => {
|
|
||||||
const separatorTime = document.getElementById("promptTime").value;
|
|
||||||
const dateString =
|
|
||||||
document.querySelector("#tl_date").value + `T${separatorTime}`;
|
|
||||||
const separtorDate = new moment.tz(dateString, TIMEZONE);
|
|
||||||
|
|
||||||
const assiduite_debut = new moment.tz(assiduite.date_debut, TIMEZONE);
|
|
||||||
const assiduite_fin = new moment.tz(assiduite.date_fin, TIMEZONE);
|
|
||||||
|
|
||||||
if (
|
|
||||||
separtorDate.isAfter(assiduite_debut) &&
|
|
||||||
separtorDate.isBefore(assiduite_fin)
|
|
||||||
) {
|
|
||||||
const assiduite_avant = {
|
|
||||||
etat: assiduite.etat,
|
|
||||||
date_debut: assiduite_debut.format(),
|
|
||||||
date_fin: separtorDate.format(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const assiduite_apres = {
|
|
||||||
etat: assiduite.etat,
|
|
||||||
date_debut: separtorDate.format(),
|
|
||||||
date_fin: assiduite_fin.format(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (assiduite.moduleimpl_id) {
|
|
||||||
assiduite_apres["moduleimpl_id"] = assiduite.moduleimpl_id;
|
|
||||||
assiduite_avant["moduleimpl_id"] = assiduite.moduleimpl_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteAssiduite(assiduite.assiduite_id);
|
|
||||||
|
|
||||||
const path = getUrl() + `/api/assiduite/${assiduite.etudid}/create`;
|
|
||||||
sync_post(
|
|
||||||
path,
|
|
||||||
[assiduite_avant, assiduite_apres],
|
|
||||||
(data, status) => {
|
|
||||||
//success
|
|
||||||
},
|
|
||||||
(data, status) => {
|
|
||||||
//error
|
|
||||||
console.error(data, status);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
actualizeEtud(assiduite.etudid);
|
|
||||||
refreshModal(assiduites[assiduite.etudid]);
|
|
||||||
resetSelection();
|
|
||||||
} else {
|
|
||||||
const att = document.createTextNode(
|
|
||||||
"L'heure de séparation doit être compris dans la période de l'assiduité sélectionnée."
|
|
||||||
);
|
|
||||||
|
|
||||||
openAlertModal("Attention", att, "", "#ecb52a");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openPromptModal("Entrée demandée", fieldSet, success, () => {}, "#37f05f");
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Modification d'une assiduité conflictuelle
|
|
||||||
* @param {Assiduité} selectedAssiduite l'assiduité sélectionnée
|
|
||||||
*/
|
|
||||||
function editAssiduiteModal(selectedAssiduite) {
|
|
||||||
if (!selectedAssiduite) return;
|
|
||||||
|
|
||||||
//Préparation du modal d'édition
|
|
||||||
const htmlPrompt = `<legend>Entrez l'état de l'assiduité :</legend>
|
|
||||||
<select name="promptSelect" id="promptSelect" required>
|
|
||||||
<option value="">Choissez l'état</option>
|
|
||||||
<option value="present">Présent</option>
|
|
||||||
<option value="retard">En Retard</option>
|
|
||||||
<option value="absent">Absent</option>
|
|
||||||
</select>`;
|
|
||||||
|
|
||||||
const fieldSet = document.createElement("fieldset");
|
|
||||||
fieldSet.classList.add("fieldsplit");
|
|
||||||
fieldSet.innerHTML = htmlPrompt;
|
|
||||||
|
|
||||||
//Callback d'action d'édition
|
|
||||||
const success = () => {
|
|
||||||
const newState = document.getElementById("promptSelect").value;
|
|
||||||
if (!["present", "absent", "retard"].includes(newState.toLowerCase())) {
|
|
||||||
const att = document.createTextNode(
|
|
||||||
"L'état doit être 'present', 'absent' ou 'retard'."
|
|
||||||
);
|
|
||||||
openAlertModal("Attention", att, "", "#ecb52a");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actualiser l'affichage
|
|
||||||
|
|
||||||
editAssiduite(selectedAssiduite.assiduite_id, newState);
|
|
||||||
actualizeEtud(selectedAssiduite.etudid);
|
|
||||||
refreshModal(assiduites[selectedAssiduite.etudid]);
|
|
||||||
|
|
||||||
// Désélection de l'assiduité
|
|
||||||
resetSelection();
|
|
||||||
};
|
|
||||||
|
|
||||||
//Affichage du prompt
|
|
||||||
openPromptModal("Entrée demandée", fieldSet, success, () => {}, "#37f05f");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remise à zéro de la sélection
|
|
||||||
* Désactivation des boutons d'actions de conflit
|
|
||||||
*/
|
|
||||||
function resetSelection() {
|
|
||||||
selectedAssiduite = null;
|
|
||||||
deleteBtn.disabled = true;
|
|
||||||
splitBtn.disabled = true;
|
|
||||||
editBtn.disabled = true;
|
|
||||||
|
|
||||||
document.querySelector(".modal-assiduite-content").classList.remove("show");
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Ajout des évents sur les boutons du modal
|
|
||||||
*/
|
|
||||||
window.onload = () => {
|
|
||||||
modal = document.getElementById("myModal");
|
|
||||||
if (modal) {
|
|
||||||
closeBtn = document.querySelector(".close");
|
|
||||||
timeline = document.getElementById("timeline");
|
|
||||||
deleteBtn = document.getElementById("delete");
|
|
||||||
splitBtn = document.getElementById("split");
|
|
||||||
editBtn = document.getElementById("edit");
|
|
||||||
selectedAssiduite = null;
|
|
||||||
|
|
||||||
closeBtn?.addEventListener("click", closeModal);
|
|
||||||
|
|
||||||
deleteBtn?.addEventListener("click", deleteAssiduiteModal);
|
|
||||||
splitBtn?.addEventListener("click", () => {
|
|
||||||
if (selectedAssiduite) {
|
|
||||||
splitAssiduiteModal(selectedAssiduite);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
editBtn?.addEventListener("click", () => {
|
|
||||||
if (selectedAssiduite) {
|
|
||||||
editAssiduiteModal(selectedAssiduite);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// <<== Gestion de la récupération d'informations ==>>
|
// <<== Gestion de la récupération d'informations ==>>
|
||||||
|
|
||||||
@ -1840,7 +1474,6 @@ function createJustificatif(justif) {
|
|||||||
if (data.success.length > 0) {
|
if (data.success.length > 0) {
|
||||||
console.table(data[0]);
|
console.table(data[0]);
|
||||||
}
|
}
|
||||||
console.warn(data);
|
|
||||||
},
|
},
|
||||||
(data, status) => {
|
(data, status) => {
|
||||||
//error
|
//error
|
||||||
|
459
app/templates/assiduites/conflict.j2
Normal file
459
app/templates/assiduites/conflict.j2
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Transformation d'une date de début en position sur la timeline
|
||||||
|
* @param {String} start
|
||||||
|
* @returns {String} un déplacement par rapport à la gauche en %
|
||||||
|
*/
|
||||||
|
function getLeftPosition(start) {
|
||||||
|
const startTime = new moment.tz(start, TIMEZONE);
|
||||||
|
const startMins = (startTime.hours() - 8) * 60 + startTime.minutes();
|
||||||
|
return (startMins / (18 * 60 - 8 * 60)) * 100 + "%";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Ajustement de l'espacement vertical entre les assiduités superposées
|
||||||
|
* @param {HTMLElement} container le conteneur des assiduités
|
||||||
|
* @param {String} start la date début de l'assiduité à placer
|
||||||
|
* @param {String} end la date de fin de l'assiduité à placer
|
||||||
|
* @returns {String} La position en px
|
||||||
|
*/
|
||||||
|
function getTopPosition(container, start, end) {
|
||||||
|
const overlaps = (a, b) => {
|
||||||
|
return a.start < b.end && a.end > b.start;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startTime = new moment.tz(start, TIMEZONE);
|
||||||
|
const endTime = new moment.tz(end, TIMEZONE);
|
||||||
|
const assiduiteDuration = { start: startTime, end: endTime };
|
||||||
|
|
||||||
|
let position = 0;
|
||||||
|
let hasOverlap = true;
|
||||||
|
|
||||||
|
while (hasOverlap) {
|
||||||
|
hasOverlap = false;
|
||||||
|
Array.from(container.children).some((el) => {
|
||||||
|
const elStart = new moment.tz(el.getAttribute("data-start"));
|
||||||
|
const elEnd = new moment.tz(el.getAttribute("data-end"));
|
||||||
|
const elDuration = { start: elStart, end: elEnd };
|
||||||
|
|
||||||
|
if (overlaps(assiduiteDuration, elDuration)) {
|
||||||
|
position += 25; // Pour ajuster l'espacement vertical entre les assiduités superposées
|
||||||
|
hasOverlap = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return position + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformation d'un état en couleur
|
||||||
|
* @param {String} state l'état
|
||||||
|
* @returns {String} la couleur correspondant à l'état
|
||||||
|
*/
|
||||||
|
function getColor(state) {
|
||||||
|
switch (state) {
|
||||||
|
case "PRESENT":
|
||||||
|
return "#9CF1AF";
|
||||||
|
case "ABSENT":
|
||||||
|
return "#F1A69C";
|
||||||
|
case "RETARD":
|
||||||
|
return "#F1D99C";
|
||||||
|
default:
|
||||||
|
return "gray";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule de la largeur de l'assiduité sur la timeline
|
||||||
|
* @param {String} start date iso de début
|
||||||
|
* @param {String} end date iso de fin
|
||||||
|
* @returns {String} la taille en %
|
||||||
|
*/
|
||||||
|
function getWidth(start, end) {
|
||||||
|
const startTime = new moment.tz(start, TIMEZONE);
|
||||||
|
const endTime = new moment.tz(end, TIMEZONE);
|
||||||
|
|
||||||
|
const duration = (endTime - startTime) / 1000 / 60;
|
||||||
|
|
||||||
|
const percent = (duration / (18 * 60 - 8 * 60)) * 100
|
||||||
|
|
||||||
|
if (percent > 100) {
|
||||||
|
console.log(start, end);
|
||||||
|
console.log(startTime, endTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
return percent + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConflitResolver {
|
||||||
|
constructor(assiduitesList, conflictPeriod, interval) {
|
||||||
|
this.list = assiduitesList;
|
||||||
|
this.conflictPeriod = conflictPeriod;
|
||||||
|
this.interval = interval;
|
||||||
|
this.selectedAssiduite = null;
|
||||||
|
|
||||||
|
this.element = undefined;
|
||||||
|
|
||||||
|
this.callbacks = {
|
||||||
|
delete: () => { },
|
||||||
|
split: () => { },
|
||||||
|
edit: () => { },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(assiduitesList, periode) {
|
||||||
|
this.list = assiduitesList;
|
||||||
|
if (periode) {
|
||||||
|
this.conflictPeriod = periode;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAssiduite() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
const html = `
|
||||||
|
<div id="myModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close">×</span>
|
||||||
|
<h2>Veuillez régler le conflit pour poursuivre</h2>
|
||||||
|
<!-- Ajout de la frise chronologique -->
|
||||||
|
<div class="modal-timeline">
|
||||||
|
<div class="time-labels"></div>
|
||||||
|
<div class="assiduites-container"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button id="delete" class="btnPrompt" disabled>Supprimer</button>
|
||||||
|
<button id="split" class="btnPrompt" disabled>Séparer</button>
|
||||||
|
<button id="edit" class="btnPrompt" disabled>Modifier l'état</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-assiduite-content">
|
||||||
|
<h2>Information de l'assiduité sélectionnée</h2>
|
||||||
|
<div class="infos">
|
||||||
|
<p>Assiduite id : <span id="modal-assiduite-id">A</span></p>
|
||||||
|
<p>Etat : <span id="modal-assiduite-etat">B</span></p>
|
||||||
|
<p>Date de début : <span id="modal-assiduite-deb">C</span></p>
|
||||||
|
<p>Date de fin: <span id="modal-assiduite-fin">D</span></p>
|
||||||
|
<p>Module : <span id="modal-assiduite-module">E</span></p>
|
||||||
|
<p><span id="modal-assiduite-user">F</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.insertAdjacentHTML("afterbegin", html);
|
||||||
|
this.element = document.getElementById('myModal');
|
||||||
|
this.deleteBtn = document.querySelector('#myModal #delete');
|
||||||
|
this.editBtn = document.querySelector('#myModal #edit');
|
||||||
|
this.splitBtn = document.querySelector('#myModal #split');
|
||||||
|
this.deleteBtn.addEventListener('click', () => { this.deleteAssiduiteModal() });
|
||||||
|
this.editBtn.addEventListener('click', () => { this.editAssiduiteModal() });
|
||||||
|
this.splitBtn.addEventListener('click', () => { this.splitAssiduiteModal() });
|
||||||
|
|
||||||
|
document.querySelector('#myModal .close').addEventListener('click', () => { this.close() })
|
||||||
|
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this.element) {
|
||||||
|
this.element.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sélection d'une assiduité sur la timeline
|
||||||
|
* @param {Assiduité} assiduite l'assiduité sélectionnée
|
||||||
|
*/
|
||||||
|
selectAssiduite(assiduite) {
|
||||||
|
// Désélectionner l'assiduité précédemment sélectionnée
|
||||||
|
if (this.selectedAssiduite) {
|
||||||
|
const prevSelectedEl = document.querySelector(
|
||||||
|
`.assiduite[data-id="${this.selectedAssiduite.assiduite_id}"]`
|
||||||
|
);
|
||||||
|
if (prevSelectedEl) {
|
||||||
|
prevSelectedEl.classList.remove("selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sélectionner la nouvelle assiduité
|
||||||
|
this.selectedAssiduite = assiduite;
|
||||||
|
const selectedEl = document.querySelector(
|
||||||
|
`.assiduite[data-id="${assiduite.assiduite_id}"]`
|
||||||
|
);
|
||||||
|
if (selectedEl) {
|
||||||
|
selectedEl.classList.add("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Mise à jour de la partie information du modal
|
||||||
|
const selectedModal = document.querySelector(".modal-assiduite-content");
|
||||||
|
|
||||||
|
selectedModal.classList.add("show");
|
||||||
|
|
||||||
|
document.getElementById("modal-assiduite-id").textContent =
|
||||||
|
assiduite.assiduite_id;
|
||||||
|
document.getElementById(
|
||||||
|
"modal-assiduite-user"
|
||||||
|
).textContent = `saisi le ${formatDateModal(
|
||||||
|
assiduite.entry_date,
|
||||||
|
"à"
|
||||||
|
)} \npar ${getUserFromId(assiduite.user_id)}`;
|
||||||
|
document.getElementById("modal-assiduite-module").textContent =
|
||||||
|
assiduite.moduleimpl_id;
|
||||||
|
document.getElementById("modal-assiduite-deb").textContent = formatDateModal(
|
||||||
|
assiduite.date_debut
|
||||||
|
);
|
||||||
|
document.getElementById("modal-assiduite-fin").textContent = formatDateModal(
|
||||||
|
assiduite.date_fin
|
||||||
|
);
|
||||||
|
document.getElementById("modal-assiduite-etat").textContent =
|
||||||
|
assiduite.etat.capitalize();
|
||||||
|
|
||||||
|
//Activation des boutons d'actions de conflit
|
||||||
|
this.deleteBtn.disabled = false;
|
||||||
|
this.splitBtn.disabled = false;
|
||||||
|
this.editBtn.disabled = false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Suppression de l'assiduité sélectionnée
|
||||||
|
*/
|
||||||
|
deleteAssiduiteModal() {
|
||||||
|
if (!this.selectedAssiduite) return;
|
||||||
|
deleteAssiduite(this.selectedAssiduite.assiduite_id);
|
||||||
|
|
||||||
|
this.callbacks.delete(this.selectedAssiduite)
|
||||||
|
|
||||||
|
this.refresh(assiduites[this.selectedAssiduite.etudid]);
|
||||||
|
|
||||||
|
// Désélection de l'assiduité
|
||||||
|
this.resetSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Division d'une assiduité
|
||||||
|
*/
|
||||||
|
splitAssiduiteModal() {
|
||||||
|
//Préparation du prompt
|
||||||
|
const htmlPrompt = `<legend>Entrez l'heure de séparation (HH:mm) :</legend>
|
||||||
|
<input type="time" id="promptTime" name="appt"
|
||||||
|
min="08:00" max="18:00" required>`;
|
||||||
|
|
||||||
|
const fieldSet = document.createElement("fieldset");
|
||||||
|
fieldSet.classList.add("fieldsplit");
|
||||||
|
fieldSet.innerHTML = htmlPrompt;
|
||||||
|
|
||||||
|
//Callback de division
|
||||||
|
const success = () => {
|
||||||
|
const separatorTime = document.getElementById("promptTime").value;
|
||||||
|
const dateString =
|
||||||
|
document.querySelector("#tl_date").value + `T${separatorTime}`;
|
||||||
|
const separtorDate = new moment.tz(dateString, TIMEZONE);
|
||||||
|
|
||||||
|
const assiduite_debut = new moment.tz(this.selectedAssiduite.date_debut, TIMEZONE);
|
||||||
|
const assiduite_fin = new moment.tz(this.selectedAssiduite.date_fin, TIMEZONE);
|
||||||
|
|
||||||
|
if (
|
||||||
|
separtorDate.isAfter(assiduite_debut) &&
|
||||||
|
separtorDate.isBefore(assiduite_fin)
|
||||||
|
) {
|
||||||
|
const assiduite_avant = {
|
||||||
|
etat: this.selectedAssiduite.etat,
|
||||||
|
date_debut: assiduite_debut.format(),
|
||||||
|
date_fin: separtorDate.format(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const assiduite_apres = {
|
||||||
|
etat: this.selectedAssiduite.etat,
|
||||||
|
date_debut: separtorDate.format(),
|
||||||
|
date_fin: assiduite_fin.format(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.selectedAssiduite.moduleimpl_id) {
|
||||||
|
assiduite_apres["moduleimpl_id"] = this.selectedAssiduite.moduleimpl_id;
|
||||||
|
assiduite_avant["moduleimpl_id"] = this.selectedAssiduite.moduleimpl_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAssiduite(this.selectedAssiduite.assiduite_id);
|
||||||
|
|
||||||
|
const path = getUrl() + `/api/assiduite/${this.selectedAssiduite.etudid}/create`;
|
||||||
|
sync_post(
|
||||||
|
path,
|
||||||
|
[assiduite_avant, assiduite_apres],
|
||||||
|
(data, status) => {
|
||||||
|
//success
|
||||||
|
},
|
||||||
|
(data, status) => {
|
||||||
|
//error
|
||||||
|
console.error(data, status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.callbacks.split(this.selectedAssiduite)
|
||||||
|
this.refresh(assiduites[this.selectedAssiduite.etudid]);
|
||||||
|
this.resetSelection();
|
||||||
|
} else {
|
||||||
|
const att = document.createTextNode(
|
||||||
|
"L'heure de séparation doit être compris dans la période de l'assiduité sélectionnée."
|
||||||
|
);
|
||||||
|
|
||||||
|
openAlertModal("Attention", att, "", "#ecb52a");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openPromptModal("Entrée demandée", fieldSet, success, () => { }, "#37f05f");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modification d'une assiduité conflictuelle
|
||||||
|
*/
|
||||||
|
editAssiduiteModal() {
|
||||||
|
if (!this.selectedAssiduite) return;
|
||||||
|
|
||||||
|
//Préparation du modal d'édition
|
||||||
|
const htmlPrompt = `<legend>Entrez l'état de l'assiduité :</legend>
|
||||||
|
<select name="promptSelect" id="promptSelect" required>
|
||||||
|
<option value="">Choissez l'état</option>
|
||||||
|
<option value="present">Présent</option>
|
||||||
|
<option value="retard">En Retard</option>
|
||||||
|
<option value="absent">Absent</option>
|
||||||
|
</select>`;
|
||||||
|
|
||||||
|
const fieldSet = document.createElement("fieldset");
|
||||||
|
fieldSet.classList.add("fieldsplit");
|
||||||
|
fieldSet.innerHTML = htmlPrompt;
|
||||||
|
|
||||||
|
//Callback d'action d'édition
|
||||||
|
const success = () => {
|
||||||
|
const newState = document.getElementById("promptSelect").value;
|
||||||
|
if (!["present", "absent", "retard"].includes(newState.toLowerCase())) {
|
||||||
|
const att = document.createTextNode(
|
||||||
|
"L'état doit être 'present', 'absent' ou 'retard'."
|
||||||
|
);
|
||||||
|
openAlertModal("Attention", att, "", "#ecb52a");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualiser l'affichage
|
||||||
|
|
||||||
|
editAssiduite(this.selectedAssiduite.assiduite_id, newState);
|
||||||
|
this.callbacks.edit(this.selectedAssiduite)
|
||||||
|
this.refresh(assiduites[this.selectedAssiduite.etudid]);
|
||||||
|
|
||||||
|
// Désélection de l'assiduité
|
||||||
|
this.resetSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Affichage du prompt
|
||||||
|
openPromptModal("Entrée demandée", fieldSet, success, () => { }, "#37f05f");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génération du modal
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const timeLabels = document.querySelector(".time-labels");
|
||||||
|
const assiduitesContainer = document.querySelector(".assiduites-container");
|
||||||
|
|
||||||
|
timeLabels.innerHTML = "";
|
||||||
|
assiduitesContainer.innerHTML = '<div class="assiduite-special"></div>';
|
||||||
|
|
||||||
|
// Ajout des labels d'heure sur la frise chronologique
|
||||||
|
// TODO permettre la modification des bornes (8 et 18)
|
||||||
|
for (let i = 8; i <= 18; i++) {
|
||||||
|
const timeLabel = document.createElement("div");
|
||||||
|
timeLabel.className = "time-label";
|
||||||
|
timeLabel.textContent = i < 10 ? `0${i}:00` : `${i}:00`;
|
||||||
|
timeLabels.appendChild(timeLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Placement de la période conflictuelle sur la timeline
|
||||||
|
const specialAssiduiteEl = document.querySelector(".assiduite-special");
|
||||||
|
console.error("special")
|
||||||
|
specialAssiduiteEl.style.width = getWidth(
|
||||||
|
this.conflictPeriod.deb,
|
||||||
|
this.conflictPeriod.fin
|
||||||
|
);
|
||||||
|
specialAssiduiteEl.style.left = getLeftPosition(this.conflictPeriod.deb);
|
||||||
|
specialAssiduiteEl.style.top = "0";
|
||||||
|
specialAssiduiteEl.style.zIndex = "0"; // Place l'assiduité spéciale en arrière-plan
|
||||||
|
assiduitesContainer.appendChild(specialAssiduiteEl);
|
||||||
|
console.error(this.conflictPeriod)
|
||||||
|
console.error(specialAssiduiteEl)
|
||||||
|
|
||||||
|
//Placement des assiduités sur la timeline
|
||||||
|
this.list.forEach((assiduite) => {
|
||||||
|
const period = {
|
||||||
|
deb: new moment.tz(assiduite.date_debut, TIMEZONE),
|
||||||
|
fin: new moment.tz(assiduite.date_fin, TIMEZONE),
|
||||||
|
};
|
||||||
|
if (!hasTimeConflict(period, this.interval)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const el = document.createElement("div");
|
||||||
|
el.className = "assiduite";
|
||||||
|
el.style.backgroundColor = getColor(assiduite.etat);
|
||||||
|
console.error("normal")
|
||||||
|
|
||||||
|
el.style.width = getWidth(assiduite.date_debut, assiduite.date_fin);
|
||||||
|
el.style.left = getLeftPosition(assiduite.date_debut);
|
||||||
|
el.style.top = "10px";
|
||||||
|
el.setAttribute("data-id", assiduite.assiduite_id);
|
||||||
|
el.addEventListener("click", () => this.selectAssiduite(assiduite));
|
||||||
|
|
||||||
|
// Ajout des informations dans la visualisation d'une assiduité
|
||||||
|
const infoContainer = document.createElement("div");
|
||||||
|
infoContainer.className = "assiduite-info";
|
||||||
|
|
||||||
|
const idDiv = document.createElement("div");
|
||||||
|
idDiv.className = "assiduite-id";
|
||||||
|
idDiv.textContent = `ID: ${assiduite.assiduite_id}`;
|
||||||
|
infoContainer.appendChild(idDiv);
|
||||||
|
|
||||||
|
const periodDivDeb = document.createElement("div");
|
||||||
|
periodDivDeb.className = "assiduite-period";
|
||||||
|
periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`;
|
||||||
|
infoContainer.appendChild(periodDivDeb);
|
||||||
|
const periodDivFin = document.createElement("div");
|
||||||
|
periodDivFin.className = "assiduite-period";
|
||||||
|
periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`;
|
||||||
|
infoContainer.appendChild(periodDivFin);
|
||||||
|
|
||||||
|
const stateDiv = document.createElement("div");
|
||||||
|
stateDiv.className = "assiduite-state";
|
||||||
|
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
|
||||||
|
infoContainer.appendChild(stateDiv);
|
||||||
|
|
||||||
|
const userIdDiv = document.createElement("div");
|
||||||
|
userIdDiv.className = "assiduite-user_id";
|
||||||
|
userIdDiv.textContent = `saisi le ${formatDateModal(
|
||||||
|
assiduite.entry_date,
|
||||||
|
"à"
|
||||||
|
)} \npar ${getUserFromId(assiduite.user_id)}`;
|
||||||
|
infoContainer.appendChild(userIdDiv);
|
||||||
|
|
||||||
|
el.appendChild(infoContainer);
|
||||||
|
assiduitesContainer.appendChild(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Remise à zéro de la sélection
|
||||||
|
* Désactivation des boutons d'actions de conflit
|
||||||
|
*/
|
||||||
|
resetSelection() {
|
||||||
|
this.selectedAssiduite = null;
|
||||||
|
this.deleteBtn.disabled = true;
|
||||||
|
this.splitBtn.disabled = true;
|
||||||
|
this.editBtn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
807
app/templates/assiduites/differee.j2
Normal file
807
app/templates/assiduites/differee.j2
Normal file
@ -0,0 +1,807 @@
|
|||||||
|
<div id="studentTable">
|
||||||
|
<div class="thead">
|
||||||
|
<div class="tr">
|
||||||
|
<div class="th sticky" order="ASC" onclick="reOrderEtudiants()">Noms</div>
|
||||||
|
<button id="addColumn" class="floating-button">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tbody">
|
||||||
|
|
||||||
|
{% for etud in etudiants %}
|
||||||
|
<div class="tr" etudid="{{etud.etudid}}">
|
||||||
|
<div class="td sticky">
|
||||||
|
<span>{{etud.nomprenom}}</span>
|
||||||
|
<img class="pdp-hover" src="/ScoDoc/GEII/api/etudiant/etudid/{{etud.etudid}}/photo?size=small">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.td.sticky .pdp-hover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td.sticky:hover .pdp-hover {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
border-radius: 15px;
|
||||||
|
transition: all 0.5s;
|
||||||
|
left: calc(80% - 45px);
|
||||||
|
top: -25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err-assi {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 0 0 1rem 0 rgba(0, 0, 0, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thead .tr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thead .tr .th {
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th.sticky {
|
||||||
|
z-index: 5;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th,
|
||||||
|
.td {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
width: 225px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-form {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-form input,
|
||||||
|
.mini-form select {
|
||||||
|
display: block;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
max-width: 195px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addColumn {
|
||||||
|
font-size: 24px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
right: -60px;
|
||||||
|
top: calc(50% - 50px /2);
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addColumn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th {
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th.error {
|
||||||
|
background-color: crimson;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody .tr:nth-child(even) {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody .tr:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td .etat {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 33% 33% 33%;
|
||||||
|
grid-template-rows: 50% 50%;
|
||||||
|
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btngroup {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btngroup>button {
|
||||||
|
background-color: transparent;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th .closeCol {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activate {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th[activated] {
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th[activated='false'] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num {
|
||||||
|
border: 1px whitesmoke solid;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assi_rbtn,
|
||||||
|
.assi_lbl {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.td[assiduite_id='conflit'] {
|
||||||
|
background-color: #ff0000c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='radio']:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const etatDef = "{{etat_def}}";
|
||||||
|
|
||||||
|
window.forceModule = "{{ forcer_module }}"
|
||||||
|
window.forceModule = window.forceModule == "True" ? true : false
|
||||||
|
|
||||||
|
let colCount = 1;
|
||||||
|
|
||||||
|
let currentDate = "{{date}}";
|
||||||
|
if (currentDate == "") {
|
||||||
|
currentDate = moment().tz(TIMEZONE).format("YYYY-MM-DDTHH:mm");
|
||||||
|
} else {
|
||||||
|
currentDate = moment(currentDate).tz(TIMEZONE).format("YYYY-MM-DDTHH:mm");
|
||||||
|
}
|
||||||
|
|
||||||
|
function createColumn() {
|
||||||
|
let table = document.getElementById("studentTable");
|
||||||
|
let th = document.createElement("div");
|
||||||
|
th.classList.add("th", "error");
|
||||||
|
const col_id = `${colCount++}`;
|
||||||
|
th.setAttribute("col", col_id);
|
||||||
|
th.setAttribute("activated", "true");
|
||||||
|
th.innerHTML = `
|
||||||
|
<div class="mini-form">
|
||||||
|
<div class="btngroup">
|
||||||
|
<span class="num">${col_id}</span>
|
||||||
|
<button class="closeCol" onclick="removeColumn(this)">x</button>
|
||||||
|
</div>
|
||||||
|
<input type="datetime-local" id="dateStart">
|
||||||
|
<input type="datetime-local" id="dateEnd">
|
||||||
|
{{moduleimpl_select|safe}}
|
||||||
|
<select name="mass_action_${col_id}" id="mass_action_${col_id}" onchange="setEtatCol(${col_id},this.value)" disabled>
|
||||||
|
<option value="">Sélectionner une assiduité</option>
|
||||||
|
<option value="0">Présent</option>
|
||||||
|
<option value="1">Retard</option>
|
||||||
|
<option value="2">Absent</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
table
|
||||||
|
.querySelector(".thead .tr")
|
||||||
|
.insertBefore(th, document.querySelector("#addColumn"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const last = [...document.querySelectorAll("#dateStart")].pop();
|
||||||
|
defaultDate(last);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sl = th.querySelector('.dynaSelect');
|
||||||
|
sl.id = `dynaSelect_${col_id}`;
|
||||||
|
const miniform = sl.parentElement;
|
||||||
|
const dateIso = miniform.querySelector("#dateStart").value;
|
||||||
|
if (dateIso != "") {
|
||||||
|
updateSelect("", `#${sl.id}`, dateIso)
|
||||||
|
}
|
||||||
|
|
||||||
|
miniform.querySelector("#dateStart").addEventListener('focusout', (el) => {
|
||||||
|
updateSelect("", `#${sl.id}`, el.target.value);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
th.querySelectorAll('input, select.dynaSelect, select#moduleimpl_select').forEach((el) => {
|
||||||
|
el.addEventListener("change", () => {
|
||||||
|
getAndUpdateCol(col_id);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let rows = table.querySelector(".tbody").querySelectorAll(".tr");
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
let td = document.createElement("div");
|
||||||
|
td.setAttribute("colid", col_id)
|
||||||
|
td.classList.add("td", "etat");
|
||||||
|
const etudid = rows[i].getAttribute("etudid");
|
||||||
|
td.innerHTML = `
|
||||||
|
<div class="assi_rbtn">
|
||||||
|
<input type="radio" name="etat_${col_id}_${etudid}" value="present" disabled onchange="updateEtudAssiduite(this)">
|
||||||
|
<input type="radio" name="etat_${col_id}_${etudid}" value="retard" disabled onchange="updateEtudAssiduite(this)">
|
||||||
|
<input type="radio" name="etat_${col_id}_${etudid}" value="absent" disabled onchange="updateEtudAssiduite(this)">
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="assi_lbl">
|
||||||
|
<span>Present</span>
|
||||||
|
<span>Retard</span>
|
||||||
|
<span>Absent</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
rows[i].appendChild(td);
|
||||||
|
if (etatDef != "" && etatDef != "aucun") {
|
||||||
|
const inp = td.querySelector(`[value='${etatDef}']`).checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reOrderEtudiants() {
|
||||||
|
const th = document.querySelector(".th.sticky");
|
||||||
|
let lines = [...document.querySelectorAll('.tr[etudid]')];
|
||||||
|
const tbody = document.querySelector('.tbody')
|
||||||
|
|
||||||
|
let order = (a, b) => { return a > b }
|
||||||
|
|
||||||
|
if (th.getAttribute("order") == "ASC") {
|
||||||
|
order = (a, b) => { return b > a }
|
||||||
|
th.setAttribute("order", "DESC")
|
||||||
|
} else {
|
||||||
|
th.setAttribute("order", "ASC")
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = lines.sort((a, b) => {
|
||||||
|
const nameA = a.querySelector(".td.sticky").textContent.split(" ").pop();
|
||||||
|
const nameB = b.querySelector(".td.sticky").textContent.split(" ").pop();
|
||||||
|
return order(nameA, nameB)
|
||||||
|
})
|
||||||
|
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
tbody.append(...lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
function previousCol(column) {
|
||||||
|
const cols = [...document.querySelectorAll("[col]")]
|
||||||
|
let previousCol = null;
|
||||||
|
|
||||||
|
let i = 1;
|
||||||
|
|
||||||
|
while (i < cols.length) {
|
||||||
|
previousCol = cols[i - 1];
|
||||||
|
if (cols[i] == column)
|
||||||
|
break;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return previousCol
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeColumn(element) {
|
||||||
|
const col = element.parentElement.parentElement.parentElement;
|
||||||
|
const col_id = col.getAttribute("col");
|
||||||
|
document.querySelectorAll(`[col='${col_id}'],[colid='${col_id}']`).forEach((el) => { el.remove() })
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultDate(element) {
|
||||||
|
const num = previousCol(element.parentElement.parentElement)?.getAttribute("col");
|
||||||
|
const last = document.querySelector(`[col='${num}'] #dateEnd`);
|
||||||
|
let date = undefined;
|
||||||
|
if (last == undefined) {
|
||||||
|
date = currentDate;
|
||||||
|
} else {
|
||||||
|
date = last.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.value = date;
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
"focusout",
|
||||||
|
() => {
|
||||||
|
const el = element.parentElement.querySelector("#dateEnd");
|
||||||
|
const el2 = element.parentElement.querySelector("#dateStart");
|
||||||
|
el.value = moment(el2.valueAsDate).tz(TIMEZONE).utc()
|
||||||
|
.add(2, "hours")
|
||||||
|
.format("YYYY-MM-DDTHH:mm");
|
||||||
|
const colid = element.parentElement.parentElement.getAttribute('col');
|
||||||
|
getAndUpdateCol(colid);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEtatCol(colId, etatId) {
|
||||||
|
const tds = [...document.querySelectorAll(`.td[colid='${colId}']`)]
|
||||||
|
tds.forEach((td) => {
|
||||||
|
setEtatLine(td, etatId);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEtatLine(line, etatId) {
|
||||||
|
let inputs = [...line.querySelectorAll("input")]
|
||||||
|
inputs.forEach((el) => { el.checked = false })
|
||||||
|
if (etatId !== "") {
|
||||||
|
inputs[Number.parseInt(etatId)].checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEtatCol(colId) {
|
||||||
|
const etats = {};
|
||||||
|
const tds = [...document.querySelectorAll(`.td[colid='${colId}']`)]
|
||||||
|
tds.forEach((td) => {
|
||||||
|
const tr = td.parentElement
|
||||||
|
const etudid = tr.getAttribute("etudid");
|
||||||
|
let inputs = [...td.querySelectorAll("input")]
|
||||||
|
|
||||||
|
etatInput = inputs.filter((e) => e.checked).pop()
|
||||||
|
|
||||||
|
if (etatInput == undefined) {
|
||||||
|
etats[etudid] = "";
|
||||||
|
} else {
|
||||||
|
etats[etudid] = etatInput.value;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return etats;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createAssiduites(inputDeb, inputFin, moduleSelect, etudid, etat, colId) {
|
||||||
|
if (moduleSelect == "") {
|
||||||
|
return {
|
||||||
|
"date_debut": inputDeb,
|
||||||
|
"date_fin": inputFin,
|
||||||
|
"etudid": etudid,
|
||||||
|
"etat": etat,
|
||||||
|
"colid": colId,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
"date_debut": inputDeb,
|
||||||
|
"date_fin": inputFin,
|
||||||
|
"etudid": etudid,
|
||||||
|
"moduleimpl_id": moduleSelect,
|
||||||
|
"etat": etat,
|
||||||
|
"colid": colId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAndVerify() {
|
||||||
|
const assiduites = [];
|
||||||
|
const cols = [...document.querySelectorAll("[col]")];
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
cols.forEach((col) => {
|
||||||
|
const col_id = col.getAttribute("col");
|
||||||
|
const etats = getEtatCol(col_id);
|
||||||
|
|
||||||
|
const inputDeb = col.querySelector("#dateStart").value;
|
||||||
|
const inputFin = col.querySelector("#dateEnd").value;
|
||||||
|
const moduleSelect = col.querySelector("#moduleimpl_select,.dynaSelect").value;
|
||||||
|
|
||||||
|
const d_debut = moment(inputDeb);
|
||||||
|
const d_fin = moment(inputFin);
|
||||||
|
|
||||||
|
if (inputDeb == "" || inputFin == "" || d_debut > d_fin) {
|
||||||
|
errors.push(`La période de la colonne n°${col_id} n'est pas valide`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% if periode %}
|
||||||
|
|
||||||
|
const testPeriode = [
|
||||||
|
d_debut.isBefore("{{periode.deb}}"),
|
||||||
|
d_debut.isAfter("{{periode.fin}}"),
|
||||||
|
d_fin.isBefore("{{periode.deb}}"),
|
||||||
|
d_fin.isAfter("{{periode.fin}}"),
|
||||||
|
]
|
||||||
|
if (testPeriode.some((e) => e)) {
|
||||||
|
errors.push(`La période de la colonne n°${col_id} n'est pas dans le semestre`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
if (window.forceModule && moduleSelect == "") {
|
||||||
|
errors.push(`Le module de la colonne n°${col_id} n'a pas été entré. (Préférence de semestre)`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO Mettre une erreur lorsque assiduité forcé (pref)
|
||||||
|
|
||||||
|
Object.keys(etats).forEach((key) => {
|
||||||
|
const etat = etats[key];
|
||||||
|
|
||||||
|
if (etat != "") {
|
||||||
|
assiduites.push(_createAssiduites(inputDeb, inputFin, moduleSelect, key, etat, col_id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
const texte = document.createElement("div");
|
||||||
|
errors.map((err) => document.createTextNode(err)).forEach((err) => {
|
||||||
|
texte.appendChild(err);
|
||||||
|
texte.appendChild(document.createElement('br'));
|
||||||
|
})
|
||||||
|
|
||||||
|
openAlertModal("Erreur(s) détéctée(s)", texte)
|
||||||
|
} else {
|
||||||
|
if (assiduites.length > 0) {
|
||||||
|
createAllAssiduites(assiduites);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAllAssiduites(createQueue) {
|
||||||
|
if (createQueue.length < 0)
|
||||||
|
return;
|
||||||
|
const path = getUrl() + `/api/assiduites/create`;
|
||||||
|
sync_post(
|
||||||
|
path,
|
||||||
|
createQueue,
|
||||||
|
(data, status) => {
|
||||||
|
const { success, errors } = data;
|
||||||
|
|
||||||
|
const indexes = [...Object.keys(errors)];
|
||||||
|
if (indexes.length > 0) {
|
||||||
|
const incriminated = indexes.map((i) => {
|
||||||
|
return createQueue[Number.parseInt(i)];
|
||||||
|
})
|
||||||
|
|
||||||
|
const error_message = document.createElement('div');
|
||||||
|
|
||||||
|
for (let i = 0; i < incriminated.length; i++) {
|
||||||
|
const err = errors[indexes[i]];
|
||||||
|
const crimi = incriminated[i];
|
||||||
|
const nom = document.querySelector(`[etudid='${crimi.etudid}']`).firstElementChild.textContent.trim();
|
||||||
|
const col = crimi.colid;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add("err-assi")
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.setAttribute("title", err);
|
||||||
|
span.textContent = "ℹ️"
|
||||||
|
|
||||||
|
const span2 = document.createElement("span");
|
||||||
|
span2.textContent = `L'assiduité (Colonne n°${col}) de ${nom} n'a pas pu être enregistrée`;
|
||||||
|
|
||||||
|
div.appendChild(span2);
|
||||||
|
div.appendChild(span);
|
||||||
|
|
||||||
|
error_message.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
openAlertModal("Certaines assiduités non pas été enregistrées", error_message)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
openAlertModal("Tous les assiduités ont bien été enregistrée", document.createTextNode(""), null, "#09AD2A")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(data, status) => {
|
||||||
|
//error
|
||||||
|
console.error(data, status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAssiduitesCol(col_id, get = true) {
|
||||||
|
const col = document.querySelector(`[col='${col_id}']`);
|
||||||
|
|
||||||
|
const etats = getEtatCol(col_id);
|
||||||
|
|
||||||
|
const inputDeb = col.querySelector("#dateStart").value;
|
||||||
|
const inputFin = col.querySelector("#dateEnd").value;
|
||||||
|
const moduleSelect = col.querySelector("#moduleimpl_select,.dynaSelect").value;
|
||||||
|
const d_debut = moment(inputDeb).tz(TIMEZONE);
|
||||||
|
const d_fin = moment(inputFin).tz(TIMEZONE);
|
||||||
|
|
||||||
|
|
||||||
|
if (inputDeb == "" || inputFin == "" || d_debut > d_fin) {
|
||||||
|
//errors.push(`La période de la colonne n°${col_id} n'est pas valide`);
|
||||||
|
return 0x2;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% if periode %}
|
||||||
|
const t_before = "{{periode.deb}}T00:00";
|
||||||
|
const t_after = "{{periode.fin}}T23:59";
|
||||||
|
const testPeriode = [
|
||||||
|
d_debut.isBefore(t_before),
|
||||||
|
d_debut.isAfter(t_after),
|
||||||
|
d_fin.isBefore(t_before),
|
||||||
|
d_fin.isAfter(t_after),
|
||||||
|
]
|
||||||
|
if (testPeriode.some((e) => e)) {
|
||||||
|
//errors.push(`La période de la colonne n°${col_id} n'est pas dans le semestre`);
|
||||||
|
return 0x3;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
if (window.forceModule && moduleSelect == "") {
|
||||||
|
//errors.push(`Le module de la colonne n°${col_id} n'a pas été entré. (Préférence de semestre)`);
|
||||||
|
return 0x4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get) {
|
||||||
|
getAssiduitesFromEtuds(false, false, d_debut.format(), d_fin.format())
|
||||||
|
return 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
moduleimpl: moduleSelect,
|
||||||
|
deb: d_debut.format(),
|
||||||
|
fin: d_fin.format(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAssiduitesCol(col_id) {
|
||||||
|
const col = document.querySelector(`[col='${col_id}']`);
|
||||||
|
const tds = [...document.querySelectorAll(`.td[colid='${col_id}']`)]
|
||||||
|
|
||||||
|
const inputDeb = col.querySelector("#dateStart").value;
|
||||||
|
const inputFin = col.querySelector("#dateEnd").value;
|
||||||
|
const d_debut = moment(inputDeb).tz(TIMEZONE);
|
||||||
|
const d_fin = moment(inputFin).tz(TIMEZONE);
|
||||||
|
|
||||||
|
const periode = {
|
||||||
|
deb: d_debut,
|
||||||
|
fin: d_fin,
|
||||||
|
}
|
||||||
|
|
||||||
|
tds.forEach((td) => {
|
||||||
|
tds.forEach((el) => {
|
||||||
|
const inputs = [...el.querySelectorAll('input')];
|
||||||
|
inputs.forEach((i) => { i.disabled = false })
|
||||||
|
});
|
||||||
|
setEtatLine(td, "")
|
||||||
|
const etu = td.parentElement.getAttribute('etudid');
|
||||||
|
const conflits = getAssiduitesConflict(etu, periode);
|
||||||
|
if (conflits.length == 0) {
|
||||||
|
td.setAttribute('assiduite_id', "-1");
|
||||||
|
} else if (conflits.length == 1 && isConflictSameAsPeriod(conflits[0], periode)) {
|
||||||
|
const assi = conflits[0];
|
||||||
|
td.setAttribute('assiduite_id', assi.assiduite_id);
|
||||||
|
const ind = ["PRESENT", "RETARD", "ABSENT"].indexOf(assi.etat.toUpperCase());
|
||||||
|
setEtatLine(td, ind)
|
||||||
|
} else {
|
||||||
|
td.setAttribute('assiduite_id', "conflit");
|
||||||
|
const inputs = [...td.querySelectorAll('input')];
|
||||||
|
inputs.forEach((i) => {
|
||||||
|
i.disabled = true;
|
||||||
|
i.addEventListener('click', () => { /*updateEtudAssiduite(i)*/ });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEtuds() {
|
||||||
|
if (!isSingleEtud()) {
|
||||||
|
const etudids = [...document.querySelectorAll("[etudid]")]
|
||||||
|
.map((el) => Number.parseInt(el.getAttribute("etudid")))
|
||||||
|
.filter((value, index, array) => array.indexOf(value) === index);
|
||||||
|
|
||||||
|
etudids.forEach((etu) => {
|
||||||
|
etuds[etu] = { etudid: etu }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAndUpdateCol(colid) {
|
||||||
|
if (getAssiduitesCol(colid) == 0) {
|
||||||
|
document.querySelector(`[col='${colid}']`).classList.remove('error');
|
||||||
|
document.getElementById(`mass_action_${colid}`).removeAttribute('disabled')
|
||||||
|
updateAssiduitesCol(colid)
|
||||||
|
} else {
|
||||||
|
const column = document.querySelector(`[col='${colid}']`)
|
||||||
|
document.getElementById(`mass_action_${colid}`).setAttribute('disabled', 'true')
|
||||||
|
column.classList.add('error');
|
||||||
|
const tds = [...document.querySelectorAll(`.td[colid='${colid}']`)]
|
||||||
|
tds.forEach((el) => {
|
||||||
|
const inputs = [...el.querySelectorAll('input')];
|
||||||
|
inputs.forEach((i) => { i.disabled = true })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAllCol() {
|
||||||
|
const colIds = [...document.querySelectorAll("[col]")].map((col) => { return col.getAttribute('col') });
|
||||||
|
colIds.forEach((colid) => {
|
||||||
|
updateAssiduitesCol(colid);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateEtudAssiduite(rbtn) {
|
||||||
|
const [_, colid, etudid] = rbtn.name.split("_");
|
||||||
|
|
||||||
|
const etat = rbtn.value;
|
||||||
|
|
||||||
|
const etudLine = rbtn.parentElement.parentElement;
|
||||||
|
|
||||||
|
const assi = etudLine.getAttribute('assiduite_id');
|
||||||
|
|
||||||
|
const { moduleimpl, deb, fin } = getAssiduitesCol(colid, false);
|
||||||
|
|
||||||
|
switch (assi) {
|
||||||
|
case "-1":
|
||||||
|
// création d'une nouvelle assiduité
|
||||||
|
const assiduite = _createAssiduites(deb, fin, moduleimpl, etudid, etat, colid);
|
||||||
|
asyncCreateAssiduite(assiduite, (data) => {
|
||||||
|
if (Object.keys(data.success).length > 0) {
|
||||||
|
const assi_id = data.success['0'].assiduite_id;
|
||||||
|
etudLine.setAttribute('assiduite_id', assi_id);
|
||||||
|
assiduite["assiduite_id"] = assi_id;
|
||||||
|
assiduites[etudid].push(assiduite);
|
||||||
|
updateAllCol()
|
||||||
|
|
||||||
|
// TODO Envoyer toast
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case "conflit":
|
||||||
|
// Conflit, afficher résolveur
|
||||||
|
const assiduitesList = assiduites[etudid];
|
||||||
|
const d_debut = new moment.tz(deb, TIMEZONE);
|
||||||
|
const d_fin = new moment.tz(fin, TIMEZONE);
|
||||||
|
|
||||||
|
const period = {
|
||||||
|
deb: deb,
|
||||||
|
fin: fin,
|
||||||
|
}
|
||||||
|
const conflitResolver = new ConflitResolver(
|
||||||
|
assiduites[etudid],
|
||||||
|
period,
|
||||||
|
{
|
||||||
|
deb: new moment.tz(d_debut.startOf('day'), TIMEZONE),
|
||||||
|
fin: new moment.tz(d_fin.endOf('day'), TIMEZONE),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const update = () => {
|
||||||
|
assiduites = {}
|
||||||
|
const cols = [...document.querySelectorAll("[col]")].map((col) => col.getAttribute('col'));
|
||||||
|
|
||||||
|
cols.forEach((c) => getAndUpdateCol(c));
|
||||||
|
|
||||||
|
};
|
||||||
|
conflitResolver.callbacks = {
|
||||||
|
delete: update,
|
||||||
|
edit: update,
|
||||||
|
split: update,
|
||||||
|
};
|
||||||
|
|
||||||
|
conflitResolver.open();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Une assiduité est déjà connue -> modifier assiduité
|
||||||
|
const edit = {
|
||||||
|
moduleimpl_id: moduleimpl == "" ? null : moduleimpl,
|
||||||
|
etat: etat,
|
||||||
|
assiduite_id: Number.parseInt(assi),
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncEditAssiduite(edit, (data) => {
|
||||||
|
console.log(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function asyncCreateAssiduite(assi, callback = () => { }) {
|
||||||
|
const path = getUrl() + `/api/assiduite/${assi.etudid}/create`;
|
||||||
|
async_post(
|
||||||
|
path,
|
||||||
|
[assi],
|
||||||
|
callback,
|
||||||
|
(data, status) => {
|
||||||
|
//error
|
||||||
|
console.error(data, status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function asyncEditAssiduite(assi, callback = () => { }) {
|
||||||
|
const path = getUrl() + `/api/assiduite/${assi.assiduite_id}/edit`;
|
||||||
|
async_post(
|
||||||
|
path,
|
||||||
|
assi,
|
||||||
|
callback,
|
||||||
|
(data, status) => {
|
||||||
|
//error
|
||||||
|
console.error(data, status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
document.getElementById("addColumn").addEventListener("click", () => {
|
||||||
|
createColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
createColumn();
|
||||||
|
setEtuds();
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
</script>
|
@ -177,7 +177,6 @@
|
|||||||
const endDate = timelineDate.clone().set({ 'hour': 13, 'minute': 0 });
|
const endDate = timelineDate.clone().set({ 'hour': 13, 'minute': 0 });
|
||||||
const duration = moment.duration(endDate.diff(dayStart)).asMinutes();
|
const duration = moment.duration(endDate.diff(dayStart)).asMinutes();
|
||||||
const widthPercentage = (duration / dayDuration) * 100;
|
const widthPercentage = (duration / dayDuration) * 100;
|
||||||
console.log(endDate, duration, widthPercentage)
|
|
||||||
const tick = document.createElement('span');
|
const tick = document.createElement('span');
|
||||||
tick.className = "mini_tick"
|
tick.className = "mini_tick"
|
||||||
tick.textContent = "13h"
|
tick.textContent = "13h"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<label for="moduleimpl_select">
|
<label for="moduleimpl_select">
|
||||||
Module
|
Module
|
||||||
<select id="moduleimpl_select">
|
<select id="moduleimpl_select" class="dynaSelect">
|
||||||
<option value="" selected> Non spécifié </option>
|
<option value="" selected> Non spécifié </option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
@ -16,14 +16,14 @@
|
|||||||
return semestre;
|
return semestre;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterFormSemestres(semestres) {
|
function filterFormSemestres(semestres, dateIso) {
|
||||||
const date = new moment.tz(
|
const date = new moment.tz(
|
||||||
document.querySelector("#tl_date").value,
|
dateIso,
|
||||||
TIMEZONE
|
TIMEZONE
|
||||||
);
|
);
|
||||||
|
|
||||||
semestres = semestres.filter((fm) => {
|
semestres = semestres.filter((fm) => {
|
||||||
return date.isBetween(fm.date_debut_iso, fm.date_fin_iso)
|
return date.isBetween(fm.date_debut_iso, fm.date_fin_iso, null, '[]')
|
||||||
})
|
})
|
||||||
|
|
||||||
return semestres;
|
return semestres;
|
||||||
@ -67,8 +67,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function populateSelect(sems, selected) {
|
function populateSelect(sems, selected, query) {
|
||||||
const select = document.getElementById('moduleimpl_select');
|
const select = document.querySelector(query);
|
||||||
select.innerHTML = `<option value="" selected> Non spécifié </option>`
|
select.innerHTML = `<option value="" selected> Non spécifié </option>`
|
||||||
|
|
||||||
sems.forEach((mods, label) => {
|
sems.forEach((mods, label) => {
|
||||||
@ -90,24 +90,25 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelect(moduleimpl_id) {
|
function updateSelect(moduleimpl_id, query = "#moduleimpl_select", dateIso = null) {
|
||||||
let sem = getEtudFormSemestres()
|
let sem = getEtudFormSemestres()
|
||||||
sem = filterFormSemestres(sem)
|
if (dateIso == null) {
|
||||||
|
dateIso = document.querySelector("#tl_date").value
|
||||||
|
}
|
||||||
|
sem = filterFormSemestres(sem, dateIso)
|
||||||
const mod = getModulesImplByFormsemestre(sem)
|
const mod = getModulesImplByFormsemestre(sem)
|
||||||
populateSelect(mod, moduleimpl_id);
|
populateSelect(mod, moduleimpl_id, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedSelect(moduleimpl_id) {
|
function updateSelectedSelect(moduleimpl_id, query = "#moduleimpl_select") {
|
||||||
const mod_id = moduleimpl_id != null ? moduleimpl_id : ""
|
const mod_id = moduleimpl_id != null ? moduleimpl_id : ""
|
||||||
document.getElementById('moduleimpl_select').value = mod_id;
|
document.querySelector(query).value = mod_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.onload = () => {
|
window.addEventListener("load", () => {
|
||||||
document.getElementById('moduleimpl_select').addEventListener('change', () => {
|
document.getElementById('moduleimpl_select').addEventListener('change', (el) => {
|
||||||
const mod_id = document.getElementById('moduleimpl_select').value;
|
|
||||||
|
|
||||||
const assi = getCurrentAssiduite(etudid);
|
const assi = getCurrentAssiduite(etudid);
|
||||||
if (assi) {
|
if (assi) {
|
||||||
editAssiduite(assi.assiduite_id, assi.etat);
|
editAssiduite(assi.assiduite_id, assi.etat);
|
||||||
@ -118,7 +119,7 @@
|
|||||||
if (conflicts.length > 0) {
|
if (conflicts.length > 0) {
|
||||||
updateSelectedSelect(conflicts[0].moduleimpl_id);
|
updateSelectedSelect(conflicts[0].moduleimpl_id);
|
||||||
}
|
}
|
||||||
}
|
}, { once: true });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,512 +1,8 @@
|
|||||||
<h2>Signalement différé des assiduités {{gr |safe}}</h2>
|
<h2>Signalement différé des assiduités {{gr |safe}}</h2>
|
||||||
<h3>{{sem | safe }}</h3>
|
<h3>{{sem | safe }}</h3>
|
||||||
<button onclick="getAndVerify()">Valider les assiduités</button>
|
|
||||||
<div id="studentTable">
|
|
||||||
<div class="thead">
|
|
||||||
<div class="tr">
|
|
||||||
<div class="th sticky" order="ASC" onclick="reOrderEtudiants()">Noms</div>
|
|
||||||
<button id="addColumn" class="floating-button">+</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tbody">
|
|
||||||
|
|
||||||
{% for etud in etudiants %}
|
{{diff | safe}}
|
||||||
<div class="tr" etudid="{{etud.etudid}}">
|
|
||||||
<div class="td sticky">{{etud.nomprenom}}</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include "assiduites/alert.j2" %}
|
{% include "assiduites/alert.j2" %}
|
||||||
{% include "assiduites/prompt.j2" %}
|
{% include "assiduites/prompt.j2" %}
|
||||||
|
{% include "assiduites/conflict.j2" %}
|
||||||
|
|
||||||
<style>
|
|
||||||
button {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.err-assi {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container {
|
|
||||||
overflow: auto;
|
|
||||||
position: relative;
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-shadow: 0 0 1rem 0 rgba(0, 0, 0, .2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thead .tr {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thead .tr .th {
|
|
||||||
height: 200px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
|
|
||||||
.th.sticky {
|
|
||||||
z-index: 5;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.th,
|
|
||||||
.td {
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
width: 200px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tr {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
width: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky {
|
|
||||||
position: sticky;
|
|
||||||
left: 0;
|
|
||||||
background-color: #fafafa;
|
|
||||||
border-right: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-form {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-form input,
|
|
||||||
.mini-form select {
|
|
||||||
display: block;
|
|
||||||
margin: 5px;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addColumn {
|
|
||||||
font-size: 24px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 50%;
|
|
||||||
right: -60px;
|
|
||||||
top: calc(50% - 50px /2);
|
|
||||||
background-color: #007BFF;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addColumn:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.th {
|
|
||||||
background-color: #007BFF;
|
|
||||||
color: white;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tbody .tr:nth-child(even) {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tbody .tr:hover {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.etat {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 33% 33% 33%;
|
|
||||||
grid-template-rows: 50% 50%;
|
|
||||||
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
#moduleimpl_select {
|
|
||||||
max-width: 190px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btngroup {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btngroup>button {
|
|
||||||
background-color: transparent;
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
color: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close {
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activate {
|
|
||||||
font-size: large;
|
|
||||||
}
|
|
||||||
|
|
||||||
.th[activated] {
|
|
||||||
transition: all 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.th[activated='false'] {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
let verified = false;
|
|
||||||
const etatDef = "{{etat_def}}";
|
|
||||||
|
|
||||||
let forceModule = "{{ forcer_module }}"
|
|
||||||
forceModule = forceModule == "True" ? true : false
|
|
||||||
|
|
||||||
moment.tz.setDefault("Etc/UTC");
|
|
||||||
function createColumn() {
|
|
||||||
let table = document.getElementById("studentTable");
|
|
||||||
let th = document.createElement("div");
|
|
||||||
th.classList.add("th");
|
|
||||||
const col_id = `${document.querySelectorAll("[col]").length + 1}`;
|
|
||||||
th.setAttribute("col", col_id);
|
|
||||||
th.setAttribute("activated", "true");
|
|
||||||
th.innerHTML = `
|
|
||||||
<div class="mini-form">
|
|
||||||
<div class="btngroup">
|
|
||||||
<button class="activate" onclick="toggleColumn(${col_id})">Désactiver</button>
|
|
||||||
<button class="close" onclick="removeColumn(this)">x</button>
|
|
||||||
</div>
|
|
||||||
<input type="datetime-local" id="dateStart">
|
|
||||||
<input type="datetime-local" id="dateEnd">
|
|
||||||
{{moduleimpl_select|safe}}
|
|
||||||
<select name="mass_action_${col_id}" id="mass_action_${col_id}" onchange="setEtatCol(${col_id},this.value)">
|
|
||||||
<option value="">Sélectionner une assiduité</option>
|
|
||||||
<option value="0">Présent</option>
|
|
||||||
<option value="1">Retard</option>
|
|
||||||
<option value="2">Absent</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
table
|
|
||||||
.querySelector(".thead .tr")
|
|
||||||
.insertBefore(th, document.querySelector("#addColumn"));
|
|
||||||
|
|
||||||
const last = [...document.querySelectorAll("#dateStart")].pop();
|
|
||||||
defaultDate(last);
|
|
||||||
|
|
||||||
let rows = table.querySelector(".tbody").querySelectorAll(".tr");
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
|
||||||
let td = document.createElement("div");
|
|
||||||
td.setAttribute("colid", col_id)
|
|
||||||
td.classList.add("td", "etat");
|
|
||||||
const etudid = rows[i].getAttribute("etudid");
|
|
||||||
td.innerHTML = `
|
|
||||||
<input type="radio" name="etat_${col_id}_${etudid}" value="present">
|
|
||||||
<input type="radio" name="etat_${col_id}_${etudid}" value="retard">
|
|
||||||
<input type="radio" name="etat_${col_id}_${etudid}" value="absent">
|
|
||||||
<span>Present</span>
|
|
||||||
<span>Retard</span>
|
|
||||||
<span>Absent</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
rows[i].appendChild(td);
|
|
||||||
if (etatDef != "" && etatDef != "aucun") {
|
|
||||||
const inp = td.querySelector(`[value='${etatDef}']`).checked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reOrderEtudiants() {
|
|
||||||
const th = document.querySelector(".th.sticky");
|
|
||||||
let lines = [...document.querySelectorAll('.tr[etudid]')];
|
|
||||||
const tbody = document.querySelector('.tbody')
|
|
||||||
|
|
||||||
let order = (a, b) => { return a > b }
|
|
||||||
|
|
||||||
if (th.getAttribute("order") == "ASC") {
|
|
||||||
order = (a, b) => { return b > a }
|
|
||||||
th.setAttribute("order", "DESC")
|
|
||||||
} else {
|
|
||||||
th.setAttribute("order", "ASC")
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = lines.sort((a, b) => {
|
|
||||||
const nameA = a.querySelector(".td.sticky").textContent.split(" ").pop();
|
|
||||||
const nameB = b.querySelector(".td.sticky").textContent.split(" ").pop();
|
|
||||||
return order(nameA, nameB)
|
|
||||||
})
|
|
||||||
|
|
||||||
tbody.innerHTML = "";
|
|
||||||
tbody.append(...lines)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleColumn(colId) {
|
|
||||||
const column = document.querySelector(`[col='${colId}']`)
|
|
||||||
const btn = column.querySelector(".activate");
|
|
||||||
if (column.getAttribute('activated') == "true") {
|
|
||||||
column.setAttribute("activated", "false");
|
|
||||||
btn.textContent = "Activer"
|
|
||||||
} else {
|
|
||||||
column.setAttribute("activated", "true");
|
|
||||||
btn.textContent = "Déactiver"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function previousCol(column) {
|
|
||||||
const cols = [...document.querySelectorAll("[col]")]
|
|
||||||
let previousCol = null;
|
|
||||||
|
|
||||||
let i = 1;
|
|
||||||
|
|
||||||
while (i < cols.length) {
|
|
||||||
previousCol = cols[i - 1];
|
|
||||||
if (cols[i] == column)
|
|
||||||
break;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return previousCol
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeColumn(element) {
|
|
||||||
const col = element.parentElement.parentElement.parentElement;
|
|
||||||
const col_id = col.getAttribute("col");
|
|
||||||
document.querySelectorAll(`[col='${col_id}'],[colid='${col_id}']`).forEach((el) => { el.remove() })
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultDate(element) {
|
|
||||||
const num = previousCol(element.parentElement.parentElement)?.getAttribute("col");
|
|
||||||
const last = document.querySelector(`[col='${num}'] #dateEnd`);
|
|
||||||
let date = undefined;
|
|
||||||
if (last == undefined) {
|
|
||||||
date = moment().tz("Europe/Paris").format("YYYY-MM-DDTHH:mm");
|
|
||||||
} else {
|
|
||||||
date = last.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
element.value = date;
|
|
||||||
|
|
||||||
element.addEventListener(
|
|
||||||
"focusout",
|
|
||||||
() => {
|
|
||||||
const el = element.parentElement.querySelector("#dateEnd");
|
|
||||||
const el2 = element.parentElement.querySelector("#dateStart");
|
|
||||||
el.value = moment(el2.valueAsDate)
|
|
||||||
.add(2, "hours")
|
|
||||||
.format("YYYY-MM-DDTHH:mm");
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEtatCol(colId, etatId) {
|
|
||||||
const tds = [...document.querySelectorAll(`.td[colid='${colId}']`)]
|
|
||||||
tds.forEach((td) => {
|
|
||||||
let inputs = [...td.querySelectorAll("input")]
|
|
||||||
inputs.forEach((el) => { el.checked = false })
|
|
||||||
if (etatId != "") {
|
|
||||||
inputs[Number.parseInt(etatId)].checked = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEtatCol(colId) {
|
|
||||||
const etats = {};
|
|
||||||
const tds = [...document.querySelectorAll(`.td[colid='${colId}']`)]
|
|
||||||
tds.forEach((td) => {
|
|
||||||
const tr = td.parentElement
|
|
||||||
const etudid = tr.getAttribute("etudid");
|
|
||||||
let inputs = [...td.querySelectorAll("input")]
|
|
||||||
|
|
||||||
etatInput = inputs.filter((e) => e.checked).pop()
|
|
||||||
|
|
||||||
if (etatInput == undefined) {
|
|
||||||
etats[etudid] = "";
|
|
||||||
} else {
|
|
||||||
etats[etudid] = etatInput.value;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return etats;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createAssiduites(inputDeb, inputFin, moduleSelect, etudid, etat, colId) {
|
|
||||||
if (moduleSelect == "") {
|
|
||||||
return {
|
|
||||||
"date_debut": inputDeb,
|
|
||||||
"date_fin": inputFin,
|
|
||||||
"etudid": etudid,
|
|
||||||
"etat": etat,
|
|
||||||
"colid": colId,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
"date_debut": inputDeb,
|
|
||||||
"date_fin": inputFin,
|
|
||||||
"etudid": etudid,
|
|
||||||
"moduleimpl_id": moduleSelect,
|
|
||||||
"etat": etat,
|
|
||||||
"colid": colId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAndVerify() {
|
|
||||||
const assiduites = [];
|
|
||||||
const cols = [...document.querySelectorAll("[col]")];
|
|
||||||
const errors = [];
|
|
||||||
|
|
||||||
cols.forEach((col) => {
|
|
||||||
const col_id = col.getAttribute("col");
|
|
||||||
if (col.getAttribute("activated") == "false") return;
|
|
||||||
toggleColumn(col_id);
|
|
||||||
const etats = getEtatCol(col_id);
|
|
||||||
|
|
||||||
const inputDeb = col.querySelector("#dateStart").value;
|
|
||||||
const inputFin = col.querySelector("#dateEnd").value;
|
|
||||||
const moduleSelect = col.querySelector("#moduleimpl_select").value;
|
|
||||||
|
|
||||||
if (inputDeb == "" || inputFin == "") {
|
|
||||||
errors.push(`La période de la colonne n°${col_id} n'est pas valide`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const d_debut = moment(inputDeb)
|
|
||||||
const d_fin = moment(inputFin)
|
|
||||||
const testPeriode = [
|
|
||||||
d_debut.isBefore("{{formsemestre_date_debut}}"),
|
|
||||||
d_debut.isAfter("{{formsemestre_date_fin}}"),
|
|
||||||
d_fin.isBefore("{{formsemestre_date_debut}}"),
|
|
||||||
d_fin.isAfter("{{formsemestre_date_fin}}"),
|
|
||||||
]
|
|
||||||
if (testPeriode.some((e) => e)) {
|
|
||||||
errors.push(`La période de la colonne n°${col_id} n'est pas dans le semestre`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceModule && moduleSelect == "") {
|
|
||||||
errors.push(`Le module de la colonne n°${col_id} n'a pas été entré. (Préférence de semestre)`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO Mettre une erreur lorsque assiduité forcé (pref)
|
|
||||||
|
|
||||||
Object.keys(etats).forEach((key) => {
|
|
||||||
const etat = etats[key];
|
|
||||||
|
|
||||||
if (etat != "") {
|
|
||||||
assiduites.push(_createAssiduites(inputDeb, inputFin, moduleSelect, key, etat, col_id))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
const texte = document.createElement("div");
|
|
||||||
errors.map((err) => document.createTextNode(err)).forEach((err) => {
|
|
||||||
texte.appendChild(err);
|
|
||||||
texte.appendChild(document.createElement('br'));
|
|
||||||
})
|
|
||||||
|
|
||||||
openAlertModal("Erreur(s) détéctée(s)", texte)
|
|
||||||
} else {
|
|
||||||
if (assiduites.length > 0) {
|
|
||||||
createAllAssiduites(assiduites);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAllAssiduites(createQueue) {
|
|
||||||
if (createQueue.length < 0)
|
|
||||||
return;
|
|
||||||
const path = getUrl() + `/api/assiduites/create`;
|
|
||||||
sync_post(
|
|
||||||
path,
|
|
||||||
createQueue,
|
|
||||||
(data, status) => {
|
|
||||||
verified = true;
|
|
||||||
const { success, errors } = data;
|
|
||||||
|
|
||||||
const indexes = [...Object.keys(errors)];
|
|
||||||
if (indexes.length > 0) {
|
|
||||||
const incriminated = indexes.map((i) => {
|
|
||||||
return createQueue[Number.parseInt(i)];
|
|
||||||
})
|
|
||||||
|
|
||||||
const error_message = document.createElement('div');
|
|
||||||
|
|
||||||
for (let i = 0; i < incriminated.length; i++) {
|
|
||||||
const err = errors[indexes[i]];
|
|
||||||
const crimi = incriminated[i];
|
|
||||||
const nom = document.querySelector(`[etudid='${crimi.etudid}']`).firstElementChild.textContent.trim();
|
|
||||||
const col = crimi.colid;
|
|
||||||
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.classList.add("err-assi")
|
|
||||||
const span = document.createElement("span");
|
|
||||||
span.setAttribute("title", err);
|
|
||||||
span.textContent = "ℹ️"
|
|
||||||
|
|
||||||
const span2 = document.createElement("span");
|
|
||||||
span2.textContent = `L'assiduité (Colonne n°${col}) de ${nom} n'a pas pu être enregistrée`;
|
|
||||||
|
|
||||||
div.appendChild(span2);
|
|
||||||
div.appendChild(span);
|
|
||||||
|
|
||||||
error_message.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
openAlertModal("Certaines assiduités non pas été enregistrées", error_message)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
openAlertModal("Tous les assiduités ont bien été enregistrée", document.createTextNode(""), null, "#09AD2A")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(data, status) => {
|
|
||||||
//error
|
|
||||||
console.error(data, status);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementById("addColumn").addEventListener("click", () => {
|
|
||||||
createColumn();
|
|
||||||
});
|
|
||||||
|
|
||||||
const onConfirmRefresh = function (event) {
|
|
||||||
if (!verified)
|
|
||||||
return event.returnValue = "Attention, certaines données n'ont pas été enregistrées";
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("beforeunload", onConfirmRefresh, { capture: true });
|
|
||||||
createColumn();
|
|
||||||
|
|
||||||
</script>
|
|
@ -1,36 +1,8 @@
|
|||||||
{# -*- mode: jinja-html -*- #}
|
{# -*- mode: jinja-html -*- #}
|
||||||
<div id="myModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<span class="close">×</span>
|
|
||||||
<h2>Veuillez régler le conflit pour poursuivre</h2>
|
|
||||||
<!-- Ajout de la frise chronologique -->
|
|
||||||
<div class="modal-timeline">
|
|
||||||
<div class="time-labels"></div>
|
|
||||||
<div class="assiduites-container"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button id="delete" class="btnPrompt" disabled>Supprimer</button>
|
|
||||||
<button id="split" class="btnPrompt" disabled>Séparer</button>
|
|
||||||
<button id="edit" class="btnPrompt" disabled>Modifier</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-assiduite-content">
|
|
||||||
<h2>Information de l'assiduité sélectionnée</h2>
|
|
||||||
<div class="infos">
|
|
||||||
<p>Assiduite id : <span id="modal-assiduite-id">A</span></p>
|
|
||||||
<p>Etat : <span id="modal-assiduite-etat">B</span></p>
|
|
||||||
<p>Date de début : <span id="modal-assiduite-deb">C</span></p>
|
|
||||||
<p>Date de fin: <span id="modal-assiduite-fin">D</span></p>
|
|
||||||
<p>Module : <span id="modal-assiduite-module">E</span></p>
|
|
||||||
<p><span id="modal-assiduite-user">F</span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include "assiduites/toast.j2" %}
|
{% include "assiduites/toast.j2" %}
|
||||||
{% include "assiduites/alert.j2" %}
|
{% include "assiduites/alert.j2" %}
|
||||||
{% include "assiduites/prompt.j2" %}
|
{% include "assiduites/prompt.j2" %}
|
||||||
|
{% include "assiduites/conflict.j2" %}
|
||||||
<div id="page-assiduite-content">
|
<div id="page-assiduite-content">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Signalement de l'assiduité de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
<h2>Signalement de l'assiduité de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
||||||
@ -44,7 +16,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{% include "assiduites/moduleimpl_dynamic_selector.j2" %}
|
{{moduleimpl_select | safe }}
|
||||||
<button class="btn" onclick="fastJustify(getCurrentAssiduite(etudid))" id="justif-rapide">Justifier</button>
|
<button class="btn" onclick="fastJustify(getCurrentAssiduite(etudid))" id="justif-rapide">Justifier</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -59,6 +31,30 @@
|
|||||||
<div class="index"></div>
|
<div class="index"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
{{diff | safe}}
|
||||||
|
|
||||||
|
<div class="legende">
|
||||||
|
<h3>Explication diverses</h3>
|
||||||
|
<p>
|
||||||
|
Si la période indiquée par la timeline provoque un conflit d'assiduité pour un étudiant sa ligne deviendra
|
||||||
|
rouge.
|
||||||
|
<br>
|
||||||
|
Dans ce cas il faut résoudre manuellement le conflit : cliquez sur un des boutons d'assiduités pour ouvrir
|
||||||
|
le
|
||||||
|
résolveur de conflit.
|
||||||
|
<br>
|
||||||
|
Correspondance des couleurs :
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Vert -> présence de l'étudiant lors de la période</li>
|
||||||
|
<li>Orange -> retard de l'étudiant lors de la période</li>
|
||||||
|
<li>Rouge -> absence de l'étudiant lors de la période</li>
|
||||||
|
<li>Hachure Bleu -> l'assiduité est justifiée par un justificatif valide</li>
|
||||||
|
<li>Hachure Rouge -> l'assiduité est justifiée par un justificatif non valide / en attente de validation
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Ajout d'un conteneur pour le loader -->
|
<!-- Ajout d'un conteneur pour le loader -->
|
||||||
@ -99,8 +95,13 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let forceModule = "{{ forcer_module }}"
|
window.forceModule = "{{ forcer_module }}"
|
||||||
forceModule = forceModule == "True" ? true : false
|
window.forceModule = window.forceModule == "True" ? true : false
|
||||||
|
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
loading();
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -110,6 +111,11 @@
|
|||||||
background-color: rgb(104, 104, 252);
|
background-color: rgb(104, 104, 252);
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<fieldset class="selectors">
|
<fieldset class="selectors">
|
||||||
<div>Groupes : {{grp|safe}}</div>
|
<div>Groupes : {{grp|safe}}</div>
|
||||||
|
|
||||||
<div>Modules :{{moduleimpl_select|safe}}</div>
|
<div>Module :{{moduleimpl_select|safe}}</div>
|
||||||
|
|
||||||
<div class="infos">
|
<div class="infos">
|
||||||
Date: <span id="datestr"></span>
|
Date: <span id="datestr"></span>
|
||||||
@ -31,35 +31,31 @@
|
|||||||
{{timeline|safe}}
|
{{timeline|safe}}
|
||||||
|
|
||||||
<div class="etud_holder">
|
<div class="etud_holder">
|
||||||
|
<p class="placeholder">
|
||||||
|
Veillez à choisir le groupe concerné par la saisie ainsi que la date de la saisie.
|
||||||
|
Après validation, il faudra recharger la page pour changer les informations de la saisie.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="myModal" class="modal">
|
<div class="legende">
|
||||||
<div class="modal-content">
|
<h3>Explication diverses</h3>
|
||||||
<span class="close">×</span>
|
<p>
|
||||||
<h2>Veuillez régler le conflit pour poursuivre</h2>
|
Si la période indiquée par la timeline provoque un conflit d'assiduité pour un étudiant sa ligne deviendra
|
||||||
<!-- Ajout de la frise chronologique -->
|
rouge.
|
||||||
<div class="modal-timeline">
|
<br>
|
||||||
<div class="time-labels"></div>
|
Dans ce cas il faut résoudre manuellement le conflit : cliquez sur un des boutons d'assiduités pour ouvrir
|
||||||
<div class="assiduites-container"></div>
|
le
|
||||||
</div>
|
résolveur de conflit.
|
||||||
|
<br>
|
||||||
<div class="action-buttons">
|
Correspondance des couleurs :
|
||||||
<button id="delete" class="btnPrompt" disabled>Supprimer</button>
|
</p>
|
||||||
<button id="split" class="btnPrompt" disabled>Séparer</button>
|
<ul>
|
||||||
<button id="edit" class="btnPrompt" disabled>Modifier l'état</button>
|
<li>Vert -> présence de l'étudiant lors de la période</li>
|
||||||
</div>
|
<li>Orange -> retard de l'étudiant lors de la période</li>
|
||||||
</div>
|
<li>Rouge -> absence de l'étudiant lors de la période</li>
|
||||||
|
<li>Hachure Bleu -> l'assiduité est justifiée par un justificatif valide</li>
|
||||||
<div class="modal-assiduite-content">
|
<li>Hachure Rouge -> l'assiduité est justifiée par un justificatif non valide / en attente de validation
|
||||||
<h2>Information de l'assiduité sélectionnée</h2>
|
</li>
|
||||||
<div class="infos">
|
</ul>
|
||||||
<p>Assiduite id : <span id="modal-assiduite-id">A</span></p>
|
|
||||||
<p>Etat : <span id="modal-assiduite-etat">B</span></p>
|
|
||||||
<p>Date de début : <span id="modal-assiduite-deb">C</span></p>
|
|
||||||
<p>Date de fin: <span id="modal-assiduite-fin">D</span></p>
|
|
||||||
<p>Module : <span id="modal-assiduite-module">E</span></p>
|
|
||||||
<p><span id="modal-assiduite-user">F</span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Ajout d'un conteneur pour le loader -->
|
<!-- Ajout d'un conteneur pour le loader -->
|
||||||
<div class="loader-container" id="loaderContainer">
|
<div class="loader-container" id="loaderContainer">
|
||||||
@ -68,15 +64,18 @@
|
|||||||
|
|
||||||
{% include "assiduites/alert.j2" %}
|
{% include "assiduites/alert.j2" %}
|
||||||
{% include "assiduites/prompt.j2" %}
|
{% include "assiduites/prompt.j2" %}
|
||||||
|
{% include "assiduites/conflict.j2" %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const nonWorkDays = [{{ nonworkdays| safe }}];
|
const nonWorkDays = [{{ nonworkdays| safe }}];
|
||||||
|
|
||||||
updateDate();
|
updateDate();
|
||||||
setupDate();
|
setupDate();
|
||||||
setupTimeLine();
|
setupTimeLine();
|
||||||
|
|
||||||
let forceModule = "{{ forcer_module }}"
|
window.forceModule = "{{ forcer_module }}"
|
||||||
forceModule = forceModule == "True" ? true : false
|
window.forceModule = window.forceModule == "True" ? true : false
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
@ -12,6 +12,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,8 +186,7 @@ def signal_assiduites_etud():
|
|||||||
"libjs/moment.new.min.js",
|
"libjs/moment.new.min.js",
|
||||||
"libjs/moment-timezone.js",
|
"libjs/moment-timezone.js",
|
||||||
],
|
],
|
||||||
cssstyles=CSSSTYLES
|
cssstyles=[
|
||||||
+ [
|
|
||||||
"css/assiduites.css",
|
"css/assiduites.css",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -198,6 +197,12 @@ def signal_assiduites_etud():
|
|||||||
lunch = get_time("assi_lunch_time", "13:00:00")
|
lunch = get_time("assi_lunch_time", "13:00:00")
|
||||||
afternoon = get_time("assi_afternoon_time", "18:00:00")
|
afternoon = get_time("assi_afternoon_time", "18:00:00")
|
||||||
|
|
||||||
|
select = """
|
||||||
|
<select class="dynaSelect">
|
||||||
|
<option value="" selected> Non spécifié </option>
|
||||||
|
</select>
|
||||||
|
"""
|
||||||
|
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
_mini_timeline(),
|
_mini_timeline(),
|
||||||
@ -213,27 +218,15 @@ def signal_assiduites_etud():
|
|||||||
forcer_module=sco_preferences.get_preference(
|
forcer_module=sco_preferences.get_preference(
|
||||||
"forcer_module", dept_id=g.scodoc_dept_id
|
"forcer_module", dept_id=g.scodoc_dept_id
|
||||||
),
|
),
|
||||||
|
moduleimpl_select=_dynamic_module_selector(),
|
||||||
|
diff=_differee(
|
||||||
|
etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
|
||||||
|
moduleimpl_select=select,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
|
|
||||||
def _non_work_days():
|
|
||||||
non_travail = sco_preferences.get_preference("non_travail", None)
|
|
||||||
non_travail = non_travail.replace(" ", "").split(",")
|
|
||||||
return ",".join([f"'{i.lower()}'" for i in non_travail])
|
|
||||||
|
|
||||||
|
|
||||||
def _str_to_num(string: str):
|
|
||||||
parts = [*map(float, string.split(":"))]
|
|
||||||
hour = parts[0]
|
|
||||||
minutes = round(parts[1] / 60 * 4) / 4
|
|
||||||
return hour + minutes
|
|
||||||
|
|
||||||
|
|
||||||
def get_time(label: str, default: str):
|
|
||||||
return _str_to_num(ScoDocSiteConfig.get(label, default))
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ListeAssiduitesEtud")
|
@bp.route("/ListeAssiduitesEtud")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoAbsChange)
|
@permission_required(Permission.ScoAbsChange)
|
||||||
@ -339,6 +332,15 @@ def signal_assiduites_group():
|
|||||||
for m in groups_infos.members
|
for m in groups_infos.members
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# --- Vérification de la date ---
|
||||||
|
|
||||||
|
real_date = scu.is_iso_formated(date, True).date()
|
||||||
|
|
||||||
|
if real_date < formsemestre.date_debut:
|
||||||
|
date = formsemestre.date_debut.isoformat()
|
||||||
|
elif real_date > formsemestre.date_fin:
|
||||||
|
date = formsemestre.date_fin.isoformat()
|
||||||
|
|
||||||
# --- Restriction en fonction du moduleimpl_id ---
|
# --- Restriction en fonction du moduleimpl_id ---
|
||||||
if moduleimpl_id:
|
if moduleimpl_id:
|
||||||
mod_inscrits = {
|
mod_inscrits = {
|
||||||
@ -490,6 +492,7 @@ def get_etat_abs_date():
|
|||||||
def signal_assiduites_diff():
|
def signal_assiduites_diff():
|
||||||
group_ids: list[int] = request.args.get("group_ids", None)
|
group_ids: list[int] = request.args.get("group_ids", None)
|
||||||
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||||
|
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
||||||
etudiants: list[dict] = []
|
etudiants: list[dict] = []
|
||||||
|
|
||||||
titre = None
|
titre = None
|
||||||
@ -502,6 +505,15 @@ def signal_assiduites_diff():
|
|||||||
|
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
|
||||||
|
# --- Vérification de la date ---
|
||||||
|
|
||||||
|
real_date = scu.is_iso_formated(date, True).date()
|
||||||
|
|
||||||
|
if real_date < formsemestre.date_debut:
|
||||||
|
date = formsemestre.date_debut.isoformat()
|
||||||
|
elif real_date > formsemestre.date_fin:
|
||||||
|
date = formsemestre.date_fin.isoformat()
|
||||||
|
|
||||||
if group_ids is None:
|
if group_ids is None:
|
||||||
group_ids = []
|
group_ids = []
|
||||||
else:
|
else:
|
||||||
@ -529,6 +541,9 @@ def signal_assiduites_diff():
|
|||||||
header: str = html_sco_header.sco_header(
|
header: str = html_sco_header.sco_header(
|
||||||
page_title="Assiduités Différées",
|
page_title="Assiduités Différées",
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
|
cssstyles=[
|
||||||
|
"css/assiduites.css",
|
||||||
|
],
|
||||||
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
|
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
|
||||||
+ [
|
+ [
|
||||||
"js/assiduites.js",
|
"js/assiduites.js",
|
||||||
@ -554,15 +569,14 @@ def signal_assiduites_diff():
|
|||||||
header,
|
header,
|
||||||
render_template(
|
render_template(
|
||||||
"assiduites/signal_assiduites_diff.j2",
|
"assiduites/signal_assiduites_diff.j2",
|
||||||
etudiants=etudiants,
|
diff=_differee(
|
||||||
etat_def=sco_preferences.get_preference("assi_etat_defaut"),
|
etudiants=etudiants,
|
||||||
formsemestre_date_debut=str(formsemestre.date_debut),
|
moduleimpl_select=_module_selector(formsemestre),
|
||||||
formsemestre_date_fin=str(formsemestre.date_fin),
|
date=date,
|
||||||
moduleimpl_select=_module_selector(formsemestre),
|
periode={
|
||||||
forcer_module=sco_preferences.get_preference(
|
"deb": formsemestre.date_debut.isoformat(),
|
||||||
"forcer_module",
|
"fin": formsemestre.date_fin.isoformat(),
|
||||||
formsemestre_id=formsemestre_id,
|
},
|
||||||
dept_id=g.scodoc_dept_id,
|
|
||||||
),
|
),
|
||||||
gr=gr_tit,
|
gr=gr_tit,
|
||||||
sem=sem["titre_num"],
|
sem=sem["titre_num"],
|
||||||
@ -571,6 +585,35 @@ def signal_assiduites_diff():
|
|||||||
).build()
|
).build()
|
||||||
|
|
||||||
|
|
||||||
|
def _differee(
|
||||||
|
etudiants, moduleimpl_select, date=None, periode=None, formsemestre_id=None
|
||||||
|
):
|
||||||
|
if date is None:
|
||||||
|
date = datetime.date.today().isoformat()
|
||||||
|
|
||||||
|
forcer_module = sco_preferences.get_preference(
|
||||||
|
"forcer_module",
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
dept_id=g.scodoc_dept_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
etat_def = sco_preferences.get_preference(
|
||||||
|
"assi_etat_defaut",
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
dept_id=g.scodoc_dept_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"assiduites/differee.j2",
|
||||||
|
etudiants=etudiants,
|
||||||
|
etat_def=etat_def,
|
||||||
|
forcer_module=forcer_module,
|
||||||
|
moduleimpl_select=moduleimpl_select,
|
||||||
|
date=date,
|
||||||
|
periode=periode,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _module_selector(
|
def _module_selector(
|
||||||
formsemestre: FormSemestre, moduleimpl_id: int = None
|
formsemestre: FormSemestre, moduleimpl_id: int = None
|
||||||
) -> HTMLElement:
|
) -> HTMLElement:
|
||||||
@ -608,6 +651,10 @@ def _module_selector(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _dynamic_module_selector():
|
||||||
|
return render_template("assiduites/moduleimpl_dynamic_selector.j2")
|
||||||
|
|
||||||
|
|
||||||
def _timeline(formsemestre_id=None) -> HTMLElement:
|
def _timeline(formsemestre_id=None) -> HTMLElement:
|
||||||
return render_template(
|
return render_template(
|
||||||
"assiduites/timeline.j2",
|
"assiduites/timeline.j2",
|
||||||
@ -626,3 +673,20 @@ def _mini_timeline() -> HTMLElement:
|
|||||||
t_start=get_time("assi_morning_time", "08:00:00"),
|
t_start=get_time("assi_morning_time", "08:00:00"),
|
||||||
t_end=get_time("assi_afternoon_time", "18:00:00"),
|
t_end=get_time("assi_afternoon_time", "18:00:00"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _non_work_days():
|
||||||
|
non_travail = sco_preferences.get_preference("non_travail", None)
|
||||||
|
non_travail = non_travail.replace(" ", "").split(",")
|
||||||
|
return ",".join([f"'{i.lower()}'" for i in non_travail])
|
||||||
|
|
||||||
|
|
||||||
|
def _str_to_num(string: str):
|
||||||
|
parts = [*map(float, string.split(":"))]
|
||||||
|
hour = parts[0]
|
||||||
|
minutes = round(parts[1] / 60 * 4) / 4
|
||||||
|
return hour + minutes
|
||||||
|
|
||||||
|
|
||||||
|
def get_time(label: str, default: str):
|
||||||
|
return _str_to_num(ScoDocSiteConfig.get(label, default))
|
||||||
|
Loading…
Reference in New Issue
Block a user