Merge branch 'iziram-modif_assi'

This commit is contained in:
Emmanuel Viennet 2024-05-25 18:13:16 +02:00
commit 5c6935337e
19 changed files with 215 additions and 429 deletions

View File

@ -353,12 +353,12 @@ class Assiduite(ScoDocModel):
elif self.external_data is not None and "module" in self.external_data: elif self.external_data is not None and "module" in self.external_data:
return ( return (
"Tout module" "Autre module (pas dans la liste)"
if self.external_data["module"] == "Autre" if self.external_data["module"] == "Autre"
else self.external_data["module"] else self.external_data["module"]
) )
return "Non spécifié" if traduire else None return "Module non spécifié" if traduire else None
def get_saisie(self) -> str: def get_saisie(self) -> str:
""" """

View File

@ -25,8 +25,7 @@
# #
############################################################################## ##############################################################################
"""HTML Header/Footer for ScoDoc pages """HTML Header/Footer for ScoDoc pages"""
"""
import html import html
@ -101,7 +100,7 @@ _HTML_BEGIN = f"""<!DOCTYPE html>
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script> <script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script> <script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
<script> <script>
window.onload=function(){{enableTooltips("gtrcontent")}}; window.onload=function(){{enableTooltips("gtrcontent"); enableTooltips("sidebar");}};
</script> </script>
<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script> <script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
@ -218,7 +217,7 @@ def sco_header(
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script> <script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script> <script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
<script> <script>
window.onload=function(){{enableTooltips("gtrcontent")}}; window.onload=function(){{enableTooltips("gtrcontent"); enableTooltips("sidebar");}};
const SCO_URL="{url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)}"; const SCO_URL="{url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)}";
const SCO_TIMEZONE="{scu.TIME_ZONE}"; const SCO_TIMEZONE="{scu.TIME_ZONE}";

View File

@ -28,6 +28,7 @@
""" """
Génération de la "sidebar" (marge gauche des pages HTML) Génération de la "sidebar" (marge gauche des pages HTML)
""" """
from flask import render_template, url_for from flask import render_template, url_for
from flask import g, request from flask import g, request
from flask_login import current_user from flask_login import current_user
@ -151,7 +152,7 @@ def sidebar(etudid: int = None):
H = [ H = [
f""" f"""
<!-- sidebar py --> <!-- sidebar py -->
<div class="sidebar"> <div class="sidebar" id="sidebar">
{ sidebar_common() } { sidebar_common() }
<div class="box-chercheetud">Chercher étudiant:<br> <div class="box-chercheetud">Chercher étudiant:<br>
<form method="get" id="form-chercheetud" <form method="get" id="form-chercheetud"
@ -193,7 +194,7 @@ def sidebar(etudid: int = None):
formsemestre.date_debut.strftime(scu.DATE_FMT) formsemestre.date_debut.strftime(scu.DATE_FMT)
} au { } au {
formsemestre.date_fin.strftime(scu.DATE_FMT) formsemestre.date_fin.strftime(scu.DATE_FMT)
}">({ }" data-tooltip>({
sco_preferences.get_preference("assi_metrique", None)}) sco_preferences.get_preference("assi_metrique", None)})
<br>{nbabsjust:1g} J., {nbabsnj:1g} N.J.</span>""" <br>{nbabsjust:1g} J., {nbabsnj:1g} N.J.</span>"""
) )
@ -227,12 +228,9 @@ def sidebar(etudid: int = None):
<li><a href="{ url_for('assiduites.calendrier_assi_etud', <li><a href="{ url_for('assiduites.calendrier_assi_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid) scodoc_dept=g.scodoc_dept, etudid=etudid)
}">Calendrier</a></li> }">Calendrier</a></li>
<li><a href="{ url_for('assiduites.liste_assiduites_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid)
}">Liste</a></li>
<li><a href="{ url_for('assiduites.bilan_etud', <li><a href="{ url_for('assiduites.bilan_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid) scodoc_dept=g.scodoc_dept, etudid=etudid)
}">Bilan</a></li> }" title="Les pages bilan et liste ont été fusionnées">Liste/Bilan</a></li>
</ul> </ul>
""" """
) )

View File

@ -822,6 +822,35 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
</div> </div>
</div> </div>
<div class="sem-groups-assi"> <div class="sem-groups-assi">
"""
)
if can_edit_abs:
H.append(
f"""
<div>
<a class="stdlink" href="{
url_for("assiduites.signal_assiduites_group",
scodoc_dept=g.scodoc_dept,
jour=datetime.date.today().isoformat(),
formsemestre_id=formsemestre.id,
group_ids=group.id,
)}">
Saisir l'assiduité</a>
</div>
<div>
<a class="stdlink" href="{
url_for("assiduites.bilan_dept",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
group_ids=group.id,
)}">
Justificatifs en attente</a>
</div>
"""
)
H.append(
f"""
<div> <div>
<a class="stdlink" href="{ <a class="stdlink" href="{
url_for("assiduites.visu_assi_group", url_for("assiduites.visu_assi_group",
@ -834,46 +863,18 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
</div> </div>
""" """
) )
if can_edit_abs: if can_edit_abs:
H.append( H.append(
f""" f"""
<div> <div>
<a class="stdlink" href="{
url_for("assiduites.visu_assiduites_group",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
jour = datetime.date.today().isoformat(),
group_ids=group.id,
)}">
Visualiser</a>
</div>
<div>
<a class="stdlink" href="{
url_for("assiduites.signal_assiduites_group",
scodoc_dept=g.scodoc_dept,
jour=datetime.date.today().isoformat(),
formsemestre_id=formsemestre.id,
group_ids=group.id,
)}">
Saisie journalière</a>
</div>
<div>
<a class="stdlink" href="{ <a class="stdlink" href="{
url_for("assiduites.signal_assiduites_diff", url_for("assiduites.signal_assiduites_diff",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, formsemestre_id=formsemestre.id,
group_ids=group.id, group_ids=group.id,
)}"> )}" title="Page en cours de fusion et sera prochainement supprimée. Veuillez utiliser la page `Saisir l'assiduité`">
Saisie différée</a> (Saisie différée)</a>
</div>
<div>
<a class="stdlink" href="{
url_for("assiduites.bilan_dept",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
group_ids=group.id,
)}">
Justificatifs en attente</a>
</div> </div>
""" """
) )

View File

@ -110,6 +110,7 @@ get_base_preferences(formsemestre_id)
Return base preferences for current scodoc_dept (instance BasePreferences) Return base preferences for current scodoc_dept (instance BasePreferences)
""" """
import flask import flask
from flask import current_app, flash, g, request, url_for from flask import current_app, flash, g, request, url_for
@ -622,18 +623,6 @@ class BasePreferences:
"explanation": "Désactive la saisie et l'affichage des présences", "explanation": "Désactive la saisie et l'affichage des présences",
}, },
), ),
(
"periode_defaut",
{
"initvalue": 2.0,
"size": 10,
"title": "Durée par défaut d'un créneau",
"type": "float",
"category": "assi",
"only_global": True,
"explanation": "Durée d'un créneau en heure. Utilisé dans les pages de saisie",
},
),
( (
"nb_heures_par_jour", "nb_heures_par_jour",
{ {
@ -2302,9 +2291,7 @@ class BasePreferences:
if "explanation" in descr: if "explanation" in descr:
del descr["explanation"] del descr["explanation"]
if formsemestre_id: if formsemestre_id:
descr[ descr["explanation"] = f"""ou <span class="spanlink"
"explanation"
] = f"""ou <span class="spanlink"
onclick="set_global_pref(this, '{pref_name}');" onclick="set_global_pref(this, '{pref_name}');"
>utiliser paramètre global</span>""" >utiliser paramètre global</span>"""
if formsemestre_id and self.is_global(formsemestre_id, pref_name): if formsemestre_id and self.is_global(formsemestre_id, pref_name):

View File

@ -302,7 +302,6 @@
.rbtn { .rbtn {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
cursor: pointer; cursor: pointer;
} }
@ -327,9 +326,12 @@
background-image: url(../icons/absent.svg); background-image: url(../icons/absent.svg);
} }
.rbtn.aucun::before { .rbtn.aucun {
background-image: url(../icons/aucun.svg); background-image: url("../icons/delete.svg");
background-color: var(--color-defaut-dark); background-size: calc(100% - 8px) calc(100% - 8px);
/* Adjust size to create "margin" */
background-position: center;
background-repeat: no-repeat;
} }
.rbtn.retard::before { .rbtn.retard::before {

View File

@ -0,0 +1 @@
<svg id="Layer_1" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m170.8 14.221a14.21 14.21 0 0 1 14.2-14.207l141.991-.008a14.233 14.233 0 0 1 14.2 14.223v35.117h-170.391zm233.461 477.443a21.75 21.75 0 0 1 -21.856 20.33h-254.451a21.968 21.968 0 0 1 -21.854-20.416l-21.774-318.518h343.174l-23.234 318.6zm56.568-347.452h-409.658v-33a33.035 33.035 0 0 1 33.005-33.012l343.644-.011a33.051 33.051 0 0 1 33 33.02v33zm-270.79 291.851a14.422 14.422 0 1 0 28.844 0v-202.247a14.42 14.42 0 0 0 -28.839-.01v202.257zm102.9 0a14.424 14.424 0 1 0 28.848 0v-202.247a14.422 14.422 0 0 0 -28.843-.01z" fill="#fc3333" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 689 B

View File

@ -53,7 +53,7 @@ async function async_get(path, success, errors) {
* @param {CallableFunction} errors fonction à effectuer en cas d'échec * @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/ */
async function async_post(path, data, success, errors) { async function async_post(path, data, success, errors) {
console.log("async_post " + path); // console.log("async_post " + path);
let response; let response;
try { try {
response = await fetch(path, { response = await fetch(path, {
@ -401,7 +401,7 @@ async function creerTousLesEtudiants(etuds) {
* @returns {String} * @returns {String}
*/ */
async function getModuleImpl(assiduite) { async function getModuleImpl(assiduite) {
if (assiduite == null) return "Pas de module"; if (assiduite == null) return "Module non spécifié";
const id = assiduite.moduleimpl_id; const id = assiduite.moduleimpl_id;
if (id == null || id == undefined) { if (id == null || id == undefined) {
@ -414,7 +414,7 @@ async function getModuleImpl(assiduite) {
? "Autre module (pas dans la liste)" ? "Autre module (pas dans la liste)"
: assiduite.external_data.module; : assiduite.external_data.module;
} else { } else {
return "Pas de module"; return "Module non spécifié";
} }
} }
@ -643,6 +643,9 @@ function mettreToutLeMonde(etat, el = null) {
// Suppression des assiduités // Suppression des assiduités
if (etat == "vide") { if (etat == "vide") {
if (!confirm("Effacer tout les évènements correspondant à cette plage ?")) {
return; // annulation
}
const assiduites_id = lignesEtuds const assiduites_id = lignesEtuds
.filter((e) => e.getAttribute("type") == "edition") .filter((e) => e.getAttribute("type") == "edition")
.map((e) => Number(e.getAttribute("assiduite_id"))); .map((e) => Number(e.getAttribute("assiduite_id")));
@ -759,6 +762,7 @@ function envoiToastEtudiant(etat, etud) {
pushToast(generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5)); pushToast(generateToast(span, getToastColorFromEtat(etat.toUpperCase()), 5));
} }
// TODO commenter toutes les fonctions js
function envoiToastTous(etat, count) { function envoiToastTous(etat, count) {
const span = document.createElement("span"); const span = document.createElement("span");
let etatAffiche = etat; let etatAffiche = etat;
@ -798,13 +802,16 @@ function estJourTravail(jour, nonWorkdays) {
return !nonWorkdays.includes(d); return !nonWorkdays.includes(d);
} }
function retourJourTravail(date) { function retourJourTravail(date, anti = true) {
const jourMiliSecondes = 86400000; // 24 * 3600 * 1000 | H * s * ms const jourMiliSecondes = 86400000; // 24 * 3600 * 1000 | H * s * ms
let jour = date; let jour = date;
let compte = 0; let compte = 0;
while (!estJourTravail(jour, nonWorkDays) && compte++ < 7) { while (!estJourTravail(jour, nonWorkDays) && compte++ < 7) {
jour = new Date(jour - jourMiliSecondes); let temps = anti
? jour - jourMiliSecondes
: jour.valueOf() + jourMiliSecondes;
jour = new Date(temps);
} }
return jour; return jour;
} }
@ -814,9 +821,12 @@ function dateCouranteEstTravaillee() {
if (!estJourTravail(date, nonWorkDays)) { if (!estJourTravail(date, nonWorkDays)) {
const nouvelleDate = retourJourTravail(date); const nouvelleDate = retourJourTravail(date);
$("#date").datepicker("setDate", nouvelleDate); $("#date").datepicker("setDate", nouvelleDate);
let msg = "Le jour sélectionné";
if ((new Date()).format("YYYY-MM-DD") == date.format("YYYY-MM-DD")) {
msg = "Aujourd'hui";
}
const att = document.createTextNode( const att = document.createTextNode(
`Le jour sélectionné (${Date.toFRA( `${msg} (${Date.toFRA(
date.format("YYYY-MM-DD") date.format("YYYY-MM-DD")
)}) n'est pas un jour travaillé.` )}) n'est pas un jour travaillé.`
); );
@ -837,6 +847,17 @@ function dateCouranteEstTravaillee() {
return true; return true;
} }
function jourSuivant(anti = false) {
let date = $("#date").datepicker("getDate");
date = anti ? date.add(-1, "days") : date.add(1, "days");
const nouvelleDate = retourJourTravail(date, anti);
$("#date").datepicker("setDate", nouvelleDate);
creerTousLesEtudiants(etuds);
}
/** /**
* Ajout de la visualisation des assiduités de la mini timeline * Ajout de la visualisation des assiduités de la mini timeline
* @param {HTMLElement} el l'élément survollé * @param {HTMLElement} el l'élément survollé
@ -876,6 +897,11 @@ function setupAssiduiteBubble(el, assiduite) {
actionsDiv.appendChild(infos); actionsDiv.appendChild(infos);
bubble.appendChild(actionsDiv); bubble.appendChild(actionsDiv);
const stateDiv = document.createElement("div");
stateDiv.className = "assiduite-state";
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
bubble.appendChild(stateDiv);
const idDiv = document.createElement("div"); const idDiv = document.createElement("div");
idDiv.className = "assiduite-id"; idDiv.className = "assiduite-id";
getModuleImpl(assiduite).then((modImpl) => { getModuleImpl(assiduite).then((modImpl) => {
@ -883,26 +909,32 @@ function setupAssiduiteBubble(el, assiduite) {
}); });
bubble.appendChild(idDiv); bubble.appendChild(idDiv);
const periodDivDeb = document.createElement("div"); // Affichage des dates
periodDivDeb.className = "assiduite-period"; // si les jours sont les mêmes, on affiche "jour hh:mm - hh:mm"
periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`; // sinon on affiche "jour hh:mm - jour hh:mm"
bubble.appendChild(periodDivDeb); const periodDiv = document.createElement("div");
const periodDivFin = document.createElement("div"); periodDiv.className = "assiduite-period";
periodDivFin.className = "assiduite-period"; const dateDeb = new Date(Date.removeUTC(assiduite.date_debut));
periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`; const dateFin = new Date(Date.removeUTC(assiduite.date_fin));
bubble.appendChild(periodDivFin); if (dateDeb.isSame(dateFin, "day")) {
const jour = dateDeb.format("DD/MM/YYYY");
const deb = dateDeb.format("HH:mm");
const fin = dateFin.format("HH:mm");
periodDiv.textContent = `${jour} de ${deb} à ${fin}`;
} else {
const jourDeb = dateDeb.format("DD/MM/YYYY");
const jourFin = dateFin.format("DD/MM/YYYY");
periodDiv.textContent = `du ${jourDeb} au ${jourFin}`;
}
const stateDiv = document.createElement("div"); bubble.appendChild(periodDiv);
stateDiv.className = "assiduite-state";
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
bubble.appendChild(stateDiv);
const motifDiv = document.createElement("div"); const motifDiv = document.createElement("div");
stateDiv.className = "assiduite-why"; motifDiv.className = "assiduite-why";
const motif = ["", null, undefined].includes(assiduite.desc) const motif = ["", null, undefined].includes(assiduite.desc)
? "Pas de motif" ? "Non spécifié"
: assiduite.desc.capitalize(); : assiduite.desc.capitalize();
stateDiv.textContent = `Motif: ${motif}`; motifDiv.textContent = `Motif: ${motif}`;
bubble.appendChild(motifDiv); bubble.appendChild(motifDiv);
const userIdDiv = document.createElement("div"); const userIdDiv = document.createElement("div");

View File

@ -11,8 +11,8 @@ h.id="btc";
h.setAttribute("id","btc"); h.setAttribute("id","btc");
h.style.position="absolute"; h.style.position="absolute";
document.getElementsByTagName("body")[0].appendChild(h); document.getElementsByTagName("body")[0].appendChild(h);
if(id==null) links=document.getElementsByTagName("a"); if(id==null) links=document.querySelectorAll("a, [data-tooltip]"); // was document.getElementsByTagName("a")
else links=document.getElementById(id).getElementsByTagName("a"); else links=document.getElementById(id).querySelectorAll("a, [data-tooltip]");// was document.getElementById(id).getElementsByTagName("a")
for(i=0;i<links.length;i++){ for(i=0;i<links.length;i++){
Prepare(links[i]); Prepare(links[i]);
} }

View File

@ -12,7 +12,7 @@
<h1>Traitement de l'assiduité</h1> <h1>Traitement de l'assiduité</h1>
<p class="help"> <p class="help">
Pour saisir l'assiduité ou consulter les états, il est recommandé de passer par Pour saisir l'assiduité ou consulter les états, passer par
le semestre concerné (saisie par jour ou saisie différée). le semestre concerné (saisie par jour ou saisie différée).
</p> </p>

View File

@ -86,9 +86,6 @@ Bilan assiduité de {{sco.etud.nomprenom}}
<div class="scobox"> <div class="scobox">
<section class="nonvalide"> <section class="nonvalide">
<div class="help">Le tableau n'affiche que les assiduités non justifiées
et les justificatifs soumis / modifiés
</div>
{{tableau | safe }} {{tableau | safe }}
</section> </section>
</div> </div>
@ -99,6 +96,9 @@ Bilan assiduité de {{sco.etud.nomprenom}}
département)</p> département)</p>
<p>Les statistiques sont calculées entre les deux dates sélectionnées. Après modification des dates, <p>Les statistiques sont calculées entre les deux dates sélectionnées. Après modification des dates,
appuyer sur le bouton "Actualiser"</p> appuyer sur le bouton "Actualiser"</p>
{% include "assiduites/explication_etats_justifs.j2" %}
</div> </div>
</div> </div>

View File

@ -1,27 +0,0 @@
{% extends "sco_page.j2" %}
{% block title %}
Assiduité de {{etud.nomprenom}}
{% endblock title %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
{% endblock styles %}
{% block scripts %}
{{ super() }}
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
{% endblock %}
{% block app_content %}
<div class="pageContent">
<h2>Liste de l'assiduité et des justificatifs de {{sco.etud.html_link_fiche()|safe}}</h2>
{{tableau | safe }}
</div>
{% include "assiduites/explication_etats_justifs.j2" %}
{% endblock app_content %}

View File

@ -570,6 +570,13 @@ window.addEventListener("load", main);
<h2>Signalement différé de l'assiduité {{gr |safe}}</h2> <h2>Signalement différé de l'assiduité {{gr |safe}}</h2>
<div class="ue_warning">
Attention, cette page est en cours de fusion avec la page de Saisie journalière.
<br>
Vous pouvez dès à présent cliquer sur <a href="{{url_for('assiduites.signal_assiduites_group', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, group_ids=group_ids)}}" target="_blank">ce lien</a> pour accéder à la nouvelle version.
</div>
<div id="fix"> <div id="fix">
<!-- Nouvelle Plage <!-- Nouvelle Plage

View File

@ -105,6 +105,24 @@
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css"> <link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css">
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css"> <link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css"> <link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css">
<style>
#retour-haut{
position: fixed;
bottom: 10px;
right: 10px;
font-size: 3em;
padding: 5px;
border-radius: 5px;
text-decoration: none;
}
html{
scroll-behavior: smooth !important;
}
</style>
{% endblock styles %} {% endblock styles %}
@ -113,6 +131,10 @@
{{ minitimeline|safe }} {{ minitimeline|safe }}
<section id="content"> <section id="content">
<a id="retour-haut" href="#gtrcontent">
⬆️
</a>
<div class="no-display"> <div class="no-display">
<span class="formsemestre_id">{{formsemestre_id}}</span> <span class="formsemestre_id">{{formsemestre_id}}</span>
<span id="formsemestre_date_debut">{{formsemestre_date_debut}}</span> <span id="formsemestre_date_debut">{{formsemestre_date_debut}}</span>
@ -131,12 +153,22 @@
<div class="infos"> <div class="infos">
<div class="infos-button">Groupes&nbsp;: {{grp|safe}}</div> <div class="infos-button">Groupes&nbsp;: {{grp|safe}}</div>
<div> <div>
<button class="btn_date" onclick="jourSuivant(true)">
&LeftArrowBar;
</button>
<input type="text" name="date" id="date" class="datepicker" value="{{date}}"> <input type="text" name="date" id="date" class="datepicker" value="{{date}}">
</div> </div>
<button class="btn_date" onclick="jourSuivant(false)">
&RightArrowBar;
</button>
</div> </div>
</fieldset> </fieldset>
<div style="display: {{'none' if readonly == 'true' else 'block'}};"> <div style="display: {{'none' if readonly == 'true' else 'block'}};">
{{timeline|safe}} {{timeline|safe}}
<div>
<button onclick="setPeriodValues(t_start, t_mid)">Matin</button>
<button onclick="setPeriodValues(t_mid, t_end)">Après-Midi</button>
</div>
</div> </div>
{% if readonly == "false" %} {% if readonly == "false" %}
@ -162,14 +194,14 @@
<fieldset class="btns_field mass"> <fieldset class="btns_field mass">
{% if not non_present %} {% if not non_present %}
<input type="checkbox" value="present" name="mass_btn_assiduites" id="mass_rbtn_present" <input type="checkbox" value="present" name="mass_btn_assiduites" id="mass_rbtn_present"
class="rbtn present" onclick="mettreToutLeMonde('present', this)" title="Present"> class="rbtn present" onclick="mettreToutLeMonde('present', this)" title="Indique l'état Présent pour tous les étudiants" data-tooltip>
{% endif %} {% endif %}
<input type="checkbox" value="retard" name="mass_btn_assiduites" id="mass_rbtn_retard" <input type="checkbox" value="retard" name="mass_btn_assiduites" id="mass_rbtn_retard"
class="rbtn retard" onclick="mettreToutLeMonde('retard', this)" title="Retard"> class="rbtn retard" onclick="mettreToutLeMonde('retard', this)" title="Indique l'état Retard pour tous les étudiants" data-tooltip>
<input type="checkbox" value="absent" name="mass_btn_assiduites" id="mass_rbtn_absent" <input type="checkbox" value="absent" name="mass_btn_assiduites" id="mass_rbtn_absent"
class="rbtn absent" onclick="mettreToutLeMonde('absent', this)" title="Absent"> class="rbtn absent" onclick="mettreToutLeMonde('absent', this)" title="Indique l'état Absent pour tous les étudiants" data-tooltip>
<input type="checkbox" value="remove" name="mass_btn_assiduites" id="mass_rbtn_aucun" <input type="checkbox" value="remove" name="mass_btn_assiduites" id="mass_rbtn_aucun"
class="rbtn aucun" onclick="mettreToutLeMonde('vide', this)" title="Supprimer"> class="rbtn aucun" onclick="mettreToutLeMonde('vide', this)" title="Retire l'état pour tous les étudiants" data-tooltip>
</fieldset> </fieldset>
<em>Les saisies ci-dessous sont enregistrées au fur et à mesure.</em> <em>Les saisies ci-dessous sont enregistrées au fur et à mesure.</em>
</div> </div>

View File

@ -1,8 +1,7 @@
<div class="assiduite-bubble {{etat}}"> <div class="assiduite-bubble {{etat}}">
<div class="assiduite-id">{{moduleimpl}}</div>
<div class="assiduite-period">{{date_debut}}</div>
<div class="assiduite-period">{{date_fin}}</div>
<div class="assiduite-state">État: {{etat}}</div> <div class="assiduite-state">État: {{etat}}</div>
<div class="assiduite-id">{{moduleimpl}}</div>
<div class="assiduite-period">{{date}}</div>
<div class="assiduite-why">Motif: {{motif}}</div> <div class="assiduite-why">Motif: {{motif}}</div>
<div class="assiduite-user_id">{{saisie}}</div> <div class="assiduite-user_id">{{saisie}}</div>
</div> </div>

View File

@ -17,12 +17,13 @@
const timelineContainer = document.querySelector(".timeline-container"); const timelineContainer = document.querySelector(".timeline-container");
const periodTimeLine = document.querySelector(".period"); const periodTimeLine = document.querySelector(".period");
const t_start = {{ t_start }}; const t_start = {{ t_start }};
const t_mid = {{ t_mid }};
const t_end = {{ t_end }}; const t_end = {{ t_end }};
const tick_time = 60 / {{ tick_time }}; const tick_time = 60 / {{ tick_time }};
const tick_delay = 1 / tick_time; const tick_delay = 1 / tick_time;
const period_default = {{ periode_defaut }}; const period_default = 2;
let handleMoving = false; let handleMoving = false;
@ -133,6 +134,7 @@
timelineContainer.removeEventListener("mousemove", onMouseMove); timelineContainer.removeEventListener("mousemove", onMouseMove);
handleMoving = false; handleMoving = false;
func_call(); func_call();
savePeriodInLocalStorage();
} }
timelineContainer.addEventListener("mousemove", onMouseMove); timelineContainer.addEventListener("mousemove", onMouseMove);
@ -166,6 +168,7 @@
snapHandlesToQuarters(); snapHandlesToQuarters();
timelineContainer.removeEventListener("mousemove", onMouseMove); timelineContainer.removeEventListener("mousemove", onMouseMove);
func_call(); func_call();
savePeriodInLocalStorage();
} }
timelineContainer.addEventListener("mousemove", onMouseMove); timelineContainer.addEventListener("mousemove", onMouseMove);
timelineContainer.addEventListener("touchmove", onMouseMove); timelineContainer.addEventListener("touchmove", onMouseMove);
@ -264,6 +267,7 @@
snapHandlesToQuarters(); snapHandlesToQuarters();
updatePeriodTimeLabel() updatePeriodTimeLabel()
func_call(); func_call();
savePeriodInLocalStorage();
} }
function snapHandlesToQuarters() { function snapHandlesToQuarters() {
@ -309,9 +313,23 @@
} }
} }
function savePeriodInLocalStorage(){
const dates = getPeriodValues();
localStorage.setItem("sco-timeline-values", JSON.stringify(dates));
}
function loadPeriodFromLocalStorage(){
const dates = JSON.parse(localStorage.getItem("sco-timeline-values"));
if(dates){
setPeriodValues(...dates);
}else{
setPeriodValues(t_start, t_start + period_default);
}
}
createTicks(); createTicks();
setPeriodValues(t_start, t_start + period_default); loadPeriodFromLocalStorage();
{% if heures %} {% if heures %}
let [heure_deb, heure_fin] = [{{ heures | safe }}] let [heure_deb, heure_fin] = [{{ heures | safe }}]

View File

@ -49,7 +49,7 @@
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script> <script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
<script src="{{scu.STATIC_DIR}}/DataTables/datatables.min.js"></script> <script src="{{scu.STATIC_DIR}}/DataTables/datatables.min.js"></script>
<script> <script>
window.onload = function () { enableTooltips("gtrcontent") }; window.onload = function () { enableTooltips("gtrcontent"); enableTooltips("sidebar"); };
const SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}"; const SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}";
</script> </script>

View File

@ -1,7 +1,7 @@
{# Barre marge gauche ScoDoc #} {# Barre marge gauche ScoDoc #}
{# -*- mode: jinja-html -*- #} {# -*- mode: jinja-html -*- #}
<!-- sidebar --> <!-- sidebar -->
<div class="sidebar"> <div class="sidebar" id="sidebar">
{# sidebar_common #} {# sidebar_common #}
<a class="scodoc_title" href="{{ <a class="scodoc_title" href="{{
url_for('scodoc.index', scodoc_dept=g.scodoc_dept) }}">ScoDoc {{ sco.SCOVERSION }}</a> url_for('scodoc.index', scodoc_dept=g.scodoc_dept) }}">ScoDoc {{ sco.SCOVERSION }}</a>
@ -57,7 +57,7 @@
<b>Absences</b> <b>Absences</b>
{% if sco.etud_cur_sem %} {% if sco.etud_cur_sem %}
<span title="absences du {{ sco.etud_cur_sem['date_debut'].strftime('%d/%m/%Y') }} <span title="absences du {{ sco.etud_cur_sem['date_debut'].strftime('%d/%m/%Y') }}
au {{ sco.etud_cur_sem['date_fin'].strftime('%d/%m/%Y') }}">({{sco.prefs["assi_metrique"]}}) au {{ sco.etud_cur_sem['date_fin'].strftime('%d/%m/%Y') }}" data-tooltip>({{sco.prefs["assi_metrique"]}})
<br />{{'%1g'|format(sco.nb_abs_just)}} J., {{'%1g'|format(sco.nb_abs_nj)}} N.J.</span> <br />{{'%1g'|format(sco.nb_abs_just)}} J., {{'%1g'|format(sco.nb_abs_nj)}} N.J.</span>
{% endif %} {% endif %}
<ul> <ul>
@ -73,10 +73,8 @@
{% endif %} {% endif %}
<li><a href="{{ url_for('assiduites.calendrier_assi_etud', scodoc_dept=g.scodoc_dept, <li><a href="{{ url_for('assiduites.calendrier_assi_etud', scodoc_dept=g.scodoc_dept,
etudid=sco.etud.id) }}">Calendrier</a></li> etudid=sco.etud.id) }}">Calendrier</a></li>
<li><a href="{{ url_for('assiduites.liste_assiduites_etud', scodoc_dept=g.scodoc_dept,
etudid=sco.etud.id) }}">Liste</a></li>
<li><a href="{{ url_for('assiduites.bilan_etud', scodoc_dept=g.scodoc_dept, <li><a href="{{ url_for('assiduites.bilan_etud', scodoc_dept=g.scodoc_dept,
etudid=sco.etud.id) }}">Bilan</a></li> etudid=sco.etud.id) }}" title="Les pages bilan et liste ont été fusionnées">Liste/Bilan</a></li>
</ul> </ul>
{% endif %} {% endif %}
</div> {# /etud-insidebar #} </div> {# /etud-insidebar #}

View File

@ -511,51 +511,6 @@ def _record_assiduite_etud(
return False return False
@bp.route("/liste_assiduites_etud")
@scodoc
@permission_required(Permission.ScoView)
def liste_assiduites_etud():
"""
liste_assiduites_etud Affichage de toutes les assiduites et justificatifs d'un etudiant
Args:
etudid (int): l'identifiant de l'étudiant
Returns:
str: l'html généré
"""
# Récupération de l'étudiant concerné
etudid = request.args.get("etudid", -1)
etud: Identite = Identite.query.get_or_404(etudid)
if etud.dept_id != g.scodoc_dept_id:
abort(404, "étudiant inexistant dans ce département")
# Gestion d'une assiduité unique (redirigé depuis le calendrier) TODO-Assiduites
assiduite_id: int = request.args.get("assiduite_id", -1)
# Préparation de la page
tableau = _prepare_tableau(
liste_assi.AssiJustifData.from_etudiants(
etud,
),
filename=f"assiduites-justificatifs-{etud.id}",
afficher_etu=False,
filtre=liste_assi.AssiFiltre(type_obj=0),
options=liste_assi.AssiDisplayOptions(show_module=True),
cache_key=f"tableau-etud-{etud.id}",
)
if not tableau[0]:
return tableau[1]
# Page HTML:
return render_template(
"assiduites/pages/liste_assiduites.j2",
assi_id=assiduite_id,
etud=etud,
tableau=tableau[1],
sco=ScoData(etud),
)
@bp.route("/bilan_etud") @bp.route("/bilan_etud")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
@ -583,28 +538,19 @@ def bilan_etud():
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id), sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
) )
# Récupération des assiduités et justificatifs de l'étudiant # Préparation de la page
data = liste_assi.AssiJustifData( tableau = _prepare_tableau(
etud.assiduites.filter( liste_assi.AssiJustifData.from_etudiants(
Assiduite.etat != scu.EtatAssiduite.PRESENT, Assiduite.est_just == False etud,
), ),
etud.justificatifs.filter( filename=f"assiduites-justificatifs-{etud.id}",
Justificatif.etat.in_(
[scu.EtatJustificatif.ATTENTE, scu.EtatJustificatif.MODIFIE]
)
),
)
table = _prepare_tableau(
data,
afficher_etu=False, afficher_etu=False,
filename=f"Bilan assiduité {etud.nomprenom}", filtre=liste_assi.AssiFiltre(type_obj=0),
titre="Bilan de l'assiduité de l'étudiant", options=liste_assi.AssiDisplayOptions(show_module=True),
cache_key=f"tableau-etud-{etud.id}-bilan", cache_key=f"tableau-etud-{etud.id}",
) )
if not tableau[0]:
if not table[0]: return tableau[1]
return table[1]
# Génération de la page HTML # Génération de la page HTML
return render_template( return render_template(
@ -614,7 +560,7 @@ def bilan_etud():
date_debut=date_debut, date_debut=date_debut,
date_fin=date_fin, date_fin=date_fin,
sco=ScoData(etud), sco=ScoData(etud),
tableau=table[1], tableau=tableau[1],
) )
@ -654,7 +600,7 @@ def edit_justificatif_etud(justif_id: int):
back_url = request.args.get("back_url", None) back_url = request.args.get("back_url", None)
redirect_url = back_url or url_for( redirect_url = back_url or url_for(
"assiduites.liste_assiduites_etud", "assiduites.bilan_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=justif.etudiant.id, etudid=justif.etudiant.id,
) )
@ -958,8 +904,6 @@ def choix_date() -> str:
Route utilisée uniquement si la date courante n'est pas dans le semestre Route utilisée uniquement si la date courante n'est pas dans le semestre
concerné par la requête vers une des pages suivantes : concerné par la requête vers une des pages suivantes :
- saisie_assiduites_group - saisie_assiduites_group
- visu_assiduites_group
""" """
formsemestre_id = request.args.get("formsemestre_id") formsemestre_id = request.args.get("formsemestre_id")
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
@ -989,11 +933,7 @@ def choix_date() -> str:
if ok: if ok:
return redirect( return redirect(
url_for( 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, scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
group_ids=group_ids, group_ids=group_ids,
@ -1065,7 +1005,7 @@ def signal_assiduites_group():
) )
if not groups_infos.members: if not groups_infos.members:
return ( return (
html_sco_header.sco_header(page_title="Saisie journalière de l'assiduité") html_sco_header.sco_header(page_title="Saisie de l'assiduité")
+ "<h3>Aucun étudiant ! </h3>" + "<h3>Aucun étudiant ! </h3>"
+ html_sco_header.sco_footer() + html_sco_header.sco_footer()
) )
@ -1156,137 +1096,7 @@ def signal_assiduites_group():
sco=ScoData(formsemestre=formsemestre), sco=ScoData(formsemestre=formsemestre),
sem=sem["titre_num"], sem=sem["titre_num"],
timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])), timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
title="Saisie journalière des assiduités", title="Saisie de l'assiduité",
)
@bp.route("/visu_assiduites_group")
@scodoc
@permission_required(Permission.ScoView)
def visu_assiduites_group():
"""
Visualisation des assiduités des groupes pour le jour donné
dans le formsemestre_id et le moduleimpl_id
Returns:
str: l'html généré
"""
# Récupération des paramètres de la requête
formsemestre_id: int = request.args.get("formsemestre_id", -1)
moduleimpl_id: int = request.args.get("moduleimpl_id")
date: str = request.args.get("jour", datetime.date.today().isoformat())
group_ids: list[int] = request.args.get("group_ids", None)
if group_ids is None:
group_ids = []
else:
group_ids = group_ids.split(",")
map(str, group_ids)
# Vérification du moduleimpl_id
if moduleimpl_id is not None:
try:
moduleimpl_id = int(moduleimpl_id)
except (TypeError, ValueError) as exc:
raise ScoValueError("identifiant de moduleimpl invalide") from exc
# Vérification du formsemestre_id
if formsemestre_id is not None:
try:
formsemestre_id = int(formsemestre_id)
except (TypeError, ValueError) as exc:
raise ScoValueError("identifiant de formsemestre invalide") from exc
# Récupérations des/du groupe(s)
groups_infos = sco_groups_view.DisplayedGroupsInfos(
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
)
if not groups_infos.members:
return (
html_sco_header.sco_header(page_title="Saisie journalière de l'assiduité")
+ "<h3>Aucun étudiant ! </h3>"
+ html_sco_header.sco_footer()
)
# --- Filtrage par formsemestre ---
formsemestre_id = groups_infos.formsemestre_id
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
if formsemestre.dept_id != g.scodoc_dept_id:
abort(404, "groupes inexistants dans ce département")
# Récupération des étudiants du/des groupe(s)
etuds = [
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
for m in groups_infos.members
]
# --- Vérification de la date ---
real_date = scu.is_iso_formated(date, True).date()
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:
mod_inscrits = {
x["etudid"]
for x in sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=moduleimpl_id
)
}
etuds_inscrits_module = [e for e in etuds if e["etudid"] in mod_inscrits]
if etuds_inscrits_module:
etuds = etuds_inscrits_module
else:
# Si aucun etudiant n'est inscrit au module choisi...
moduleimpl_id = None
# --- Génération du HTML ---
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>"
)
# Récupération du semestre en dictionnaire
sem = formsemestre.to_dict()
return render_template(
"assiduites/pages/signal_assiduites_group.j2",
date=_dateiso_to_datefr(date),
defdem=_get_etuds_dem_def(formsemestre),
forcer_module=sco_preferences.get_preference(
"forcer_module",
formsemestre_id=formsemestre_id,
dept_id=g.scodoc_dept_id,
),
formsemestre_date_debut=str(formsemestre.date_debut),
formsemestre_date_fin=str(formsemestre.date_fin),
formsemestre_id=formsemestre_id,
gr_tit=gr_tit,
grp=sco_groups_view.menu_groups_choice(groups_infos),
minitimeline=_mini_timeline(),
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
nonworkdays=_non_work_days(),
sem=sem["titre_num"],
timeline=_timeline(),
readonly="true",
sco=ScoData(formsemestre=formsemestre),
title="Saisie journalière de l'assiduité",
) )
@ -2028,6 +1838,8 @@ def signal_assiduites_diff():
dept_id=g.scodoc_dept_id, dept_id=g.scodoc_dept_id,
), ),
nouv_plage=nouv_plage, nouv_plage=nouv_plage,
formsemestre_id=formsemestre_id,
group_ids=group_ids,
) )
@ -2298,89 +2110,6 @@ def _get_date_str(deb: datetime.datetime, fin: datetime.datetime) -> str:
return f'du {deb.strftime("%d/%m/%Y %H:%M")} au {fin.strftime("%d/%m/%Y %H:%M")}' return f'du {deb.strftime("%d/%m/%Y %H:%M")} au {fin.strftime("%d/%m/%Y %H:%M")}'
def _get_days_between_dates(deb: str, fin: str) -> str:
"""
_get_days_between_dates récupère tous les jours entre deux dates
Args:
deb (str): date de début
fin (str): date de fin
Returns:
str: une chaine json représentant une liste des jours
['date_iso','date_iso2', ...]
"""
if deb is None or fin is None:
return "null"
try:
if isinstance(deb, str) and isinstance(fin, str):
date_deb: datetime.date = datetime.date.fromisoformat(deb)
date_fin: datetime.date = datetime.date.fromisoformat(fin)
else:
date_deb, date_fin = deb.date(), fin.date()
except ValueError:
return "null"
dates: list[str] = []
while date_deb <= date_fin:
dates.append(f'"{date_deb.isoformat()}"')
date_deb = date_deb + datetime.timedelta(days=1)
return f"[{','.join(dates)}]"
def _differee(
etudiants: list[dict],
moduleimpl_select: str,
date: str = None,
periode: dict[str, str] = None,
formsemestre_id: int = None,
) -> str:
"""
_differee Génère un tableau de saisie différé
Args:
etudiants (list[dict]): la liste des étudiants (représentés par des dictionnaires)
moduleimpl_select (str): l'html représentant le selecteur de module
date (str, optional): la première date à afficher. Defaults to None.
periode (dict[str, str], optional):La période par défaut de la première colonne.
formsemestre_id (int, optional): l'id du semestre pour le selecteur de module.
Returns:
str: le widget (html/css/js)
"""
if date is None:
date = datetime.date.today().isoformat()
forcer_module = sco_preferences.get_preference(
"forcer_module",
formsemestre_id=formsemestre_id,
dept_id=g.scodoc_dept_id,
)
assi_etat_defaut = sco_preferences.get_preference(
"assi_etat_defaut",
formsemestre_id=formsemestre_id,
dept_id=g.scodoc_dept_id,
)
periode_defaut = sco_preferences.get_preference(
"periode_defaut",
formsemestre_id=formsemestre_id,
dept_id=g.scodoc_dept_id,
)
return render_template(
"assiduites/widgets/differee.j2",
etudiants=etudiants,
assi_etat_defaut=assi_etat_defaut,
periode_defaut=periode_defaut,
forcer_module=forcer_module,
moduleimpl_select=moduleimpl_select,
date=date,
periode=periode,
)
def _module_selector(formsemestre: FormSemestre, moduleimpl_id: int = None) -> str: def _module_selector(formsemestre: FormSemestre, moduleimpl_id: int = None) -> str:
""" """
_module_selector Génère un HTMLSelectElement à partir des moduleimpl du formsemestre _module_selector Génère un HTMLSelectElement à partir des moduleimpl du formsemestre
@ -2450,7 +2179,7 @@ def _module_selector_multiple(
) )
def _timeline(formsemestre_id: int = None, heures=None) -> str: def _timeline(heures=None) -> str:
""" """
_timeline retourne l'html de la timeline _timeline retourne l'html de la timeline
@ -2465,11 +2194,9 @@ def _timeline(formsemestre_id: int = None, heures=None) -> str:
return render_template( return render_template(
"assiduites/widgets/timeline.j2", "assiduites/widgets/timeline.j2",
t_start=ScoDocSiteConfig.assi_get_rounded_time("assi_morning_time", "08:00:00"), t_start=ScoDocSiteConfig.assi_get_rounded_time("assi_morning_time", "08:00:00"),
t_mid=ScoDocSiteConfig.assi_get_rounded_time("assi_lunch_time", "13:00:00"),
t_end=ScoDocSiteConfig.assi_get_rounded_time("assi_afternoon_time", "18:00:00"), t_end=ScoDocSiteConfig.assi_get_rounded_time("assi_afternoon_time", "18:00:00"),
tick_time=ScoDocSiteConfig.get("assi_tick_time", 15), tick_time=ScoDocSiteConfig.get("assi_tick_time", 15),
periode_defaut=sco_preferences.get_preference(
"periode_defaut", formsemestre_id
),
heures=heures, heures=heures,
) )
@ -2854,14 +2581,26 @@ def _generate_assiduite_bubble(assiduite: Assiduite) -> str:
# Récupérer informations saisie # Récupérer informations saisie
saisie: str = assiduite.get_saisie() saisie: str = assiduite.get_saisie()
motif: str = assiduite.description if assiduite.description else "" motif: str = assiduite.description or "Non spécifié"
# Récupérer date
if assiduite.date_debut.date() == assiduite.date_fin.date():
jour = assiduite.date_debut.strftime("%d/%m/%Y")
heure_deb: str = assiduite.date_debut.strftime("%H:%M")
heure_fin: str = assiduite.date_fin.strftime("%H:%M")
date: str = f"{jour} de {heure_deb} à {heure_fin}"
else:
date: str = (
f"du {assiduite.date_debut.strftime('%d/%m/%Y')} "
+ f"au {assiduite.date_fin.strftime('%d/%m/%Y')}"
)
return render_template( return render_template(
"assiduites/widgets/assiduite_bubble.j2", "assiduites/widgets/assiduite_bubble.j2",
moduleimpl=moduleimpl_infos, moduleimpl=moduleimpl_infos,
etat=scu.EtatAssiduite(assiduite.etat).name.lower(), etat=scu.EtatAssiduite(assiduite.etat).name.lower(),
date_debut=assiduite.date_debut.strftime("%d/%m/%Y %H:%M"), date=date,
date_fin=assiduite.date_fin.strftime("%d/%m/%Y %H:%M"),
saisie=saisie, saisie=saisie,
motif=motif, motif=motif,
) )