387 lines
12 KiB
Django/Jinja
387 lines
12 KiB
Django/Jinja
{% block app_content %}
|
|
{% include "assiduites/widgets/tableau_base.j2" %}
|
|
<div class="pageContent">
|
|
|
|
<h2>Bilan de l'assiduité de {{sco.etud.html_link_fiche()|safe}}</span></h2>
|
|
|
|
<section class="alerte invisible">
|
|
<p>Attention, cet étudiant a trop d'absences</p>
|
|
</section>
|
|
|
|
<section class="stats">
|
|
<!-- Statistiques d'assiduité (nb pres, nb retard, nb absence) + nb justifié -->
|
|
<h4>Statistiques d'assiduité</h4>
|
|
<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>
|
|
<button onclick="stats()">Actualiser</button>
|
|
</div>
|
|
|
|
<div class="stats-values">
|
|
|
|
</div>
|
|
</section>
|
|
|
|
<section class="nonvalide">
|
|
<!-- Tableaux des assiduités (retard/abs) non justifiées -->
|
|
<h4>Absences et retards non justifiés</h4>
|
|
<span class="iconline">
|
|
<a class="icon filter" onclick="filterAssi()"></a>
|
|
<a class="icon download" onclick="downloadAssi()"></a>
|
|
</span>
|
|
{% include "assiduites/widgets/tableau_assi.j2" %}
|
|
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
|
<h4>Justificatifs en attente (ou modifiés)</h4>
|
|
<span class="iconline">
|
|
<a class="icon filter" onclick="filterJusti()"></a>
|
|
<a class="icon download" onclick="downloadJusti()"></a>
|
|
</span>
|
|
{% include "assiduites/widgets/tableau_justi.j2" %}
|
|
|
|
</section>
|
|
|
|
<section class="suppr">
|
|
<h4>Boutons de suppresions (toute suppression est définitive) </h4>
|
|
<button type="button" onclick="removeAllAssiduites()">Suppression des assiduités</button>
|
|
<button type="button" onclick="removeAllJustificatifs()">Suppression des justificatifs</button>
|
|
</section>
|
|
|
|
<div class="legende">
|
|
<h3>Statistiques</h3>
|
|
<p>Un message d'alerte apparait si le nombre d'absence dépasse le seuil (indiqué dans les préférences du
|
|
département)</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>
|
|
<h3>Gestion des justificatifs</h3>
|
|
<p>
|
|
Faites
|
|
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
|
contextuel :
|
|
</p>
|
|
<ul>
|
|
<li>Détails : affiche les détails du justificatif sélectionné</li>
|
|
<li>Éditer : modifie le justificatif (dates, état, ajouter/supprimer fichier, etc.)</li>
|
|
<li>Supprimer : supprime le justificatif (action irréversible)</li>
|
|
</ul>
|
|
|
|
<h3>Gestion de l'assiduité</h3>
|
|
<p>
|
|
Faites
|
|
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
|
contextuel :
|
|
</p>
|
|
<ul>
|
|
<li>Détails : affiche les détails de l'élément sélectionnée</li>
|
|
<li>Editer : modifie l'élément (module, état)</li>
|
|
<li>Supprimer : supprime l'élément (action irréversible)</li>
|
|
</ul>
|
|
</div>
|
|
|
|
</div>
|
|
{% endblock app_content %}
|
|
|
|
<script>
|
|
|
|
function stats() {
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
countAssiduites(date_debut.toFakeIso(), date_fin.toFakeIso())
|
|
|
|
}
|
|
|
|
function getAssiduitesCount(dateDeb, dateFin, query) {
|
|
const url_api = getUrl() + `/api/assiduites/${etudid}/count/query?date_debut=${dateDeb}&date_fin=${dateFin}&${query}`;
|
|
//Utiliser async_get au lieu de Jquery
|
|
return $.ajax({
|
|
async: true,
|
|
type: "GET",
|
|
url: url_api,
|
|
success: (data, status) => {
|
|
if (status === "success") {
|
|
}
|
|
},
|
|
error: () => { },
|
|
});
|
|
}
|
|
|
|
function countAssiduites(dateDeb, dateFin) {
|
|
//TODO Utiliser Fetch when plutot que jquery
|
|
$.when(
|
|
getAssiduitesCount(dateDeb, dateFin, `etat=present`),
|
|
getAssiduitesCount(dateDeb, dateFin, `etat=retard`),
|
|
getAssiduitesCount(dateDeb, dateFin, `etat=retard&est_just=v`),
|
|
getAssiduitesCount(dateDeb, dateFin, `etat=absent`),
|
|
getAssiduitesCount(dateDeb, dateFin, `etat=absent&est_just=v`),
|
|
).then(
|
|
(pt, rt, rj, at, aj) => {
|
|
const counter = {
|
|
"present": {
|
|
"total": pt[0],
|
|
},
|
|
"retard": {
|
|
"total": rt[0],
|
|
"justi": rj[0],
|
|
},
|
|
"absent": {
|
|
"total": at[0],
|
|
"justi": aj[0],
|
|
}
|
|
}
|
|
|
|
const values = document.querySelector('.stats-values');
|
|
values.innerHTML = "";
|
|
|
|
Object.keys(counter).forEach((key) => {
|
|
const item = document.createElement('div');
|
|
item.classList.add('stats-values-item');
|
|
|
|
const div = document.createElement('div');
|
|
div.classList.add('stats-values-part');
|
|
|
|
const withJusti = (key, metric) => {
|
|
if (key == "present") return "";
|
|
return ` dont ${counter[key].justi[metric]} justifiées`
|
|
}
|
|
|
|
const heure = document.createElement('span');
|
|
heure.textContent = `${counter[key].total.heure} heure(s)${withJusti(key, "heure")}`;
|
|
|
|
const demi = document.createElement('span');
|
|
demi.textContent = `${counter[key].total.demi} demi-journée(s)${withJusti(key, "demi")}`;
|
|
|
|
const jour = document.createElement('span');
|
|
jour.textContent = `${counter[key].total.journee} journée(s)${withJusti(key, "journee")}`;
|
|
|
|
div.append(jour, demi, heure);
|
|
|
|
const title = document.createElement('h5');
|
|
title.textContent = key.capitalize();
|
|
|
|
item.append(title, div)
|
|
|
|
values.appendChild(item);
|
|
});
|
|
|
|
const nbAbs = counter.absent.total[assi_metric] - counter.absent.justi[assi_metric];
|
|
if (nbAbs > assi_seuil) {
|
|
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 removeAllAssiduites() {
|
|
|
|
openPromptModal(
|
|
"Suppression de l'assiduité",
|
|
document.createTextNode(
|
|
'Souhaitez vous réellement supprimer toutes les informations sur l\'assiduité de cet étudiant ? Cette suppression est irréversible.')
|
|
,
|
|
() => {
|
|
getAllAssiduitesFromEtud(etudid, (data) => {
|
|
const toRemove = data.map((a) => a.assiduite_id);
|
|
console.log(toRemove)
|
|
deleteAssiduites(toRemove);
|
|
})
|
|
})
|
|
|
|
|
|
}
|
|
function removeAllJustificatifs() {
|
|
openPromptModal(
|
|
"Suppression des justificatifs",
|
|
document.createTextNode(
|
|
'Souhaitez vous réelement supprimer tous les justificatifs de cet étudiant ? Cette supression est irréversible.')
|
|
,
|
|
() => {
|
|
getAllJustificatifsFromEtud(etudid, (data) => {
|
|
const toRemove = data.map((a) => a.justif_id);
|
|
|
|
deleteJustificatifs(toRemove);
|
|
|
|
})
|
|
})
|
|
}
|
|
/**
|
|
* Suppression des assiduties
|
|
*/
|
|
function deleteAssiduites(assi) {
|
|
const path = getUrl() + `/api/assiduite/delete`;
|
|
async_post(
|
|
path,
|
|
assi,
|
|
(data, status) => {
|
|
//success
|
|
if (data.success.length > 0) {
|
|
}
|
|
location.reload();
|
|
},
|
|
(data, status) => {
|
|
//error
|
|
console.error(data, status);
|
|
errorAlert();
|
|
}
|
|
);
|
|
}
|
|
/**
|
|
* Suppression des justificatifs
|
|
*/
|
|
function deleteJustificatifs(justis) {
|
|
const path = getUrl() + `/api/justificatif/delete`;
|
|
async_post(
|
|
path,
|
|
justis,
|
|
(data, status) => {
|
|
//success
|
|
location.reload();
|
|
},
|
|
(data, status) => {
|
|
//error
|
|
console.error(data, status);
|
|
errorAlert();
|
|
}
|
|
);
|
|
}
|
|
|
|
const metriques = {
|
|
"heure": "H.",
|
|
"demi": "1/2 J.",
|
|
"journee": "J."
|
|
}
|
|
|
|
|
|
|
|
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}}";
|
|
|
|
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
|
|
|
|
|
window.addEventListener('load', () => {
|
|
filterAssiduites = {
|
|
"columns": [
|
|
"entry_date",
|
|
"date_debut",
|
|
"date_fin",
|
|
"etat",
|
|
"moduleimpl_id",
|
|
"est_just"
|
|
],
|
|
"filters": {
|
|
"etat": [
|
|
"retard",
|
|
"absent"
|
|
],
|
|
"moduleimpl_id": "",
|
|
"est_just": "false"
|
|
}
|
|
};
|
|
|
|
filterJustificatifs = {
|
|
"columns": [
|
|
"entry_date",
|
|
"date_debut",
|
|
"date_fin",
|
|
"etat",
|
|
"raison",
|
|
"fichier"
|
|
],
|
|
"filters": {
|
|
"etat": [
|
|
"attente",
|
|
"modifie"
|
|
]
|
|
}
|
|
}
|
|
|
|
document.getElementById('stats_date_fin').value = assi_date_fin;
|
|
document.getElementById('stats_date_debut').value = assi_date_debut;
|
|
|
|
|
|
|
|
loadAll();
|
|
stats();
|
|
})
|
|
|
|
</script>
|
|
<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;
|
|
}
|
|
|
|
.alerte {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 10px;
|
|
margin: 5px 0;
|
|
border-radius: 7px;
|
|
|
|
background-color: var(--color-error);
|
|
|
|
}
|
|
|
|
.alerte.invisible {
|
|
display: none;
|
|
}
|
|
|
|
.alerte p {
|
|
font-size: larger;
|
|
color: whitesmoke;
|
|
|
|
}
|
|
|
|
.suppr {
|
|
margin: 5px 0;
|
|
}
|
|
</style>
|