Assiduité : suppression signal_assiduites_diff
This commit is contained in:
parent
ba506d7f8e
commit
faa6f552d4
@ -370,20 +370,6 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
>Saisie Absences (Hebdo)</a></span>
|
>Saisie Absences (Hebdo)</a></span>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
<span class="moduleimpl_abs_link"><a class="stdlink" href="{
|
|
||||||
url_for(
|
|
||||||
"assiduites.signal_assiduites_diff",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
group_ids=group_id,
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
|
||||||
)}" title="Page en cours de fusion et sera prochainement supprimée. Veuillez utiliser la page `Saisie Absences`"
|
|
||||||
>(Saisie Absences Différée)</a></span>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
H.append("</td></tr></table>")
|
H.append("</td></tr></table>")
|
||||||
#
|
#
|
||||||
if not modimpl.check_apc_conformity(nt):
|
if not modimpl.check_apc_conformity(nt):
|
||||||
|
@ -1,702 +0,0 @@
|
|||||||
{% extends "sco_page.j2" %}
|
|
||||||
|
|
||||||
{% block styles %}
|
|
||||||
{{ super() }}
|
|
||||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
|
||||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.ui-timepicker-container,#ui-datepicker-div{
|
|
||||||
z-index: 5 !important;
|
|
||||||
}
|
|
||||||
#new_periode,
|
|
||||||
#actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: fit-content;
|
|
||||||
gap: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#actions {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
#actions label{
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fix {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 1em;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fix>.box {
|
|
||||||
border: 1px solid #444;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timepicker {
|
|
||||||
width: 5em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#moduleimpl_select {
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tableau-periode {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-x: scroll;
|
|
||||||
max-width: var(--sco-content-max-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
#tableau-periode .pdp {
|
|
||||||
width: 5em;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell, .header {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
.header{
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
width: 256px;
|
|
||||||
}
|
|
||||||
.cell p{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.sticky {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell .assiduite-bubble {
|
|
||||||
display: block;
|
|
||||||
top: 0;
|
|
||||||
z-index: 0;
|
|
||||||
width: 100% !important;
|
|
||||||
min-width: inherit !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assi-btns {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointer{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ligne{
|
|
||||||
display: flex;
|
|
||||||
gap: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock styles %}
|
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
{{ super() }}
|
|
||||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
|
||||||
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
|
||||||
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
|
||||||
{% include "sco_timepicker.j2" %}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Permet d'ajouter une nouvelle période au tableau
|
|
||||||
* Par défaut la période est générèe avec les valeurs des inputs
|
|
||||||
* Si une période est passée en paramètre, alors on utilise ses valeurs
|
|
||||||
* @param {Object} period - La période à ajouter
|
|
||||||
*/
|
|
||||||
async function nouvellePeriode(period = null) {
|
|
||||||
// On récupère l'id de la période
|
|
||||||
let periodId;
|
|
||||||
if (period) {
|
|
||||||
periodId = period.periodId;
|
|
||||||
} else {
|
|
||||||
periodId = currentPeriodId++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On récupère les valeurs des inputs
|
|
||||||
let date = document.getElementById("date").value;
|
|
||||||
let debut = document.getElementById("debut").value;
|
|
||||||
let fin = document.getElementById("fin").value;
|
|
||||||
let moduleimpl_id = document.getElementById("moduleimpl_select").value;
|
|
||||||
const moduleimpl = await getModuleImpl({ moduleimpl_id: moduleimpl_id });
|
|
||||||
|
|
||||||
// Si une période est passée en paramètre, on utilise ses valeurs
|
|
||||||
if (period) {
|
|
||||||
date = period.date_debut.format("DD/MM/YYYY");
|
|
||||||
debut = period.date_debut.format("HH:mm");
|
|
||||||
fin = period.date_fin.format("HH:mm");
|
|
||||||
moduleimpl_id = period.moduleimpl_id;
|
|
||||||
}else{
|
|
||||||
//Sinon on vérifie qu'on a bien des valeurs
|
|
||||||
const text = document.createTextNode("Veuillez remplir tous les champs pour ajouter une plage.")
|
|
||||||
if (date == "" || debut == "" || fin == "" || moduleimpl_id == "") {
|
|
||||||
openAlertModal(
|
|
||||||
"Erreur",
|
|
||||||
text
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vérification de la plage horaire
|
|
||||||
// On génère une date de début et de fin de la période
|
|
||||||
const date_debut = new Date(
|
|
||||||
$("#date").datepicker("getDate").format("YYYY-MM-DD") + "T" + debut
|
|
||||||
);
|
|
||||||
const date_fin = new Date(
|
|
||||||
$("#date").datepicker("getDate").format("YYYY-MM-DD") + "T" + fin
|
|
||||||
);
|
|
||||||
date_debut.add(1, "seconds");
|
|
||||||
|
|
||||||
// On vérifie que les dates sont valides
|
|
||||||
if (!date_debut.isValid()){
|
|
||||||
const p = document.createElement("p");
|
|
||||||
p.textContent = "La date de début n'est pas valide.";
|
|
||||||
openAlertModal(
|
|
||||||
"Erreur",
|
|
||||||
p,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!date_fin.isValid()){
|
|
||||||
const p = document.createElement("p");
|
|
||||||
p.textContent = "La date de fin n'est pas valide.";
|
|
||||||
openAlertModal(
|
|
||||||
"Erreur",
|
|
||||||
p,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On vérifie que l'heure de fin est supérieure à l'heure de début
|
|
||||||
if (date_debut >= date_fin) {
|
|
||||||
const p = document.createElement("p");
|
|
||||||
p.textContent = "La plage horaire n'est pas valide. L'heure de fin doit être "+
|
|
||||||
"supérieure à l'heure de début.";
|
|
||||||
openAlertModal(
|
|
||||||
"Erreur",
|
|
||||||
p,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On ajoute la nouvelle période au tableau
|
|
||||||
let periodeDiv = document.createElement("div");
|
|
||||||
periodeDiv.classList.add("cell", "header");
|
|
||||||
periodeDiv.id = `periode-${periodId}`;
|
|
||||||
|
|
||||||
const periodP = document.createElement("p");
|
|
||||||
periodP.textContent = `Plage du ${date} de ${debut} à ${fin}`;
|
|
||||||
|
|
||||||
// On ajoute le moduleimpl
|
|
||||||
const modP = document.createElement("p");
|
|
||||||
modP.textContent = moduleimpl;
|
|
||||||
|
|
||||||
// On ajoute le bouton pour supprimer la période
|
|
||||||
const close = document.createElement("button");
|
|
||||||
close.textContent = "❌";
|
|
||||||
close.addEventListener("click", () => {
|
|
||||||
// On supprime toutes les cases du tableau correspondant à cette période
|
|
||||||
document
|
|
||||||
.querySelectorAll(
|
|
||||||
`[data-periodeid="${periodeDiv.getAttribute("data-periodeid")}"]`
|
|
||||||
)
|
|
||||||
.forEach((e) => e.remove());
|
|
||||||
// On supprime la période de la Map periodes
|
|
||||||
periodes.delete(Number(periodeDiv.getAttribute("data-periodeid")));
|
|
||||||
});
|
|
||||||
//On ajoute les éléments au DOM
|
|
||||||
periodeDiv.appendChild(periodP);
|
|
||||||
periodeDiv.appendChild(modP);
|
|
||||||
periodeDiv.appendChild(close);
|
|
||||||
periodeDiv.setAttribute("data-periodeid", periodId);
|
|
||||||
document.getElementById("tete-table").appendChild(periodeDiv);
|
|
||||||
|
|
||||||
// On récupère les étudiants (etudids)
|
|
||||||
let etudids = [
|
|
||||||
...document.querySelectorAll(".ligne[data-etudid]"),
|
|
||||||
].map((e) => e.getAttribute("data-etudid"));
|
|
||||||
|
|
||||||
// Préparation de la requête
|
|
||||||
const url =
|
|
||||||
`../../api/assiduites/group/query?date_debut=${date_debut.toFakeIso()}` +
|
|
||||||
`&date_fin=${date_fin.toFakeIso()}&etudids=${etudids.join(
|
|
||||||
","
|
|
||||||
)}&with_justifs`;
|
|
||||||
|
|
||||||
//Si la période n'existait pas, alors on l'ajoute à la Map
|
|
||||||
if (!period) {
|
|
||||||
periodes.set(periodId, {
|
|
||||||
date_debut: date_debut.clone().add(-1, "seconds"),
|
|
||||||
date_fin: date_fin,
|
|
||||||
moduleimpl_id: moduleimpl_id,
|
|
||||||
periodId: periodId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// On récupère les incriptions au module
|
|
||||||
const inscriptions = await getInscriptionModule(moduleimpl_id);
|
|
||||||
|
|
||||||
// On récupère les assiduités
|
|
||||||
await fetch(url)
|
|
||||||
// On convertit la réponse en JSON
|
|
||||||
.then((res) => {
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Network response was not ok");
|
|
||||||
}
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
// On traite les données
|
|
||||||
.then((data) => {
|
|
||||||
for (let etudid of etudids) {
|
|
||||||
// On crée une case pour chaque étudiant
|
|
||||||
let cell = document.createElement("div");
|
|
||||||
cell.classList.add("cell");
|
|
||||||
cell.setAttribute("data-etudid", etudid);
|
|
||||||
cell.setAttribute("data-periodeid", periodId);
|
|
||||||
cell.id = `cell-${etudid}-${periodId}`;
|
|
||||||
document.querySelector(`.ligne[data-etudid="${etudid}"]`).appendChild(cell);
|
|
||||||
|
|
||||||
//Vérification inscription au module
|
|
||||||
// Si l'étudiant n'est pas inscrit, on le notifie et on passe à l'étudiant suivant
|
|
||||||
const inscrit =
|
|
||||||
inscriptions == null ? true : inscriptions.find((e) => e == etudid);
|
|
||||||
if (!inscrit) {
|
|
||||||
cell.textContent = "Non inscrit";
|
|
||||||
cell.classList.add("non-inscrit");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gestion des assiduités déjà existantes
|
|
||||||
const assiduites = data[etudid];
|
|
||||||
// Si l'étudiant n'a pas d'assiduité, on crée les boutons assiduité
|
|
||||||
if (assiduites.length == 0) {
|
|
||||||
|
|
||||||
const assi_btns = document.createElement('div');
|
|
||||||
assi_btns.classList.add('assi-btns');
|
|
||||||
const etats = ["retard", "absent"];
|
|
||||||
|
|
||||||
if(!window.nonPresent){
|
|
||||||
etats.splice(0,0,"present");
|
|
||||||
}
|
|
||||||
|
|
||||||
etats.forEach((value) => {
|
|
||||||
const cbox = document.createElement("input");
|
|
||||||
cbox.type = "checkbox";
|
|
||||||
cbox.value = value;
|
|
||||||
cbox.name = `rbtn_${etudid}_${periodId}`;
|
|
||||||
cbox.classList.add("rbtn", value);
|
|
||||||
|
|
||||||
// Event pour être sur qu'un seul bouton est coché à la fois
|
|
||||||
cbox.addEventListener("click", (event) => {
|
|
||||||
const parent = event.target.parentElement;
|
|
||||||
parent.querySelectorAll(".rbtn").forEach((ele) => {
|
|
||||||
if (ele.value != value) {
|
|
||||||
ele.checked = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Si une valeur par défaut est donnée alors on l'applique
|
|
||||||
cbox.checked = etatDef.value == value;
|
|
||||||
|
|
||||||
assi_btns.appendChild(cbox);
|
|
||||||
});
|
|
||||||
cell.appendChild(assi_btns);
|
|
||||||
} else {
|
|
||||||
// Si une (ou plus) assiduité sont trouvée pour la période
|
|
||||||
// alors on affiche les informations de la première assiduité
|
|
||||||
setupAssiduiteBubble(cell, assiduites[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
//Si jamais la requête échoue, on affiche un message d'erreur dans la console
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error:", error);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("tableau-periode").classList.remove("hidden");
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Permet de récupérer la saisie puis créer les assiduités grâce à l'api
|
|
||||||
*/
|
|
||||||
function sauvegarderAssiduites() {
|
|
||||||
// Initialisation de la liste des assiduités à créer
|
|
||||||
let assiduitesData = [];
|
|
||||||
// Pour chaque période, on récupère les assiduités saisies
|
|
||||||
for (let [periodeId, periode] of periodes.entries()) {
|
|
||||||
// On prend chaque cellule correspondant à la période
|
|
||||||
const cells = document.querySelectorAll(
|
|
||||||
`.cell[data-periodeid="${periodeId}"][data-etudid]`
|
|
||||||
);
|
|
||||||
// Pour chaque cellule, on récupère l'état de l'assiduité
|
|
||||||
cells.forEach((cell) => {
|
|
||||||
const etudid = cell.getAttribute("data-etudid");
|
|
||||||
const etat = cell.querySelector(".rbtn:checked")?.value;
|
|
||||||
// Il est possible que l'état soit null
|
|
||||||
// - Cas où l'étudiant n'est pas inscrit
|
|
||||||
// - Cas où l'étudiant avait déjà une assiduité
|
|
||||||
if (etat) {
|
|
||||||
// On génère un objet "assiduité"
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
etudid: <int>,
|
|
||||||
etat: <string>,
|
|
||||||
date_debut: <string>,
|
|
||||||
date_fin: <string>,
|
|
||||||
moduleimpl_id: <int>,
|
|
||||||
periodId: <int>
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
assiduitesData.push({
|
|
||||||
etudid: etudid,
|
|
||||||
etat: etat,
|
|
||||||
...periode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Une fois les assiduités générées, on les envoie à l'api
|
|
||||||
async_post(
|
|
||||||
"../../api/assiduites/create",
|
|
||||||
assiduitesData,
|
|
||||||
// Si la requête passe
|
|
||||||
async (data) => {
|
|
||||||
// On supprime toutes les cases du tableau pour le mettre à jour
|
|
||||||
document.querySelectorAll("[data-periodeid]").forEach((e)=>e.remove())
|
|
||||||
|
|
||||||
// On recrée les périodes
|
|
||||||
// (cela permet de redemander les assiduités, donc mettre à jour les cases)
|
|
||||||
for (let periode of periodes.values()) {
|
|
||||||
await nouvellePeriode(periode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si il n'y a pas d'erreur, on affiche un message de succès
|
|
||||||
if (data.errors.length == 0) {
|
|
||||||
const span = document.createElement("span");
|
|
||||||
span.textContent = "Le relevé d'assiduité a été enregistré.";
|
|
||||||
openAlertModal(
|
|
||||||
"Enregistrement de l'assiduité",
|
|
||||||
span,
|
|
||||||
null,
|
|
||||||
"var(--color-present)"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Si il y a des erreurs, on les affiche
|
|
||||||
if (data.errors.length > 0) {
|
|
||||||
// On crée une map pour regrouper les erreurs par période
|
|
||||||
const erreurs = new Map();
|
|
||||||
data.errors.forEach((err) => {
|
|
||||||
// Pour chaque période on créer une liste d'erreurs
|
|
||||||
// format : [message, etudid]
|
|
||||||
const assi = assiduitesData[err.indice];
|
|
||||||
const msg = err.message;
|
|
||||||
const periodErrors = erreurs.get(assi.periodId) || [];
|
|
||||||
|
|
||||||
// Récupération du nom de l'étudiant
|
|
||||||
const etud = document.querySelector(
|
|
||||||
`#head-${assi.etudid} span`
|
|
||||||
).textContent;
|
|
||||||
periodErrors.push([`Erreur pour ${etud} : ${msg}`, assi.etudid]);
|
|
||||||
erreurs.set(assi.periodId, periodErrors);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Création du DOM
|
|
||||||
/*
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Période du ... de ... à ...
|
|
||||||
<ul>
|
|
||||||
<li>Erreur pour ...</li>
|
|
||||||
<li>Erreur pour ...</li>
|
|
||||||
</ul>
|
|
||||||
/li>
|
|
||||||
</ul>
|
|
||||||
*/
|
|
||||||
|
|
||||||
const ul = document.createElement("ul");
|
|
||||||
//Pour chaque période on créer un titre "periode du ... de ... à ..."
|
|
||||||
for (let [periodeId, periodErrors] of erreurs.entries()) {
|
|
||||||
const period = periodes.get(periodeId);
|
|
||||||
const li = document.createElement("li");
|
|
||||||
// On affiche la période
|
|
||||||
li.textContent = `Plage du ${period.date_debut.format(
|
|
||||||
"DD/MM/YYYY HH:mm"
|
|
||||||
)} à ${period.date_fin.format("HH:mm")}`;
|
|
||||||
|
|
||||||
// Nous emmène à la période lorsqu'on clique dessus
|
|
||||||
li.addEventListener("click", () => {
|
|
||||||
location.href = `#periode-${periodeId}`;
|
|
||||||
});
|
|
||||||
li.classList.add("pointer");
|
|
||||||
|
|
||||||
// Pour chaque erreur, on créer un élément de liste
|
|
||||||
const ul2 = document.createElement("ul");
|
|
||||||
periodErrors.forEach((err) => {
|
|
||||||
const li2 = document.createElement("li");
|
|
||||||
li2.textContent = err[0];
|
|
||||||
li2.classList.add("pointer");
|
|
||||||
|
|
||||||
// Nous emmène à la case de l'étudiant lorsqu'on clique dessus
|
|
||||||
li2.addEventListener("click", () => {
|
|
||||||
location.href = `#cell-${err[1]}-${periodeId}`;
|
|
||||||
});
|
|
||||||
ul2.appendChild(li2);
|
|
||||||
});
|
|
||||||
li.appendChild(ul2);
|
|
||||||
ul.appendChild(li);
|
|
||||||
}
|
|
||||||
|
|
||||||
openAlertModal(
|
|
||||||
"Erreurs lors de la sauvegarde des assiduités",
|
|
||||||
ul,
|
|
||||||
"Les autres assiduités ont bien été sauvegardées."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(e) => {
|
|
||||||
console.error("Erreur lors de la création des assiduités", e);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mis en place des variables globales
|
|
||||||
let currentPeriodId = 0;
|
|
||||||
const periodes = new Map();
|
|
||||||
const moduleimpls = new Map();
|
|
||||||
const inscriptionsModules = new Map();
|
|
||||||
const nonWorkDays = [{{ nonworkdays| safe }}];
|
|
||||||
|
|
||||||
window.nonPresent = {{ 'true' if non_present else 'false' }};
|
|
||||||
|
|
||||||
// Vérification du forçage de module
|
|
||||||
window.forceModule = "{{ forcer_module }}" == "True";
|
|
||||||
if (window.forceModule) {
|
|
||||||
if (moduleimpl_select.value == "") {
|
|
||||||
document.getElementById("forcemodule").style.display = "block";
|
|
||||||
add_periode.disabled = true;
|
|
||||||
}
|
|
||||||
// Désactivation du bouton d'ajout de période si aucun module n'est sélectionné
|
|
||||||
// et affichage du message de forçage de module
|
|
||||||
moduleimpl_select?.addEventListener("change", (e) => {
|
|
||||||
if (e.target.value != "") {
|
|
||||||
document.getElementById("forcemodule").style.display = "none";
|
|
||||||
add_periode.disabled = false;
|
|
||||||
} else {
|
|
||||||
document.getElementById("forcemodule").style.display = "block";
|
|
||||||
add_periode.disabled = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultPlage = {{ nouv_plage | safe}} || [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fonction exécutée au lancement de la page
|
|
||||||
* - On affiche ou non les photos des étudiants
|
|
||||||
* - On vérifie si la date est un jour travaillé
|
|
||||||
*/
|
|
||||||
async function main() {
|
|
||||||
|
|
||||||
// On initialise les sélecteurs avec les valeurs par défaut (si elles existent)
|
|
||||||
if (defaultPlage.every((e) => e)) {
|
|
||||||
$("#date").datepicker("setDate", defaultPlage[0]);
|
|
||||||
$("#debut").val(defaultPlage[1]);
|
|
||||||
$("#fin").val(defaultPlage[2]);
|
|
||||||
|
|
||||||
// On ajoute la période si la date est un jour travaillé
|
|
||||||
if(dateCouranteEstTravaillee()){
|
|
||||||
await nouvellePeriode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const checked = localStorage.getItem("scodoc-etud-pdp") == "true";
|
|
||||||
afficherPDP(checked);
|
|
||||||
$("#date").on("change", async function (d) {
|
|
||||||
// On vérifie si la date est un jour travaillé
|
|
||||||
dateCouranteEstTravaillee();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("load", main);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock scripts %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{{title}}
|
|
||||||
{% endblock title %}
|
|
||||||
|
|
||||||
{% block app_content %}
|
|
||||||
|
|
||||||
<h2>Signalement différé de l'assiduité {{gr |safe}}</h2>
|
|
||||||
|
|
||||||
<div class="ue_warning warning">
|
|
||||||
Attention, cette page va prochainement être supprimée, car il est plus facile d'utiliser
|
|
||||||
<ul>
|
|
||||||
la page
|
|
||||||
<li><a class="stdlink" href="{{
|
|
||||||
url_for('assiduites.signal_assiduites_group',
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
group_ids=group_ids)
|
|
||||||
}}">
|
|
||||||
saisie de l'assiduité</a> pour saisir à une seule date quelconque
|
|
||||||
</li>
|
|
||||||
<li>ou <a class="stdlink" href="{{
|
|
||||||
url_for('assiduites.signal_assiduites_hebdo',
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
group_ids=group_ids,
|
|
||||||
)
|
|
||||||
}}">saisie hebdomadaire</a> pour saisir sur une semaine.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>Ci-dessous le formulaire vous permettant de saisir plusieurs plages à la fois,
|
|
||||||
qui va bientôt être retiré.
|
|
||||||
</p>
|
|
||||||
<p>N'hésitez pas à commenter sur le <a href="{{scu.SCO_DISCORD_ASSISTANCE}}">salon Discord</a>
|
|
||||||
si vous avez d'autres besoins.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="fix">
|
|
||||||
<!-- Nouvelle Plage
|
|
||||||
Permet de créer une nouvelle ligne pour une nouvelle Plage
|
|
||||||
(
|
|
||||||
Jour, -> datepicker
|
|
||||||
Heure de début, -> timepicker
|
|
||||||
Heure de fin -> timepicker
|
|
||||||
ModuleImplId -> select (liste des modules tout semestre confondu)
|
|
||||||
)
|
|
||||||
--->
|
|
||||||
|
|
||||||
<div id="new_periode" class="box">
|
|
||||||
<label for="date">
|
|
||||||
Date :
|
|
||||||
<input type="text" name="date" id="date" class="datepicker">
|
|
||||||
</label>
|
|
||||||
<label for="debut">
|
|
||||||
Heure de début :
|
|
||||||
<input type="text" name="debut" id="debut" class="timepicker">
|
|
||||||
</label>
|
|
||||||
<label for="fin">
|
|
||||||
Heure de fin :
|
|
||||||
<input type="text" name="fin" id="fin" class="timepicker">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label for="moduleimpl_select">
|
|
||||||
<div id="forcemodule" style="display: none; margin:10px 0px;">
|
|
||||||
Vous devez spécifier le module ! (voir réglage préférence du semestre)
|
|
||||||
</div>
|
|
||||||
Module :
|
|
||||||
{{moduleimpl_select | safe}}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<button id="add_periode" onclick="nouvellePeriode()">Ajouter une plage</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Boutons d'actions
|
|
||||||
- Sauvegarder
|
|
||||||
- Afficher la photo de profil
|
|
||||||
- Assiduité par défaut (aucune, present, retard, absent)
|
|
||||||
--->
|
|
||||||
<br>
|
|
||||||
<div id="actions" class="flex">
|
|
||||||
<button id="save" onclick="sauvegarderAssiduites()">ENREGISTRER</button>
|
|
||||||
<label for="pdp">
|
|
||||||
Photo de profil :
|
|
||||||
<input type="checkbox" name="pdp" id="pdp" checked onclick="afficherPDP(this.checked)">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label for="etatDef">
|
|
||||||
Intialiser les étudiants comme :
|
|
||||||
<select name="etatDef" id="etatDef">
|
|
||||||
<option value="">-</option>
|
|
||||||
{% if not non_present %}
|
|
||||||
<option value="present">présents</option>
|
|
||||||
{% endif %}
|
|
||||||
<option value="retard">en retard</option>
|
|
||||||
<option value="absent">absents</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tableau à double entrée
|
|
||||||
Colonne : Etudiants (Header = Nom, Prénom, Photo (si actif))
|
|
||||||
Ligne : Période (Header = Jour, Heure de début, Heure de fin, ModuleImplId)
|
|
||||||
Contenu :
|
|
||||||
- bouton assiduité (présent, retard, absent)
|
|
||||||
- Bouton conflit si conflit de période
|
|
||||||
--->
|
|
||||||
|
|
||||||
<div id="tableau-periode" class="grid-table hidden">
|
|
||||||
<!-- Première ligne : Plages -->
|
|
||||||
<div class="ligne" id="tete-table">
|
|
||||||
<div class="cell header sticky">Étudiants</div>
|
|
||||||
{# <div class="cell header" periode-id="X">Plage X</div> #}
|
|
||||||
</div>
|
|
||||||
{# ... #}
|
|
||||||
|
|
||||||
<hr class="hidden" id="separator">
|
|
||||||
|
|
||||||
{% for etud in etudiants %}
|
|
||||||
<div class="ligne" data-etudid="{{etud.etudid}}">
|
|
||||||
<div class="cell etudinfo sticky" id="head-{{etud.etudid}}">
|
|
||||||
<img src="../../api/etudiant/etudid/{{etud.etudid}}/photo?size=small" alt="{{etud.nomprenom}}" class="pdp">
|
|
||||||
<span>{{ etud.nomprenom }}</span>
|
|
||||||
</div>
|
|
||||||
{# <div class="cell" periode-id="X">Assiduité Plage 1</div> #}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% include "assiduites/widgets/alert.j2" %}
|
|
||||||
{% endblock app_content %}
|
|
@ -1724,117 +1724,6 @@ def _preparer_objet(
|
|||||||
return objet_prepare
|
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")
|
|
||||||
+ "<h3>Aucun étudiant ! </h3>"
|
|
||||||
+ html_sco_header.sco_footer()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Récupération des étudiants
|
|
||||||
etudiants.extend(
|
|
||||||
[Identite.get_etud(etudid=m["etudid"]) for m in groups_infos.members]
|
|
||||||
)
|
|
||||||
etudiants = list(sorted(etudiants, key=lambda etud: etud.sort_key))
|
|
||||||
|
|
||||||
if groups_infos.tous_les_etuds_du_sem:
|
|
||||||
gr_tit = "en"
|
|
||||||
else:
|
|
||||||
if len(groups_infos.group_ids) > 1:
|
|
||||||
grp = "des groupes"
|
|
||||||
else:
|
|
||||||
grp = "du groupe"
|
|
||||||
gr_tit = (
|
|
||||||
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Pré-remplissage des sélecteurs
|
|
||||||
moduleimpl_id = request.args.get("moduleimpl_id", -1)
|
|
||||||
try:
|
|
||||||
moduleimpl_id = int(moduleimpl_id)
|
|
||||||
except ValueError:
|
|
||||||
moduleimpl_id = -1
|
|
||||||
# date fra (dd/mm/yyyy)
|
|
||||||
date = request.args.get("date", "")
|
|
||||||
# heures (hh:mm)
|
|
||||||
heure_deb = request.args.get("heure_debut", "")
|
|
||||||
heure_fin = request.args.get("heure_fin", "")
|
|
||||||
|
|
||||||
# vérifications des sélecteurs
|
|
||||||
date = date if re.match(r"^\d{2}\/\d{2}\/\d{4}$", date) else ""
|
|
||||||
heure_deb = heure_deb if re.match(r"^[0-2]\d:[0-5]\d$", heure_deb) else ""
|
|
||||||
heure_fin = heure_fin if re.match(r"^[0-2]\d:[0-5]\d$", heure_fin) else ""
|
|
||||||
nouv_plage: list[str] = [date, heure_deb, heure_fin]
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
"assiduites/pages/signal_assiduites_diff.j2",
|
|
||||||
etudiants=etudiants,
|
|
||||||
moduleimpl_select=_module_selector(
|
|
||||||
formsemestre=formsemestre, moduleimpl_id=moduleimpl_id
|
|
||||||
),
|
|
||||||
gr=gr_tit,
|
|
||||||
nonworkdays=_non_work_days(),
|
|
||||||
sco=ScoData(formsemestre=formsemestre),
|
|
||||||
forcer_module=sco_preferences.get_preference(
|
|
||||||
"forcer_module",
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
dept_id=g.scodoc_dept_id,
|
|
||||||
),
|
|
||||||
non_present=sco_preferences.get_preference(
|
|
||||||
"non_present",
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
dept_id=g.scodoc_dept_id,
|
|
||||||
),
|
|
||||||
nouv_plage=nouv_plage,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
group_ids=group_ids,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/signale_evaluation_abs/<int:evaluation_id>/<int:etudid>")
|
@bp.route("/signale_evaluation_abs/<int:evaluation_id>/<int:etudid>")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
|
Loading…
Reference in New Issue
Block a user