ScoDoc/app/templates/assiduites/pages/bilan_etud.j2

261 lines
8.8 KiB
Django/Jinja

{% extends "sco_page_dept.j2" %}
{% block title %}
Bilan assiduité de {{sco.etud.nomprenom}}
{% endblock title %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
<style>
.stats-values-item {
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
}
.stats {
border: 1px solid #333;
padding: 5px 2px;
width: fit-content;
}
.stats-values {
display: flex;
justify-content: flex-start;
gap: 15px;
}
.stats-values-item h5 {
font-weight: bold;
text-decoration-line: underline;
}
.stats-values-part {
display: flex;
flex-direction: column;
}
.scobox.alerte {
text-align: center;
border-radius: 7px;
background-color: var(--color-error);
}
.alerte.invisible {
display: none;
}
.alerte p {
font-size: larger;
color: whitesmoke;
}
.suppr {
margin: 5px 0;
}
</style>
{% endblock styles %}
{% block app_content %}
{% include "assiduites/widgets/alert.j2" %}
<div class="pageContent">
<h2>Bilan de l'assiduité de {{sco.etud.html_link_fiche()|safe}}</span></h2>
<div class="scobox alerte invisible">
<p>Attention, cet étudiant a trop d'absences</p>
</div>
<div class="scobox">
<!-- Statistiques d'assiduité (nb pres, nb retard, nb absence) + nb justifié -->
<div class="scobox-title">Statistiques d'assiduité</div>
<div class="stats-inputs">
<label class="stats-label"> Date de début <input type="text" class="datepicker" name="stats_date_debut"
id="stats_date_debut" value="{{date_debut}}"></label>
<label class="stats-label"> Date de fin <input type="text" class="datepicker" name="stats_date_fin"
id="stats_date_fin" value="{{date_fin}}"></label>
<br mobile="true">
<button onclick="stats()">Actualiser</button>
</div>
<div class="stats-values">
</div>
</div>
<div class="scobox">
<section class="nonvalide">
{{tableau | safe }}
</section>
</div>
<div class="help scobox explanation">
<p>Un message d'alerte apparait si le nombre d'absence dépasse le seuil indiqué dans les préférences du
département (actuellement {{assi_seuil}}).
</p>
<p>Les statistiques sont calculées entre les deux dates sélectionnées. Après modification des dates,
appuyer sur le bouton "Actualiser".
</p>
{% include "assiduites/explication_etats_justifs.j2" %}
</div>
</div>
{% endblock app_content %}
{% block scripts %}
{{ super() }}
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
<script>
// Récupération des statistiques d'assiduité et affichage
// Fonction appelée lors du clic sur le bouton "Actualiser"
// Et au chargement de la page
function stats() {
// On prend les dates de début et de fin
// (format DD/MM/YYYY) et on les convertit en Date()
const dd_val = document.getElementById('stats_date_debut').value;
const df_val = document.getElementById('stats_date_fin').value;
let date_debut = new Date(Date.fromFRA(dd_val));
let date_fin = new Date(Date.fromFRA(df_val));
if (dd_val == "" || df_val == "" || !date_debut.isValid() || !date_debut.isValid()) {
openAlertModal("Dates invalides", document.createTextNode('Les dates sélectionnées sont invalides'));
return;
}
// On met les dates à 00h et 23h59 pour avoir la journée entière
date_debut = date_debut.startOf("day")
date_fin = date_fin.endOf("day")
if (date_debut.isAfter(date_fin)) {
openAlertModal("Dates invalides", document.createTextNode('La date de début se situe après la date de fin.'));
return;
}
// Appel à l'api et affichage des stats sur la page
countAssiduites(date_debut.toFakeIso(), date_fin.toFakeIso())
}
// Appel à l'api pour récupérer les statistiques d'assiduité
// Effectue l'action passée en paramètre sur les données récupérées
// dateDeb : date de début au format ISO
// dateFin : date de fin au format ISO
// action : fonction à appeler sur les données récupérées
function getAssiduitesCount(dateDeb, dateFin, action) {
const url_api = `../../api/assiduites/${etudid}/count/query?date_debut=${dateDeb}&date_fin=${dateFin}&etat=absent,retard,present&split`;
async_get(
url_api,
action,
()=>{},
);
}
function showStats(data){
// Initialisation d'un objet contenant les résultats
// Sera mis à jour avec le reste des valeurs dans la suite
// du code
const counter = {
"present": {
"total": data["present"],
},
"retard": {
"total": data["retard"],
"justi": data["retard"]["justifie"],
},
"absent": {
"total": data["absent"],
"justi": data["absent"]["justifie"],
}
}
// Reset du DOM
const values = document.querySelector('.stats-values');
values.innerHTML = "";
// Pour chaque état d'assiduité (present, retard, absent)
Object.keys(counter).forEach((key) => {
// On créé les éléments HTML qui serviront d'affichage
const item = document.createElement('div');
item.classList.add('stats-values-item');
const div = document.createElement('div');
div.classList.add('stats-values-part');
// Fonction anonyme pour éviter de réécrire tout le temps un test
// Si l'état est "present" alors cela renvoie "" (=> pas de nb justifié)
// Sinon cela renvoie "dont X justifiées"
const withJusti = (key, metric) => {
if (key == "present") return "";
return ` dont ${counter[key].justi[metric]} justifiées`
}
// HEURE : aroundie à 2 décimales.
const heure = document.createElement('span');
heure.textContent = `${counter[key].total.heure.toFixed(2)} heure(s)${withJusti(key, "heure")}`;
// DEMI-JOURNEE
const demi = document.createElement('span');
demi.textContent = `${counter[key].total.demi} demi-journée(s)${withJusti(key, "demi")}`;
// JOURNEE
const jour = document.createElement('span');
jour.textContent = `${counter[key].total.journee} journée(s)${withJusti(key, "journee")}`;
// On met à jour le DOM avec les valeurs calculées
// On met l'état en Titre pour chaque partie
div.append(jour, demi, heure);
const title = document.createElement('h5');
title.textContent = key.capitalize();
item.append(title, div)
values.appendChild(item);
});
// On vérifie si l'étudiant a trop d'absences
const nbAbs = data["absent"]["non_justifie"][assi_metric];
if (nbAbs > assi_seuil) {
// L'étudiant est au dessus du seuil (défini dans les préférences du département)
document.querySelector('.alerte').classList.remove('invisible');
document.querySelector('.alerte p').textContent = `Attention, cet étudiant a trop d'absences ${nbAbs} / ${assi_seuil} (${metriques[assi_metric]})`
} else {
document.querySelector('.alerte').classList.add('invisible');
}
}
function countAssiduites(dateDeb, dateFin) {
getAssiduitesCount(dateDeb, dateFin, showStats);
}
// Table de conversion des métriques
// Utilisé pour afficher les valeurs en fonction de la métrique
const metriques = {
"heure": "H.",
"demi": "1/2 J.",
"journee": "J."
}
// Récupération des données obligatoires pour les fonctions
// Depuis le contexte de la page (Jinja2)
const etudid = {{ sco.etud.id }};
const assi_metric = "{{ assi_metric | safe }}";
const assi_seuil = {{ assi_seuil }};
const assi_date_debut = "{{date_debut}}";
const assi_date_fin = "{{date_fin}}";
// Au chargement de la page, on met les dates par défaut
// Et on appelle la fonction stats() pour afficher les stats
window.addEventListener('load', () => {
document.getElementById('stats_date_fin').value = assi_date_fin;
document.getElementById('stats_date_debut').value = assi_date_debut;
stats();
})
</script>
{% endblock %}