This commit is contained in:
Emmanuel Viennet 2023-11-16 16:22:45 +01:00
commit b0daccb32e
32 changed files with 1522 additions and 2100 deletions

View File

@ -53,7 +53,6 @@ import requests
from pytz import timezone
import dateutil.parser as dtparser
import flask
from flask import g, request, Response
@ -230,9 +229,9 @@ def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or No
"""
try:
date: datetime.datetime = dtparser.isoparse(date)
date: datetime.datetime = datetime.datetime.fromisoformat(date)
return date if convert else True
except (dtparser.ParserError, ValueError, TypeError):
except (ValueError, TypeError):
return None if convert else False

View File

@ -1,3 +1,33 @@
:root {
--color-present: #6bdb83;
--color-absent: #e62a11;
--color-retard: #f0c865;
--color-justi: #7059FF;
--color-justi-invalide: #a84476;
--color-nonwork: #badfff;
--color-absent-justi: #e65ab7;
--color-retard-justi: #ffef7a;
--color-error: #FF0000;
--color-warning: #eec660;
--color-information: #658ef0;
--color-def: #d61616;
--color-conflit: #ff00009c;
--color-bg-def: #c8c8c8;
--color-primary: #7059FF;
--color-secondary: #6f9fff;
--color-defaut: #FFF;
--color-defaut-dark: #444;
--motif-justi: repeating-linear-gradient(135deg, transparent, transparent 4px, var(--color-justi) 4px, var(--color-justi) 8px);
--motif-justi-invalide: repeating-linear-gradient(-135deg, transparent, transparent 4px, var(--color-justi-invalide) 4px, var(--color-justi-invalide) 8px);
}
* {
box-sizing: border-box;
}
@ -36,10 +66,10 @@
.infos {
position: relative;
width: fit-content;
display: flex;
justify-content: space-evenly;
justify-content: start;
align-content: center;
gap: 10px;
}
#datestr {
@ -48,7 +78,7 @@
border: 1px #444 solid;
border-radius: 5px;
padding: 5px;
min-width: 100px;
min-width: 250px;
display: inline-block;
min-height: 20px;
}
@ -87,7 +117,7 @@
}
.ui-slider-range.ui-widget-header.ui-corner-all {
background-color: #F9C768;
background-color: var(--color-warning);
background-image: none;
opacity: 0.50;
visibility: visible;
@ -122,7 +152,7 @@
.etud_row.def,
.etud_row.dem {
background-color: #c8c8c8;
background-color: var(--color-bg-def);
}
/* --- Index --- */
@ -149,7 +179,7 @@
.tr.def .td.sticky span::after {
display: block;
content: " (Déf.)";
color: #d61616;
color: var(--color-def);
margin-left: 2px;
}
@ -157,7 +187,7 @@
.tr.dem .td.sticky span::after {
display: block;
content: " (Dém.)";
color: #d61616;
color: var(--color-def);
margin-left: 2px;
}
@ -213,32 +243,36 @@
}
.etud_row.conflit {
background-color: #ff0000c2;
background-color: var(--color-conflit);
}
.etud_row .assiduites_bar .absent,
.demo.absent {
background-color: #F1A69C !important;
background-color: var(--color-absent) !important;
}
.etud_row .assiduites_bar .present,
.demo.present {
background-color: #9CF1AF !important;
background-color: var(--color-present) !important;
}
.etud_row .assiduites_bar .retard,
.demo.retard {
background-color: #F1D99C !important;
background-color: var(--color-retard) !important;
}
.demo.nonwork {
background-color: var(--color-nonwork) !important;
}
.etud_row .assiduites_bar .justified,
.demo.justified {
background-image: repeating-linear-gradient(135deg, transparent, transparent 4px, #7059FF 4px, #7059FF 8px);
background-image: var(--motif-justi);
}
.etud_row .assiduites_bar .invalid_justified,
.demo.invalid_justified {
background-image: repeating-linear-gradient(225deg, transparent, transparent 4px, #d61616 4px, #d61616 8px);
background-image: var(--motif-justi-invalide);
}
@ -273,27 +307,35 @@
height: 35px;
background-position: center;
background-size: cover;
border-radius: 5px;
border: 1px solid var(--color-defaut-dark);
}
.rbtn.present::before {
background-image: url(../icons/present.svg);
background-color: var(--color-present);
}
.rbtn.absent::before {
background-color: var(--color-absent);
background-image: url(../icons/absent.svg);
}
.rbtn.aucun::before {
background-image: url(../icons/aucun.svg);
background-color: var(--color-defaut-dark);
}
.rbtn.retard::before {
background-color: var(--color-retard);
background-image: url(../icons/retard.svg);
}
.rbtn:checked:before {
outline: 5px solid #7059FF;
outline: 5px solid var(--color-primary);
border-radius: 50%;
}
@ -486,7 +528,7 @@
.loader {
border: 6px solid #f3f3f3;
border-radius: 50%;
border-top: 6px solid #3498db;
border-top: 6px solid var(--color-primary);
width: 60px;
height: 60px;
position: absolute;
@ -532,7 +574,7 @@
}
.rouge {
color: crimson;
color: var(--color-error);
}
.legende {
@ -588,7 +630,7 @@
#forcemodule {
border-radius: 8px;
background: crimson;
background: var(--color-error);
max-width: fit-content;
padding: 5px;
color: white;

16
app/static/icons/absent.svg Executable file → Normal file

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 547 B

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,11 @@
<svg width="85" height="85" viewBox="0 0 85 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="85" height="85" rx="15" fill=""/>
<g opacity="0.7" clip-path="url(#clip0_120_4425)">
<path d="M67.2116 70L43 45.707L18.7885 70L15.0809 66.3043L39.305 41.9995L15.0809 17.6939L18.7885 14L43 38.2922L67.2116 14L70.9191 17.6939L46.695 41.9995L70.9191 66.3043L67.2116 70Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_120_4425">
<rect width="56" height="56" fill="white" transform="matrix(1 0 0 -1 15 70)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 540 B

View File

@ -1,5 +1,5 @@
<svg width="85" height="85" viewBox="0 0 85 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="85" height="85" rx="15" fill="#BBB"/>
<rect width="85" height="85" rx="15" fill=""/>
<defs>
<clipPath id="clip0_120_4425">
<rect width="56" height="56" fill="white" transform="matrix(1 0 0 -1 15 70)"/>

Before

Width:  |  Height:  |  Size: 291 B

After

Width:  |  Height:  |  Size: 287 B

View File

@ -1,7 +1,7 @@
<svg width="85" height="85" viewBox="0 0 85 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="85" height="85" rx="15" fill="#9CF1AF"/>
<rect width="85" height="85" rx="15" fill=""/>
<g clip-path="url(#clip0_120_4405)">
<g opacity="0.5">
<g opacity="0.7">
<path d="M70.7713 27.5875L36.0497 62.3091C35.7438 62.6149 35.2487 62.6149 34.9435 62.3091L15.2286 42.5935C14.9235 42.2891 14.9235 41.7939 15.2286 41.488L20.0191 36.6976C20.3249 36.3924 20.8201 36.3924 21.1252 36.6976L35.4973 51.069L64.8754 21.6909C65.1819 21.3858 65.6757 21.3858 65.9815 21.6909L70.7713 26.4814C71.0771 26.7865 71.0771 27.281 70.7713 27.5875Z" fill="black"/>
</g>
</g>

Before

Width:  |  Height:  |  Size: 729 B

After

Width:  |  Height:  |  Size: 722 B

View File

@ -1,6 +1,6 @@
<svg width="85" height="85" viewBox="0 0 85 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="85" height="85" rx="15" fill="#F1D99C"/>
<g opacity="0.5" clip-path="url(#clip0_120_4407)">
<rect width="85" height="85" rx="15" fill=""/>
<g opacity="0.7" clip-path="url(#clip0_120_4407)">
<path d="M55.2901 49.1836L44.1475 41.3918V28C44.1475 27.3688 43.6311 26.8524 43 26.8524C42.3688 26.8524 41.8524 27.3688 41.8524 28V42C41.8524 42.3787 42.036 42.7229 42.3459 42.941L53.9819 51.077C54.177 51.2147 54.4065 51.2836 54.636 51.2836C54.9918 51.2836 55.3475 51.1115 55.577 50.7787C55.9327 50.2623 55.8065 49.5508 55.2901 49.1836Z" fill="black"/>
<path d="M62.7836 22.2164C57.482 16.9148 50.459 14 43 14C35.541 14 28.518 16.9148 23.2164 22.2164C17.9148 27.518 15 34.541 15 42C15 49.459 17.9148 56.482 23.2164 61.7836C28.518 67.0852 35.541 70 43 70C50.459 70 57.482 67.0852 62.7836 61.7836C68.0852 56.482 71 49.459 71 42C71 34.541 68.0852 27.518 62.7836 22.2164ZM44.1475 67.682V63C44.1475 62.3689 43.6311 61.8525 43 61.8525C42.3689 61.8525 41.8525 62.3689 41.8525 63V67.682C28.5869 67.0967 17.9033 56.4131 17.318 43.1475H22C22.6311 43.1475 23.1475 42.6311 23.1475 42C23.1475 41.3689 22.6311 40.8525 22 40.8525H17.318C17.9033 27.5869 28.5869 16.9033 41.8525 16.318V21C41.8525 21.6311 42.3689 22.1475 43 22.1475C43.6311 22.1475 44.1475 21.6311 44.1475 21V16.318C57.4131 16.9033 68.0967 27.5869 68.682 40.8525H64C63.3689 40.8525 62.8525 41.3689 62.8525 42C62.8525 42.6311 63.3689 43.1475 64 43.1475H68.682C68.0967 56.4131 57.4131 67.0967 44.1475 67.682Z" fill="black"/>
</g>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -32,6 +32,17 @@ Object.defineProperty(String.prototype, "capitalize", {
},
enumerable: false,
});
const DatePrecisions = [
"year",
"month",
"day",
"hour",
"minute",
"second",
"millisecond",
];
// <<== Outils ==>>
Object.defineProperty(Array.prototype, "reversed", {
value: function () {
@ -101,6 +112,7 @@ function validateSelectors(btn) {
getAssiduitesFromEtuds(true);
document.querySelector(".selectors").disabled = true;
$("#tl_date").datepicker("option", "disabled", true);
generateMassAssiduites();
generateAllEtudRow();
btn.remove();
@ -126,7 +138,7 @@ function validateSelectors(btn) {
}
function onlyAbs() {
if (getDate() > moment()) {
if (getDate() > Date.now()) {
document
.querySelectorAll(".rbtn.present, .rbtn.retard")
.forEach((el) => el.remove());
@ -162,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,
@ -177,16 +190,22 @@ 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) {
console.error(error);
if (errors) errors(error);
}
}
/**
* Fait une requête POST de façon synchrone
* @param {String} path adresse distante
@ -195,6 +214,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,
@ -212,17 +232,29 @@ 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) {
console.error(error);
if (errors) errors(error);
}
}
// <<== Gestion des actions de masse ==>>
const massActionQueue = new Map();
@ -268,8 +300,8 @@ function executeMassActionQueue() {
*/
const tlTimes = getTimeLineTimes();
let assiduite = {
date_debut: tlTimes.deb.format(),
date_fin: tlTimes.fin.format(),
date_debut: tlTimes.deb.toIsoUtcString(),
date_fin: tlTimes.fin.toIsoUtcString(),
};
assiduite = setModuleImplId(assiduite);
@ -572,10 +604,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 {
@ -595,7 +630,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;
@ -613,19 +652,10 @@ function getNearestWorkDay(date) {
}
function verifyDateInSemester() {
const date = new moment.tz(
document.querySelector("#tl_date").value,
TIMEZONE
);
const date = getDate();
const periodSemester = getFormSemestreDates();
return date.isBetween(
periodSemester.deb,
periodSemester.fin,
undefined,
"[]"
);
return date.isBetween(periodSemester.deb, periodSemester.fin, "[]");
}
/**
@ -637,15 +667,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);
}
}
@ -664,8 +694,8 @@ function getAssiduitesOnDateChange() {
* @param {String} separator le séparateur de la date intelligible (01/01/2000 {separtor} 10:00)
* @returns {String} la date intelligible
*/
function formatDateModal(str, separator = "·") {
return new moment.tz(str, TIMEZONE).format(`DD/MM/Y ${separator} HH:mm`);
function formatDateModal(str, separator = " ") {
return new Date(str).format("DD/MM/Y HH:mm").replace(" ", separator);
}
/**
@ -705,8 +735,8 @@ function verifyNonWorkDays(day, nonWorkdays) {
* Fonction qui vérifie si une période est dans un interval
* Objet période / interval
* {
* deb: moment.tz(<Date>),
* fin: moment.tz(<Date>),
* deb: Date,
* fin: Date,
* }
* @param {object} period
* @param {object} interval
@ -718,19 +748,19 @@ function hasTimeConflict(period, interval) {
/**
* On récupère la période de la timeline
* @returns {deb : moment.tz(), fin: moment.tz()}
* @returns {deb : Date, fin: Date)}
*/
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 (moment.tz)
//On génère des objets temps
values = values.map((el) => {
el = toTime(el).replace("h", ":");
el = `${dateiso}T${el}`;
return moment.tz(el, TIMEZONE);
return new Date(el);
});
return { deb: values[0], fin: values[1] };
@ -744,8 +774,8 @@ function getTimeLineTimes() {
function isConflictSameAsPeriod(conflict, period = undefined) {
const tlTimes = period == undefined ? getTimeLineTimes() : period;
const clTimes = {
deb: moment.tz(conflict.date_debut, TIMEZONE),
fin: moment.tz(conflict.date_fin, TIMEZONE),
deb: new Date(conflict.date_debut),
fin: new Date(conflict.date_fin),
};
return tlTimes.deb.isSame(clTimes.deb) && tlTimes.fin.isSame(clTimes.fin);
}
@ -755,9 +785,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");
}
/**
@ -766,10 +797,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
@ -777,10 +805,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");
}
/**
@ -788,44 +813,19 @@ 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 moment.tz
* Transforme un temps numérique en une date
* @param {number} nb
* @returns {moment.tz} Une date formée du temps donné et de la date courante
* @returns {Date} Une date formée du temps donné et de la date courante
*/
function numberTimeToDate(nb) {
time = toTime(nb).replace("h", ":");
date = document.querySelector("#tl_date").value;
date = getDate().format("YYYY-MM-DD");
datetime = `${date}T${time}`;
return moment.tz(datetime, TIMEZONE);
return new Date(datetime);
}
// <<== Gestion des assiduités ==>>
@ -841,8 +841,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 = {};
@ -885,8 +885,8 @@ function getAssiduitesFromEtuds(clear, deb, fin) {
function createAssiduite(etat, etudid) {
const tlTimes = getTimeLineTimes();
let assiduite = {
date_debut: tlTimes.deb.format(),
date_fin: tlTimes.fin.format(),
date_debut: tlTimes.deb.toIsoUtcString(),
date_fin: tlTimes.fin.toIsoUtcString(),
etat: etat,
};
@ -1067,10 +1067,11 @@ function getAssiduitesConflict(etudid, periode) {
return etudAssiduites.filter((assi) => {
const interval = {
deb: moment.tz(assi.date_debut, TIMEZONE),
fin: moment.tz(assi.date_fin, TIMEZONE),
deb: new Date(assi.date_debut),
fin: new Date(assi.date_fin),
};
return hasTimeConflict(periode, interval);
const test = hasTimeConflict(periode, interval);
return test;
});
}
@ -1085,21 +1086,21 @@ function getLastAssiduiteOfPrevDate(etudid) {
return "";
}
const period = {
deb: moment.tz(getPrevDate(), TIMEZONE),
fin: moment.tz(getDate(), TIMEZONE),
deb: getPrevDate(),
fin: getDate(),
};
const prevAssiduites = etudAssiduites
.filter((assi) => {
const interval = {
deb: moment.tz(assi.date_debut, TIMEZONE),
fin: moment.tz(assi.date_fin, TIMEZONE),
deb: new Date(assi.date_debut),
fin: new Date(assi.date_fin),
};
return hasTimeConflict(period, interval);
})
.sort((a, b) => {
const a_fin = moment.tz(a.date_fin, TIMEZONE);
const b_fin = moment.tz(b.date_fin, TIMEZONE);
const a_fin = new Date(a.date_fin);
const b_fin = new Date(b.date_fin);
return b_fin < a_fin;
});
@ -1134,8 +1135,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() +
@ -1163,7 +1164,7 @@ function getAllAssiduitesFromEtud(
.replace("°", courant ? "&courant" : "")
: ""
}`;
//TODO Utiliser async_get au lieu de jquery
$.ajax({
async: true,
type: "GET",
@ -1232,8 +1233,8 @@ function assiduiteAction(element) {
assiduites[etudid],
getTimeLineTimes(),
{
deb: new moment.tz(getDate(), TIMEZONE),
fin: new moment.tz(getNextDate(), TIMEZONE),
deb: getDate(),
fin: getNextDate(),
}
);
const update = (assi) => {
@ -1377,7 +1378,6 @@ function insertEtudRow(etud, index, output = false) {
date_fin: null,
prevAssiduites: prevAssiduite,
};
if (conflict.length > 0) {
assiduite.etatAssiduite = conflict[0].etat;
@ -1545,8 +1545,8 @@ function getFormSemestreDates() {
const dateFin = document.getElementById("formsemestre_date_fin").textContent;
return {
deb: dateDeb,
fin: dateFin,
deb: new Date(dateDeb),
fin: new Date(dateFin),
};
}
@ -1613,8 +1613,10 @@ function getJustificatifFromPeriod(date, etudid, update) {
url:
getUrl() +
`/api/justificatifs/${etudid}/query?date_debut=${date.deb
.add(1, "s")
.format()}&date_fin=${date.fin.subtract(1, "s").format()}`,
.add(1, "seconds")
.toIsoUtcString()}&date_fin=${date.fin
.add(-1, "seconds")
.toIsoUtcString()}`,
success: (data) => {
update(data);
},
@ -1646,8 +1648,8 @@ function fastJustify(assiduite) {
}
const period = {
deb: new moment.tz(assiduite.date_debut, TIMEZONE),
fin: new moment.tz(assiduite.date_fin, TIMEZONE),
deb: new Date(assiduite.date_debut),
fin: new Date(assiduite.date_fin),
};
const action = (justifs) => {
//créer un nouveau justificatif
@ -1660,8 +1662,8 @@ function fastJustify(assiduite) {
//créer justificatif
const justif = {
date_debut: new moment.tz(assiduite.date_debut, TIMEZONE).format(),
date_fin: new moment.tz(assiduite.date_fin, TIMEZONE).format(),
date_debut: new Date(assiduite.date_debut).toIsoUtcString(),
date_fin: new Date(assiduite.date_fin).toIsoUtcString(),
raison: raison,
etat: etat,
};
@ -1694,7 +1696,7 @@ function fastJustify(assiduite) {
content,
success,
() => {},
"#7059FF"
"var(--color-primary)"
);
};
if (assiduite.etudid) {
@ -1744,6 +1746,8 @@ function getAllJustificatifsFromEtud(
`/api/justificatifs/${etudid}${
order ? "/query?order°".replace("°", courant ? "&courant" : "") : ""
}`;
//TODO Utiliser async_get au lieu de jquery
$.ajax({
async: true,
type: "GET",

576
app/static/js/date_utils.js Normal file
View File

@ -0,0 +1,576 @@
/**
* 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)}`;
};
/**
* Vérifie si le début de l'une des périodes est avant la fin de l'autre
* et si la fin de cette période est après le début de l'autre.
* @param {Object} period {deb:Object, fin:Object}
* @param {Object} interval {deb:Object, fin:Object}
* @returns vrai si la periode et l'interval ont une intersection commune
*/
Date.intersect = function (period, interval) {
return period.deb <= interval.fin && period.fin >= interval.deb;
};
Object.defineProperty(Date.prototype, "isValid", {
value: function () {
return !Number.isNaN(this.getTime());
},
});
Object.defineProperty(Date.prototype, "startOf", {
/**
* Génère u la date à la plus petite valeur pour la précision donnée.
* @param {string} precision - La précision souhaitée (year, month, day, hours, minutes, seconds, milliseconds).
* @returns {Date} - Une nouvelle date ajustée.
*/
value: function (precision) {
const newDate = this.clone();
switch (precision) {
case "year":
newDate.setMonth(0);
case "month":
newDate.setDate(1);
case "day":
newDate.setHours(0);
case "hours":
newDate.setMinutes(0);
case "minutes":
newDate.setSeconds(0);
case "seconds":
newDate.setMilliseconds(0);
break;
case "milliseconds":
break;
default:
throw new Error(
`Invalid precision for startOf function [${precision}]`
);
}
return newDate;
},
});
Object.defineProperty(Date.prototype, "endOf", {
/**
* Ajuste la date à la plus grande valeur pour la précision donnée.
* @param {string} precision - La précision souhaitée (year, month, day, hours, minutes, seconds, milliseconds).
* @returns {Date} - Une nouvelle date ajustée.
*/
value: function (precision) {
const newDate = this.clone();
switch (precision) {
case "year":
newDate.setMonth(11); // Décembre est le 11ème mois (0-indexé)
case "month":
newDate.setDate(0); // Le jour 0 du mois suivant est le dernier jour du mois courant
newDate.setMonth(newDate.getMonth() + 1);
case "day":
newDate.setHours(23); // 23 heures est la dernière heure de la journée
case "hours":
newDate.setMinutes(59); // 59 minutes est la dernière minute de l'heure
case "minutes":
newDate.setSeconds(59); // 59 secondes est la dernière seconde de la minute
case "seconds":
newDate.setMilliseconds(999); // 999 millisecondes est la dernière milliseconde de la seconde
break;
case "milliseconds":
// Rien à faire pour les millisecondes
break;
default:
throw new Error("Invalid precision for endOf function");
}
return newDate;
},
});
Object.defineProperty(Date.prototype, "isBefore", {
/**
* Retourne vrai si la date est située avant la date fournie
* @param {Date} date
* @returns {boolean}
*/
value: function (date) {
return this.valueOf() < date.valueOf();
},
});
Object.defineProperty(Date.prototype, "isAfter", {
/**
* Retourne vrai si la date est située après la date fournie
* @param {Date} date
* @returns {boolean}
*/
value: function (date) {
return this.valueOf() > date.valueOf();
},
});
Object.defineProperty(Date.prototype, "isSame", {
/**
* Retourne vrai si les dates sont les mêmes
* @param {Date} date
* @param {string} precision default : "milliseconds"
* @returns boolean
*/
value: function (date, precision = "milliseconds") {
return (
this.startOf(precision).valueOf() == date.startOf(precision).valueOf()
);
},
});
Object.defineProperty(Date.prototype, "isBetween", {
/**
* Vérifie si la date est comprise dans une période avec une précision et une inclusivité optionnelles
* @param {Date} deb - La date de début de la période
* @param {Date} fin - La date de fin de la période
* @param {String} bornes - L'inclusivité/exclusivité de la comparaison ("[]", "()", "[)", "(]")
* - bornes incluses : []
* - bornes excluses : ()
* - borne gauche incluse et borne droit excluse : [)
* - borne gauche excluse et borne droit incluse : (]
*/
value: function (deb, fin, bornes = "[]") {
// Ajuste la date actuelle, la date de début et la date de fin à la précision spécifiée
// Vérifie les bornes en fonction de l'inclusivité/exclusivité spécifiée dans 'bornes'
const check_deb =
bornes[0] === "("
? this.valueOf() > deb.valueOf()
: this.valueOf() >= deb.valueOf();
const check_fin =
bornes[1] === ")"
? fin.valueOf() > this.valueOf()
: fin.valueOf() >= this.valueOf();
return check_deb && check_fin;
},
});
Object.defineProperty(Date.prototype, "toIsoUtcString", {
/**
* @returns date au format iso utc (yyyy-mm-ddThh:MM±oo:oo:oo)
*/
value: function () {
const date = this;
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)
);
},
});
Object.defineProperty(Date.prototype, "clone", {
/**
* @returns Retourne une copie de la date (copie non liée)
*/
value: function () {
return structuredClone(this);
},
});
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", {
day: "2-digit",
month: "2-digit",
year: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
case "DD/MM/YYYY HH:mm":
return this.toLocaleString("fr-FR", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
case "YYYY-MM-DDTHH:mm":
// 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();
}
},
});
Object.defineProperty(Date.prototype, "add", {
/**
* Ajoute une valeur spécifiée à un élément de la date.
* @param {number} value - La valeur à ajouter.
* @param {string} type - Le type de la valeur (year, month, day, hours, minutes, seconds).
*/
value: function (value, type) {
switch (type) {
case "years":
this.setFullYear(this.getFullYear() + value);
break;
case "months":
this.setMonth(this.getMonth() + value);
break;
case "days":
this.setDate(this.getDate() + value);
break;
case "hours":
this.setHours(this.getHours() + value);
break;
case "minutes":
this.setMinutes(this.getMinutes() + value);
break;
case "seconds":
this.setSeconds(this.getSeconds() + value);
break;
default:
throw new Error(
`Invalid type for adding to date | type : ${type} value : ${value}`
);
}
return this; // Return the modified date
},
});
class Duration {
/**
* Constructeur de la classe Duration.
* @param {Date} start - La date de début de la période.
* @param {Date} end - La date de fin de la période.
*/
constructor(start, end) {
this.start = start; // Stocke la date de début.
this.end = end; // Stocke la date de fin.
this.duration = end - start; // Calcule la durée en millisecondes entre les deux dates.
}
/**
* Calcule le nombre d'années entre les deux dates et arrondit le résultat à quatre décimales.
* @return {number} Le nombre d'années arrondi à quatre décimales.
*/
get years() {
const startYear = this.start.getFullYear(); // Obtient l'année de la date de début.
const endYear = this.end.getFullYear(); // Obtient l'année de la date de fin.
// Calcule la différence en années et arrondit à quatre décimales.
return parseFloat((endYear - startYear).toFixed(4));
}
/**
* Calcule le nombre de mois entre les deux dates, en tenant compte des années et des jours, et arrondit le résultat à quatre décimales.
* @return {number} Le nombre de mois arrondi à quatre décimales.
*/
get months() {
const years = this.years; // Nombre d'années complètes.
// Calcule la différence en mois, en ajoutant la différence en jours divisée par 30 pour une approximation.
const months =
years * 12 +
(this.end.getMonth() - this.start.getMonth()) +
(this.end.getDate() - this.start.getDate()) / 30;
// Arrondit à quatre décimales.
return parseFloat(months.toFixed(4));
}
/**
* Calcule le nombre de jours entre les deux dates et arrondit le résultat à quatre décimales.
* @return {number} Le nombre de jours arrondi à quatre décimales.
*/
get days() {
// Convertit la durée en millisecondes en jours et arrondit à quatre décimales.
return parseFloat((this.duration / (24 * 60 * 60 * 1000)).toFixed(4));
}
/**
* Calcule le nombre d'heures entre les deux dates et arrondit le résultat à quatre décimales.
* @return {number} Le nombre d'heures arrondi à quatre décimales.
*/
get hours() {
// Convertit la durée en millisecondes en heures et arrondit à quatre décimales.
return parseFloat((this.duration / (60 * 60 * 1000)).toFixed(4));
}
/**
* Calcule le nombre de minutes entre les deux dates et arrondit le résultat à quatre décimales.
* @return {number} Le nombre de minutes arrondi à quatre décimales.
*/
get minutes() {
// Convertit la durée en millisecondes en minutes et arrondit à quatre décimales.
return parseFloat((this.duration / (60 * 1000)).toFixed(4));
}
/**
* Calcule le nombre de secondes entre les deux dates et arrondit le résultat à quatre décimales.
* @return {number} Le nombre de secondes arrondi à quatre décimales.
*/
get seconds() {
// Convertit la durée en millisecondes en secondes et arrondit à quatre décimales.
return parseFloat((this.duration / 1000).toFixed(4));
}
/**
* Obtient le nombre de millisecondes entre les deux dates et arrondit le résultat à quatre décimales.
* @return {number} Le nombre de millisecondes arrondi à quatre décimales.
*/
get milliseconds() {
// Arrondit la durée totale en millisecondes à quatre décimales.
return parseFloat(this.duration.toFixed(4));
}
}
class ScoDocDateTimePicker extends HTMLElement {
constructor() {
super();
// Définir si le champ est requis
this.required = this.hasAttribute("required");
// Initialiser le shadow DOM
const shadow = this.attachShadow({ mode: "open" });
// Créer l'input pour la date
const dateInput = document.createElement("input");
dateInput.type = "date";
dateInput.id = "date";
// Créer l'input pour l'heure
const timeInput = document.createElement("input");
timeInput.type = "time";
timeInput.id = "time";
timeInput.step = 60;
// Ajouter les inputs dans le shadow DOM
shadow.appendChild(dateInput);
shadow.appendChild(timeInput);
// Gestionnaires d'événements pour la mise à jour de la valeur
dateInput.addEventListener("change", () => this.updateValue());
timeInput.addEventListener("change", () => this.updateValue());
// Style CSS pour les inputs
const style = document.createElement("style");
style.textContent = `
input {
display: inline-block;
}
input:invalid {
border: 1px solid red;
}
`;
// Ajouter le style au shadow DOM
shadow.appendChild(style);
}
static get observedAttributes() {
return ["show"]; // Ajoute 'show' à la liste des attributs observés
}
connectedCallback() {
// Récupérer l'attribut 'name'
this.name = this.getAttribute("name");
// Créer un input caché pour la valeur datetime
this.hiddenInput = document.createElement("input");
this.hiddenInput.type = "hidden";
this.hiddenInput.name = this.name;
this.appendChild(this.hiddenInput);
// Gérer la soumission du formulaire
this.closest("form")?.addEventListener("submit", (e) => {
if (!this.validate()) {
e.preventDefault(); // Empêcher la soumission si non valide
this.dispatchEvent(
new Event("invalid", { bubbles: true, cancelable: true })
);
} else {
// Mettre à jour la valeur de l'input caché avant la soumission
this.hiddenInput.value = this.isValid()
? this.valueAsDate.toIsoUtcString()
: "";
}
});
this.updateDisplay();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "show") {
this.updateDisplay(); // Met à jour l'affichage si l'attribut 'show' change
}
}
updateDisplay() {
const mode = this.getAttribute("show") || "both";
const dateInput = this.shadowRoot.querySelector("#date");
const timeInput = this.shadowRoot.querySelector("#time");
switch (mode) {
case "date":
dateInput.style.display = "inline-block";
timeInput.style.display = "none";
break;
case "time":
dateInput.style.display = "none";
timeInput.style.display = "inline-block";
break;
case "both":
default:
dateInput.style.display = "inline-block";
timeInput.style.display = "inline-block";
}
}
// Vérifier si la valeur forme une date valide
isValid() {
return !Number.isNaN(this.valueAsDate.getTime());
}
// Valider l'élément
validate() {
if (this.required && !this.isValid()) {
return false;
}
return true;
}
// Mettre à jour la valeur interne
updateValue() {
const dateInput = this.shadowRoot.querySelector("#date");
const timeInput = this.shadowRoot.querySelector("#time");
this._value = `${dateInput.value}T${timeInput.value}`;
this.dispatchEvent(new Event("change", { bubbles: true }));
// Appliquer le style 'invalid' si nécessaire
dateInput.classList.toggle("invalid", this.required && !this.isValid());
timeInput.classList.toggle("invalid", this.required && !this.isValid());
}
// Getter pour obtenir la valeur actuelle.
get value() {
return this._value;
}
get valueAsObject() {
const dateInput = this.shadowRoot.querySelector("#date");
const timeInput = this.shadowRoot.querySelector("#time");
return {
date: dateInput.value,
time: timeInput.value,
};
}
// Getter pour obtenir la valeur en tant qu'objet Date.
get valueAsDate() {
return new Date(this._value);
}
// Setter pour définir la valeur. Sépare la valeur en date et heure et les définit individuellement.
set value(val) {
let [date, time] = val.split("T");
this.shadowRoot.querySelector("#date").value = date;
time = time.substring(0, 5);
this.shadowRoot.querySelector("#time").value = time;
this._value = val;
}
// Setter pour définir la valeur à partir d'un objet avec les propriétés 'date' et 'time'.
set valueAsObject(obj) {
const dateInput = this.shadowRoot.querySelector("#date");
const timeInput = this.shadowRoot.querySelector("#time");
if (obj.hasOwnProperty("date")) {
dateInput.value = obj.date || ""; // Définit la valeur de l'input de date si elle est fournie
}
if (obj.hasOwnProperty("time")) {
timeInput.value = obj.time.substring(0, 5) || ""; // Définit la valeur de l'input d'heure si elle est fournie
}
// Met à jour la valeur interne en fonction des nouvelles valeurs des inputs
this.updateValue();
}
// Setter pour définir la valeur à partir d'un objet Date.
set valueAsDate(dateVal) {
// Formatage de l'objet Date en string et mise à jour de la valeur.
this.value = `${dateVal.getFullYear()}-${String(
dateVal.getMonth() + 1
).padStart(2, "0")}-${String(dateVal.getDate()).padStart(2, "0")}T${String(
dateVal.getHours()
).padStart(2, "0")}:${String(dateVal.getMinutes()).padStart(2, "0")}`;
}
}
// Définition du nouvel élément personnalisé 'scodoc-datetime'.
customElements.define("scodoc-datetime", ScoDocDateTimePicker);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -11,19 +11,15 @@
<section class="justi-form page">
<fieldset>
<div class="justi-row">
<button onclick="validerFormulaire(this)">Créer le justificatif</button>
<button onclick="effacerFormulaire()">Remettre à zero</button>
</div>
<div class="justi-row">
<div class="justi-label">
<legend for="justi_date_debut" required>Date de début</legend>
<input type="datetime-local" name="justi_date_debut" id="justi_date_debut">
<span>Journée(s) entière(s)</span> <input type="checkbox" name="justi_journee" id="justi_journee">
<scodoc-datetime name="justi_date_debut" id="justi_date_debut"> </scodoc-datetime>
<span>Journée entière</span> <input type="checkbox" name="justi_journee" id="justi_journee">
</div>
<div class="justi-label" id="date_fin">
<legend for="justi_date_fin" required>Date de fin</legend>
<input type="datetime-local" name="justi_date_fin" id="justi_date_fin">
<scodoc-datetime name="justi_date_fin" id="justi_date_fin"></scodoc-datetime>
</div>
</div>
@ -55,6 +51,10 @@
</div>
</div>
<div class="justi-row">
<button onclick="validerFormulaire(this)">Créer le justificatif</button>
<button onclick="effacerFormulaire()">Remettre à zero</button>
</div>
</fieldset>
@ -112,15 +112,14 @@
function validateFields() {
const field = document.querySelector('.justi-form')
const { deb, fin } = getDates()
const date_debut = new Date(deb);
const date_fin = new Date(fin);
if (deb == "" || fin == "") {
if (deb == "" || fin == "" || !date_debut.isValid() || !date_fin.isValid()) {
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin valide."), "", color = "crimson");
return false;
}
const date_debut = moment.tz(deb, TIMEZONE);
const date_fin = moment.tz(fin, TIMEZONE);
if (date_fin.isBefore(date_debut)) {
openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
return false;
@ -138,8 +137,8 @@
const raison = field.querySelector('#justi_raison').value;
return {
date_debut: moment.tz(deb, TIMEZONE).format(),
date_fin: moment.tz(fin, TIMEZONE).format(),
date_debut: new Date(deb).toIsoUtcString(),
date_fin: new Date(fin).toIsoUtcString(),
etat: etat,
raison: raison,
}
@ -153,7 +152,7 @@
const requests = []
Array.from(in_files.files).forEach((f) => {
pushToast(generateToast(document.createTextNode(`Importation du fichier : ${f.name} commencée`), color = "#f0c865"));
pushToast(generateToast(document.createTextNode(`Importation du fichier : ${f.name} commencée`), color = "var(--color-information)"));
const fd = new FormData();
fd.append('file', f);
requests.push(
@ -219,39 +218,31 @@
}
function dayOnly() {
const { deb, fin } = getDates();
const date_deb = document.getElementById("justi_date_debut");
const date_fin = document.getElementById("justi_date_fin");
if (document.getElementById('justi_journee').checked) {
document.getElementById("justi_date_debut").type = "date"
document.getElementById("justi_date_debut").value = deb.slice(0, deb.indexOf('T'))
document.getElementById("justi_date_fin").type = "date"
document.getElementById("justi_date_fin").value = fin.slice(0, fin.indexOf('T'))
date_deb.setAttribute("show", "date")
date_fin.setAttribute("show", "date")
document.getElementById("date_fin").classList.add("hidden");
} else {
document.getElementById("justi_date_debut").type = "datetime-local"
document.getElementById("justi_date_debut").value = `${deb}T${assi_morning}`
date_deb.removeAttribute("show")
date_fin.removeAttribute("show")
document.getElementById("date_fin").classList.remove("hidden");
document.getElementById("justi_date_fin").type = "datetime-local"
document.getElementById("justi_date_fin").value = `${fin}T${assi_evening}`
}
}
function getDates() {
if (document.querySelector('.page #justi_journee').checked) {
const date_str_deb = document.querySelector(".page #justi_date_debut").value
const date_str_fin = document.querySelector(".page #justi_date_fin").value
return {
"deb": date_str_deb ? `${date_str_deb}T${assi_morning}` : "",
"fin": date_str_fin ? `${date_str_fin}T${assi_evening}` : "",
}
}
const date_deb = document.querySelector(".page #justi_date_debut")
const date_fin = document.querySelector(".page #justi_date_fin")
const journee = document.querySelector('.page #justi_journee').checked
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
const fin = (journee ? date_deb.valueAsObject.date : date_fin.valueAsObject.date) + "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
return {
"deb": document.querySelector(".page #justi_date_debut").value,
"fin": document.querySelector(".page #justi_date_fin").value,
"deb": deb,
"fin": fin,
}
}
@ -265,6 +256,9 @@
loadAll();
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
dayOnly()
document.getElementById("justi_date_debut").valueAsObject = { time: assi_morning }
document.getElementById("justi_date_fin").valueAsObject = { time: assi_evening }
}
</script>
{% endblock pageContent %}

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,27 +87,27 @@
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 moment.tz(dd_val + "T00:00", TIMEZONE);
const date_fin = new moment.tz(df_val + "T23:59", TIMEZONE);
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.format(), date_fin.format())
countAssiduites(date_debut.toIsoUtcString(), date_fin.toIsoUtcString())
}
function getAssiduitesCount(dateDeb, dateFin, query) {
const url_api = getUrl() + `/api/assiduites/${etudid}/count/query?date_debut=${dateDeb}&date_fin=${dateFin}&${query}`;
//Utiliser async_get au lieu de Jquery
return $.ajax({
async: true,
type: "GET",
@ -121,6 +121,7 @@
}
function countAssiduites(dateDeb, dateFin) {
//TODO Utiliser Fetch when plutot que jquery
$.when(
getAssiduitesCount(dateDeb, dateFin, `etat=present`),
getAssiduitesCount(dateDeb, dateFin, `etat=retard`),

View File

@ -4,37 +4,121 @@
<div class="pageContent">
{{minitimeline | safe }}
<h2>Assiduité de {{sco.etud.nomprenom}}</h2>
<div class="options">
<input type="checkbox" id="show_pres" name="show_pres"><label for="show_pres">afficher les présences</label>
<input type="checkbox" name="show_reta" id="show_reta"><label for="show_reta">afficher les retards</label>
<input type="checkbox" name="mode_demi" id="mode_demi" checked><label for="mode_demi">mode demi journée</label>
</div>
<div class="calendrier">
</div>
<div class="annee">
<span>Année scolaire 2022-2023 Changer année: </span>
<span id="label-annee">Année scolaire 2022-2023</span><span id="label-changer" style="margin-left: 5px;">Changer
année: </span>
<select name="" id="annee" onchange="setterAnnee(this.value)">
</select>
<span id="label-nom">Assiduité de {{sco.etud.nomprenom}}</span>
</div>
<div class="help">
<h3>Calendrier</h3>
<p>Les jours non travaillés sont affiché en violet</p>
<p>Les jours possèdant une bordure "bleu" sont des jours où des absences/retards ont été justifiées par un
justificatif valide</p>
<p>Les jours possèdant une bordure "rouge" sont des jours où des absences/retards ont été justifiées par un
justificatif non valide</p>
<p>Le jour sera affiché en : </p>
<ul>
<li>Rouge : s'il y a une absence enregistrée</li>
<li>Orange : s'il y a un retard et pas d'absence</li>
<li>Vert : s'il y a une présence enregistrée mais pas d'absence ni de retard</li>
<li>Blanc : s'il n'y a rien d'enregistré</li>
<p>Code couleur</p>
<ul class="couleurs">
<li><span title="Vert" class="present demo"></span> &rightarrow; présence de l'étudiant lors de la période
</li>
<li><span title="Bleu clair" class="nonwork demo"></span> &rightarrow; la période n'est pas travaillée
</li>
<li><span title="Rouge" class="absent demo"></span> &rightarrow; absence de l'étudiant lors de la période
</li>
<li><span title="Rose" class="demo color absent est_just"></span> &rightarrow; absence justifiée
</li>
<li><span title="Orange" class="retard demo"></span> &rightarrow; retard de l'étudiant lors de la période
</li>
<li><span title="Jaune clair" class="demo color retard est_just"></span> &rightarrow; retard justifié
</li>
<li><span title="Quart Bleu" class="est_just demo"></span> &rightarrow; la période est justifiée par un
justificatif valide</li>
<li><span title="Quart Violet" class="invalide demo"></span> &rightarrow; la période est
justifiée par un justificatif non valide / en attente de validation
</li>
</ul>
<p>Vous pouvez passer le curseur sur les jours colorés afin de voir les informations de cette journée.</p>
<p>Vous pouvez passer le curseur sur les jours colorés afin de voir les informations supplémentaires</p>
</div>
<ul class="couleurs print">
<li><span title="Vert" class="present demo"></span> présence
</li>
<li><span title="Bleu clair" class="nonwork demo"></span> non travaillé
</li>
<li><span title="Rouge" class="absent demo"></span> absence
</li>
<li><span title="Rose" class="demo color absent est_just"></span> absence justifiée
</li>
<li><span title="Orange" class="retard demo"></span> retard
</li>
<li><span title="Jaune clair" class="demo color retard est_just"></span>retard justifié
</li>
<li><span title="Quart Bleu" class="est_just demo"></span>
justificatif valide</li>
<li><span title="Quart Violet" class="invalide demo"></span> justificatif non valide
</li>
</ul>
</div>
<style>
.help .couleurs {
grid-template-columns: 2;
grid-template-rows: auto;
display: grid;
}
.couleurs.print {
display: none;
}
.help .couleurs li:nth-child(odd) {
grid-column: 1;
list-style-type: none;
}
.help .couleurs li:nth-child(even) {
grid-column: 2;
list-style-type: none;
}
.color.present {
background-color: var(--color-present) !important;
}
.color.absent {
background-color: var(--color-absent) !important;
}
.color.absent.est_just {
background-color: var(--color-absent-justi) !important;
}
.color.retard {
background-color: var(--color-retard) !important;
}
.color.retard.est_just {
background-color: var(--color-retard-justi) !important;
}
.color.nonwork {
background-color: var(--color-nonwork) !important;
}
.color {
background-color: var(--color-defaut) !important;
}
.pageContent {
margin-top: 1vh;
max-width: var(--sco-content-max-width);
@ -45,38 +129,71 @@
justify-content: space-evenly;
flex-wrap: wrap;
border: 1px solid #444;
border-radius: 12px;
margin-bottom: 12px;
}
.month h2 {
.month h3 {
text-align: center;
}
.day {
border: 1px solid #444;
border-radius: 8px;
padding: 0 5px;
text-align: center;
.day,
.demi .day.color.nonwork {
text-align: left;
margin: 2px;
cursor: default;
font-size: 12px;
font-size: 13px;
position: relative;
font-weight: normal;
min-width: 6em;
display: flex;
justify-content: start;
}
.day.est_just {
border-left: 10px solid #7059FF;
.color.est_just.sans_etat::before {
content: "";
position: absolute;
width: 25%;
height: 100%;
background-color: var(--color-justi) !important;
right: 0;
}
.day.est_just.invalide {
border-left: 10px solid #f64e4e;
.color.invalide::before {
content: "";
position: absolute;
width: 25%;
height: 100%;
right: 0;
background-color: var(--color-justi-invalide) !important;
}
.demo.invalide {
background-color: var(--color-justi-invalide) !important;
}
.demo.est_just {
background-color: var(--color-justi) !important;
}
.demi .day.nonwork>span {
flex: none;
border: none;
}
.demi .day {
border-radius: 0;
}
.day .dayline {
position: absolute;
display: none;
left: -237%;
bottom: -420%;
bottom: -390%;
z-index: 50;
width: 250px;
width: max-content;
height: 75px;
background-color: #dedede;
border-radius: 15px;
@ -88,7 +205,11 @@
}
.dayline .mini-timeline {
margin-top: 14%;
margin-top: 10%;
}
.dayline-title {
margin: 0;
}
.dayline .mini_tick {
@ -107,14 +228,127 @@
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;
}
@media print {
.couleurs.print {
display: flex;
justify-content: space-evenly;
align-items: center;
}
.couleurs.print li {
list-style-type: none !important;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
.day,
.demi .day.color.color.nonwork {
min-width: 5em;
font-size: 11px;
}
.demi .day>span:first-of-type {
width: 2.5em;
min-width: 2.5em;
}
.color {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
.day.est_just,
.demi .day span.est_just {
background-image: none;
}
.day.invalide,
.demi .day span.invalide {
background-image: none;
}
.demi .day span.est_just::before {
content: "J";
}
.demi .day span.invalide::before {
content: "JI";
}
#sidebar,
.help,
h2,
#annee,
#label-changer,
.options {
display: none;
}
#label-nom,
#label-justi {
display: inline;
}
#gtrcontent {
margin: 5px;
}
.annee {
display: flex;
justify-content: space-evenly;
align-items: center;
}
}
</style>
<script>
function getDaysBetweenDates(start, end) {
let now = moment(start);
let now = new Date(start);
end = new Date(end);
let dates = [];
while (now.isSameOrBefore(end)) {
while (now.isBefore(end) || now.isSame(end)) {
dates.push(now.clone());
now.add(1, "days");
}
@ -126,7 +360,7 @@
let datesByMonth = {};
dates.forEach((date) => {
let month = date.format("MMMM"); // Obtenir le mois
let month = date.toLocaleString('fr-FR', { month: "short" }); // Obtenir le mois
if (!datesByMonth[month]) {
datesByMonth[month] = [];
@ -146,24 +380,22 @@
datesByMonth[month].forEach((date) => {
let dayAssiduities = assiduities.filter((assiduity) => {
return moment.tz(date, TIMEZONE).isBetween(
moment.tz(assiduity.date_debut, TIMEZONE),
moment.tz(assiduity.date_fin, TIMEZONE),
"day",
return new Date(date).isBetween(
new Date(assiduity.date_debut).startOf("day"),
new Date(assiduity.date_fin).startOf("day"),
"[]"
)
});
let dayJustificatifs = justificatifs.filter((justif) => {
return moment.tz(date, TIMEZONE).isBetween(
moment.tz(justif.date_debut, TIMEZONE),
moment.tz(justif.date_fin, TIMEZONE),
"day",
return new Date(date).isBetween(
new Date(justif.date_debut).startOf("day"),
new Date(justif.date_fin).startOf("day"),
"[]"
)
});
assiduitiesByDay[month][date.format("YYYY-MM-DD")] = {
assiduitiesByDay[month][date.toLocaleDateString("en-US")] = {
assiduites: dayAssiduities,
justificatifs: dayJustificatifs
};
@ -173,108 +405,190 @@
return assiduitiesByDay;
}
function getDayColor(etat) {
let color;
switch (etat.toUpperCase()) {
case "PRESENT":
color = "#6bdb83";
break;
case "ABSENT":
color = "#F1A69C";
break;
case "RETARD":
color = "#f0c865";
break;
case "NONWORK":
color = "#bd81ca"
break;
default:
color = "#FFF";
break;
}
return color;
}
function generateCalendar(assiduitiesByDay, nonWorkdays = []) {
const calendar = document.querySelector('.calendrier')
const options = getOptions();
calendar.innerHTML = ""
const days = {
Mon: "Lun",
Tue: "Mar",
Wed: "Mer",
Thu: "Jeu",
Fri: "Ven",
Sat: "Sam",
Sun: "Dim",
};
const months = {
January: "Jan.",
February: "Fev.",
March: "Mar.",
April: "Avr.",
May: "Mai",
June: "Juin",
July: "Juil.",
August: "Août",
September: "Sep.",
October: "Oct.",
November: "Nov.",
December: "Déc.",
1: "Lun",
2: "Mar",
3: "Mer",
4: "Jeu",
5: "Ven",
6: "Sam",
0: "Dim",
};
Object.keys(assiduitiesByDay).forEach((month) => {
const monthEl = document.createElement('div')
monthEl.classList.add("month")
const title = document.createElement('h2');
title.textContent = `${months[month]}`;
const title = document.createElement('h3');
title.textContent = `${month.capitalize()}`;
monthEl.appendChild(title)
const daysEl = document.createElement('div')
daysEl.classList.add('days');
if (options.mode_demi) daysEl.classList.add("demi");
Object.keys(assiduitiesByDay[month]).forEach((date) => {
let dayAssiduities = assiduitiesByDay[month][date].assiduites;
let dayJustificatifs = assiduitiesByDay[month][date].justificatifs;
let color = "white";
let color = "sans_etat";
if (dayAssiduities.some((a) => a.etat.toLowerCase() === "absent")) color = "absent";
else if (dayAssiduities.some((a) => a.etat.toLowerCase() === "retard"))
else if (dayAssiduities.some((a) => a.etat.toLowerCase() === "retard") && options.show_reta)
color = "retard";
else if (dayAssiduities.some((a) => a.etat.toLowerCase() === "present"))
else if (dayAssiduities.some((a) => a.etat.toLowerCase() === "present") && options.show_pres)
color = "present";
let est_just = ""
if (dayJustificatifs.some((j) => j.etat.toLowerCase() === "valide")) {
est_just = "est_just";
} else if (dayJustificatifs.some((j) => j.etat.toLowerCase() !== "valide")) {
est_just = "est_just invalide";
est_just = "invalide";
}
const momentDate = moment.tz(date, TIMEZONE);
let dayOfMonth = momentDate.format("D");
let dayOfWeek = momentDate.format("ddd");
const momentDate = new Date(date);
let dayOfMonth = momentDate.getDate();
let dayOfWeek = momentDate.getDay();
dayOfWeek = days[dayOfWeek];
if (nonWorkdays.includes(dayOfWeek.toLowerCase())) color = "nonwork";
let isNonWorkDay = nonWorkdays.includes(dayOfWeek.toLowerCase());
const day = document.createElement('div');
day.className = `day ${est_just}`
day.className = `day`;
if (isNonWorkDay) {
color = "nonwork";
} else if (!options.mode_demi) {
day.className = `day ${est_just}`;
}
day.style.backgroundColor = getDayColor(color);
day.textContent = `${dayOfWeek} ${dayOfMonth}`;
if (options.mode_demi && !isNonWorkDay) {
est_just = []
// affichage n° jour + matin + aprem
const span_jour = document.createElement("span")
span_jour.textContent = dayOfWeek[0] + dayOfMonth;
const span_matin = document.createElement("span");
span_matin.classList.add("color");
const matin = [new Date(date), new Date(date)]
color = "sans_etat"
matin[0].setHours(0, 0, 0, 0)
matin[1].setHours(12, 59, 59)
const assiduitesMatin = dayAssiduities.filter((el) => {
const deb = new Date(el.date_debut);
const fin = new Date(el.date_fin);
return Date.intersect({ deb: deb, fin: fin }, { deb: matin[0], fin: matin[1] })
})
const justificatifsMatin = dayJustificatifs.filter((el) => {
const deb = new Date(el.date_debut);
const fin = new Date(el.date_fin);
return Date.intersect({ deb: deb, fin: fin }, { deb: matin[0], fin: matin[1] })
})
if (assiduitesMatin.some((a) => a.etat.toLowerCase() === "absent")) color = "absent";
else if (assiduitesMatin.some((a) => a.etat.toLowerCase() === "retard") && options.show_reta)
color = "retard";
else if (assiduitesMatin.some((a) => a.etat.toLowerCase() === "present") && options.show_pres)
color = "present";
if (color != "") {
span_matin.classList.add(color);
}
if (justificatifsMatin.some((j) => j.etat.toLowerCase() === "valide")) {
est_just = ["est_just"];
} else if (justificatifsMatin.some((j) => j.etat.toLowerCase() !== "valide")) {
est_just = ["invalide"];
}
span_matin.classList.add(...est_just)
est_just = []
const span_aprem = document.createElement("span");
span_aprem.classList.add("color");
const aprem = [new Date(date), new Date(date)]
color = "sans_etat"
aprem[0].setHours(13, 0, 0, 0)
aprem[1].setHours(23, 59, 59)
const assiduitesAprem = dayAssiduities.filter((el) => {
const deb = new Date(el.date_debut);
const fin = new Date(el.date_fin);
return Date.intersect({ deb: deb, fin: fin }, { deb: aprem[0], fin: aprem[1] })
})
const justificatifsAprem = dayJustificatifs.filter((el) => {
const deb = new Date(el.date_debut);
const fin = new Date(el.date_fin);
return Date.intersect({ deb: deb, fin: fin }, { deb: aprem[0], fin: aprem[1] })
})
if (assiduitesAprem.some((a) => a.etat.toLowerCase() === "absent")) color = "absent";
else if (assiduitesAprem.some((a) => a.etat.toLowerCase() === "retard") && options.show_reta)
color = "retard";
else if (assiduitesAprem.some((a) => a.etat.toLowerCase() === "present") && options.show_pres)
color = "present";
if (color != "") {
span_aprem.classList.add(color);
}
if (justificatifsAprem.some((j) => j.etat.toLowerCase() === "valide")) {
est_just = ["est_just"];
} else if (justificatifsAprem.some((j) => j.etat.toLowerCase() !== "valide")) {
est_just = ["invalide"];
}
span_aprem.classList.add(...est_just)
day.appendChild(span_jour)
day.appendChild(span_matin)
day.appendChild(span_aprem)
} else {
day.classList.add("color")
if (color != "") {
day.classList.add(color);
}
if (isNonWorkDay) {
const span_jour = document.createElement("span")
span_jour.textContent = dayOfWeek[0] + dayOfMonth;
day.appendChild(span_jour);
} else {
day.textContent = `${dayOfWeek} ${dayOfMonth}`;
}
}
console.warn(day.classList, day.classList.length)
if (!nonWorkdays.includes(dayOfWeek.toLowerCase()) && dayAssiduities.length > 0) {
const cache = document.createElement('div')
cache.classList.add('dayline');
const title = document.createElement('div')
title.className = "dayline-title";
title.innerHTML = "<span>Assiduité du </span><br>" + `<span>${formatDate(momentDate)}</span>`;
cache.appendChild(title)
cache.appendChild(
createMiniTimeline(dayAssiduities, date)
)
day.appendChild(cache)
}
daysEl.appendChild(day);
});
monthEl.appendChild(daysEl)
@ -287,13 +601,19 @@
const url_api =
getUrl() +
`/api/assiduites/${etudid}/query?date_debut=${deb}&date_fin=${fin}`;
async_get(url_api, (data, status) => {
if (status === "success") {
callback(data);
}
async_get(url_api, (data) => {
callback(data);
});
}
function getOptions() {
return {
"show_pres": document.getElementById("show_pres").checked,
"show_reta": document.getElementById("show_reta").checked,
"mode_demi": document.getElementById("mode_demi").checked,
}
}
function getEtudJustificatifs(deb, fin) {
let list = [];
const url_api =
@ -309,7 +629,6 @@
}
function generate(annee) {
if (annee < 1999 || annee > 2999) {
openAlertModal("Année impossible", document.createTextNode("L'année demandé n'existe pas."));
return;
@ -331,10 +650,11 @@
function setterAnnee(annee) {
annee = parseInt(annee);
document.querySelector('.annee span').textContent = `Année scolaire ${annee}-${annee + 1} Changer année: `
document.querySelector('.annee #label-annee').textContent = `Année scolaire ${annee}-${annee + 1}`
generate(annee)
}
const defAnnee = {{ annee }}
let annees = {{ annees | safe }}
annees = annees.filter((x, i) => annees.indexOf(x) === i)
@ -351,6 +671,12 @@
}
select.appendChild(opt)
})
document.querySelectorAll(".options input").forEach((e) => {
e.addEventListener("click", () => {
setterAnnee(select.value)
})
})
setterAnnee(defAnnee)
};

View File

@ -41,7 +41,7 @@
if (defaultDates != null) {
defaultDates.forEach((dateString) => {
d = moment(dateString).weekday();
d = new Date(dateString).getDay();
if (verifyNonWorkDays(d, nonWorkDays)) return;

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}}
@ -59,18 +59,9 @@
Correspondance des couleurs :
</p>
<ul>
<li><span title="Vert" class="present demo"></span> &rightarrow; présence de l'étudiant lors de la période
</li>
<li><span title="Orange" class="retard demo"></span> &rightarrow; retard de l'étudiant lors de la période
</li>
<li><span title="Rouge" class="absent demo"></span> &rightarrow; absence de l'étudiant lors de la période
</li>
<li><span title="Hachure Bleue" class="justified demo"></span> &rightarrow; l'assiduité est justifiée par un
justificatif valide</li>
<li><span title="Hachure Rouge" class="invalid_justified demo"></span> &rightarrow; l'assiduité est
justifiée par un justificatif non valide / en attente de validation
</li>
{% include "assiduites/widgets/legende_couleur.j2" %}
</ul>
<p>Vous pouvez justifier rapidement une assiduité en saisisant l'assiduité puis en appuyant sur "Justifier"</p>
<h3>Explication de la saisie différée</h3>

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>
@ -69,17 +70,7 @@
Correspondance des couleurs :
</p>
<ul>
<li><span title="Vert" class="present demo"></span> &rightarrow; présence de l'étudiant lors de la période
</li>
<li><span title="Orange" class="retard demo"></span> &rightarrow; retard de l'étudiant lors de la période
</li>
<li><span title="Rouge" class="absent demo"></span> &rightarrow; absence de l'étudiant lors de la période
</li>
<li><span title="Hachure Bleue" class="justified demo"></span> &rightarrow; l'assiduité est justifiée par un
justificatif valide</li>
<li><span title="Hachure Rouge" class="invalid_justified demo"></span> &rightarrow; l'assiduité est
justifiée par un justificatif non valide / en attente de validation
</li>
{% include "assiduites/widgets/legende_couleur.j2" %}
</ul>
</div>
<!-- Ajout d'un conteneur pour le loader -->

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test DateUtils</title>
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
</head>
<body>
<form action="" method="post">
<scodoc-datetime name="scodoc-datetime"></scodoc-datetime>
<input type="submit" value="valider">
</form>
</body>
</html>

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

@ -127,7 +127,7 @@
<script>
const alertmodal = document.getElementById("alertModal");
function openAlertModal(titre, contenu, footer, color = "crimson") {
function openAlertModal(titre, contenu, footer, color = "var(--color-error)") {
alertmodal.classList.add('is-active');
alertmodal.querySelector('.alertmodal-title').textContent = titre;

View File

@ -5,8 +5,8 @@
* @returns {String} un déplacement par rapport à la gauche en %
*/
function getLeftPosition(start) {
const startTime = new moment.tz(start, TIMEZONE);
const startMins = (startTime.hours() - 8) * 60 + startTime.minutes();
const startTime = new Date(start);
const startMins = (startTime.getHours() - 8) * 60 + startTime.getMinutes();
return (startMins / (18 * 60 - 8 * 60)) * 100 + "%";
}
/**
@ -21,8 +21,8 @@
return a.start < b.end && a.end > b.start;
};
const startTime = new moment.tz(start, TIMEZONE);
const endTime = new moment.tz(end, TIMEZONE);
const startTime = new Date(start);
const endTime = new Date(end);
const assiduiteDuration = { start: startTime, end: endTime };
let position = 0;
@ -31,8 +31,8 @@
while (hasOverlap) {
hasOverlap = false;
Array.from(container.children).some((el) => {
const elStart = new moment.tz(el.getAttribute("data-start"));
const elEnd = new moment.tz(el.getAttribute("data-end"));
const elStart = new Date(el.getAttribute("data-start"));
const elEnd = new Date(el.getAttribute("data-end"));
const elDuration = { start: elStart, end: elEnd };
if (overlaps(assiduiteDuration, elDuration)) {
@ -54,13 +54,13 @@
function getColor(state) {
switch (state) {
case "PRESENT":
return "#9CF1AF";
return "var(--color-present)";
case "ABSENT":
return "#F1A69C";
return "var(--color-absent)";
case "RETARD":
return "#F1D99C";
return "var(--color-retard)";
default:
return "gray";
return "var(--color-defaut-dark)";
}
}
@ -71,8 +71,8 @@
* @returns {String} la taille en %
*/
function getWidth(start, end) {
const startTime = new moment.tz(start, TIMEZONE);
const endTime = new moment.tz(end, TIMEZONE);
const startTime = new Date(start);
const endTime = new Date(end);
const duration = (endTime - startTime) / 1000 / 60;
@ -258,11 +258,11 @@
const success = () => {
const separatorTime = document.getElementById("promptTime").value;
const dateString =
document.querySelector("#tl_date").value + `T${separatorTime}`;
const separtorDate = new moment.tz(dateString, TIMEZONE);
getDate().format("YYYY-MM-DD") + `T${separatorTime}`;
const separtorDate = new Date(dateString);
const assiduite_debut = new moment.tz(this.selectedAssiduite.date_debut, TIMEZONE);
const assiduite_fin = new moment.tz(this.selectedAssiduite.date_fin, TIMEZONE);
const assiduite_debut = new Date(this.selectedAssiduite.date_debut);
const assiduite_fin = new Date(this.selectedAssiduite.date_fin);
if (
separtorDate.isAfter(assiduite_debut) &&
@ -270,14 +270,14 @@
) {
const assiduite_avant = {
etat: this.selectedAssiduite.etat,
date_debut: assiduite_debut.format(),
date_fin: separtorDate.format(),
date_debut: assiduite_debut.toIsoUtcString()(),
date_fin: separtorDate.toIsoUtcString()(),
};
const assiduite_apres = {
etat: this.selectedAssiduite.etat,
date_debut: separtorDate.format(),
date_fin: assiduite_fin.format(),
date_debut: separtorDate.toIsoUtcString()(),
date_fin: assiduite_fin.toIsoUtcString()(),
};
if (this.selectedAssiduite.moduleimpl_id) {
@ -309,11 +309,11 @@
"L'heure de séparation doit être compris dans la période de l'assiduité sélectionnée."
);
openAlertModal("Attention", att, "", "#ecb52a");
openAlertModal("Attention", att, "", "var(--color-warning))");
}
};
openPromptModal("Séparation de l'assiduité sélectionnée", fieldSet, success, () => { }, "#23aa40");
openPromptModal("Séparation de l'assiduité sélectionnée", fieldSet, success, () => { }, "var(--color-present)");
}
/**
@ -342,7 +342,7 @@
const att = document.createTextNode(
"L'état doit être 'present', 'absent' ou 'retard'."
);
openAlertModal("Attention", att, "", "#ecb52a");
openAlertModal("Attention", att, "", "var(--color-warning)");
return;
}
@ -357,7 +357,7 @@
};
//Affichage du prompt
openPromptModal("Modification de l'état de l'assiduité sélectionnée", fieldSet, success, () => { }, "#23aa40");
openPromptModal("Modification de l'état de l'assiduité sélectionnée", fieldSet, success, () => { }, "var(--color-present)");
}
/**
@ -396,8 +396,8 @@
//Placement des assiduités sur la timeline
this.list.forEach((assiduite) => {
const period = {
deb: new moment.tz(assiduite.date_debut, TIMEZONE),
fin: new moment.tz(assiduite.date_fin, TIMEZONE),
deb: new Date(assiduite.date_debut),
fin: new Date(assiduite.date_fin),
};
if (!hasTimeConflict(period, this.interval)) {
return;

View File

@ -147,7 +147,7 @@
}
#addColumn:hover {
background-color: #0056b3;
background-color: var(--color-primary);
}
.th {
@ -157,7 +157,7 @@
}
.th.error {
background-color: #d71111;
background-color: var(--color-error);
}
.tbody .tr:nth-child(even) {
@ -226,7 +226,7 @@
}
.td[assiduite_id='conflit'] {
background-color: #ff0000c2;
background-color: var(--color-error);
}
input[type='radio']:disabled {
@ -237,7 +237,7 @@
.th.error:hover .col-error {
display: block;
z-index: 2000;
background-color: #d71111;
background-color: var(--color-error);
width: 100%;
min-height: 25%;
bottom: -25%;
@ -274,17 +274,21 @@
<script>
const assi_etat_defaut = "{{assi_etat_defaut}}";
window.forceModule = "{{ forcer_module }}"
window.forceModule = window.forceModule == "True" ? true : false
{% if scu.is_assiduites_module_forced(request.args.get('formsemestre_id', None)) %}
window.forceModule = true;
{% else %}
window.forceModule = false;
{% endif %}
let colCount = 1;
let currentDate = "{{date}}";
if (currentDate == "") {
currentDate = moment().tz(TIMEZONE).format("YYYY-MM-DDTHH:mm");
currentDate = new Date();
currentDate = currentDate.format("YYYY-MM-DDTHH:mm");
} else {
currentDate = moment(currentDate).tz(TIMEZONE).format("YYYY-MM-DDTHH:mm");
currentDate = new Date(currentDate);
currentDate = currentDate.format("YYYY-MM-DDTHH:mm");
}
@ -432,7 +436,10 @@
const last = document.querySelector(`[col='${num}'] #dateEnd`);
let date = undefined;
if (last == undefined) {
date = currentDate;
date = new Date(currentDate);
const start = typeof mt_start !== 'undefined' ? mt_start : 0
date.setHours(start);
date = date.format("YYYY-MM-DDTHH:mm")
} else {
date = last.value;
}
@ -444,9 +451,10 @@
() => {
const el = element.parentElement.querySelector("#dateEnd");
const el2 = element.parentElement.querySelector("#dateStart");
el.value = moment(el2.valueAsDate).tz(TIMEZONE).utc()
.add(2, "hours")
.format("YYYY-MM-DDTHH:mm");
const newDate = new Date(el2.valueAsDate)
.add(2, "hours");
el.valueAsDate = newDate;
const colid = element.parentElement.parentElement.getAttribute('col');
getAndUpdateCol(colid);
},
@ -473,16 +481,16 @@
let color;
switch (etatId) {
case 0:
color = "#9CF1AF";
color = "var(--color-present)";
break
case 1:
color = "#F1D99C";
color = "var(--color-retard)";
break
case 2:
color = "#F1A69C";
color = "var(--color-absent)";
break
default:
color = "white";
color = "var(--color-defaut)";
break;
}
@ -517,8 +525,8 @@
const inputDeb = col.querySelector("#dateStart").value;
const inputFin = col.querySelector("#dateEnd").value;
const moduleSelect = col.querySelector("#moduleimpl_select,.dynaSelect").value;
const d_debut = moment(inputDeb).tz(TIMEZONE);
const d_fin = moment(inputFin).tz(TIMEZONE);
const d_debut = new Date(inputDeb);
const d_fin = new Date(inputFin);
if (inputDeb == "" || inputFin == "" || d_debut >= d_fin) {
@ -547,14 +555,14 @@
}
if (get) {
getAssiduitesFromEtuds(false, d_debut.format(), d_fin.format())
getAssiduitesFromEtuds(false, d_debut.toIsoUtcString(), d_fin.toIsoUtcString())
return 0x0;
}
return {
moduleimpl: moduleSelect,
deb: d_debut.format(),
fin: d_fin.format(),
deb: d_debut.toIsoUtcString(),
fin: d_fin.toIsoUtcString(),
}
@ -568,8 +576,8 @@
const inputDeb = col.querySelector("#dateStart").value;
const inputFin = col.querySelector("#dateEnd").value;
const d_debut = moment(inputDeb).tz(TIMEZONE);
const d_fin = moment(inputFin).tz(TIMEZONE);
const d_debut = new Date(inputDeb);
const d_fin = new Date(inputFin);
const moduleimpl_id = col.querySelector("#moduleimpl_select,.dynaSelect").value;
@ -671,13 +679,13 @@
switch (etat.toUpperCase()) {
case "PRESENT":
color = "#6bdb83";
color = "var(--color-present)";
break;
case "ABSENT":
color = "#F1A69C";
color = "var(--color-absent)";
break;
case "RETARD":
color = "#f0c865";
color = "var(--color-retard)";
break;
default:
color = "#AAA";
@ -715,15 +723,27 @@
assiduites[etudid].push(assiduite);
updateAllCol()
launchToast(etudid, etat);
} else {
let error = data.errors[Object.keys(data.errors)[0]];
if (error.message.indexOf("Module") != -1) {
const html = `
<h3>Aucun module n'a été spécifié (préférence du semestre concerné)</h3>
`;
const div = document.createElement("div");
div.innerHTML = html;
openAlertModal("Erreur Module", div);
rbtn.checked = false;
}
}
})
break;
case "conflit":
// Conflit, afficher résolveur
const assiduitesList = assiduites[etudid];
const d_debut = new moment.tz(deb, TIMEZONE);
const d_fin = new moment.tz(fin, TIMEZONE);
const d_debut = new Date(deb);
const d_fin = new Date(fin);
const period = {
deb: deb,
@ -733,8 +753,8 @@
assiduites[etudid],
period,
{
deb: new moment.tz(d_debut.startOf('day'), TIMEZONE),
fin: new moment.tz(d_fin.endOf('day'), TIMEZONE),
deb: d_debut.startOf('day'),
fin: d_fin.endOf('day'),
}
);
const update = () => {
@ -944,11 +964,24 @@
})
).done((c, e) => {
let error;
Object.keys(c[0].success).forEach((k) => {
const assiduite = createList[Number.parseInt(k)];
assiduite["assiduite_id"] = c[0].success[k].message.assiduite_id;
assiduites[assiduite.etudid].push(assiduite);
})
if (c[0].errors.length > 0) {
error = c[0].errors[Object.keys(c[0].errors)[0]];
if (error.message.indexOf("Module") != -1) {
const html = `
<h3>Aucun module n'a été spécifié (préférence du semestre concerné)</h3>
`;
const div = document.createElement("div");
div.innerHTML = html;
openAlertModal("Erreur Module", div);
toCreate.length = 0
}
}
Object.keys(e[0].success).forEach((k) => {
const { etudid, assiduite_id, moduleimpl_id, etat } = editList[Number.parseInt(k)]
assiduites[etudid].map((a) => {

View File

@ -0,0 +1,12 @@
<li><span title="Vert" class="present demo"></span> &rightarrow; présence de l'étudiant lors de la période
</li>
<li><span title="Orange" class="retard demo"></span> &rightarrow; retard de l'étudiant lors de la période
</li>
<li><span title="Rouge" class="absent demo"></span> &rightarrow; absence de l'étudiant lors de la période
</li>
<li><span title="Hachure Bleue" class="justified demo"></span> &rightarrow; l'assiduité est justifiée par un
justificatif valide</li>
<li><span title="Hachure Rouge" class="invalid_justified demo"></span> &rightarrow; l'assiduité est
justifiée par un justificatif non valide / en attente de validation
</li>

View File

@ -13,16 +13,17 @@
*/
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()) {
timeline.classList.add("single");
}
const timelineDate = moment(dateiso).startOf("day");
const timelineDate = new Date(dateiso).startOf("day");
const dayStart = timelineDate.clone().add(mt_start, "hours");
const dayEnd = timelineDate.clone().add(mt_end, "hours");
const dayDuration = moment.duration(dayEnd.diff(dayStart)).asMinutes();
const dayDuration = new Duration(dayStart, dayEnd).minutes;
timeline.appendChild(setMiniTick(timelineDate, dayStart, dayDuration));
@ -38,8 +39,8 @@
}
array.forEach((assiduité) => {
let startDate = moment(assiduité.date_debut);
let endDate = moment(assiduité.date_fin);
let startDate = new Date(assiduité.date_debut);
let endDate = new Date(assiduité.date_fin);
if (startDate.isBefore(dayStart)) {
startDate = dayEnd.clone().startOf("day").add(mt_start, "hours");
@ -51,8 +52,8 @@
const block = document.createElement("div");
block.className = "mini-timeline-block";
const duration = moment.duration(endDate.diff(startDate)).asMinutes();
const startOffset = moment.duration(startDate.diff(dayStart)).asMinutes();
const duration = new Duration(startDate, endDate).minutes;
const startOffset = new Duration(dayStart, startDate).minutes;
const leftPercentage = (startOffset / dayDuration) * 100;
const widthPercentage = (duration / dayDuration) * 100;
@ -61,8 +62,8 @@
if (assiduité.etat != "CRENEAU") {
block.addEventListener("click", () => {
let deb = startDate.hours() + startDate.minutes() / 60;
let fin = endDate.hours() + endDate.minutes() / 60;
let deb = startDate.getHours() + startDate.getMinutes() / 60;
let fin = endDate.getHours() + endDate.getMinutes() / 60;
deb = Math.max(mt_start, deb);
fin = Math.min(mt_end, fin);
@ -98,8 +99,8 @@
if (assiduité.etudid) {
getJustificatifFromPeriod(
{
deb: new moment.tz(assiduité.date_debut, TIMEZONE),
fin: new moment.tz(assiduité.date_fin, TIMEZONE),
deb: new Date(assiduité.date_debut),
fin: new Date(assiduité.date_fin),
},
assiduité.etudid,
action
@ -166,7 +167,7 @@
userIdDiv.className = "assiduite-user_id";
userIdDiv.textContent = `saisie le ${formatDateModal(
assiduite.entry_date,
"à"
" à "
)}`;
if (assiduite.user_id != null) {
@ -184,8 +185,9 @@
}
function setMiniTick(timelineDate, dayStart, dayDuration) {
const endDate = timelineDate.clone().set({ 'hour': 13, 'minute': 0 });
const duration = moment.duration(endDate.diff(dayStart)).asMinutes();
const endDate = timelineDate.clone().startOf("day");
endDate.setHours(13, 0);
const duration = new Duration(dayStart, endDate).minutes;
const widthPercentage = (duration / dayDuration) * 100;
const tick = document.createElement('span');
tick.className = "mini_tick"
@ -245,15 +247,15 @@
}
.assiduite-bubble.absent {
border-color: #F1A69C !important;
border-color: var(--color-absent) !important;
}
.assiduite-bubble.present {
border-color: #9CF1AF !important;
border-color: var(--color-present) !important;
}
.assiduite-bubble.retard {
border-color: #F1D99C !important;
border-color: var(--color-retard) !important;
}
.mini-timeline {
@ -295,27 +297,27 @@
}
.mini-timeline-block.creneau {
outline: 3px solid #7059FF;
outline: 3px solid var(--color-primary);
pointer-events: none;
}
.mini-timeline-block.absent {
background-color: #F1A69C !important;
background-color: var(--color-absent) !important;
}
.mini-timeline-block.present {
background-color: #9CF1AF !important;
background-color: var(--color-present) !important;
}
.mini-timeline-block.retard {
background-color: #F1D99C !important;
background-color: var(--color-retard) !important;
}
.mini-timeline-block.justified {
background-image: repeating-linear-gradient(135deg, transparent, transparent 4px, #7059FF 4px, #7059FF 8px);
background-image: var(--motif-justi);
}
.mini-timeline-block.invalid_justified {
background-image: repeating-linear-gradient(135deg, transparent, transparent 4px, #d61616 4px, #d61616 8px);
background-image: var(--motif-justi-invalide);
}
</style>

View File

@ -21,13 +21,9 @@
}
function filterFormSemestres(semestres, dateIso) {
const date = new moment.tz(
dateIso,
TIMEZONE
);
const date = new Date(dateIso);
semestres = semestres.filter((fm) => {
return date.isBetween(fm.date_debut_iso, fm.date_fin_iso, null, '[]')
return date.isBetween(new Date(fm.date_debut_iso), new Date(fm.date_fin_iso), '[]');
})
return semestres;
@ -97,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

@ -158,7 +158,7 @@
<script>
const promptModal = document.getElementById("promptModal");
function openPromptModal(titre, contenu, success, cancel = () => { }, color = "crimson") {
function openPromptModal(titre, contenu, success, cancel = () => { }, color = "var(--color-error)") {
promptModal.classList.add('is-active');
promptModal.querySelector('.promptModal-title').textContent = titre;

View File

@ -83,7 +83,7 @@
filterAssiduites.columns.forEach((k) => {
const td = document.createElement('td');
if (k.indexOf('date') != -1) {
td.textContent = moment.tz(assiduite[k], TIMEZONE).format(`DD/MM/Y HH:mm`)
td.textContent = new Date(assiduite[k]).format(`DD/MM/Y HH:mm`)
} else if (k.indexOf("module") != -1) {
td.textContent = getModuleImpl(assiduite);
} else if (k.indexOf('est_just') != -1) {
@ -115,9 +115,9 @@
const user = getUser(data);
const module = getModuleImpl(data);
const date_debut = moment.tz(data.date_debut, TIMEZONE).format("DD/MM/YYYY HH:mm");
const date_fin = moment.tz(data.date_fin, TIMEZONE).format("DD/MM/YYYY HH:mm");
const entry_date = moment.tz(data.entry_date, TIMEZONE).format("DD/MM/YYYY HH:mm");
const date_debut = new Date(data.date_debut).format("DD/MM/YYYY HH:mm");
const date_fin = new Date(data.date_fin).format("DD/MM/YYYY HH:mm");
const entry_date = new Date(data.entry_date).format("DD/MM/YYYY HH:mm");
const etat = data.etat.capitalize();
const desc = data.desc == null ? "" : data.desc;
@ -174,7 +174,7 @@
const el = document.createElement('div');
el.innerHTML = html;
openAlertModal("Détails", el.firstElementChild, null, "green")
openAlertModal("Détails", el.firstElementChild, null, "var(--color-information)")
}
)
}
@ -245,7 +245,7 @@
})
}, () => { }, "green");
}, () => { }, "var(--color-information)");
}
);
}
@ -433,7 +433,7 @@
if (l.querySelector(`#${key}_time`).value != "") {
filterAssiduites.filters[key] = {
pref: pref,
time: new moment.tz(time, TIMEZONE)
time: new Date(time)
}
}
} else if (key.indexOf('etat') != -1) {
@ -449,7 +449,7 @@
getAssi(assiduiteCallBack)
}, () => { }, "#7059FF");
}, () => { }, "var(--color-primary)");
}

View File

@ -75,7 +75,7 @@
}
}
if (k.indexOf('date') != -1) {
const assi_time = moment.tz(el[k], TIMEZONE);
const assi_time = new Date(el[k]);
const filter_time = f[k].time;
switch (f[k].pref) {
@ -262,8 +262,8 @@
let keyValueB = b[keyword];
if (keyword.indexOf("date") != -1) {
keyValueA = moment.tz(keyValueA, TIMEZONE)
keyValueB = moment.tz(keyValueB, TIMEZONE)
keyValueA = new Date(keyValueA)
keyValueB = new Date(keyValueB)
}
if (keyword.indexOf("module") != -1) {
@ -385,7 +385,7 @@
downloadStr(data, input.value ? input.value : "download.csv")
}, () => { }, "green");
}, () => { }, "var(--color-present)");
}
function toCSV(array, filters) {
@ -489,24 +489,24 @@
}
#deleteOption {
background-color: #F1A69C;
background-color: var(--color-absent);
}
.l-present {
background-color: #9CF1AF;
background-color: var(--color-present)
}
.l-absent,
.l-invalid {
background-color: #F1A69C;
background-color: var(--color-absent);
}
.l-valid {
background-color: #8f7eff;
background-color: var(--color-primary);
}
.l-retard {
background-color: #F1D99C;
background-color: var(--color-retard);
}
/* Ajoutez des styles pour le conteneur de pagination et les boutons */
@ -531,9 +531,9 @@
}
.pagination-button.active {
background-color: #007bff;
background-color: var(--color-primary);
color: #fff;
border-color: #007bff;
border-color: var(--color-primary);
}
th>div {

View File

@ -82,7 +82,7 @@
filterJustificatifs.columns.forEach((k) => {
const td = document.createElement('td');
if (k.indexOf('date') != -1) {
td.textContent = moment.tz(justificatif[k], TIMEZONE).format(`DD/MM/Y HH:mm`)
td.textContent = new Date(justificatif[k]).format(`DD/MM/Y HH:mm`)
} else if (k.indexOf('fichier') != -1) {
td.textContent = justificatif.fichier ? "Oui" : "Non";
} else if (k.indexOf('etudid') != -1) {
@ -120,9 +120,9 @@
path,
(data) => {
const user = getUser(data);
const date_debut = moment.tz(data.date_debut, TIMEZONE).format("DD/MM/YYYY HH:mm");
const date_fin = moment.tz(data.date_fin, TIMEZONE).format("DD/MM/YYYY HH:mm");
const entry_date = moment.tz(data.entry_date, TIMEZONE).format("DD/MM/YYYY HH:mm");
const date_debut = new Date(data.date_debut).format("DD/MM/YYYY HH:mm");
const date_fin = new Date(data.date_fin).format("DD/MM/YYYY HH:mm");
const entry_date = new Date(data.entry_date).format("DD/MM/YYYY HH:mm");
const etat = data.etat.capitalize();
const desc = data.raison == null ? "" : data.raison;
@ -201,7 +201,7 @@
fichContent.appendChild(a);
})
openAlertModal("Détails", el.firstElementChild, null, "green")
openAlertModal("Détails", el.firstElementChild, null, "var(--color-information)")
}
)
}
@ -305,9 +305,8 @@
assiEdit.querySelector('#justi_etat').value = data.etat.toLowerCase();
assiEdit.querySelector('#justi_raison').value = desc;
const d_deb = moment.tz(data.date_debut, TIMEZONE).format("YYYY-MM-DDTHH:mm")
const d_fin = moment.tz(data.date_fin, TIMEZONE).format("YYYY-MM-DDTHH:mm")
console.warn(d_deb, d_fin, data.date_debut, data.date_fin)
const d_deb = new Date(data.date_debut).format("YYYY-MM-DDTHH:mm")
const d_fin = new Date(data.date_fin).format("YYYY-MM-DDTHH:mm")
assiEdit.querySelector('#justi_date_debut').value = d_deb
assiEdit.querySelector('#justi_date_fin').value = d_fin
@ -357,8 +356,8 @@
openAlertModal("Dates erronées", document.createTextNode('Les dates sont invalides'));
return true
}
date_debut = moment.tz(date_debut, TIMEZONE)
date_fin = moment.tz(date_fin, TIMEZONE)
date_debut = new Date(date_debut)
date_fin = new Date(date_fin)
if (date_debut >= date_fin) {
openAlertModal("Dates erronées", document.createTextNode('La date de fin doit être après la date de début'));
@ -389,7 +388,7 @@
})
}, () => { }, "green");
}, () => { }, "var(--color-information)");
}
);
}
@ -626,7 +625,7 @@
if (l.querySelector(`#${key}_time`).value != "") {
filterJustificatifs.filters[key] = {
pref: pref,
time: new moment.tz(time, TIMEZONE)
time: new Date(time)
}
}
} else if (key.indexOf('etat') != -1) {
@ -643,7 +642,7 @@
loadAll();
}, () => { }, "#7059FF");
}, () => { }, "var(--color-primary)");
}
function downloadJusti() {

View File

@ -144,7 +144,6 @@
const startLeft = parseFloat(periodTimeLine.style.left);
const onMouseMove = (moveEvent) => {
console.warn("move Period")
if (handleMoving) return;
const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
const containerWidth = timelineContainer.clientWidth;
@ -320,7 +319,7 @@
.period {
position: absolute;
height: 100%;
background-color: rgba(0, 183, 255, 0.5);
background-color: var(--color-secondary);
border-radius: 15px;
}
@ -352,7 +351,7 @@
.period:hover .period-time {
display: flex;
background-color: rgba(0, 183, 255, 1);
background-color: var(--color-secondary);
border-radius: 15px;
padding: 5px;
}

View File

@ -68,7 +68,7 @@
<script>
function generateToast(content, color = "#12d3a5", ttl = 5) {
function generateToast(content, color = "var(--color-present)", ttl = 5) {
const toast = document.createElement('div')
toast.classList.add('toast', 'fadeIn')
@ -97,13 +97,13 @@
let color;
switch (etat.toUpperCase()) {
case "PRESENT":
color = "#6bdb83";
color = "var(--color-present)";
break;
case "ABSENT":
color = "#F1A69C";
color = "var(--color-absent)";
break;
case "RETARD":
color = "#f0c865";
color = "var(--color-retard)";
break;
default:
color = "#AAA";

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
@ -167,8 +168,7 @@ def bilan_dept():
page_title="Saisie de l'assiduité",
javascripts=[
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
],
cssstyles=[
"css/assiduites.css",
@ -290,8 +290,7 @@ def signal_assiduites_etud():
init_qtip=True,
javascripts=[
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
"js/etud_info.js",
],
cssstyles=[
@ -321,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])),
@ -371,8 +370,7 @@ def liste_assiduites_etud():
init_qtip=True,
javascripts=[
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
],
cssstyles=CSSSTYLES
+ [
@ -419,8 +417,7 @@ def bilan_etud():
init_qtip=True,
javascripts=[
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
],
cssstyles=CSSSTYLES
+ [
@ -429,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(
@ -480,8 +477,7 @@ def ajout_justificatif_etud():
init_qtip=True,
javascripts=[
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
],
cssstyles=CSSSTYLES
+ [
@ -530,8 +526,7 @@ def calendrier_etud():
init_qtip=True,
javascripts=[
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
],
cssstyles=CSSSTYLES
+ [
@ -540,10 +535,12 @@ def calendrier_etud():
)
# Récupération des années d'étude de l'étudiant
annees: list[int] = sorted(
[ins.formsemestre.date_debut.year for ins in etud.formsemestre_inscriptions],
reverse=True,
)
annees: list[int] = []
for ins in etud.formsemestre_inscriptions:
annees.extend(
(ins.formsemestre.date_debut.year, ins.formsemestre.date_fin.year)
)
annees = sorted(annees, reverse=True)
# Transformation en une liste "json"
# (sera utilisé pour générer le selecteur d'année)
@ -681,12 +678,10 @@ def signal_assiduites_group():
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
+ [
# Voir fonctionnement JS
# XXX Retirer moment
"js/etud_info.js",
"js/groups_view.js",
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
],
cssstyles=CSSSTYLES
+ [
@ -705,7 +700,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),
@ -833,8 +828,7 @@ def visu_assiduites_group():
"js/etud_info.js",
"js/groups_view.js",
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
],
cssstyles=CSSSTYLES
+ [
@ -852,7 +846,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),
@ -1024,8 +1018,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()),
@ -1113,8 +1107,7 @@ def signal_assiduites_diff():
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
+ [
"js/assiduites.js",
"libjs/moment-2.29.4.min.js",
"libjs/moment-timezone.js",
"js/date_utils.js",
"js/etud_info.js",
],
)
@ -1315,9 +1308,44 @@ def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
)
@bp.route("/test", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoView)
def test():
"""XXX fonction de test a retirer"""
if request.method == "POST":
print("test date_utils : ", request.form)
return render_template("assiduites/pages/test.j2")
# --- 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