diff --git a/app/scodoc/sco_abs_notification.py b/app/scodoc/sco_abs_notification.py
index 54a645e3..0a63fc56 100644
--- a/app/scodoc/sco_abs_notification.py
+++ b/app/scodoc/sco_abs_notification.py
@@ -31,6 +31,7 @@
Il suffit d'appeler abs_notify() après chaque ajout d'absence.
"""
+
import datetime
from typing import Optional
@@ -281,6 +282,9 @@ def abs_notification_message(
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid, _external=True
)
+ # Formsemestre concerné (ex: "BUT Informatique semestre 2")
+ values["semestre"] = formsemestre.titre_num()
+
template = prefs["abs_notification_mail_tmpl"]
txt = ""
if template:
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index 1890b87d..abe85457 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -371,20 +371,6 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
>Saisie Absences (Hebdo)
"""
)
- H.append(
- f"""
- (Saisie Absences Différée)
- """
- )
-
H.append("")
#
if not modimpl.check_apc_conformity(nt):
diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js
index 0d12635c..4bcb8396 100644
--- a/app/static/js/assiduites.js
+++ b/app/static/js/assiduites.js
@@ -168,6 +168,10 @@ function creerLigneEtudiant(etud, index) {
date_fin: null,
};
+ /**Retourne une liste d'assiduité en conflit avec la période actuelle
+ * @param {Array} assiduites - Les assiduités de l'étudiant
+ * @returns {Array} Les assiduités en conflit
+ */
function recupConflitsAssiduites(assiduites) {
const period = getPeriodAsDate();
@@ -182,9 +186,12 @@ function creerLigneEtudiant(etud, index) {
);
});
}
-
+ // Pas de conflit en readonly
const conflits = readOnly ? [] : recupConflitsAssiduites(etud.assiduites);
+ // Si il y a des conflits, on prend le premier pour l'afficher
+ // si les dates de début et de fin sont les mêmes, c'est une édition
+ // sinon c'est un conflit
if (conflits.length > 0) {
currentAssiduite = conflits[0];
@@ -200,6 +207,49 @@ function creerLigneEtudiant(etud, index) {
: "conflit";
}
+ // Création de la ligne étudiante en DOM
+ /* exemple de ligne étudiante
+
+ */
const ligneEtud = document.createElement("div");
ligneEtud.classList.add("etud_row");
if (Object.keys(etudsDefDem).includes(etud.id)) {
@@ -388,6 +438,9 @@ async function creerTousLesEtudiants(etuds) {
etudsDiv.innerHTML = "";
const moduleImplId = readOnly ? null : $("#moduleimpl_select").val();
const inscriptions = await getInscriptionModule(moduleImplId);
+ // on trie les étudiants par ordre alphabétique
+ // et on garde ceux qui sont inscrits au module
+ // puis pour chaque étudiant on crée une ligne
[...etuds.values()]
.sort((a, b) => {
return a.sort_key > b.sort_key ? 1 : -1;
@@ -496,10 +549,9 @@ async function getInscriptionModule(moduleimpl_id) {
return inscriptionsModules.get(moduleimpl_id);
}
-
+// Mise à jour de la ligne étudiant
async function MiseAJourLigneEtud(etud) {
//Récupérer ses assiduités
-
function RecupAssiduitesEtudiant(etudid) {
const date = $("#date").datepicker("getDate");
const date_debut = date.add(-1, "days").format("YYYY-MM-DDTHH:mm");
@@ -527,6 +579,8 @@ async function MiseAJourLigneEtud(etud) {
}
await RecupAssiduitesEtudiant(etud.id);
+ // Une fois les assiduités récupérées, on met à jour la ligne étudiant
+ // on replace l'ancienne ligne par la nouvellement générée
const etudRow = document.getElementById(`etud_row_${etud.id}`);
if (etudRow == null) return;
@@ -540,12 +594,14 @@ async function MiseAJourLigneEtud(etud) {
etudRow.replaceWith(ligneEtud);
}
+// Action appelée lors d'un clic sur un bouton d'assiduité
+// Création, édition ou suppression d'une assiduité
async function actionAssiduite(etud, etat, type, assiduite = null) {
const modimpl_id = $("#moduleimpl_select").val();
if (assiduite && assiduite.etat.toLowerCase() === etat) type = "suppression";
const { deb, fin } = getPeriodAsDate();
-
+ // génération d'un objet assiduité basique qui sera complété
let assiduiteObjet = assiduite ?? {
date_debut: deb,
date_fin: fin,
@@ -554,7 +610,8 @@ async function actionAssiduite(etud, etat, type, assiduite = null) {
assiduiteObjet.etat = etat;
assiduiteObjet.moduleimpl_id = modimpl_id;
-
+ // En fonction du type d'action on appelle la bonne route
+ // avec les bonnes valeurs
if (type === "creation") {
await async_post(
`../../api/assiduite/${etud.id}/create`,
@@ -606,7 +663,9 @@ async function actionAssiduite(etud, etat, type, assiduite = null) {
);
}
}
-
+// Fonction pour afficher un message d'erreur si le module n'est pas renseigné
+// ou si l'étudiant n'est pas inscrit au module.
+// On donne le message d'erreur d'une requête api et cela affiche le message correspondant
function erreurModuleImpl(message) {
if (message == "Module non renseigné") {
const HTML = `
@@ -635,7 +694,9 @@ function erreurModuleImpl(message) {
openAlertModal("Sélection du module", content);
}
}
-
+// Fonction pour ajouter en lot une assiduité à tous les étudiants
+// Fonctionne uniquement pour créer ou supprimer des assiduités
+// Pas d'édition possible
function mettreToutLeMonde(etat, el = null) {
const lignesEtuds = [...document.querySelectorAll("fieldset.btns_field")];
@@ -709,7 +770,7 @@ function mettreToutLeMonde(etat, el = null) {
}
envoiToastTous("remove", assiduites_id.length);
if (Object.keys(unDeleted).length == 0) return;
-
+ // CAS : des assiduités d'étudiants n'ont pas pu être supprimés
let unDeletedEtuds = `
${Object.keys(unDeleted)
@@ -771,6 +832,7 @@ function mettreToutLeMonde(etat, el = null) {
});
}
+// Affichage d'un loader (animation jeu pong)
function afficheLoader() {
const loaderDiv = document.createElement("div");
loaderDiv.id = "loader";
@@ -782,11 +844,13 @@ function afficheLoader() {
loaderDiv.appendChild(loader);
document.body.appendChild(loaderDiv);
}
-
+// Retrait du loader (animation jeu pong)
function retirerLoader() {
document.getElementById("loader").remove();
}
+// Simplification de l'envoie de toast pour un étudiant
+// affiche le nom, le prénom et l'état de l'assiduité avec une couleur spécifique
function envoiToastEtudiant(etat, etud) {
let etatAffiche;
@@ -810,8 +874,10 @@ function envoiToastEtudiant(etat, etud) {
pushToast(generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5));
}
-
-// TODO commenter toutes les fonctions js
+// Fonction pour simplifier l'envoie de toast avec le bouton "mettre tout le monde"
+// On donne un etat et un compte et cela affichera le message associé.
+// ex : 12 assiduités ont été supprimées
+// ex : 15 étudiants ont été mis Absent.
function envoiToastTous(etat, count) {
const span = document.createElement("span");
let etatAffiche = etat;
@@ -840,7 +906,9 @@ function envoiToastTous(etat, count) {
pushToast(generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5));
}
-
+// Permet de savoir si un jour est travaillé ou pas
+// jour : Date
+// nonWorkdays : Array[str] => ["mar", "sam", "dim"]
function estJourTravail(jour, nonWorkdays) {
const d = Intl.DateTimeFormat("fr-FR", {
timeZone: SCO_TIMEZONE,
@@ -851,6 +919,9 @@ function estJourTravail(jour, nonWorkdays) {
return !nonWorkdays.includes(d);
}
+// Renvoie le dernier jour travaillé disponible.
+// par défaut va en arrière (dans le passé)
+// si anti == False => va dans le futur
function retourJourTravail(date, anti = true) {
const jourMiliSecondes = 86400000; // 24 * 3600 * 1000 | H * s * ms
let jour = date;
@@ -864,12 +935,18 @@ function retourJourTravail(date, anti = true) {
}
return jour;
}
-
+// Vérifie si la date courante est travaillée
+// Si ce n'est pas le cas, on change la date pour le dernier jour travaillé (passé)
+// et on affiche une alerte
+// (utilise le datepicker #date)
function dateCouranteEstTravaillee() {
const date = $("#date").datepicker("getDate");
+
if (!estJourTravail(date, nonWorkDays)) {
+ // récupération du jour travaillé le plus proche
const nouvelleDate = retourJourTravail(date);
$("#date").datepicker("setDate", nouvelleDate);
+ // Création du message d'alerte
let msg = "Le jour sélectionné";
if (new Date().format("YYYY-MM-DD") == date.format("YYYY-MM-DD")) {
msg = "Aujourd'hui";
@@ -889,13 +966,15 @@ function dateCouranteEstTravaillee() {
)}.`
)
);
+ // Affichage de l'alerte
openAlertModal("Attention", div, "", "#eec660");
return false;
}
return true;
}
-
+// Fonction pour passer au jour suivant
+// anti : bool => si true, on va dans le passé
function jourSuivant(anti = false) {
let date = $("#date").datepicker("getDate");
diff --git a/app/templates/assiduites/pages/bilan_etud.j2 b/app/templates/assiduites/pages/bilan_etud.j2
index 6bb97053..74586c5f 100644
--- a/app/templates/assiduites/pages/bilan_etud.j2
+++ b/app/templates/assiduites/pages/bilan_etud.j2
@@ -111,7 +111,13 @@ Bilan assiduité de {{sco.etud.nomprenom}}
-
-
-{% include "sco_timepicker.j2" %}
-
-
-
-{% endblock scripts %}
-
-{% block title %}
-{{title}}
-{% endblock title %}
-
-{% block app_content %}
-
-
Signalement différé de l'assiduité {{gr |safe}}
-
-
-Attention, cette page va prochainement être supprimée, car il est plus facile d'utiliser
-
Ci-dessous le formulaire vous permettant de saisir plusieurs plages à la fois,
-qui va bientôt être retiré.
-
-
N'hésitez pas à commenter sur le salon Discord
-si vous avez d'autres besoins.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Étudiants
- {#
Plage X
#}
-
- {# ... #}
-
-
-
- {% for etud in etudiants %}
-
-
-
- {{ etud.nomprenom }}
-
- {#
Assiduité Plage 1
#}
-
-
- {% endfor %}
-
-
-
-
-
-
-{% include "assiduites/widgets/alert.j2" %}
-{% endblock app_content %}
diff --git a/app/templates/assiduites/pages/visu_assi_group.j2 b/app/templates/assiduites/pages/visu_assi_group.j2
index 2b99c8a1..3dc48d26 100644
--- a/app/templates/assiduites/pages/visu_assi_group.j2
+++ b/app/templates/assiduites/pages/visu_assi_group.j2
@@ -44,10 +44,15 @@ label.stats_checkbox {
const date_fin = "{{date_fin}}";
const group_ids = "{{group_ids}}";
+ // Changement de la date de début ou de fin des statitiques
+ // Recharge la page avec les nouvelles dates
function stats() {
const deb = Date.fromFRA(document.querySelector('#stats_date_debut').value);
const fin = Date.fromFRA(document.querySelector('#stats_date_fin').value);
- location.href = `visu_assi_group?group_ids=${group_ids}&date_debut=${deb}&date_fin=${fin}`;
+ let url = new URL(window.location.href);
+ url.searchParams.set('date_debut', deb);
+ url.searchParams.set('date_fin', fin);
+ location.href = url.href;
}
window.addEventListener('load', () => {
diff --git a/app/templates/assiduites/widgets/alert.j2 b/app/templates/assiduites/widgets/alert.j2
index bf1f0bfe..7aa527ba 100644
--- a/app/templates/assiduites/widgets/alert.j2
+++ b/app/templates/assiduites/widgets/alert.j2
@@ -127,19 +127,29 @@
{% endblock promptModal %}
\ No newline at end of file
diff --git a/app/templates/assiduites/widgets/tableau.j2 b/app/templates/assiduites/widgets/tableau.j2
index 1517ebcb..a392b7f8 100644
--- a/app/templates/assiduites/widgets/tableau.j2
+++ b/app/templates/assiduites/widgets/tableau.j2
@@ -154,7 +154,7 @@
-
diff --git a/app/templates/assiduites/widgets/timeline.j2 b/app/templates/assiduites/widgets/timeline.j2
index d7acdbe8..eaac2b84 100644
--- a/app/templates/assiduites/widgets/timeline.j2
+++ b/app/templates/assiduites/widgets/timeline.j2
@@ -27,21 +27,25 @@
let handleMoving = false;
+ // Création des graduations de la timeline
+ // On créé des grandes graduations pour les heures
+ // On créé des petites graduations pour les "tick"
function createTicks() {
let i = t_start;
while (i <= t_end) {
+ // création d'un tick Heure (grand)
const hourTick = document.createElement("div");
hourTick.classList.add("tick", "hour");
hourTick.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`;
timelineContainer.appendChild(hourTick);
-
+ // on ajoute un label pour l'heure (ex : 12:00)
const tickLabel = document.createElement("div");
tickLabel.classList.add("tick-label");
tickLabel.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`;
tickLabel.textContent = numberToTime(i);
timelineContainer.appendChild(tickLabel);
-
+ // Si on est pas à la fin, on ajoute les graduations intermédiaires
if (i < t_end) {
let j = Math.floor(i + 1);
@@ -49,6 +53,7 @@
i += tick_delay;
if (i <= t_end) {
+ // création d'un tick (petit)
const quarterTick = document.createElement("div");
quarterTick.classList.add("tick", "quarter");
quarterTick.style.left = `${computePercentage(i, t_start)}%`;
@@ -62,7 +67,8 @@
}
}
}
-
+ // Convertit un nombre en heure
+ // ex : 12.5 => "12:30"
function numberToTime(num) {
const integer = Math.floor(num);
const decimal = Math.round((num % 1) * 60);
@@ -80,13 +86,12 @@
return int + dec;
}
-
+ // Arrondi un nombre au tick le plus proche
function snapToQuarter(value) {
-
-
return Math.round(value * tick_time) / tick_time;
}
-
+ // Mise à jour des valeurs des timepickers
+ // En fonction des valeurs de la timeline
function updatePeriodTimeLabel() {
const values = getPeriodValues();
const deb = numberToTime(values[0])
@@ -102,96 +107,112 @@
}
+ // Gestion des évènements de la timeline
+ // - Déplacement des poignées
+ // - Déplacement de la période
function timelineMainEvent(event) {
+ // Position de départ de l'événement (souris ou tactile)
const startX = (event.clientX || event.changedTouches[0].clientX);
+ // Vérifie si l'événement concerne une poignée de période
if (event.target.classList.contains("period-handle")) {
+ // Initialisation des valeurs de départ
const startWidth = parseFloat(periodTimeLine.style.width);
const startLeft = parseFloat(periodTimeLine.style.left);
const isLeftHandle = event.target.classList.contains("left");
- handleMoving = true
+ handleMoving = true;
+
+ // Fonction de déplacement de la poignée
const onMouseMove = (moveEvent) => {
if (!handleMoving) return;
+ // Calcul du déplacement en pixels
const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
const containerWidth = timelineContainer.clientWidth;
- const newWidth =
- startWidth + ((isLeftHandle ? -deltaX : deltaX) / containerWidth) * 100;
+ // Calcul de la nouvelle largeur en pourcentage
+ const newWidth = startWidth + ((isLeftHandle ? -deltaX : deltaX) / containerWidth) * 100;
if (isLeftHandle) {
+ // Si la poignée gauche est déplacée, ajuste également la position gauche
const newLeft = startLeft + (deltaX / containerWidth) * 100;
adjustPeriodPosition(newLeft, newWidth);
} else {
adjustPeriodPosition(parseFloat(periodTimeLine.style.left), newWidth);
}
+ // Met à jour l'étiquette de temps de la période
updatePeriodTimeLabel();
};
+
+ // Fonction de relâchement de la souris ou du tactile
+ // - Alignement des poignées sur les ticks
+ // - Appel des callbacks
+ // - Sauvegarde des valeurs dans le local storage
+ // - Réinitialisation de la variable de déplacement des poignées
const mouseUp = () => {
snapHandlesToQuarters();
timelineContainer.removeEventListener("mousemove", onMouseMove);
handleMoving = false;
func_call();
savePeriodInLocalStorage();
+ };
- }
+ // Ajoute les écouteurs d'événement pour le déplacement et le relâchement
timelineContainer.addEventListener("mousemove", onMouseMove);
timelineContainer.addEventListener("touchmove", onMouseMove);
- document.addEventListener(
- "mouseup",
- mouseUp,
- { once: true }
- );
- document.addEventListener(
- "touchend",
- mouseUp,
- { once: true }
+ document.addEventListener("mouseup", mouseUp, { once: true });
+ document.addEventListener("touchend", mouseUp, { once: true });
- );
+ // Vérifie si l'événement concerne la période elle-même
} else if (event.target === periodTimeLine) {
const startLeft = parseFloat(periodTimeLine.style.left);
+ // Fonction de déplacement de la période
const onMouseMove = (moveEvent) => {
if (handleMoving) return;
const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
const containerWidth = timelineContainer.clientWidth;
+ // Calcul de la nouvelle position gauche en pourcentage
const newLeft = startLeft + (deltaX / containerWidth) * 100;
adjustPeriodPosition(newLeft, parseFloat(periodTimeLine.style.width));
-
updatePeriodTimeLabel();
};
+
+ // Fonction de relâchement de la souris ou du tactile
+ // - Alignement des poignées sur les ticks
+ // - Appel des callbacks
+ // - Sauvegarde des valeurs dans le local storage
const mouseUp = () => {
snapHandlesToQuarters();
timelineContainer.removeEventListener("mousemove", onMouseMove);
func_call();
savePeriodInLocalStorage();
- }
+ };
+
+ // Ajoute les écouteurs d'événement pour le déplacement et le relâchement
timelineContainer.addEventListener("mousemove", onMouseMove);
timelineContainer.addEventListener("touchmove", onMouseMove);
- document.addEventListener(
- "mouseup",
- mouseUp,
- { once: true }
- );
- document.addEventListener(
- "touchend",
- mouseUp,
- { once: true }
- );
+ document.addEventListener("mouseup", mouseUp, { once: true });
+ document.addEventListener("touchend", mouseUp, { once: true });
}
}
+
let func_call = () => { };
+ // Fonction initialisant la timeline
+ // La fonction "callback" est appelée à chaque modification de la période
function setupTimeLine(callback) {
func_call = callback;
timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e) });
timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e) });
+ // Initialisation des timepickers (à gauche de la timeline)
+ // lors d'un changement, cela met à jour la timeline
const updateFromInputs = ()=>{
let deb = $('#deb').val();
let fin = $('#fin').val();
@@ -209,9 +230,11 @@
$('#deb').data('TimePicker').options.change = updateFromInputs;
$('#fin').data('TimePicker').options.change = updateFromInputs;
+ // actualise l'affichage des inputs avec les valeurs de la timeline
updatePeriodTimeLabel();
}
-
+ // Ajuste la position de la période en fonction de la nouvelle position et largeur
+ // Vérifie que la période ne dépasse pas les limites de la timeline
function adjustPeriodPosition(newLeft, newWidth) {
const snappedLeft = snapToQuarter(newLeft);
@@ -224,30 +247,36 @@
periodTimeLine.style.left = `${clampedLeft}%`;
periodTimeLine.style.width = `${snappedWidth}%`;
}
-
+ // Récupère les valeurs de la période
function getPeriodValues() {
+ // On prend les pourcentages
const leftPercentage = parseFloat(periodTimeLine.style.left);
const widthPercentage = parseFloat(periodTimeLine.style.width);
+ // On calcule l'inverse des pourcentages pour obtenir les heures
const startHour = (leftPercentage / 100) * (t_end - t_start) + t_start;
const endHour = ((leftPercentage + widthPercentage) / 100) * (t_end - t_start) + t_start;
-
+ // On les arrondit aux ticks les plus proches
const startValue = snapToQuarter(startHour);
const endValue = snapToQuarter(endHour);
-
+
+ // on verifie que les valeurs sont bien dans les bornes
const computedValues = [Math.max(startValue, t_start), Math.min(t_end, endValue)];
-
+
+ // si les valeurs sont hors des bornes, on les ajuste
if (computedValues[0] > t_end || computedValues[1] < t_start) {
return [t_start, Math.min(t_end, t_start + period_default)];
}
-
+ // Si la période est trop petite, on l'agrandit artificiellement (il faut au moins 1 tick de largeur)
if (computedValues[1] - computedValues[0] <= tick_delay && computedValues[1] < t_end - tick_delay) {
computedValues[1] += tick_delay;
}
return computedValues;
}
-
+ // Met à jour les valeurs de la période
+ // Met à jour l'affichage de la timeline
+ // Appelle les callbacks associés
function setPeriodValues(deb, fin) {
if (fin < deb) {
throw new RangeError(`le paramètre 'deb' doit être inférieur au paramètre 'fin' ([${deb};${fin}])`)
@@ -259,8 +288,8 @@
deb = snapToQuarter(deb);
fin = snapToQuarter(fin);
- let leftPercentage = (deb - t_start) / (t_end - t_start) * 100;
- let widthPercentage = (fin - deb) / (t_end - t_start) * 100;
+ let leftPercentage = computePercentage(deb, t_start);
+ let widthPercentage = computePercentage(fin, deb);
periodTimeLine.style.left = `${leftPercentage}%`;
periodTimeLine.style.width = `${widthPercentage}%`;
@@ -269,7 +298,9 @@
func_call();
savePeriodInLocalStorage();
}
-
+ // Aligne les poignées de la période sur les ticks les plus proches
+ // ex : 12h39 => 12h45 (si les ticks sont à 15min)
+ // evite aussi les dépassements de la timeline (max et min)
function snapHandlesToQuarters() {
const periodValues = getPeriodValues();
let lef = Math.min(computePercentage(Math.abs(periodValues[0]), t_start), computePercentage(Math.abs(t_end), tick_delay));
@@ -288,15 +319,20 @@
updatePeriodTimeLabel()
}
-
+ // Retourne le pourcentage d'une valeur par rapport à t_start et t_end
+ // ex : 12h par rapport à 8h et 20h => 25%
function computePercentage(a, b) {
return ((a - b) / (t_end - t_start)) * 100;
}
+ // Convertit une heure (string) en nombre
+ // ex : "12:30" => 12.5
function fromTime(time, separator = ":") {
const [hours, minutes] = time.split(separator).map((el) => Number(el))
return hours + minutes / 60
}
-
+ // Renvoie les valeurs de la période sous forme de date
+ // Les heures sont récupérées depuis la timeline
+ // la date est récupérée depuis un champ "#date" (datepicker)
function getPeriodAsDate(){
let [deb, fin] = getPeriodValues();
deb = numberToTime(deb);
@@ -305,19 +341,21 @@
const dateStr = $("#date")
.datepicker("getDate")
.format("yyyy-mm-dd")
- .substring(0, 10);
+ .substring(0, 10); // récupération que de la date, pas des heures
return {
deb: new Date(`${dateStr}T${deb}`),
fin: new Date(`${dateStr}T${fin}`)
}
}
-
+ // Sauvegarde les valeurs de la période dans le local storage
function savePeriodInLocalStorage(){
const dates = getPeriodValues();
localStorage.setItem("sco-timeline-values", JSON.stringify(dates));
}
+ // Récupère les valeurs de la période depuis le local storage
+ // Si elles n'existent pas, on les initialise avec les valeurs par défaut
function loadPeriodFromLocalStorage(){
const dates = JSON.parse(localStorage.getItem("sco-timeline-values"));
if(dates){
@@ -326,11 +364,13 @@
setPeriodValues(t_start, t_start + period_default);
}
}
+ // == Initialisation par défaut de la timeline ==
- createTicks();
+ createTicks(); // création des graduations
- loadPeriodFromLocalStorage();
+ loadPeriodFromLocalStorage(); // chargement des valeurs si disponible
+ // Si on donne les heures en appelant le template alors on met à jour la timeline
{% if heures %}
let [heure_deb, heure_fin] = [{{ heures | safe }}]
if (heure_deb != '' && heure_fin != '') {
diff --git a/app/templates/assiduites/widgets/toast.j2 b/app/templates/assiduites/widgets/toast.j2
index 6081d227..b75cc1e4 100644
--- a/app/templates/assiduites/widgets/toast.j2
+++ b/app/templates/assiduites/widgets/toast.j2
@@ -68,32 +68,49 @@
\ No newline at end of file
diff --git a/app/views/assiduites.py b/app/views/assiduites.py
index 409bb43a..00c4d317 100644
--- a/app/views/assiduites.py
+++ b/app/views/assiduites.py
@@ -1537,116 +1537,10 @@ def tableau_assiduite_actions():
flash(f"{objet_name} justifiée")
return redirect(request.referrer)
- if request.method == "GET":
- module: str | int = "" # moduleimpl_id ou chaine libre
-
- if obj_type == "assiduite":
- # Construction du menu module
- module = _module_selector_multiple(objet.etudiant, objet.moduleimpl_id)
-
- return render_template(
- "assiduites/pages/tableau_assiduite_actions.j2",
- action=action,
- can_view_justif_detail=current_user.has_permission(Permission.AbsJustifView)
- or (obj_type == "justificatif" and current_user.id == objet.user_id),
- etud=objet.etudiant,
- moduleimpl=module,
- obj_id=obj_id,
- objet_name=objet_name,
- objet=_preparer_objet(obj_type, objet),
- sco=ScoData(etud=objet.etudiant),
- title=f"Assiduité {objet.etudiant.nom_short}",
- # type utilisé dans les actions modifier / détails (modifier.j2, details.j2)
- type="Justificatif" if obj_type == "justificatif" else "Assiduité",
- )
- # ----- Cas POST
- if obj_type == "assiduite":
- try:
- _action_modifier_assiduite(objet)
- except ScoValueError as error:
- raise ScoValueError(error.args[0], request.referrer) from error
- flash("L'assiduité a bien été modifiée.")
- else:
- try:
- _action_modifier_justificatif(objet)
- except ScoValueError as error:
- raise ScoValueError(error.args[0], request.referrer) from error
- flash("Le justificatif a bien été modifié.")
- return redirect(request.form["table_url"])
-
-
-def _action_modifier_assiduite(assi: Assiduite):
- form = request.form
-
- # Gestion de l'état
- etat = scu.EtatAssiduite.get(form["etat"])
- if etat is not None:
- assi.etat = etat
- if etat == scu.EtatAssiduite.PRESENT:
- assi.est_just = False
- else:
- assi.est_just = len(get_assiduites_justif(assi.assiduite_id, False)) > 0
-
- # Gestion de la description
- assi.description = form["description"]
-
- possible_moduleimpl_id: str = form["moduleimpl_select"]
-
- # Raise ScoValueError (si None et force module | Etudiant non inscrit | Module non reconnu)
- assi.set_moduleimpl(possible_moduleimpl_id)
-
- db.session.add(assi)
- db.session.commit()
- scass.simple_invalidate_cache(assi.to_dict(True), assi.etudid)
-
-
-def _action_modifier_justificatif(justi: Justificatif):
- "Modifie le justificatif avec les valeurs dans le form"
- form = request.form
-
- # Gestion des Dates
- date_debut: datetime = scu.is_iso_formated(form["date_debut"], True)
- date_fin: datetime = scu.is_iso_formated(form["date_fin"], True)
- if date_debut is None or date_fin is None or date_fin < date_debut:
- raise ScoValueError("Dates invalides", request.referrer)
- justi.date_debut = date_debut
- justi.date_fin = date_fin
-
- # Gestion de l'état
- etat = scu.EtatJustificatif.get(form["etat"])
- if etat is not None:
- justi.etat = etat
- else:
- raise ScoValueError("État invalide", request.referrer)
-
- # Gestion de la raison
- justi.raison = form["raison"]
-
- # Gestion des fichiers
- files = request.files.getlist("justi_fich")
- if len(files) != 0:
- files = request.files.values()
-
- archive_name: str = justi.fichier
- # Utilisation de l'archiver de justificatifs
- archiver: JustificatifArchiver = JustificatifArchiver()
-
- for fich in files:
- archive_name, _ = archiver.save_justificatif(
- justi.etudiant,
- filename=fich.filename,
- data=fich.stream.read(),
- archive_name=archive_name,
- user_id=current_user.id,
- )
-
- justi.fichier = archive_name
-
- justi.dejustifier_assiduites()
- db.session.add(justi)
- db.session.commit()
- justi.justifier_assiduites()
- scass.simple_invalidate_cache(justi.to_dict(True), justi.etudid)
+ # Si on arrive ici, c'est que l'action n'est pas autorisée
+ # cette fonction ne sert plus qu'à supprimer ou justifier
+ flash("Méthode non autorisée", "error")
+ return redirect(request.referrer)
def _preparer_objet(
@@ -1724,117 +1618,6 @@ def _preparer_objet(
return objet_prepare
-@bp.route("/signal_assiduites_diff")
-@scodoc
-@permission_required(Permission.AbsChange)
-def signal_assiduites_diff():
- """
- Utilisé notamment par "Saisie différée" sur tableau de bord semetstre"
-
- Arguments de la requête:
-
- - group_ids : liste des groupes
- example : group_ids=1,2,3
- - formsemestre_id : id du formsemestre
- example : formsemestre_id=1
- - moduleimpl_id : id du moduleimpl
- example : moduleimpl_id=1
-
- (Permet de pré-générer une plage. Si non renseigné, la plage sera vide)
- (Les trois valeurs suivantes doivent être renseignées ensemble)
- - date
- example : date=01/01/2021
- - heure_debut
- example : heure_debut=08:00
- - heure_fin
- example : heure_fin=10:00
-
- Exemple de requête :
- signal_assiduites_diff?formsemestre_id=67&group_ids=400&moduleimpl_id=1229&date=15/04/2024&heure_debut=12:34&heure_fin=12:55
- """
- # Récupération des paramètres de la requête
- group_ids: list[int] = request.args.get("group_ids", None)
- formsemestre_id: int = request.args.get("formsemestre_id", -1)
- formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
-
- etudiants: list[Identite] = []
-
- # Vérification des groupes
- if group_ids is None:
- group_ids = []
- else:
- group_ids = group_ids.split(",")
- map(str, group_ids)
- groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, formsemestre_id=formsemestre.id, select_all_when_unspecified=True
- )
- if not groups_infos.members:
- return (
- html_sco_header.sco_header(page_title="Assiduité: saisie différée")
- + "