Assiduité : signal_assiduites_hebdo : v2 sans mobile

This commit is contained in:
Iziram 2024-05-29 15:59:19 +02:00
parent fac36fa11c
commit f4f6c13d79
3 changed files with 197 additions and 32 deletions

View File

@ -67,9 +67,15 @@ async function async_post(path, data, success, errors) {
if (response.ok) { if (response.ok) {
const responseData = await response.json(); const responseData = await response.json();
success(responseData); success(responseData);
} else {
if (response.status == 404) {
response.json().then((data) => {
if (errors) errors(data);
});
} else { } else {
throw new Error("Network response was not ok."); throw new Error("Network response was not ok.");
} }
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
if (errors) errors(error); if (errors) errors(error);
@ -615,7 +621,10 @@ function erreurModuleImpl(message) {
openAlertModal("Sélection du module", content); 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 = ` const HTML = `
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p> <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> <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); const nouvelleDate = retourJourTravail(date);
$("#date").datepicker("setDate", nouvelleDate); $("#date").datepicker("setDate", nouvelleDate);
let msg = "Le jour sélectionné"; 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"; msg = "Aujourd'hui";
} }
const att = document.createTextNode( const att = document.createTextNode(

View File

@ -108,6 +108,11 @@
background-color: var(--color-conflit); background-color: var(--color-conflit);
} }
.conflit_calendar{
font-size: 1.5em;
cursor: pointer;
}
</style> </style>
@ -185,6 +190,9 @@
<script> <script>
const readonly = "{{readonly | safe}}" == "True";
const non_present = "{{non_present | safe}}" == "True";
const etuds = [ const etuds = [
{% for etud in etudiants %} {% for etud in etudiants %}
{ {
@ -231,6 +239,8 @@
date_fin: fin.toFakeIso(), date_fin: fin.toFakeIso(),
} }
let cancelEvent = false;
if (assiduite_id != "") { if (assiduite_id != "") {
if (same) { if (same) {
// Suppression // Suppression
@ -243,13 +253,13 @@
td.setAttribute("assiduite_id", ""); td.setAttribute("assiduite_id", "");
} else { } else {
console.error(data.errors["0"].message); console.error(data.errors["0"].message);
cancelEvent = true;
erreurModuleImpl(data.errors["0"].message); erreurModuleImpl(data.errors["0"].message);
} }
}, },
(error) => { (error) => {
console.error("Erreur lors de la suppression de l'assiduité", error); console.error("Erreur lors de la suppression de l'assiduité", error);
cancelEvent = true;
} }
); );
} else { } else {
@ -262,6 +272,8 @@
}, },
(error) => { (error) => {
console.error("Erreur lors de la modification de l'assiduité", error); console.error("Erreur lors de la modification de l'assiduité", error);
cancelEvent = true;
erreurModuleImpl(error.message);
} }
); );
} }
@ -278,7 +290,7 @@
} else { } else {
console.error(data.errors["0"].message); console.error(data.errors["0"].message);
erreurModuleImpl(data.errors["0"].message); erreurModuleImpl(data.errors["0"].message);
cancelEvent = true;
} }
}, },
(error) => { (error) => {
@ -288,6 +300,8 @@
); );
} }
return cancelEvent;
} }
async function recupAssiduitesHebdo(callback) { async function recupAssiduitesHebdo(callback) {
@ -324,6 +338,14 @@
function updateTable(assiduites) { 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 // Suppression existant
document.querySelectorAll("td.btns").forEach((el) => { document.querySelectorAll("td.btns").forEach((el) => {
el.remove(); el.remove();
@ -395,15 +417,18 @@
// Peuplement des boutons en fonction des assiduités // Peuplement des boutons en fonction des assiduités
boutons = ` let boutons = `
<input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
class="rbtn present" value="present">
<input type="checkbox" name="matin-${etudid}" id="matin-${etudid}" <input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
class="rbtn retard" value="retard"> class="rbtn retard" value="retard">
<input type="checkbox" name="matin-${etudid}" id="matin-${etudid}" <input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
class="rbtn absent" value="absent"> 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 // matin
tdMatin.innerHTML = boutons tdMatin.innerHTML = boutons
tdMatin.setAttribute("assiduite_id", "") tdMatin.setAttribute("assiduite_id", "")
@ -417,13 +442,15 @@
if (deb.isSame(morningPeriod.deb, "minutes") && fin.isSame(morningPeriod.fin, "minutes")) { if (deb.isSame(morningPeriod.deb, "minutes") && fin.isSame(morningPeriod.fin, "minutes")) {
let etat = assi.etat.toLowerCase(); 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); tdMatin.setAttribute("assiduite_id", assi.assiduite_id);
} else { } else {
tdMatin.innerHTML = "" tdMatin.innerHTML = img_conflit;
tdMatin.querySelector(".conflit_calendar").href = `calendrier_assi_etud?etudid=${etudid}`;
tdMatin.classList.add("conflit"); 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")) { if (deb.isSame(afternoonPeriod.deb, "minutes") && fin.isSame(afternoonPeriod.fin, "minutes")) {
let etat = assi.etat.toLowerCase(); 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); tdApresmidi.setAttribute("assiduite_id", assi.assiduite_id);
} else { } else {
tdApresmidi.innerHTML = "" tdApresmidi.innerHTML = img_conflit;
tdApresmidi.querySelector(".conflit_calendar").href = `calendrier_assi_etud?etudid=${etudid}`;
tdApresmidi.classList.add("conflit"); 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) => { 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 target = e.target;
let parent = target.parentElement; 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"); let inputs = parent.querySelectorAll(".rbtn");
inputs.forEach((input) => { inputs.forEach((input) => {
if (input != target) { if (input != target) {
input.checked = false; input.checked = false;
} }
}); });
actionButton(target, !target.checked);
}); });
}); });
@ -507,10 +549,109 @@
function allPresent(day, time) { function allPresent(day, time) {
// Version naive : coche tous les boutons de la colonne // Version naive : coche tous les boutons de la colonne
// TODO - Optimiser avec une seule requête API // TODO - Optimiser avec une seule requête API
let inputs = document.querySelectorAll(`td[day="${day}"][time="${time}"] .rbtn[value="present"]`); let tds = document.querySelectorAll(`td[day="${day}"][time="${time}"]`);
inputs.forEach((input) => { const real_time = time == "am" ? "matin" : "apresmidi";
input.click(); 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> </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> 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> </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"> <table id="table">
<thead> <thead>
<tr class="premier"> <tr class="premier">
@ -676,8 +826,9 @@ document.addEventListener("DOMContentLoaded", ()=>{
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</tr> </tr>
{% if not readonly and not non_present %}
<tr> <tr>
{# Ne pas afficher si preference "non presences" #} {# Ne pas afficher si preference "non presences" / "readonly" #}
<th></th> <th></th>
{% for jour in hebdo_jours %} {% for jour in hebdo_jours %}
{% if not jour[0] or jour[1][0] not in ['Samedi', 'Dimanche'] %} {% if not jour[0] or jour[1][0] not in ['Samedi', 'Dimanche'] %}
@ -689,13 +840,13 @@ document.addEventListener("DOMContentLoaded", ()=>{
</th> </th>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</tr> </tr>
{% endif %}
</thead> </thead>
<tbody> <tbody>
{% for etud in etudiants %} {% for etud in etudiants %}
<tr etudid="{{etud.etudid}}" id="row-{{etud.etudid}}"> <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 #} {# Sera rempli en JS #}
{# Ne pas afficher bouton présent si pref "non présences" #} {# Ne pas afficher bouton présent si pref "non présences" #}
{# <td> {# <td>

View File

@ -2070,8 +2070,6 @@ def signal_assiduites_hebdo():
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>" grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
) )
# TODO vérif perm AbsChange -> readonly
# Gestion des jours # Gestion des jours
jours: dict[str, list[str]] = { jours: dict[str, list[str]] = {
"lun": [ "lun": [
@ -2113,12 +2111,19 @@ def signal_assiduites_hebdo():
return render_template( return render_template(
"assiduites/pages/signal_assiduites_hebdo.j2", "assiduites/pages/signal_assiduites_hebdo.j2",
title="Assiduité: saisie hebdomadaire",
gr=gr_tit, gr=gr_tit,
etudiants=etudiants, etudiants=etudiants,
moduleimpl_select=_module_selector( moduleimpl_select=_module_selector(
formsemestre=formsemestre, moduleimpl_id=moduleimpl_id formsemestre=formsemestre, moduleimpl_id=moduleimpl_id
), ),
hebdo_jours=hebdo_jours, 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,
),
) )