From 3e1f563ecd828f5d43e0f9e5abf1fa3a8d9ea3c9 Mon Sep 17 00:00:00 2001 From: Iziram Date: Tue, 16 Jan 2024 16:15:48 +0100 Subject: [PATCH] =?UTF-8?q?Assiduites=20:=20am=C3=A9liorations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - - duplication const - + bouton justifier `ajout_assiduite_etud` - + auto actualiser `signal_assiduite_group` - - enum au lieu de str - + mettre css minitimeline.css - + resize timeline fix --- app/forms/assiduite/ajout_assiduite_etud.py | 2 + app/scodoc/sco_assiduites.py | 6 +- app/scodoc/sco_utils.py | 2 +- app/static/css/assiduites.css | 10 +- app/static/css/minitimeline.css | 212 ++++++++++++++++ app/static/js/assiduites.js | 40 ++- .../assiduites/pages/ajout_assiduite_etud.j2 | 7 + .../assiduites/pages/calendrier_assi_etud.j2 | 227 +----------------- .../pages/signal_assiduites_group.j2 | 3 - .../assiduites/widgets/minitimeline.j2 | 198 +++------------ app/templates/assiduites/widgets/timeline.j2 | 14 +- app/views/assiduites.py | 136 ++++------- 12 files changed, 361 insertions(+), 496 deletions(-) create mode 100644 app/static/css/minitimeline.css diff --git a/app/forms/assiduite/ajout_assiduite_etud.py b/app/forms/assiduite/ajout_assiduite_etud.py index deeec72c5..d5783e2d0 100644 --- a/app/forms/assiduite/ajout_assiduite_etud.py +++ b/app/forms/assiduite/ajout_assiduite_etud.py @@ -32,6 +32,7 @@ Formulaire ajout d'un justificatif sur un étudiant from flask_wtf import FlaskForm from flask_wtf.file import MultipleFileField from wtforms import ( + BooleanField, SelectField, StringField, SubmitField, @@ -136,6 +137,7 @@ class AjoutAssiduiteEtudForm(AjoutAssiOrJustForm): "Module", choices={}, # will be populated dynamically ) + est_just = BooleanField("Justifiée") class AjoutJustificatifEtudForm(AjoutAssiOrJustForm): diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index b31366350..7269654d0 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -390,13 +390,11 @@ def get_assiduites_stats( # Récupération des états etats: list[str] = ( - filtered["etat"].split(",") - if "etat" in filtered - else ["absent", "present", "retard"] + filtered["etat"].split(",") if "etat" in filtered else scu.EtatAssiduite.all() ) # être sur que les états sont corrects - etats = [etat for etat in etats if etat in ["absent", "present", "retard"]] + etats = [etat for etat in etats if etat.upper() in scu.EtatAssiduite.all()] # Préparation du dictionnaire de retour avec les valeurs du calcul count: dict = calculator.to_dict(only_total=False) diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 011038c20..4ba283e15 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -476,7 +476,7 @@ MONTH_NAMES_ABBREV = ( "Avr ", "Mai ", "Juin", - "Jul ", + "Juil ", "Août", "Sept", "Oct ", diff --git a/app/static/css/assiduites.css b/app/static/css/assiduites.css index a19f0d9ea..da872491b 100644 --- a/app/static/css/assiduites.css +++ b/app/static/css/assiduites.css @@ -256,17 +256,17 @@ background-color: var(--color-conflit); } -.etud_row .assiduites_bar .absent, +.etud_row .assiduites_bar>.absent, .demo.absent { background-color: var(--color-absent) !important; } -.etud_row .assiduites_bar .present, +.etud_row .assiduites_bar>.present, .demo.present { background-color: var(--color-present) !important; } -.etud_row .assiduites_bar .retard, +.etud_row .assiduites_bar>.retard, .demo.retard { background-color: var(--color-retard) !important; } @@ -275,12 +275,12 @@ background-color: var(--color-nonwork) !important; } -.etud_row .assiduites_bar .justified, +.etud_row .assiduites_bar>.justified, .demo.justified { background-image: var(--motif-justi); } -.etud_row .assiduites_bar .invalid_justified, +.etud_row .assiduites_bar>.invalid_justified, .demo.invalid_justified { background-image: var(--motif-justi-invalide); } diff --git a/app/static/css/minitimeline.css b/app/static/css/minitimeline.css new file mode 100644 index 000000000..04c713c44 --- /dev/null +++ b/app/static/css/minitimeline.css @@ -0,0 +1,212 @@ +.day .dayline { + position: absolute; + display: none; + top: 100%; + z-index: 50; + width: max-content; + height: 75px; + background-color: #dedede; + border-radius: 15px; + padding: 5px; +} + +.day:hover .dayline { + display: block; +} + +.dayline .mini-timeline { + margin-top: 10%; +} + +.dayline-title { + margin: 0; +} + +.dayline .mini_tick { + position: absolute; + text-align: center; + top: 0; + transform: translateY(-110%); + z-index: 50; +} + +.dayline .mini_tick::after { + display: block; + content: "|"; + position: absolute; + bottom: -69%; + z-index: 2; + transform: translateX(200%); +} + +#label-nom, +#label-justi { + display: none; +} + +.demi .day { + display: flex; + justify-content: space-evenly; +} + +.demi .day>span { + display: block; + flex: 1; + text-align: center; + z-index: 1; + width: 100%; + border: 1px solid #d5d5d5; + position: relative; +} + +.demi .day>span:first-of-type { + width: 3em; + min-width: 3em; +} + +.options>* { + margin-right: 5px; +} + +.options input { + margin-right: 6px; +} + +.options label { + font-weight: normal; + margin-right: 16px; +} + + +/*Gestion des bubbles*/ +.assiduite-bubble { + position: relative; + display: none; + background-color: #f9f9f9; + border-radius: 5px; + padding: 8px; + border: 3px solid #ccc; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + font-size: 12px; + line-height: 1.4; + z-index: 3; + min-width: max-content; + top: 200%; +} + +.mini-timeline-block:hover .assiduite-bubble { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: auto; + max-height: 150px; +} + +.assiduite-bubble::before { + content: ""; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 6px; + border-style: solid; + border-color: transparent transparent #f9f9f9 transparent; +} + +.assiduite-bubble::after { + content: ""; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 5px; + border-style: solid; + border-color: transparent transparent #ccc transparent; +} + +.assiduite-id, +.assiduite-period, +.assiduite-state, +.assiduite-user_id { + margin-bottom: 4px; +} + +.assiduite-bubble.absent { + border-color: var(--color-absent) !important; +} + +.assiduite-bubble.present { + border-color: var(--color-present) !important; +} + +.assiduite-bubble.retard { + border-color: var(--color-retard) !important; +} + +/*Gestion des minitimelines*/ +.mini-timeline { + height: 7px; + border: 1px solid black; + position: relative; + background-color: white; +} + +.mini-timeline.single { + height: 9px; +} + +.mini-timeline-block { + position: absolute; + height: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: column; +} + +.mini-timeline-block { + cursor: pointer; +} + +.mini_tick { + position: absolute; + text-align: start; + top: -40px; + transform: translateX(-50%); + z-index: 2; + +} + +.mini_tick::after { + display: block; + content: "|"; + position: absolute; + bottom: -2px; + z-index: 2; +} + +.mini-timeline-block.creneau { + outline: 3px solid var(--color-primary); + pointer-events: none; +} + +.mini-timeline-block.absent { + background-color: var(--color-absent) !important; +} + +.mini-timeline-block.present { + background-color: var(--color-present) !important; +} + +.mini-timeline-block.retard { + background-color: var(--color-retard) !important; +} + +.mini-timeline-block.justified { + background-image: var(--motif-justi); +} + +.mini-timeline-block.invalid_justified { + background-image: var(--motif-justi-invalide); +} \ No newline at end of file diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index 9f7e69e7a..b1522e7d8 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -68,6 +68,25 @@ function setupCheckBox(parent = document) { }); } +function updateEtudList() { + const group_ids = getGroupIds(); + etuds = {}; + group_ids.forEach((group_id) => { + sync_get(getUrl() + `/api/group/${group_id}/etudiants`, (data, status) => { + if (status === "success") { + data.forEach((etud) => { + if (!(etud.id in etuds)) { + etuds[etud.id] = etud; + } + }); + } + }); + }); + + getAssiduitesFromEtuds(true); + generateAllEtudRow(); +} + /** * Validation préalable puis désactivation des chammps : * - Groupe @@ -108,14 +127,16 @@ function validateSelectors(btn) { return; } - getAssiduitesFromEtuds(true); - - // document.querySelector(".selectors").disabled = true; - // $("#tl_date").datepicker("option", "disabled", true); generateMassAssiduites(); + + getAssiduitesFromEtuds(true); generateAllEtudRow(); - // btn.remove(); - btn.textContent = "Actualiser"; + + btn.remove(); + // Auto actualisation + $("#tl_date").on("change", updateEtudList); + $("#group_ids_sel").on("change", updateEtudList); + onlyAbs(); }; @@ -648,16 +669,15 @@ function updateDate() { ); openAlertModal("Attention", div, "", "#eec660"); - /* BUG TODO MATHIAS - $(dateInput).datepicker("setDate", date_fra); // XXX ??? non définie - dateInput.value = date_fra; - */ date = lastWorkDay; dateStr = formatDate(lastWorkDay, { dateStyle: "full", timeZone: SCO_TIMEZONE, }).capitalize(); + + $(dateInput).datepicker("setDate", date); + $(dateInput).change(); } document.querySelector("#datestr").textContent = dateStr; diff --git a/app/templates/assiduites/pages/ajout_assiduite_etud.j2 b/app/templates/assiduites/pages/ajout_assiduite_etud.j2 index 260ba5e45..7871bf903 100644 --- a/app/templates/assiduites/pages/ajout_assiduite_etud.j2 +++ b/app/templates/assiduites/pages/ajout_assiduite_etud.j2 @@ -87,6 +87,13 @@ div.submit > input { {{ form.modimpl }} {{ render_field_errors(form, 'modimpl') }} + {# Justifiée #} +
+ {{ form.est_just.label }} : + {{ form.est_just }} + génère un justificatif valide ayant la même période que l'assiduité signalée + {{ render_field_errors(form, 'est_just') }} +
{# Description #}
{{ form.description.label }}
diff --git a/app/templates/assiduites/pages/calendrier_assi_etud.j2 b/app/templates/assiduites/pages/calendrier_assi_etud.j2 index 3eaedb52b..f0478610f 100644 --- a/app/templates/assiduites/pages/calendrier_assi_etud.j2 +++ b/app/templates/assiduites/pages/calendrier_assi_etud.j2 @@ -1,4 +1,14 @@ -{% block pageContent %} +{% extends "sco_page.j2" %} +{% block title %} +Calendrier de l'assiduité +{% endblock title %} +{% block styles %} + {{ super() }} + + +{% endblock styles %} + +{% block app_content %} {% include "assiduites/widgets/alert.j2" %}
@@ -250,219 +260,6 @@ } - - .day .dayline { - position: absolute; - display: none; - top: 100%; - z-index: 50; - width: max-content; - height: 75px; - background-color: #dedede; - border-radius: 15px; - padding: 5px; - } - - .day:hover .dayline { - display: block; - } - - .dayline .mini-timeline { - margin-top: 10%; - } - - .dayline-title { - margin: 0; - } - - .dayline .mini_tick { - position: absolute; - text-align: center; - top: 0; - transform: translateY(-110%); - z-index: 50; - } - - .dayline .mini_tick::after { - display: block; - content: "|"; - position: absolute; - bottom: -69%; - z-index: 2; - transform: translateX(200%); - } - - #label-nom, - #label-justi { - display: none; - } - - .demi .day { - display: flex; - justify-content: space-evenly; - } - - .demi .day>span { - display: block; - flex: 1; - text-align: center; - z-index: 1; - width: 100%; - border: 1px solid #d5d5d5; - position: relative; - } - - .demi .day>span:first-of-type { - width: 3em; - min-width: 3em; - } - - .options>* { - margin-right: 5px; - } - - .options input { - margin-right: 6px; - } - - .options label { - font-weight: normal; - margin-right: 16px; - } - - - /*Gestion des bubbles*/ - .assiduite-bubble { - position: relative; - display: none; - background-color: #f9f9f9; - border-radius: 5px; - padding: 8px; - border: 3px solid #ccc; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); - font-size: 12px; - line-height: 1.4; - z-index: 500; - min-width: max-content; - top: 200%; - } - - .mini-timeline-block:hover .assiduite-bubble { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - } - - .assiduite-bubble::before { - content: ""; - position: absolute; - bottom: 100%; - left: 50%; - transform: translateX(-50%); - border-width: 6px; - border-style: solid; - border-color: transparent transparent #f9f9f9 transparent; - } - - .assiduite-bubble::after { - content: ""; - position: absolute; - bottom: 100%; - left: 50%; - transform: translateX(-50%); - border-width: 5px; - border-style: solid; - border-color: transparent transparent #ccc transparent; - } - - .assiduite-id, - .assiduite-period, - .assiduite-state, - .assiduite-user_id { - margin-bottom: 4px; - } - - .assiduite-bubble.absent { - border-color: var(--color-absent) !important; - } - - .assiduite-bubble.present { - border-color: var(--color-present) !important; - } - - .assiduite-bubble.retard { - border-color: var(--color-retard) !important; - } - - /*Gestion des minitimelines*/ - .mini-timeline { - height: 7px; - border: 1px solid black; - position: relative; - background-color: white; - } - - .mini-timeline.single { - height: 9px; - } - - .mini-timeline-block { - position: absolute; - height: 100%; - z-index: 1; - display: flex; - justify-content: flex-start; - align-items: center; - flex-direction: column; - } - - .mini-timeline-block { - cursor: pointer; - } - - .mini_tick { - position: absolute; - text-align: start; - top: -40px; - transform: translateX(-50%); - z-index: 50; - - } - - .mini_tick::after { - display: block; - content: "|"; - position: absolute; - bottom: -2px; - z-index: 2; - } - - .mini-timeline-block.creneau { - outline: 3px solid var(--color-primary); - pointer-events: none; - } - - .mini-timeline-block.absent { - background-color: var(--color-absent) !important; - } - - .mini-timeline-block.present { - background-color: var(--color-present) !important; - } - - .mini-timeline-block.retard { - background-color: var(--color-retard) !important; - } - - .mini-timeline-block.justified { - background-image: var(--motif-justi); - } - - .mini-timeline-block.invalid_justified { - background-image: var(--motif-justi-invalide); - } - @media print { .couleurs.print { @@ -593,4 +390,4 @@ -{% endblock pageContent %} +{% endblock app_content %} diff --git a/app/templates/assiduites/pages/signal_assiduites_group.j2 b/app/templates/assiduites/pages/signal_assiduites_group.j2 index 238dd25c7..2ce3672ee 100644 --- a/app/templates/assiduites/pages/signal_assiduites_group.j2 +++ b/app/templates/assiduites/pages/signal_assiduites_group.j2 @@ -47,7 +47,6 @@ Faire la saisie {% endif %} -

Utilisez le bouton "Actualiser" si vous modifier la date ou le(s) groupe(s) sélectionné(s)

@@ -97,9 +96,7 @@ updateDate(); if (!readOnly){ setupTimeLine(()=>{ - if(document.querySelector('.etud_holder .placeholder') != null){ generateAllEtudRow(); - } }); } diff --git a/app/templates/assiduites/widgets/minitimeline.j2 b/app/templates/assiduites/widgets/minitimeline.j2 index 8671d74a1..335ac7014 100644 --- a/app/templates/assiduites/widgets/minitimeline.j2 +++ b/app/templates/assiduites/widgets/minitimeline.j2 @@ -73,11 +73,6 @@ updateSelectedSelect(getCurrentAssiduiteModuleImplId()); updateJustifyBtn(); } - try { - if (isCalendrier()) { - window.location = `liste_assiduites_etud?etudid=${etudid}&assiduite_id=${assiduité.assiduite_id}` - } - } catch { } }); //ajouter affichage assiduites on over setupAssiduiteBuble(block, assiduité); @@ -138,51 +133,43 @@ */ function setupAssiduiteBuble(el, assiduite) { if (!assiduite) return; - el.addEventListener("mouseenter", (event) => { - const bubble = document.querySelector(".assiduite-bubble"); - bubble.className = "assiduite-bubble"; - bubble.classList.add("is-active", assiduite.etat.toLowerCase()); - bubble.innerHTML = ""; + const bubble = document.createElement('div'); + bubble.className = "assiduite-bubble"; + bubble.classList.add(assiduite.etat.toLowerCase()); - const idDiv = document.createElement("div"); - idDiv.className = "assiduite-id"; - idDiv.textContent = `${getModuleImpl(assiduite)}`; - bubble.appendChild(idDiv); + const idDiv = document.createElement("div"); + idDiv.className = "assiduite-id"; + idDiv.textContent = `${getModuleImpl(assiduite)}`; + bubble.appendChild(idDiv); - const periodDivDeb = document.createElement("div"); - periodDivDeb.className = "assiduite-period"; - periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`; - bubble.appendChild(periodDivDeb); - const periodDivFin = document.createElement("div"); - periodDivFin.className = "assiduite-period"; - periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`; - bubble.appendChild(periodDivFin); + const periodDivDeb = document.createElement("div"); + periodDivDeb.className = "assiduite-period"; + periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`; + bubble.appendChild(periodDivDeb); + const periodDivFin = document.createElement("div"); + periodDivFin.className = "assiduite-period"; + periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`; + bubble.appendChild(periodDivFin); - const stateDiv = document.createElement("div"); - stateDiv.className = "assiduite-state"; - stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`; - bubble.appendChild(stateDiv); + const stateDiv = document.createElement("div"); + stateDiv.className = "assiduite-state"; + stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`; + bubble.appendChild(stateDiv); - const userIdDiv = document.createElement("div"); - userIdDiv.className = "assiduite-user_id"; - userIdDiv.textContent = `saisie le ${formatDateModal( - assiduite.entry_date, - " à " - )}`; + const userIdDiv = document.createElement("div"); + userIdDiv.className = "assiduite-user_id"; + userIdDiv.textContent = `saisie le ${formatDateModal( + assiduite.entry_date, + " à " + )}`; - if (assiduite.user_id != null) { - userIdDiv.textContent += `\npar ${assiduite.user_nom_complet}` - } - bubble.appendChild(userIdDiv); + if (assiduite.user_id != null) { + userIdDiv.textContent += `\npar ${assiduite.user_nom_complet}` + } + bubble.appendChild(userIdDiv); - bubble.style.left = `${event.clientX - bubble.offsetWidth / 2}px`; - bubble.style.top = `${event.clientY + 20}px`; - }); - el.addEventListener("mouseout", () => { - const bubble = document.querySelector(".assiduite-bubble"); - bubble.classList.remove("is-active"); - }); + el.appendChild(bubble); } function setMiniTick(timelineDate, dayStart, dayDuration) { @@ -198,127 +185,4 @@ return tick } - - - + \ No newline at end of file diff --git a/app/templates/assiduites/widgets/timeline.j2 b/app/templates/assiduites/widgets/timeline.j2 index 24338b0c9..754fb2dad 100644 --- a/app/templates/assiduites/widgets/timeline.j2 +++ b/app/templates/assiduites/widgets/timeline.j2 @@ -89,8 +89,7 @@ } - function timelineMainEvent(event, callback) { - const func_call = callback ? callback : () => { }; + function timelineMainEvent(event) { const startX = (event.clientX || event.changedTouches[0].clientX); @@ -152,7 +151,6 @@ updatePeriodTimeLabel(); }; const mouseUp = () => { - generateAllEtudRow(); snapHandlesToQuarters(); timelineContainer.removeEventListener("mousemove", onMouseMove); func_call(); @@ -172,9 +170,12 @@ } } + let func_call = () => { }; + function setupTimeLine(callback) { - timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e, callback) }); - timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e, callback) }); + func_call = callback; + timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e) }); + timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e) }); } function adjustPeriodPosition(newLeft, newWidth) { @@ -230,8 +231,8 @@ periodTimeLine.style.width = `${widthPercentage}%`; snapHandlesToQuarters(); - generateAllEtudRow(); updatePeriodTimeLabel() + func_call(); } function snapHandlesToQuarters() { @@ -270,7 +271,6 @@ if (heure_deb != '' && heure_fin != '') { heure_deb = fromTime(heure_deb); heure_fin = fromTime(heure_fin); - console.warn(heure_deb, heure_fin) setPeriodValues(heure_deb, heure_fin) } {% endif %} diff --git a/app/views/assiduites.py b/app/views/assiduites.py index f9f2887a7..54265c6e5 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -462,11 +462,13 @@ def _record_assiduite_etud( case _: moduleimpl = ModuleImpl.query.get(moduleimpl_id) try: + assi_etat: scu.EtatAssiduite = scu.EtatAssiduite.get(form.assi_etat.data) + ass = Assiduite.create_assiduite( etud, dt_debut_tz_server, dt_fin_tz_server, - scu.EtatAssiduite.get(form.assi_etat.data), + assi_etat, description=form.description.data, entry_date=dt_entry_date_tz_server, external_data=external_data, @@ -477,6 +479,19 @@ def _record_assiduite_etud( db.session.add(ass) db.session.commit() + if assi_etat != scu.EtatAssiduite.PRESENT and form.est_just.data: + # si la case "justifiée est cochée alors on créé un justificatif de même période" + justi: Justificatif = Justificatif.create_justificatif( + etudiant=etud, + date_debut=dt_debut_tz_server, + date_fin=dt_fin_tz_server, + etat=scu.EtatJustificatif.VALIDE, + user_id=current_user.id, + ) + + # On met à jour les assiduités en fonction du nouveau justificatif + compute_assiduites_justified(etud.id, [justi]) + # Invalider cache scass.simple_invalidate_cache(ass.to_dict(), etud.id) @@ -863,36 +878,20 @@ def calendrier_assi_etud(): annees_str += f"{ann}," annees_str += "]" - # Préparation de la page - header: str = html_sco_header.sco_header( - page_title="Calendrier de l'assiduité", - init_qtip=True, - javascripts=[ - "js/assiduites.js", - "js/date_utils.js", - ], - cssstyles=CSSSTYLES - + [ - "css/assiduites.css", - ], - ) + calendrier: dict[str, list["Jour"]] = generate_calendar(etud, annee) - calendrier = generate_calendar(etud, annee) # Peuplement du template jinja - return HTMLBuilder( - header, - render_template( - "assiduites/pages/calendrier_assi_etud.j2", - sco=ScoData(etud), - annee=annee, - nonworkdays=_non_work_days(), - annees=annees_str, - calendrier=calendrier, - mode_demi=mode_demi, - show_pres=show_pres, - show_reta=show_reta, - ), - ).build() + return render_template( + "assiduites/pages/calendrier_assi_etud.j2", + sco=ScoData(etud), + annee=annee, + nonworkdays=_non_work_days(), + annees=annees_str, + calendrier=calendrier, + mode_demi=mode_demi, + show_pres=show_pres, + show_reta=show_reta, + ) @bp.route("/choix_date", methods=["GET", "POST"]) @@ -927,7 +926,9 @@ def choix_date() -> str: if ok: return redirect( url_for( - "assiduites.signal_assiduites_group", + "assiduites.signal_assiduites_group" + if request.args.get("readonly") is None + else "assiduites.visu_assiduites_group", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, group_ids=group_ids, @@ -1071,6 +1072,7 @@ def signal_assiduites_group(): cssstyles=CSSSTYLES + [ "css/assiduites.css", + "css/minitimeline.css", ], ) @@ -1168,13 +1170,19 @@ def visu_assiduites_group(): ] # --- Vérification de la date --- - real_date = scu.is_iso_formated(date, True).date() - - if real_date < formsemestre.date_debut: - date = formsemestre.date_debut.isoformat() - elif real_date > formsemestre.date_fin: - date = formsemestre.date_fin.isoformat() + if real_date < formsemestre.date_debut or real_date > formsemestre.date_fin: + # Si le jour est hors semestre, renvoyer vers choix date + return redirect( + url_for( + "assiduites.choix_date", + formsemestre_id=formsemestre_id, + group_ids=group_ids, + moduleimpl_id=moduleimpl_id, + scodoc_dept=g.scodoc_dept, + readonly="true", + ) + ) # --- Restriction en fonction du moduleimpl_id --- if moduleimpl_id: @@ -1218,6 +1226,7 @@ def visu_assiduites_group(): cssstyles=CSSSTYLES + [ "css/assiduites.css", + "css/minitimeline.css", ], ) @@ -1555,21 +1564,6 @@ def tableau_assiduite_actions(): flash(f"{objet_name} supprimé") return redirect(request.referrer) - # Justification d'une assiduité depuis le tableau - if action == "justifier" and obj_type == "assiduite": - # Création du justificatif correspondant - justificatif_correspondant: Justificatif = Justificatif.create_justificatif( - etudiant=objet.etudiant, - date_debut=objet.date_debut, - date_fin=objet.date_fin, - etat=scu.EtatJustificatif.VALIDE, - user_id=current_user.id, - ) - - compute_assiduites_justified(objet.etudiant.id, [justificatif_correspondant]) - - flash(f"{objet_name} justifiée") - return redirect(request.referrer) # Justification d'une assiduité depuis le tableau if action == "justifier" and obj_type == "assiduite": @@ -2318,7 +2312,7 @@ def _get_etuds_dem_def(formsemestre) -> str: def generate_calendar( etudiant: Identite, annee: int = None, -): +) -> dict[str, list["Jour"]]: # Si pas d'année alors on prend l'année scolaire en cours if annee is None: annee = scu.annee_scolaire() @@ -2342,7 +2336,7 @@ def generate_calendar( ) # Récupération des jours de l'année et de leurs assiduités/justificatifs - annee_par_mois: dict[int, list[datetime.date]] = _organize_by_month( + annee_par_mois: dict[str, list[Jour]] = _organize_by_month( _get_dates_between( deb=date_debut.date(), fin=date_fin.date(), @@ -2354,32 +2348,6 @@ def generate_calendar( return annee_par_mois -WEEKDAYS = { - 0: "Lun ", - 1: "Mar ", - 2: "Mer ", - 3: "Jeu ", - 4: "Ven ", - 5: "Sam ", - 6: "Dim ", -} - -MONTHS = { - 1: "Janv.", - 2: "Févr.", - 3: "Mars", - 4: "Avr.", - 5: "Mai", - 6: "Juin", - 7: "Juil.", - 8: "Août", - 9: "Sept.", - 10: "Oct.", - 11: "Nov.", - 12: "Déc.", -} - - class Jour: """Jour Jour du calendrier @@ -2392,8 +2360,8 @@ class Jour: self.justificatifs = justificatifs def get_nom(self, mode_demi: bool = True) -> str: - str_jour: str = WEEKDAYS.get(self.date.weekday()) - return f"{str_jour[0] if mode_demi or self.is_non_work() else str_jour}{self.date.day}" + str_jour: str = scu.DAY_NAMES[self.date.weekday()].capitalize() + return f"{str_jour[0] if mode_demi or self.is_non_work() else str_jour[:3]+' '}{self.date.day}" def get_date(self) -> str: return self.date.strftime("%d/%m/%Y") @@ -2605,14 +2573,14 @@ def _get_dates_between(deb: datetime.date, fin: datetime.date) -> list[datetime. return resultat -def _organize_by_month(days, assiduites, justificatifs): +def _organize_by_month(days, assiduites, justificatifs) -> dict[str, list[Jour]]: """ Organiser les dates par mois. """ organized = {} for date in days: - # Utiliser le numéro du mois comme clé - month = MONTHS.get(date.month) + # Récupérer le mois en français + month = scu.MONTH_NAMES_ABBREV[date.month - 1] # Ajouter le jour à la liste correspondante au mois if month not in organized: organized[month] = []