diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js
index a9ded03c84087cbfcc29fe6cc3e3f06e1368ba14..089469eb039242b0834ac05b77cd7d8be383d09d 100644
--- a/app/static/js/assiduites.js
+++ b/app/static/js/assiduites.js
@@ -68,7 +68,13 @@ async function async_post(path, data, success, errors) {
const responseData = await response.json();
success(responseData);
} else {
- throw new Error("Network response was not ok.");
+ if (response.status == 404) {
+ response.json().then((data) => {
+ if (errors) errors(data);
+ });
+ } else {
+ throw new Error("Network response was not ok.");
+ }
}
} catch (error) {
console.error(error);
@@ -615,7 +621,10 @@ function erreurModuleImpl(message) {
openAlertModal("Sélection du module", content);
}
- if (message == "L'étudiant n'est pas inscrit au module") {
+ if (
+ message == "L'étudiant n'est pas inscrit au module" ||
+ message == "param 'moduleimpl_id': etud non inscrit"
+ ) {
const HTML = `
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
@@ -822,7 +831,7 @@ function dateCouranteEstTravaillee() {
const nouvelleDate = retourJourTravail(date);
$("#date").datepicker("setDate", nouvelleDate);
let msg = "Le jour sélectionné";
- if ((new Date()).format("YYYY-MM-DD") == date.format("YYYY-MM-DD")) {
+ if (new Date().format("YYYY-MM-DD") == date.format("YYYY-MM-DD")) {
msg = "Aujourd'hui";
}
const att = document.createTextNode(
diff --git a/app/templates/assiduites/pages/signal_assiduites_hebdo.j2 b/app/templates/assiduites/pages/signal_assiduites_hebdo.j2
index 5b2a7f28f32ad6d2b7727a63f097890d6a5df30b..09a0e750b159c37acccaab4f0d82235e9b22928e 100644
--- a/app/templates/assiduites/pages/signal_assiduites_hebdo.j2
+++ b/app/templates/assiduites/pages/signal_assiduites_hebdo.j2
@@ -108,6 +108,11 @@
background-color: var(--color-conflit);
}
+ .conflit_calendar{
+ font-size: 1.5em;
+ cursor: pointer;
+ }
+
</style>
@@ -185,6 +190,9 @@
<script>
+ const readonly = "{{readonly | safe}}" == "True";
+ const non_present = "{{non_present | safe}}" == "True";
+
const etuds = [
{% for etud in etudiants %}
{
@@ -231,6 +239,8 @@
date_fin: fin.toFakeIso(),
}
+ let cancelEvent = false;
+
if (assiduite_id != "") {
if (same) {
// Suppression
@@ -243,13 +253,13 @@
td.setAttribute("assiduite_id", "");
} else {
console.error(data.errors["0"].message);
+ cancelEvent = true;
erreurModuleImpl(data.errors["0"].message);
-
}
},
(error) => {
console.error("Erreur lors de la suppression de l'assiduité", error);
-
+ cancelEvent = true;
}
);
} else {
@@ -262,6 +272,8 @@
},
(error) => {
console.error("Erreur lors de la modification de l'assiduité", error);
+ cancelEvent = true;
+ erreurModuleImpl(error.message);
}
);
}
@@ -278,7 +290,7 @@
} else {
console.error(data.errors["0"].message);
erreurModuleImpl(data.errors["0"].message);
-
+ cancelEvent = true;
}
},
(error) => {
@@ -288,6 +300,8 @@
);
}
+ return cancelEvent;
+
}
async function recupAssiduitesHebdo(callback) {
@@ -324,6 +338,14 @@
function updateTable(assiduites) {
+ const img_conflit = `
+ <a
+ class="conflit_calendar"
+ title="Des assiduités existent déjà pour cette période. Cliquez ici pour voir le calendrier de l'assiduité de l'étudiant"
+ data-tooltip
+ target="_blank"
+ >📅</a>`
+
// Suppression existant
document.querySelectorAll("td.btns").forEach((el) => {
el.remove();
@@ -395,14 +417,17 @@
// Peuplement des boutons en fonction des assiduités
- boutons = `
- <input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
- class="rbtn present" value="present">
- <input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
- class="rbtn retard" value="retard">
- <input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
- class="rbtn absent" value="absent">
- `
+ let boutons = `
+ <input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
+ class="rbtn retard" value="retard">
+ <input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
+ class="rbtn absent" value="absent">
+ `
+
+ if (!non_present) {
+ boutons = `<input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
+ class="rbtn present" value="present">`+boutons;
+ }
// matin
tdMatin.innerHTML = boutons
@@ -417,13 +442,15 @@
if (deb.isSame(morningPeriod.deb, "minutes") && fin.isSame(morningPeriod.fin, "minutes")) {
let etat = assi.etat.toLowerCase();
- tdMatin.querySelector(`[value="${etat}"]`).checked = true;
+ const input = tdMatin.querySelector(`[value="${etat}"]`)
+ if (input) {
+ input.checked = true;
+ }
tdMatin.setAttribute("assiduite_id", assi.assiduite_id);
} else {
- tdMatin.innerHTML = ""
+ tdMatin.innerHTML = img_conflit;
+ tdMatin.querySelector(".conflit_calendar").href = `calendrier_assi_etud?etudid=${etudid}`;
tdMatin.classList.add("conflit");
- tdMatin.title = "Des assiduités existent déjà pour cette période"
- tdMatin.setAttribute("data-tooltip", "");
}
}
@@ -440,13 +467,15 @@
if (deb.isSame(afternoonPeriod.deb, "minutes") && fin.isSame(afternoonPeriod.fin, "minutes")) {
let etat = assi.etat.toLowerCase();
- tdApresmidi.querySelector(`[value="${etat}"]`).checked = true;
+ const input = tdApresmidi.querySelector(`[value="${etat}"]`)
+ if (input) {
+ input.checked = true;
+ }
tdApresmidi.setAttribute("assiduite_id", assi.assiduite_id);
} else {
- tdApresmidi.innerHTML = ""
+ tdApresmidi.innerHTML = img_conflit;
+ tdApresmidi.querySelector(".conflit_calendar").href = `calendrier_assi_etud?etudid=${etudid}`;
tdApresmidi.classList.add("conflit");
- tdApresmidi.title = "Des assiduités existent déjà pour cette période"
- tdApresmidi.setAttribute("data-tooltip", "");
}
}
@@ -454,16 +483,29 @@
}
document.querySelectorAll("td .rbtn").forEach((el) => {
- el.addEventListener("click", (e) => {
+ el.addEventListener("click", async (e) => {
+
+ if (readonly) {
+ e.preventDefault();
+ return;
+ }
+
let target = e.target;
let parent = target.parentElement;
+
+ let isCancelled = await actionButton(target, !target.checked);
+ if (isCancelled) {
+ e.preventDefault();
+ target.checked = !target.checked;
+ return;
+ }
+
let inputs = parent.querySelectorAll(".rbtn");
inputs.forEach((input) => {
if (input != target) {
input.checked = false;
}
});
- actionButton(target, !target.checked);
});
});
@@ -507,10 +549,109 @@
function allPresent(day, time) {
// Version naive : coche tous les boutons de la colonne
// TODO - Optimiser avec une seule requête API
- let inputs = document.querySelectorAll(`td[day="${day}"][time="${time}"] .rbtn[value="present"]`);
- inputs.forEach((input) => {
- input.click();
+ let tds = document.querySelectorAll(`td[day="${day}"][time="${time}"]`);
+ const real_time = time == "am" ? "matin" : "apresmidi";
+ const assi = {
+ etat: "present",
+ moduleimpl_id: document.getElementById("moduleimpl_select").value,
+ date_debut: new Date(days[day].date.format('YYYY-MM-DD') + "T" + temps[real_time].debut).toFakeIso(),
+ date_fin: new Date(days[day].date.format('YYYY-MM-DD') + "T" + temps[real_time].fin).toFakeIso(),
+ }
+
+ let toCreate = []; // [{etudid:<int>}]
+ let toEdit = [];// [{etudid:<int>, assiduite_id:<int>}]
+
+ tds.forEach((td) => {
+ // on ne touche pas aux conflits
+ if (td.classList.contains("conflit")) {
+ return;
+ }
+
+ const tr = td.parentElement;
+ const etudid = Number(tr.getAttribute("etudid"));
+
+ const assiduite_id = td.getAttribute("assiduite_id");
+ if (assiduite_id == "") {
+ toCreate.push({ etudid: etudid });
+ } else {
+ toEdit.push({ etudid: etudid, assiduite_id: Number(assiduite_id) });
+ }
+ })
+
+ // Création
+ toCreate = toCreate.map((el) => {
+ return {
+ ...assi,
+ etudid: el.etudid,
+ }
+ });
+
+ // Modification
+ toEdit = toEdit.map((el) => {
+ return {
+ ...assi,
+ etudid: el.etudid,
+ assiduite_id: el.assiduite_id,
+ }
});
+
+ // Appel API
+ let counts = {
+ create: toCreate.length,
+ edit: toEdit.length
+ }
+ const promiseCreate = async_post(
+ `../../api/assiduites/create`,
+ toCreate,
+ async (data) => {
+ if (data.errors.length > 0) {
+ console.error(data.errors);
+ data.errors.forEach((err) => {
+ let obj = toCreate[err.indice];
+ let etu = etuds.find((el) => el.id == obj.etudid);
+
+ const text = document.createTextNode(`Erreur pour ${etu.nom} ${etu.prenom} : ${err.message}`);
+ const toast = generateToast(text, "var(--color-error)", 10);
+ pushToast(toast);
+ });
+ }
+ counts.create = data.success.length;
+ },
+ (error) => {
+ console.error("Erreur lors de la création de l'assiduité", error);
+ }
+ );
+ const promiseEdit = async_post(
+ `../../api/assiduites/edit`,
+ toEdit,
+ async (data) => {
+ if (data.errors.length > 0) {
+ console.error(data.errors);
+ data.errors.forEach((err) => {
+ let obj = toEdit[err.indice];
+ let etu = etuds.find((el) => el.id == obj.etudid);
+
+ const text = document.createTextNode(`Erreur pour ${etu.nom} ${etu.prenom} : ${err.message}`);
+ const toast = generateToast(text, "var(--color-error)");
+ pushToast(toast);
+ });
+ }
+ counts.edit = data.success.length;
+ },
+ (error) => {
+ console.error("Erreur lors de l'édition de l'assiduité", error);
+ }
+ );
+
+ // Affiche un loader
+ afficheLoader();
+
+ Promise.all([promiseCreate, promiseEdit]).then(async () => {
+ retirerLoader();
+ await recupAssiduitesHebdo(updateTable);
+ envoiToastTous("present", counts.create + counts.edit);
+ });
+
}
</script>
@@ -654,6 +795,15 @@ document.addEventListener("DOMContentLoaded", ()=>{
Le matin <a href="#" id="text-matin" title="Cliquer pour modifier les horaires">9h à 12h</a> et l'après-midi de <a href="#" id="text-apresmidi" title="Cliquer pour modifier les horaires">13h à 17h</a>
</h3>
+{% if readonly %}
+<h4
+ title="Vous n'avez pas les permissions nécessaires afin de modifier les assiduités"
+ data-tooltip
+>
+ Ouvert en mode <span class="rouge">lecture seule</span>.
+</h4>
+
+{% endif %}
<table id="table">
<thead>
<tr class="premier">
@@ -676,8 +826,9 @@ document.addEventListener("DOMContentLoaded", ()=>{
{% endif %}
{% endfor %}
</tr>
+ {% if not readonly and not non_present %}
<tr>
- {# Ne pas afficher si preference "non presences" #}
+ {# Ne pas afficher si preference "non presences" / "readonly" #}
<th></th>
{% for jour in hebdo_jours %}
{% if not jour[0] or jour[1][0] not in ['Samedi', 'Dimanche'] %}
@@ -689,13 +840,13 @@ document.addEventListener("DOMContentLoaded", ()=>{
</th>
{% endif %}
{% endfor %}
-
</tr>
+ {% endif %}
</thead>
<tbody>
{% for etud in etudiants %}
<tr etudid="{{etud.etudid}}" id="row-{{etud.etudid}}">
- <td class="etudinfo" id="etud-{{etud.etudid}}">{{ etud.nomprenom }}</td>
+ <td class="etudinfo" id="etud-{{etud.etudid}}">{{ etud.nom_prenom() }}</td>
{# Sera rempli en JS #}
{# Ne pas afficher bouton présent si pref "non présences" #}
{# <td>
diff --git a/app/views/assiduites.py b/app/views/assiduites.py
index f84330b6404a57fb10982370c770d886f9585ed0..99ce6ab6f62cbc1d5c0900e807dff6fd7f3c6467 100644
--- a/app/views/assiduites.py
+++ b/app/views/assiduites.py
@@ -2070,8 +2070,6 @@ def signal_assiduites_hebdo():
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
)
- # TODO vérif perm AbsChange -> readonly
-
# Gestion des jours
jours: dict[str, list[str]] = {
"lun": [
@@ -2113,12 +2111,19 @@ def signal_assiduites_hebdo():
return render_template(
"assiduites/pages/signal_assiduites_hebdo.j2",
+ title="Assiduité: saisie hebdomadaire",
gr=gr_tit,
etudiants=etudiants,
moduleimpl_select=_module_selector(
formsemestre=formsemestre, moduleimpl_id=moduleimpl_id
),
hebdo_jours=hebdo_jours,
+ readonly=not current_user.has_permission(Permission.AbsChange),
+ non_present=sco_preferences.get_preference(
+ "non_present",
+ formsemestre_id=formsemestre_id,
+ dept_id=g.scodoc_dept_id,
+ ),
)