{% block app_content %} {% include "assiduites/widgets/tableau_base.j2" %} <div class="pageContent"> <h2>Bilan de l'assiduité de <span class="rouge">{{sco.etud.nomprenom}}</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="date" name="stats_date_debut" id="stats_date_debut" value="{{date_debut}}"></label> <label class="stats-label"> Date de fin<input type="date" 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; if (dd_val == "" || df_val == "") { openAlertModal("Dates invalides", document.createTextNode('Les dates sélectionnées sont invalides')); return; } const date_debut = new moment.tz(dd_val + "T00:00", TIMEZONE); const date_fin = new moment.tz(df_val + "T23:59", TIMEZONE); if (date_debut.valueOf() > date_fin.valueOf()) { openAlertModal("Dates invalides", document.createTextNode('La date de début se situe après la date de fin.')); return; } countAssiduites(date_debut.format(), date_fin.format()) } function getAssiduitesCount(dateDeb, dateFin, query) { const url_api = getUrl() + `/api/assiduites/${etudid}/count/query?date_debut=${dateDeb}&date_fin=${dateFin}&${query}`; return $.ajax({ async: true, type: "GET", url: url_api, success: (data, status) => { if (status === "success") { } }, error: () => { }, }); } function countAssiduites(dateDeb, dateFin) { $.when( getAssiduitesCount(dateDeb, dateFin, `etat=present`), getAssiduitesCount(dateDeb, dateFin, `etat=present&est_just=v`), 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, pj, rt, rj, at, aj) => { const counter = { "present": { "total": pt[0], "justi": pj[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 heure = document.createElement('span'); heure.textContent = `${counter[key].total.heure} heure(s) dont ${counter[key].justi.heure} justifiées`; const demi = document.createElement('span'); demi.textContent = `${counter[key].total.demi} demi-journée(s) dont ${counter[key].justi.demi} justifiées`; const jour = document.createElement('span'); jour.textContent = `${counter[key].total.journee} journée(s) dont ${counter[key].justi.journee} justifiées`; 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: crimson; } .alerte.invisible { display: none; } .alerte p { font-size: larger; color: whitesmoke; } .suppr { margin: 5px 0; } </style>