Assiduites : utilisation datepicker

This commit is contained in:
Iziram 2023-11-15 14:11:47 +01:00
parent 0ca2a35129
commit 3a963cb1b4
11 changed files with 179 additions and 102 deletions

View File

@ -36,10 +36,10 @@
.infos {
position: relative;
width: fit-content;
display: flex;
justify-content: space-evenly;
justify-content: start;
align-content: center;
gap: 10px;
}
#datestr {
@ -48,7 +48,7 @@
border: 1px #444 solid;
border-radius: 5px;
padding: 5px;
min-width: 100px;
min-width: 250px;
display: inline-block;
min-height: 20px;
}

View File

@ -112,6 +112,7 @@ function validateSelectors(btn) {
getAssiduitesFromEtuds(true);
document.querySelector(".selectors").disabled = true;
$("#tl_date").datepicker("option", "disabled", true);
generateMassAssiduites();
generateAllEtudRow();
btn.remove();
@ -173,6 +174,7 @@ function uniqueCheckBox(box) {
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/
function sync_get(path, success, errors) {
//TODO Optimiser : rendre asynchrone + sans jquery
console.log("sync_get " + path);
$.ajax({
async: false,
@ -188,16 +190,21 @@ function sync_get(path, success, errors) {
* @param {CallableFunction} success fonction à effectuer en cas de succès
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/
function async_get(path, success, errors) {
async function async_get(path, success, errors) {
console.log("async_get " + path);
$.ajax({
async: true,
type: "GET",
url: path,
success: success,
error: errors,
});
try {
const response = await fetch(path);
if (response.ok) {
const data = await response.json();
success(data);
} else {
throw new Error("Network response was not ok.");
}
} catch (error) {
errors(error);
}
}
/**
* Fait une requête POST de façon synchrone
* @param {String} path adresse distante
@ -206,6 +213,7 @@ function async_get(path, success, errors) {
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/
function sync_post(path, data, success, errors) {
//TODO Optimiser : rendre asynchrone + sans jquery
console.log("sync_post " + path);
$.ajax({
async: false,
@ -223,17 +231,28 @@ function sync_post(path, data, success, errors) {
* @param {CallableFunction} success fonction à effectuer en cas de succès
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/
function async_post(path, data, success, errors) {
console.log("sync_post " + path);
return $.ajax({
async: true,
type: "POST",
url: path,
data: JSON.stringify(data),
success: success,
error: errors,
});
async function async_post(path, data, success, errors) {
console.log("async_post " + path);
try {
const response = await fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
const responseData = await response.json();
success(responseData);
} else {
throw new Error("Network response was not ok.");
}
} catch (error) {
errors(error);
}
}
// <<== Gestion des actions de masse ==>>
const massActionQueue = new Map();
@ -583,10 +602,13 @@ function formatDate(date, styles = { dateStyle: "full" }) {
*/
function updateDate() {
const dateInput = document.querySelector("#tl_date");
let date = $(dateInput).datepicker("getDate");
if (date == null) {
date = new Date(Date.fromFRA(dateInput.value));
}
const date = dateInput.valueAsDate ?? new Date();
let dateStr = "";
if (!verifyNonWorkDays(date.getDay(), nonWorkDays)) {
dateStr = formatDate(date).capitalize();
} else {
@ -606,7 +628,11 @@ function updateDate() {
)
);
openAlertModal("Attention", div, "", "#eec660");
dateInput.value = lastWorkDay.toISOString().split("T")[0];
$(dateInput).datepicker(
"setDate",
Date.toFRA(lastWorkDay.toIsoUtcString().split("T")[0])
);
dateStr = formatDate(lastWorkDay).capitalize();
}
document.querySelector("#datestr").textContent = dateStr;
@ -639,15 +665,15 @@ function setupDate(onchange = null) {
const input = document.querySelector("#tl_date");
datestr.addEventListener("click", () => {
if (!input.disabled) {
if (!document.querySelector(".selectors").disabled) {
try {
input.showPicker();
document.querySelector(".infos .ui-datepicker-trigger").click();
} catch {}
}
});
if (onchange != null) {
input.addEventListener("change", onchange);
$(input).change(onchange);
}
}
@ -726,7 +752,7 @@ function getTimeLineTimes() {
//getPeriodValues() -> retourne la position de la timeline [a,b] avec a et b des number
let values = getPeriodValues();
//On récupère la date
const dateiso = document.querySelector("#tl_date").value;
const dateiso = getDate().format("YYYY-MM-DD");
//On génère des objets temps
values = values.map((el) => {
@ -757,9 +783,10 @@ function isConflictSameAsPeriod(conflict, period = undefined) {
* @returns {Date} la date sélectionnée
*/
function getDate() {
const date = new Date(document.querySelector("#tl_date").value);
date.setHours(0, 0, 0, 0);
return date;
const date =
$("#tl_date").datepicker("getDate") ??
new Date(Date.fromFRA(document.querySelector("#tl_date").value));
return date.startOf("day");
}
/**
@ -768,10 +795,7 @@ function getDate() {
*/
function getNextDate() {
const date = getDate();
const next = new Date(date.valueOf());
next.setDate(date.getDate() + 1);
next.setHours(0, 0, 0, 0);
return next;
return date.clone().add(1, "days");
}
/**
* Retourne un objet date représentant le jour précédent
@ -779,10 +803,7 @@ function getNextDate() {
*/
function getPrevDate() {
const date = getDate();
const next = new Date(date.valueOf());
next.setDate(date.getDate() - 1);
next.setHours(0, 0, 0, 0);
return next;
return date.clone().add(-1, "days");
}
/**
@ -790,31 +811,6 @@ function getPrevDate() {
* @param {Date} date
* @returns {string} la date iso avec le timezone
*/
function toIsoString(date) {
var tzo = -date.getTimezoneOffset(),
dif = tzo >= 0 ? "+" : "-",
pad = function (num) {
return (num < 10 ? "0" : "") + num;
};
return (
date.getFullYear() +
"-" +
pad(date.getMonth() + 1) +
"-" +
pad(date.getDate()) +
"T" +
pad(date.getHours()) +
":" +
pad(date.getMinutes()) +
":" +
pad(date.getSeconds()) +
dif +
pad(Math.floor(Math.abs(tzo) / 60)) +
":" +
pad(Math.abs(tzo) % 60)
);
}
/**
* Transforme un temps numérique en une date
@ -823,7 +819,7 @@ function toIsoString(date) {
*/
function numberTimeToDate(nb) {
time = toTime(nb).replace("h", ":");
date = document.querySelector("#tl_date").value;
date = getDate().format("YYYY-MM-DD");
datetime = `${date}T${time}`;
@ -843,8 +839,8 @@ function numberTimeToDate(nb) {
function getAssiduitesFromEtuds(clear, deb, fin) {
const etudIds = Object.keys(etuds).join(",");
const date_debut = deb ? deb : toIsoString(getPrevDate());
const date_fin = fin ? fin : toIsoString(getNextDate());
const date_debut = deb ? deb : getPrevDate().toIsoUtcString();
const date_fin = fin ? fin : getNextDate().toIsoUtcString();
if (clear) {
assiduites = {};
@ -1137,8 +1133,8 @@ function getAssiduiteValue(field) {
* @param {String | Number} etudid identifiant de l'étudiant
*/
function actualizeEtudAssiduite(etudid) {
const date_debut = toIsoString(getPrevDate());
const date_fin = toIsoString(getNextDate());
const date_debut = getPrevDate().toIsoUtcString();
const date_fin = getNextDate().toIsoUtcString();
const url_api =
getUrl() +

View File

@ -1,3 +1,53 @@
/**
* Transforme une date du format français (DD/MM/YYYY) au format iso (YYYY-MM-DD)
* Exemple d'utilisation :
* new Date(Date.fromFRA("30/06/2024")) -> new Date("2024-06-30")
* @param {string} dateFra
* @returns {string} dateIso
*/
Date.fromFRA = function (dateFra) {
if (dateFra == "") return "";
// Expression régulière pour valider le format de date ISO (YYYY-MM-DD)
const regexDateFra = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/;
// Vérification du format de la date ISO
if (!regexDateFra.test(dateFra)) {
throw new Error(
`La date (format français) passée en paramètre [${dateFra}] n'est pas valide.`
);
}
// Conversion du format français (DD/MM/YYYY) au format ISO (YYYY-MM-DD)
return `${dateFra.substring(6, 10)}-${dateFra.substring(
3,
5
)}-${dateFra.substring(0, 2)}`;
};
/**
* Transforme une date du format iso (YYYY-MM-DD) au format français (DD/MM/YYYY)
* Exemple d'utilisation :
* Date.toFRA("2024-06-30") -> "30/06/2024"
* @param {string} dateIso
* @returns {string} dateFra
*/
Date.toFRA = function (dateIso) {
if (dateIso == "") return "";
// Expression régulière pour valider le format de date ISO (YYYY-MM-DD)
const regexDateIso = /^\d{4}-(0\d|1[0-2])-([0-2]\d|3[01])$/;
// Vérification du format de la date ISO
if (!regexDateIso.test(dateIso)) {
throw new Error(
`La date ISO passée en paramètre [${dateIso}] n'est pas valide.`
);
}
// Conversion du format ISO (YYYY-MM-DD) en format français (DD/MM/YYYY)
return `${dateIso.substring(8, 10)}/${dateIso.substring(
5,
7
)}/${dateIso.substring(0, 4)}`;
};
Object.defineProperty(Date.prototype, "isValid", {
value: function () {
return !Number.isNaN(this.getTime());
@ -175,6 +225,7 @@ Object.defineProperty(Date.prototype, "clone", {
Object.defineProperty(Date.prototype, "format", {
value: function (formatString) {
let iso = this.toIsoUtcString();
switch (formatString) {
case "DD/MM/Y HH:mm":
return this.toLocaleString("fr-FR", {
@ -196,10 +247,11 @@ Object.defineProperty(Date.prototype, "format", {
});
case "YYYY-MM-DDTHH:mm":
let iso = this.toIsoUtcString();
// slice : YYYY-MM-DDTHH
// slice + 3 : YYYY-MM-DDTHH:mm
return iso.slice(0, iso.indexOf(":") + 3);
case "YYYY-MM-DD":
return iso.slice(0, iso.indexOf("T"));
default:
return this.toIsoUtcString();
}

View File

@ -12,10 +12,10 @@
<!-- 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>
<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>
@ -87,20 +87,20 @@
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 == "") {
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;
}
const date_debut = new Date(dd_val + "T00:00");
const date_fin = new Date(df_val + "T23:59");
date_debut = date_debut.startOf("day")
date_fin = date_fin.endOf("day")
if (date_debut.valueOf() > date_fin.valueOf()) {
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.toIsoUtcString(), date_fin.toIsoUtcString())
}

View File

@ -9,7 +9,7 @@
<div class="infos">
Date: <span id="datestr"></span>
<input type="date" name="tl_date" id="tl_date" value="{{ date }}">
<input type="text" class="datepicker" name="tl_date" id="tl_date" value="{{ date }}">
</div>
{{timeline|safe}}

View File

@ -20,7 +20,8 @@
<div class="infos-button">Groupes&nbsp;: {{grp|safe}}</div>
<div class="infos-button" style="margin-left: 24px;">Date&nbsp;: <span style="margin-left: 8px;"
id="datestr"></span>
<input type="date" name="tl_date" id="tl_date" value="{{ date }}" onchange="updateDate()">
<input type="text" class="datepicker" name="tl_date" id="tl_date" value="{{ date }}"
onchange="updateDate()">
</div>
</div>
</fieldset>

View File

@ -1,8 +1,8 @@
{% extends "sco_page.j2" %}
{% block scripts %}
{{ super() }}
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
{{ super() }}
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
{% endblock %}
{% block app_content %}
@ -10,10 +10,10 @@
<h2>Visualisation de l'assiduité {{gr_tit|safe}}</h2>
<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>
<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()">Changer</button>
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
@ -21,8 +21,8 @@
{{tableau | safe}}
<div class=""help">
Les comptes sont exprimés en {{ assi_metric | lower}}s.
<div class="" help">
Les comptes sont exprimés en {{ assi_metric | lower}}s.
</div>
<script>
@ -43,4 +43,4 @@ Les comptes sont exprimés en {{ assi_metric | lower}}s.
</script>
{% endblock %}
{% endblock %}

View File

@ -258,7 +258,7 @@
const success = () => {
const separatorTime = document.getElementById("promptTime").value;
const dateString =
document.querySelector("#tl_date").value + `T${separatorTime}`;
getDate().format("YYYY-MM-DD") + `T${separatorTime}`;
const separtorDate = new Date(dateString);
const assiduite_debut = new Date(this.selectedAssiduite.date_debut);

View File

@ -13,7 +13,8 @@
*/
function createMiniTimeline(assiduitesArray, day = null) {
const array = [...assiduitesArray];
const dateiso = day == null ? document.getElementById("tl_date").value : day;
const dateiso = day == null ? getDate().format("YYYY-MM-DD") : day;
const timeline = document.createElement("div");
timeline.className = "mini-timeline";
if (isSingleEtud()) {

View File

@ -93,9 +93,10 @@
function updateSelect(moduleimpl_id, query = "#moduleimpl_select", dateIso = null) {
let sem = getEtudFormSemestres()
if (dateIso == null) {
dateIso = document.querySelector("#tl_date").value
if (!dateIso) {
dateIso = getDate().format("YYYY-MM-DD")
}
sem = filterFormSemestres(sem, dateIso)
const mod = getModulesImplByFormsemestre(sem)
populateSelect(mod, moduleimpl_id, query);

View File

@ -25,6 +25,7 @@
##############################################################################
import datetime
import re
from flask import g, request, render_template, flash
from flask import abort, url_for, redirect
@ -319,7 +320,7 @@ def signal_assiduites_etud():
render_template(
"assiduites/pages/signal_assiduites_etud.j2",
sco=ScoData(etud),
date=date,
date=_dateiso_to_datefr(date),
morning=morning,
lunch=lunch,
timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
@ -425,8 +426,8 @@ def bilan_etud():
)
# Gestion des dates du bilan (par défaut l'année scolaire)
date_debut: str = f"{scu.annee_scolaire()}-09-01"
date_fin: str = f"{scu.annee_scolaire()+1}-06-30"
date_debut: str = f"01/09/{scu.annee_scolaire()}"
date_fin: str = f"30/06/{scu.annee_scolaire()+1}"
# Récupération de la métrique d'assiduité
assi_metric = scu.translate_assiduites_metric(
@ -697,7 +698,7 @@ def signal_assiduites_group():
"assiduites/pages/signal_assiduites_group.j2",
gr_tit=gr_tit,
sem=sem["titre_num"],
date=date,
date=_dateiso_to_datefr(date),
formsemestre_id=formsemestre_id,
grp=sco_groups_view.menu_groups_choice(groups_infos),
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
@ -843,7 +844,7 @@ def visu_assiduites_group():
"assiduites/pages/signal_assiduites_group.j2",
gr_tit=gr_tit,
sem=sem["titre_num"],
date=date,
date=_dateiso_to_datefr(date),
formsemestre_id=formsemestre_id,
grp=sco_groups_view.menu_groups_choice(groups_infos),
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
@ -1015,8 +1016,8 @@ def visu_assi_group():
inverse=False,
short=False,
),
date_debut=dates["debut"],
date_fin=dates["fin"],
date_debut=_dateiso_to_datefr(dates["debut"]),
date_fin=_dateiso_to_datefr(dates["debut"]),
gr_tit=gr_tit,
group_ids=request.args.get("group_ids", None),
sco=ScoData(formsemestre=groups_infos.get_formsemestre()),
@ -1318,6 +1319,31 @@ def test():
# --- Fonctions internes ---
def _dateiso_to_datefr(date_iso: str) -> str:
"""
_dateiso_to_datefr Transforme une date iso en date format français
Args:
date_iso (str): date au format iso (YYYY-MM-DD)
Raises:
ValueError: Si l'argument `date_iso` n'est pas au bon format
Returns:
str: date au format français (DD/MM/YYYY)
"""
regex_date_iso: str = r"^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])$"
# Vérification de la date_iso
if not re.match(regex_date_iso, date_iso):
raise ValueError(
f"La dateiso passée en paramètre [{date_iso}] n'est pas valide."
)
return f"{date_iso[8:10]}/{date_iso[5:7]}/{date_iso[0:4]}"
def _get_date_str(deb: datetime.datetime, fin: datetime.datetime) -> str:
"""
_get_date_str transforme une période en chaîne lisible