forked from ScoDoc/ScoDoc
Merge branch 'iziram-saisie_hebdo'
This commit is contained in:
commit
fd8fbb9e02
@ -838,6 +838,27 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||||||
)}">
|
)}">
|
||||||
Saisir l'assiduité</a>
|
Saisir l'assiduité</a>
|
||||||
</div>
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# YYYY-Www (ISO 8601) :
|
||||||
|
current_week: str = datetime.datetime.now().strftime("%G-W%V")
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<div>
|
||||||
|
<a class="stdlink" href="{
|
||||||
|
url_for("assiduites.signal_assiduites_hebdo",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
group_ids=group.id,
|
||||||
|
week=current_week,
|
||||||
|
)}">
|
||||||
|
Saisir l'assiduité (Hebdo)</a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
if can_edit_abs:
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
<div>
|
<div>
|
||||||
<a class="stdlink" href="{
|
<a class="stdlink" href="{
|
||||||
url_for("assiduites.bilan_dept",
|
url_for("assiduites.bilan_dept",
|
||||||
@ -847,7 +868,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||||||
)}">
|
)}">
|
||||||
Justificatifs en attente</a>
|
Justificatifs en attente</a>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""Affichage étudiants d'un ou plusieurs groupes
|
"""Affichage étudiants d'un ou plusieurs groupes
|
||||||
sous forme: de liste html (table exportable), de trombinoscope (exportable en pdf)
|
sous forme: de liste html (table exportable), de trombinoscope (exportable en pdf)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
|
# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
|
||||||
@ -585,8 +585,8 @@ def groups_table(
|
|||||||
etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
|
etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
|
||||||
etud_info["_prenom_target"] = fiche_url
|
etud_info["_prenom_target"] = fiche_url
|
||||||
|
|
||||||
etud_info["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (
|
etud_info["_nom_disp_td_attrs"] = (
|
||||||
etud_info["etudid"]
|
'id="%s" class="etudinfo"' % (etud_info["etudid"])
|
||||||
)
|
)
|
||||||
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
|
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
|
||||||
if etud_info["etat"] == "D":
|
if etud_info["etat"] == "D":
|
||||||
@ -998,12 +998,12 @@ def form_choix_saisie_semaine(groups_infos):
|
|||||||
return ""
|
return ""
|
||||||
query_args = parse_qs(request.query_string)
|
query_args = parse_qs(request.query_string)
|
||||||
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
|
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
|
||||||
semaine = datetime.date.today().isocalendar().week
|
semaine = datetime.datetime.now().strftime("%G-W%V")
|
||||||
return f"""
|
return f"""
|
||||||
<button onclick="window.location='{url_for(
|
<button onclick="window.location='{url_for(
|
||||||
"assiduites.signal_assiduites_diff",
|
"assiduites.signal_assiduites_hebdo",
|
||||||
group_ids=",".join(map(str,groups_infos.group_ids)),
|
group_ids=",".join(map(str,groups_infos.group_ids)),
|
||||||
semaine=semaine,
|
week=semaine,
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=groups_infos.formsemestre_id,
|
formsemestre_id=groups_infos.formsemestre_id,
|
||||||
moduleimpl_id=moduleimpl_id
|
moduleimpl_id=moduleimpl_id
|
||||||
|
@ -351,7 +351,23 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
}&formsemestre_id={formsemestre.id}
|
}&formsemestre_id={formsemestre.id}
|
||||||
&moduleimpl_id={moduleimpl_id}
|
&moduleimpl_id={moduleimpl_id}
|
||||||
"
|
"
|
||||||
>Saisie Absences journée</a></span>
|
>Saisie Absences</a></span>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
current_week: str = datetime.datetime.now().strftime("%G-W%V")
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<span class="moduleimpl_abs_link"><a class="stdlink" href="{
|
||||||
|
url_for("assiduites.signal_assiduites_hebdo",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
group_ids=group_id,
|
||||||
|
week=current_week,
|
||||||
|
moduleimpl_id=moduleimpl_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>Saisie Absences (Hebdo)</a></span>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
@ -363,8 +379,8 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
group_ids=group_id,
|
group_ids=group_id,
|
||||||
formsemestre_id=formsemestre.id,
|
formsemestre_id=formsemestre.id,
|
||||||
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
||||||
)}"
|
)}" title="Page en cours de fusion et sera prochainement supprimée. Veuillez utiliser la page `Saisie Absences`"
|
||||||
>Saisie Absences Différée</a></span>
|
>(Saisie Absences Différée)</a></span>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,7 +68,13 @@ async function async_post(path, data, success, errors) {
|
|||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
success(responseData);
|
success(responseData);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Network response was not ok.");
|
if (response.status == 404) {
|
||||||
|
response.json().then((data) => {
|
||||||
|
if (errors) errors(data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error("Network response was not ok.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -615,7 +621,10 @@ function erreurModuleImpl(message) {
|
|||||||
|
|
||||||
openAlertModal("Sélection du module", content);
|
openAlertModal("Sélection du module", content);
|
||||||
}
|
}
|
||||||
if (message == "L'étudiant n'est pas inscrit au module") {
|
if (
|
||||||
|
message == "L'étudiant n'est pas inscrit au module" ||
|
||||||
|
message == "param 'moduleimpl_id': etud non inscrit"
|
||||||
|
) {
|
||||||
const HTML = `
|
const HTML = `
|
||||||
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
|
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
|
||||||
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
||||||
@ -822,7 +831,7 @@ function dateCouranteEstTravaillee() {
|
|||||||
const nouvelleDate = retourJourTravail(date);
|
const nouvelleDate = retourJourTravail(date);
|
||||||
$("#date").datepicker("setDate", nouvelleDate);
|
$("#date").datepicker("setDate", nouvelleDate);
|
||||||
let msg = "Le jour sélectionné";
|
let msg = "Le jour sélectionné";
|
||||||
if ((new Date()).format("YYYY-MM-DD") == date.format("YYYY-MM-DD")) {
|
if (new Date().format("YYYY-MM-DD") == date.format("YYYY-MM-DD")) {
|
||||||
msg = "Aujourd'hui";
|
msg = "Aujourd'hui";
|
||||||
}
|
}
|
||||||
const att = document.createTextNode(
|
const att = document.createTextNode(
|
||||||
|
@ -430,3 +430,23 @@ class Duration {
|
|||||||
function hasTimeConflict(period, interval) {
|
function hasTimeConflict(period, interval) {
|
||||||
return period.deb.isBefore(interval.fin) && period.fin.isAfter(interval.deb);
|
return period.deb.isBefore(interval.fin) && period.fin.isAfter(interval.deb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fonction auxiliaire pour obtenir le numéro de semaine ISO d'une date donnée
|
||||||
|
function getISOWeek(date) {
|
||||||
|
const target = new Date(date.valueOf());
|
||||||
|
const dayNr = (date.getUTCDay() + 6) % 7;
|
||||||
|
target.setUTCDate(target.getUTCDate() - dayNr + 3);
|
||||||
|
const firstThursday = target.valueOf();
|
||||||
|
target.setUTCMonth(0, 1);
|
||||||
|
if (target.getUTCDay() !== 4) {
|
||||||
|
target.setUTCMonth(0, 1 + ((4 - target.getUTCDay() + 7) % 7));
|
||||||
|
}
|
||||||
|
return 1 + Math.ceil((firstThursday - target) / 604800000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction auxiliaire pour obtenir le nombre de semaines ISO dans une année donnée
|
||||||
|
function getISOWeeksInYear(year) {
|
||||||
|
const date = new Date(year, 11, 31);
|
||||||
|
const week = getISOWeek(date);
|
||||||
|
return week === 1 ? getISOWeek(new Date(year, 11, 24)) : week;
|
||||||
|
}
|
||||||
|
898
app/templates/assiduites/pages/signal_assiduites_hebdo.j2
Normal file
898
app/templates/assiduites/pages/signal_assiduites_hebdo.j2
Normal file
@ -0,0 +1,898 @@
|
|||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.rbtn::before {
|
||||||
|
--size: 1.5em;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-timepicker-container,
|
||||||
|
#ui-datepicker-div {
|
||||||
|
z-index: 5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new_periode,
|
||||||
|
#actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: fit-content;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#actions {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#actions label {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fix {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1em;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fix>.box {
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timepicker {
|
||||||
|
width: 5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#moduleimpl_select {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1600px;
|
||||||
|
position: relative;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.premier th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.second th {
|
||||||
|
position: sticky;
|
||||||
|
top: 38px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.sticky-col {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbtn:not(:checked)::before {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grayed {
|
||||||
|
filter: brightness(0.5);
|
||||||
|
}
|
||||||
|
.conflit {
|
||||||
|
background-color: var(--color-conflit);
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflit_calendar{
|
||||||
|
font-size: 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timePicker-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timePicker-modal.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timePicker-modal-content {
|
||||||
|
background-color: white;
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 300px;
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timePicker-close {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timePicker-close:hover,
|
||||||
|
.timePicker-close:focus {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-picker-container {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirmButton {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirmButton:hover {
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.etudinfo{
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock styles %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
||||||
|
{% include "sco_timepicker.j2" %}
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const readonly = "{{readonly | safe}}" == "True";
|
||||||
|
const non_present = "{{non_present | safe}}" == "True";
|
||||||
|
|
||||||
|
const etuds = [
|
||||||
|
{% for etud in etudiants %}
|
||||||
|
{
|
||||||
|
id: {{etud.etudid}},
|
||||||
|
nom: "{{etud.nom}}",
|
||||||
|
prenom: "{{etud.prenom}}"
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
|
||||||
|
let days = [
|
||||||
|
{% for jour in hebdo_jours %}
|
||||||
|
{
|
||||||
|
date : new Date(Date.fromFRA("{{jour[1][1]}}")),
|
||||||
|
visible : "{{not jour[0]}}" == "True",
|
||||||
|
nom : "{{jour[1][0]}}",
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
] // [0]=Lundi ... [6]=Dimanche -> à 00h00
|
||||||
|
|
||||||
|
//Une fonction d'action quand un bouton est cliqué
|
||||||
|
// 3 possibilités :
|
||||||
|
// - assiduite_id = null -> créer nv assi avec état du bouton
|
||||||
|
// - assiduite_id non null et bouton coché == etat assi -> suppression de l'assiduité
|
||||||
|
// - assiduite_id non null et bouton coché != etat assi -> modification de l'assiduité
|
||||||
|
async function actionButton(btn, same = false) {
|
||||||
|
let td = btn.parentElement;
|
||||||
|
let tr = td.parentElement;
|
||||||
|
let etudid = tr.getAttribute("etudid");
|
||||||
|
let etud = etuds.find((etud) => etud.id == etudid);
|
||||||
|
let etat = btn.value;
|
||||||
|
let assiduite_id = td.getAttribute("assiduite_id");
|
||||||
|
let dayInfo = [td.getAttribute("day"), td.getAttribute("time")]// [0]=[0..6] [1]=am/pm
|
||||||
|
let day = days[dayInfo[0]].date;
|
||||||
|
dayInfo[1] = dayInfo[1] == "am" ? "matin" : "apresmidi";
|
||||||
|
let deb = new Date(day.format('YYYY-MM-DD') + "T" + temps[dayInfo[1]].debut);
|
||||||
|
let fin = new Date(day.format('YYYY-MM-DD') + "T" + temps[dayInfo[1]].fin);
|
||||||
|
|
||||||
|
const assi = {
|
||||||
|
etudid: etudid,
|
||||||
|
etat: etat,
|
||||||
|
moduleimpl_id: document.getElementById("moduleimpl_select").value,
|
||||||
|
date_debut: deb.toFakeIso(),
|
||||||
|
date_fin: fin.toFakeIso(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelEvent = false;
|
||||||
|
|
||||||
|
if (assiduite_id != "") {
|
||||||
|
if (same) {
|
||||||
|
// Suppression
|
||||||
|
await async_post(
|
||||||
|
`../../api/assiduite/delete`,
|
||||||
|
[assiduite_id],
|
||||||
|
(data) => {
|
||||||
|
if (data.success.length > 0) {
|
||||||
|
envoiToastEtudiant("remove", etud);
|
||||||
|
td.setAttribute("assiduite_id", "");
|
||||||
|
} else {
|
||||||
|
console.error(data.errors["0"].message);
|
||||||
|
cancelEvent = true;
|
||||||
|
erreurModuleImpl(data.errors["0"].message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error("Erreur lors de la suppression de l'assiduité", error);
|
||||||
|
cancelEvent = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Modification
|
||||||
|
await async_post(
|
||||||
|
`../../api/assiduite/${assiduite_id}/edit`,
|
||||||
|
assi,
|
||||||
|
(data) => {
|
||||||
|
envoiToastEtudiant(etat, etud);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error("Erreur lors de la modification de l'assiduité", error);
|
||||||
|
cancelEvent = true;
|
||||||
|
erreurModuleImpl(error.message);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Création
|
||||||
|
await async_post(
|
||||||
|
`../../api/assiduite/${etud.id}/create`,
|
||||||
|
[assi],
|
||||||
|
(data) => {
|
||||||
|
if (data.success.length > 0) {
|
||||||
|
envoiToastEtudiant(etat, etud);
|
||||||
|
//mise à jour de l'assiduité_id dans le td
|
||||||
|
td.setAttribute("assiduite_id", data.success["0"].message.assiduite_id);
|
||||||
|
} else {
|
||||||
|
console.error(data.errors["0"].message);
|
||||||
|
erreurModuleImpl(data.errors["0"].message);
|
||||||
|
cancelEvent = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error("Erreur lors de la création de l'assiduité", error);
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cancelEvent;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function recupAssiduitesHebdo(callback) {
|
||||||
|
const etudIds = etuds.map((etud) => etud.id).join(",");
|
||||||
|
const date_debut = days[0].date.startOf("day").format("YYYY-MM-DDTHH:mm");
|
||||||
|
const date_fin = days[6].date.endOf("day").format("YYYY-MM-DDTHH:mm");
|
||||||
|
|
||||||
|
url =
|
||||||
|
`../../api/assiduites/group/query?date_debut=${date_debut}` +
|
||||||
|
`&date_fin=${date_fin}&etudids=${etudIds}&with_justifs`;
|
||||||
|
|
||||||
|
await fetch(url)
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
let assiduites = []
|
||||||
|
Object.keys(data).forEach((etudid) => {
|
||||||
|
assiduites.push(...data[etudid]);
|
||||||
|
});
|
||||||
|
callback(assiduites);
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
console.error(
|
||||||
|
"There has been a problem with your fetch operation:",
|
||||||
|
error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateTable(assiduites) {
|
||||||
|
|
||||||
|
const img_conflit = `
|
||||||
|
<a
|
||||||
|
class="conflit_calendar"
|
||||||
|
title="Des assiduités existent déjà pour cette période. Cliquez ici pour voir le calendrier de l'assiduité de l'étudiant"
|
||||||
|
data-tooltip
|
||||||
|
target="_blank"
|
||||||
|
>📅</a>`
|
||||||
|
|
||||||
|
// Suppression existant
|
||||||
|
document.querySelectorAll("td.btns").forEach((el) => {
|
||||||
|
el.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < days.length; i++) {
|
||||||
|
let day = days[i].date;
|
||||||
|
|
||||||
|
let morningPeriod = {
|
||||||
|
deb: new Date(day.format('YYYY-MM-DD') + "T" + temps.matin.debut),
|
||||||
|
fin: new Date(day.format('YYYY-MM-DD') + "T" + temps.matin.fin),
|
||||||
|
}
|
||||||
|
let afternoonPeriod = {
|
||||||
|
deb: (new Date(day.format('YYYY-MM-DD') + "T" + temps.apresmidi.debut)),
|
||||||
|
fin: new Date(day.format('YYYY-MM-DD') + "T" + temps.apresmidi.fin),
|
||||||
|
}
|
||||||
|
const assiduitesByDay = {
|
||||||
|
matin: assiduites.filter((assi) => {
|
||||||
|
const period = {
|
||||||
|
deb: new Date(assi.date_debut),
|
||||||
|
fin: new Date(assi.date_fin)
|
||||||
|
}
|
||||||
|
return hasTimeConflict(period, morningPeriod);
|
||||||
|
}),
|
||||||
|
apresmidi: assiduites.filter((assi) => {
|
||||||
|
const period = {
|
||||||
|
deb: new Date(assi.date_debut),
|
||||||
|
fin: new Date(assi.date_fin)
|
||||||
|
}
|
||||||
|
return hasTimeConflict(period, afternoonPeriod);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// Récupération des tr étudiants
|
||||||
|
let trs = document.querySelectorAll("tr[etudid]");
|
||||||
|
|
||||||
|
trs.forEach((tr) => {
|
||||||
|
let etudid = tr.getAttribute("etudid");
|
||||||
|
|
||||||
|
if (!days[i].visible && i >= 5) {
|
||||||
|
return;
|
||||||
|
} else if (!days[i].visible) {
|
||||||
|
tr.insertAdjacentHTML("beforeend", "<td class='grayed btns' colspan='2'></td>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let etudAssiMorning = assiduitesByDay.matin.filter((a) => {
|
||||||
|
return a.etudid == etudid;
|
||||||
|
});
|
||||||
|
let etudAssiAfternoon = assiduitesByDay.apresmidi.filter((a) => {
|
||||||
|
return a.etudid == etudid;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Créations des boutons
|
||||||
|
// matin
|
||||||
|
let tdMatin = document.createElement("td");
|
||||||
|
tdMatin.classList.add("btns");
|
||||||
|
tdMatin.setAttribute("day", i);
|
||||||
|
tdMatin.setAttribute("time", "am");
|
||||||
|
|
||||||
|
tr.appendChild(tdMatin);
|
||||||
|
|
||||||
|
// après-midi
|
||||||
|
let tdApresmidi = document.createElement("td");
|
||||||
|
tdApresmidi.classList.add("btns");
|
||||||
|
tdApresmidi.setAttribute("day", i);
|
||||||
|
tdApresmidi.setAttribute("time", "pm");
|
||||||
|
tr.appendChild(tdApresmidi);
|
||||||
|
|
||||||
|
|
||||||
|
// Peuplement des boutons en fonction des assiduités
|
||||||
|
let boutons = `
|
||||||
|
<input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
|
||||||
|
class="rbtn retard" value="retard">
|
||||||
|
<input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
|
||||||
|
class="rbtn absent" value="absent">
|
||||||
|
`
|
||||||
|
|
||||||
|
if (!non_present) {
|
||||||
|
boutons = `<input type="checkbox" name="matin-${etudid}" id="matin-${etudid}"
|
||||||
|
class="rbtn present" value="present">`+boutons;
|
||||||
|
}
|
||||||
|
|
||||||
|
// matin
|
||||||
|
tdMatin.innerHTML = boutons
|
||||||
|
tdMatin.setAttribute("assiduite_id", "")
|
||||||
|
if (etudAssiMorning.length != 0) {
|
||||||
|
let assi = etudAssiMorning[0];
|
||||||
|
const deb = new Date(assi.date_debut);
|
||||||
|
const fin = new Date(assi.date_fin);
|
||||||
|
|
||||||
|
// si dates == periode -> cocher bouton correspondant
|
||||||
|
// Sinon supprimer boutons et mettre case "rouge" + tooltip
|
||||||
|
|
||||||
|
if (deb.isSame(morningPeriod.deb, "minutes") && fin.isSame(morningPeriod.fin, "minutes")) {
|
||||||
|
let etat = assi.etat.toLowerCase();
|
||||||
|
const input = tdMatin.querySelector(`[value="${etat}"]`)
|
||||||
|
if (input) {
|
||||||
|
input.checked = true;
|
||||||
|
}
|
||||||
|
tdMatin.setAttribute("assiduite_id", assi.assiduite_id);
|
||||||
|
let saisie = new Date(assi.entry_date).format("DD/MM/Y HH:mm");
|
||||||
|
saisie = saisie.split(" ").join(" à ");
|
||||||
|
let text = `noté ${etat} le ${saisie} par ${assi.user_nom_complet}`;
|
||||||
|
tdMatin.setAttribute("title", text);
|
||||||
|
tdMatin.setAttribute("data-tooltip", "");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
tdMatin.innerHTML = img_conflit;
|
||||||
|
tdMatin.querySelector(".conflit_calendar").href = `calendrier_assi_etud?etudid=${etudid}`;
|
||||||
|
tdMatin.classList.add("conflit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// après-midi
|
||||||
|
tdApresmidi.innerHTML = boutons
|
||||||
|
tdApresmidi.setAttribute("assiduite_id", "")
|
||||||
|
if (etudAssiAfternoon.length != 0) {
|
||||||
|
let assi = etudAssiAfternoon[0];
|
||||||
|
const deb = new Date(assi.date_debut);
|
||||||
|
const fin = new Date(assi.date_fin);
|
||||||
|
|
||||||
|
// si dates == periode -> cocher bouton correspondant
|
||||||
|
// Sinon supprimer boutons et mettre case "rouge" + tooltip
|
||||||
|
|
||||||
|
if (deb.isSame(afternoonPeriod.deb, "minutes") && fin.isSame(afternoonPeriod.fin, "minutes")) {
|
||||||
|
let etat = assi.etat.toLowerCase();
|
||||||
|
const input = tdApresmidi.querySelector(`[value="${etat}"]`)
|
||||||
|
if (input) {
|
||||||
|
input.checked = true;
|
||||||
|
}
|
||||||
|
tdApresmidi.setAttribute("assiduite_id", assi.assiduite_id);
|
||||||
|
|
||||||
|
let saisie = new Date(assi.entry_date).format("DD/MM/Y HH:mm");
|
||||||
|
saisie = saisie.split(" ").join(" à ");
|
||||||
|
let text = `noté ${etat} le ${saisie} par ${assi.user_nom_complet}`;
|
||||||
|
tdApresmidi.setAttribute("title", text);
|
||||||
|
tdApresmidi.setAttribute("data-tooltip", "");
|
||||||
|
} else {
|
||||||
|
tdApresmidi.innerHTML = img_conflit;
|
||||||
|
tdApresmidi.querySelector(".conflit_calendar").href = `calendrier_assi_etud?etudid=${etudid}`;
|
||||||
|
tdApresmidi.classList.add("conflit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll("td .rbtn").forEach((el) => {
|
||||||
|
el.addEventListener("click", async (e) => {
|
||||||
|
|
||||||
|
if (readonly) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = e.target;
|
||||||
|
let parent = target.parentElement;
|
||||||
|
|
||||||
|
let isCancelled = await actionButton(target, !target.checked);
|
||||||
|
if (isCancelled) {
|
||||||
|
e.preventDefault();
|
||||||
|
target.checked = !target.checked;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputs = parent.querySelectorAll(".rbtn");
|
||||||
|
inputs.forEach((input) => {
|
||||||
|
if (input != target) {
|
||||||
|
input.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
enableTooltips("table");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Une fonction pour changer de semaine (précédente ou suivante)
|
||||||
|
// fait juste un location.href avec les bons paramètres
|
||||||
|
function changeWeek(prev = false) {
|
||||||
|
const currentUrl = new URL(window.location.href); // Récupère l'URL actuelle
|
||||||
|
const params = new URLSearchParams(currentUrl.search); // Récupère les paramètres de l'URL
|
||||||
|
let currentWeekParam = params.get('week');
|
||||||
|
|
||||||
|
// Extraire l'année et le numéro de semaine du paramètre de la semaine actuelle
|
||||||
|
const [year, week] = currentWeekParam.split('-W').map(Number);
|
||||||
|
|
||||||
|
// Calculer la nouvelle semaine et l'année
|
||||||
|
let newYear = year;
|
||||||
|
let newWeek = week + (prev ? -1 : 1);
|
||||||
|
|
||||||
|
if (newWeek < 1) {
|
||||||
|
newYear -= 1; // Passer à l'année précédente
|
||||||
|
newWeek = getISOWeeksInYear(newYear); // Dernière semaine de l'année précédente
|
||||||
|
} else if (newWeek > getISOWeeksInYear(newYear)) {
|
||||||
|
newYear += 1; // Passer à l'année suivante
|
||||||
|
newWeek = 1; // Première semaine de l'année suivante
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formater le nouveau paramètre de semaine
|
||||||
|
const newWeekParam = `${newYear}-W${String(newWeek).padStart(2, '0')}`;
|
||||||
|
params.set('week', newWeekParam); // Mettre à jour le paramètre 'week'
|
||||||
|
currentUrl.search = params.toString(); // Mettre à jour les paramètres de l'URL
|
||||||
|
window.location.href = currentUrl.toString(); // Rediriger vers la nouvelle URL
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Une fonction pour gérer le bouton "tout le monde présent"
|
||||||
|
// coche tous les boutons de la colonne
|
||||||
|
function allPresent(day, time) {
|
||||||
|
// Version naive : coche tous les boutons de la colonne
|
||||||
|
// TODO - Optimiser avec une seule requête API
|
||||||
|
let tds = document.querySelectorAll(`td[day="${day}"][time="${time}"]`);
|
||||||
|
const real_time = time == "am" ? "matin" : "apresmidi";
|
||||||
|
const assi = {
|
||||||
|
etat: "present",
|
||||||
|
moduleimpl_id: document.getElementById("moduleimpl_select").value,
|
||||||
|
date_debut: new Date(days[day].date.format('YYYY-MM-DD') + "T" + temps[real_time].debut).toFakeIso(),
|
||||||
|
date_fin: new Date(days[day].date.format('YYYY-MM-DD') + "T" + temps[real_time].fin).toFakeIso(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let toCreate = []; // [{etudid:<int>}]
|
||||||
|
let toEdit = [];// [{etudid:<int>, assiduite_id:<int>}]
|
||||||
|
|
||||||
|
tds.forEach((td) => {
|
||||||
|
// on ne touche pas aux conflits
|
||||||
|
if (td.classList.contains("conflit")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tr = td.parentElement;
|
||||||
|
const etudid = Number(tr.getAttribute("etudid"));
|
||||||
|
|
||||||
|
const assiduite_id = td.getAttribute("assiduite_id");
|
||||||
|
if (assiduite_id == "") {
|
||||||
|
toCreate.push({ etudid: etudid });
|
||||||
|
} else {
|
||||||
|
toEdit.push({ etudid: etudid, assiduite_id: Number(assiduite_id) });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Création
|
||||||
|
toCreate = toCreate.map((el) => {
|
||||||
|
return {
|
||||||
|
...assi,
|
||||||
|
etudid: el.etudid,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modification
|
||||||
|
toEdit = toEdit.map((el) => {
|
||||||
|
return {
|
||||||
|
...assi,
|
||||||
|
etudid: el.etudid,
|
||||||
|
assiduite_id: el.assiduite_id,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Appel API
|
||||||
|
let counts = {
|
||||||
|
create: toCreate.length,
|
||||||
|
edit: toEdit.length
|
||||||
|
}
|
||||||
|
const promiseCreate = async_post(
|
||||||
|
`../../api/assiduites/create`,
|
||||||
|
toCreate,
|
||||||
|
async (data) => {
|
||||||
|
if (data.errors.length > 0) {
|
||||||
|
console.error(data.errors);
|
||||||
|
data.errors.forEach((err) => {
|
||||||
|
let obj = toCreate[err.indice];
|
||||||
|
let etu = etuds.find((el) => el.id == obj.etudid);
|
||||||
|
|
||||||
|
const text = document.createTextNode(`Erreur pour ${etu.nom} ${etu.prenom} : ${err.message}`);
|
||||||
|
const toast = generateToast(text, "var(--color-error)", 10);
|
||||||
|
pushToast(toast);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
counts.create = data.success.length;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error("Erreur lors de la création de l'assiduité", error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const promiseEdit = async_post(
|
||||||
|
`../../api/assiduites/edit`,
|
||||||
|
toEdit,
|
||||||
|
async (data) => {
|
||||||
|
if (data.errors.length > 0) {
|
||||||
|
console.error(data.errors);
|
||||||
|
data.errors.forEach((err) => {
|
||||||
|
let obj = toEdit[err.indice];
|
||||||
|
let etu = etuds.find((el) => el.id == obj.etudid);
|
||||||
|
|
||||||
|
const text = document.createTextNode(`Erreur pour ${etu.nom} ${etu.prenom} : ${err.message}`);
|
||||||
|
const toast = generateToast(text, "var(--color-error)");
|
||||||
|
pushToast(toast);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
counts.edit = data.success.length;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error("Erreur lors de l'édition de l'assiduité", error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Affiche un loader
|
||||||
|
afficheLoader();
|
||||||
|
|
||||||
|
Promise.all([promiseCreate, promiseEdit]).then(async () => {
|
||||||
|
retirerLoader();
|
||||||
|
await recupAssiduitesHebdo(updateTable);
|
||||||
|
envoiToastTous("present", counts.create + counts.edit);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function updateTemps(temps){
|
||||||
|
let matin = document.getElementById("text-matin");
|
||||||
|
let apresmidi = document.getElementById("text-apresmidi");
|
||||||
|
matin.textContent = `${temps.matin.debut} à ${temps.matin.fin}`;
|
||||||
|
apresmidi.textContent = `${temps.apresmidi.debut} à ${temps.apresmidi.fin}`;
|
||||||
|
|
||||||
|
recupAssiduitesHebdo(updateTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const temps = {
|
||||||
|
matin: {
|
||||||
|
debut: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||||
|
fin: "{{ scu.get_assiduites_time_config("assi_lunch_time") }}"
|
||||||
|
},
|
||||||
|
apresmidi: {
|
||||||
|
debut: "{{ scu.get_assiduites_time_config("assi_lunch_time") }}",
|
||||||
|
fin: "{{ scu.get_assiduites_time_config("assi_afternoon_time") }}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("text-matin").addEventListener("click", (e)=>{
|
||||||
|
e.preventDefault();
|
||||||
|
openModal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("text-apresmidi").addEventListener("click", (e)=>{
|
||||||
|
e.preventDefault();
|
||||||
|
openModal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateTemps(temps);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function openModal(morning = true){
|
||||||
|
|
||||||
|
let text = morning ? "du matin" : "de l'après-midi";
|
||||||
|
const modal = document.getElementById("timePickerModal");
|
||||||
|
modal.querySelector("#timePicker-modal-text").textContent = text;
|
||||||
|
|
||||||
|
let time1 = $("#time1");
|
||||||
|
let time2 = $("#time2");
|
||||||
|
|
||||||
|
// Réinitialiser les champs
|
||||||
|
time1.val(morning ? temps.matin.debut : temps.apresmidi.debut);
|
||||||
|
time2.val(morning ? temps.matin.fin : temps.apresmidi.fin);
|
||||||
|
|
||||||
|
// Définir l'action du bouton de confirmation
|
||||||
|
|
||||||
|
document.getElementById("confirmButton").onclick = function(){
|
||||||
|
let debut = time1.val();
|
||||||
|
let fin = time2.val();
|
||||||
|
|
||||||
|
if (debut == "" || fin == ""){
|
||||||
|
alert("Veuillez remplir les deux champs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debut >= fin){
|
||||||
|
alert("L'heure de début doit être inférieure à l'heure de fin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (morning){
|
||||||
|
if (fin > temps.apresmidi.debut){
|
||||||
|
alert("L'heure de fin du matin doit être inférieure à l'heure de début de l'après-midi");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
temps.matin.debut = debut;
|
||||||
|
temps.matin.fin = fin;
|
||||||
|
} else {
|
||||||
|
if (debut < temps.matin.fin){
|
||||||
|
alert("L'heure de début de l'après-midi doit être supérieure à l'heure de fin du matin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
temps.apresmidi.debut = debut;
|
||||||
|
temps.apresmidi.fin = fin;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTemps(temps);
|
||||||
|
modal.classList.remove("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.classList.add("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", ()=>{
|
||||||
|
const modal = document.getElementById("timePickerModal");
|
||||||
|
modal.querySelector(".timePicker-close").onclick = function() {
|
||||||
|
modal.classList.remove("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keyup', function(e) {
|
||||||
|
if (e.key === "Escape" && modal.classList.contains("show")) {
|
||||||
|
modal.classList.remove("show");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
document.querySelectorAll("th .rbtn").forEach((el)=>{
|
||||||
|
el.addEventListener("click", (e)=>{
|
||||||
|
allPresent(...el.id.split("-"));
|
||||||
|
e.preventDefault();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock scripts %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ title }}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
<h2>Signalement hebdomadaire de l'assiduité {{ gr | safe }}</h2>
|
||||||
|
<br>
|
||||||
|
<div id="actions" class="flex">
|
||||||
|
<button onclick="changeWeek(true)">Semaine précédente</button>
|
||||||
|
<label for="moduleimpl_select">
|
||||||
|
Module:
|
||||||
|
{{moduleimpl_select | safe}}
|
||||||
|
</label>
|
||||||
|
<button onclick="changeWeek(false)">Semaine suivante</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="tableau-dates">
|
||||||
|
Le matin <a href="#" id="text-matin" title="Cliquer pour modifier les horaires">9h à 12h</a> et l'après-midi de <a href="#" id="text-apresmidi" title="Cliquer pour modifier les horaires">13h à 17h</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{% if readonly %}
|
||||||
|
<h4
|
||||||
|
title="Vous n'avez pas les permissions nécessaires afin de modifier les assiduités"
|
||||||
|
data-tooltip
|
||||||
|
>
|
||||||
|
Ouvert en mode <span class="rouge">lecture seule</span>.
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
<table id="table">
|
||||||
|
<thead>
|
||||||
|
<tr class="premier">
|
||||||
|
<th rowspan="2">Étudiants</th>
|
||||||
|
|
||||||
|
{% for jour in hebdo_jours %}
|
||||||
|
|
||||||
|
{% if not jour[0] or jour[1][0] not in ['Samedi', 'Dimanche'] %}
|
||||||
|
<th colspan="2" class="{{'grayed' if jour[0] else ''}}" >{{ jour[1][0] }} {{jour[1][1] }}</th>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr class="second">
|
||||||
|
{% for jour in hebdo_jours %}
|
||||||
|
|
||||||
|
{% if not jour[0] or jour[1][0] not in ['Samedi', 'Dimanche'] %}
|
||||||
|
<th class="{{'grayed' if jour[0] else ''}}">Matin</th>
|
||||||
|
<th class="{{'grayed' if jour[0] else ''}}">Après-midi</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% if not readonly and not non_present %}
|
||||||
|
<tr>
|
||||||
|
{# Ne pas afficher si preference "non presences" / "readonly" #}
|
||||||
|
<th></th>
|
||||||
|
{% for jour in hebdo_jours %}
|
||||||
|
{% if not jour[0] or jour[1][0] not in ['Samedi', 'Dimanche'] %}
|
||||||
|
<th class="{{'grayed' if jour[0] else ''}}">
|
||||||
|
<input title="Mettre tout le monde présent" data-tooltip type="checkbox" name="" id="{{loop.index - 1}}-am" class="rbtn present" {{'disabled' if jour[0] else ''}}>
|
||||||
|
</th>
|
||||||
|
<th class="{{'grayed' if jour[0] else ''}}">
|
||||||
|
<input title="Mettre tout le monde présent" data-tooltip type="checkbox" name="" id="{{loop.index - 1}}-pm" class="rbtn present" {{'disabled' if jour[0] else ''}}>
|
||||||
|
</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for etud in etudiants %}
|
||||||
|
<tr etudid="{{etud.etudid}}" id="row-{{etud.etudid}}">
|
||||||
|
<td class="etudinfo" id="etud-{{etud.etudid}}">{{ etud.nom_prenom() }}</td>
|
||||||
|
{# Sera rempli en JS #}
|
||||||
|
{# Ne pas afficher bouton présent si pref "non présences" #}
|
||||||
|
{# <td>
|
||||||
|
<input type="checkbox" name="" id="" class="rbtn present">
|
||||||
|
<input type="checkbox" name="" id="" class="rbtn retard">
|
||||||
|
<input type="checkbox" name="" id="" class="rbtn absent">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="" id="" class="rbtn present">
|
||||||
|
<input type="checkbox" name="" id="" class="rbtn retard">
|
||||||
|
<input type="checkbox" name="" id="" class="rbtn absent">
|
||||||
|
</td> #}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id="timePickerModal" class="timePicker-modal">
|
||||||
|
<div class="timePicker-modal-content">
|
||||||
|
<span class="timePicker-close">×</span>
|
||||||
|
<h2>Choisissez les horaires <span id="timePicker-modal-text"></span></h2>
|
||||||
|
<div class="time-picker-container">
|
||||||
|
<label for="time1">Début</label>
|
||||||
|
<input type="text" id="time1" name="time1" class="timepicker" placeholder="hh:mm">
|
||||||
|
</div>
|
||||||
|
<div class="time-picker-container">
|
||||||
|
<label for="time2">Fin</label>
|
||||||
|
<input type="text" id="time2" name="time2" class="timepicker" placeholder="hh:mm">
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
<button id="confirmButton">Confirmer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include "assiduites/widgets/alert.j2" %}
|
||||||
|
{% include "assiduites/widgets/toast.j2" %}
|
||||||
|
{% endblock app_content %}
|
@ -1987,6 +1987,146 @@ def traitement_justificatifs():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("signal_assiduites_hebdo")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def signal_assiduites_hebdo():
|
||||||
|
"""
|
||||||
|
signal_assiduites_hebdo
|
||||||
|
|
||||||
|
paramètres obligatoires :
|
||||||
|
- formsemestre_id : id du formsemestre
|
||||||
|
- week : date semaine (iso 8601 -> 20XX-WXX)
|
||||||
|
- groups_id : id des groupes (séparés par des virgules -> 1,2,3)
|
||||||
|
|
||||||
|
paramètres optionnels :
|
||||||
|
- moduleimpl_id : id du moduleimpl (par défaut None)
|
||||||
|
|
||||||
|
|
||||||
|
Permissions :
|
||||||
|
- ScoView -> page en lecture seule
|
||||||
|
- AbsChange -> page en lecture/écriture
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Récupération des paramètres
|
||||||
|
moduleimpl_id: int = request.args.get("moduleimpl_id", None)
|
||||||
|
week: str = request.args.get("week", None)
|
||||||
|
group_ids: str = request.args.get("group_ids", "") # ex: "1,2,3"
|
||||||
|
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||||
|
|
||||||
|
# Vérification des paramètres
|
||||||
|
if week is None or group_ids == "" or formsemestre_id == -1:
|
||||||
|
raise ScoValueError("Paramètres manquants", dest_url=request.referrer)
|
||||||
|
|
||||||
|
# Récupération du moduleimpl
|
||||||
|
try:
|
||||||
|
moduleimpl_id: int = int(moduleimpl_id)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
moduleimpl_id: str | None = None if moduleimpl_id != "autre" else moduleimpl_id
|
||||||
|
|
||||||
|
# Récupération du formsemestre
|
||||||
|
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
|
|
||||||
|
# Vérification semaine dans format iso 8601 et formsemestre
|
||||||
|
regex_iso8601 = r"^\d{4}-W\d{2}$"
|
||||||
|
if not re.match(regex_iso8601, week):
|
||||||
|
raise ScoValueError("Semaine invalide", dest_url=request.referrer)
|
||||||
|
|
||||||
|
fs_deb_iso8601 = formsemestre.date_debut.strftime("%Y-W%W")
|
||||||
|
fs_fin_iso8601 = formsemestre.date_fin.strftime("%Y-W%W")
|
||||||
|
|
||||||
|
# Utilisation de la propriété de la norme iso 8601
|
||||||
|
# les chaines sont triables par ordre alphanumérique croissant
|
||||||
|
# et produiront le même ordre que les dates par ordre chronologique croissant
|
||||||
|
if week < fs_deb_iso8601 or week > fs_fin_iso8601:
|
||||||
|
raise ScoValueError("Semaine hors du semestre", dest_url=request.referrer)
|
||||||
|
|
||||||
|
# Vérification des groupes
|
||||||
|
group_ids = group_ids.split(",") if group_ids != "" else []
|
||||||
|
|
||||||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
|
group_ids, formsemestre_id=formsemestre.id, select_all_when_unspecified=True
|
||||||
|
)
|
||||||
|
if not groups_infos.members:
|
||||||
|
return (
|
||||||
|
html_sco_header.sco_header(page_title="Assiduité: saisie hebdomadaire")
|
||||||
|
+ "<h3>Aucun étudiant ! </h3>"
|
||||||
|
+ html_sco_header.sco_footer()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Récupération des étudiants
|
||||||
|
etudiants: list[Identite] = [
|
||||||
|
Identite.get_etud(etudid=m["etudid"]) for m in groups_infos.members
|
||||||
|
]
|
||||||
|
|
||||||
|
if groups_infos.tous_les_etuds_du_sem:
|
||||||
|
gr_tit = "en"
|
||||||
|
else:
|
||||||
|
if len(groups_infos.group_ids) > 1:
|
||||||
|
grp = "des groupes"
|
||||||
|
else:
|
||||||
|
grp = "du groupe"
|
||||||
|
gr_tit = (
|
||||||
|
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Gestion des jours
|
||||||
|
jours: dict[str, list[str]] = {
|
||||||
|
"lun": [
|
||||||
|
"Lundi",
|
||||||
|
datetime.datetime.strptime(week + "-1", "%G-W%V-%u").strftime("%d/%m/%Y"),
|
||||||
|
],
|
||||||
|
"mar": [
|
||||||
|
"Mardi",
|
||||||
|
datetime.datetime.strptime(week + "-2", "%G-W%V-%u").strftime("%d/%m/%Y"),
|
||||||
|
],
|
||||||
|
"mer": [
|
||||||
|
"Mercredi",
|
||||||
|
datetime.datetime.strptime(week + "-3", "%G-W%V-%u").strftime("%d/%m/%Y"),
|
||||||
|
],
|
||||||
|
"jeu": [
|
||||||
|
"Jeudi",
|
||||||
|
datetime.datetime.strptime(week + "-4", "%G-W%V-%u").strftime("%d/%m/%Y"),
|
||||||
|
],
|
||||||
|
"ven": [
|
||||||
|
"Vendredi",
|
||||||
|
datetime.datetime.strptime(week + "-5", "%G-W%V-%u").strftime("%d/%m/%Y"),
|
||||||
|
],
|
||||||
|
"sam": [
|
||||||
|
"Samedi",
|
||||||
|
datetime.datetime.strptime(week + "-6", "%G-W%V-%u").strftime("%d/%m/%Y"),
|
||||||
|
],
|
||||||
|
"dim": [
|
||||||
|
"Dimanche",
|
||||||
|
datetime.datetime.strptime(week + "-7", "%G-W%V-%u").strftime("%d/%m/%Y"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
non_travail = sco_preferences.get_preference("non_travail")
|
||||||
|
non_travail = non_travail.replace(" ", "").split(",")
|
||||||
|
|
||||||
|
hebdo_jours: list[tuple[bool, str]] = []
|
||||||
|
for key, val in jours.items():
|
||||||
|
hebdo_jours.append((key in non_travail, val))
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"assiduites/pages/signal_assiduites_hebdo.j2",
|
||||||
|
title="Assiduité: saisie hebdomadaire",
|
||||||
|
gr=gr_tit,
|
||||||
|
etudiants=etudiants,
|
||||||
|
moduleimpl_select=_module_selector(
|
||||||
|
formsemestre=formsemestre, moduleimpl_id=moduleimpl_id
|
||||||
|
),
|
||||||
|
hebdo_jours=hebdo_jours,
|
||||||
|
readonly=not current_user.has_permission(Permission.AbsChange),
|
||||||
|
non_present=sco_preferences.get_preference(
|
||||||
|
"non_present",
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
dept_id=g.scodoc_dept_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
||||||
"""Génère la liste des assiduités d'un étudiant pour le bulletin mail"""
|
"""Génère la liste des assiduités d'un étudiant pour le bulletin mail"""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user