This commit is contained in:
Emmanuel Viennet 2023-11-22 17:59:10 +01:00
commit 532fb3e701
10 changed files with 409 additions and 32 deletions

View File

@ -1476,3 +1476,14 @@ def is_assiduites_module_forced(
except (TypeError, ValueError): except (TypeError, ValueError):
retour = sco_preferences.get_preference("forcer_module", dept_id=dept_id) retour = sco_preferences.get_preference("forcer_module", dept_id=dept_id)
return retour return retour
def get_assiduites_time_config(config_type: str) -> str:
from app.models import ScoDocSiteConfig
match config_type:
case "matin":
return ScoDocSiteConfig.get("assi_morning_time", "08:00:00")
case "aprem":
return ScoDocSiteConfig.get("assi_afternoon_time", "18:00:00")
case "pivot":
return ScoDocSiteConfig.get("assi_lunch_time", "13:00:00")

View File

@ -1,15 +1,17 @@
:root { :root {
--color-present: #6bdb83; --color-present: #6bdb83;
--color-absent: #e62a11; --color-absent: #e62a11;
--color-absent-clair: #F25D4A;
--color-retard: #f0c865; --color-retard: #f0c865;
--color-justi: #7059FF; --color-justi: #7059FF;
--color-justi-clair: #6885E3;
--color-justi-invalide: #a84476; --color-justi-invalide: #a84476;
--color-nonwork: #badfff; --color-nonwork: #badfff;
--color-absent-justi: #e65ab7; --color-absent-justi: #e65ab7;
--color-retard-justi: #ffef7a; --color-retard-justi: #ffef7a;
--color-error: #FF0000; --color-error: #e62a11;
--color-warning: #eec660; --color-warning: #eec660;
--color-information: #658ef0; --color-information: #658ef0;
@ -21,7 +23,7 @@
--color-defaut: #FFF; --color-defaut: #FFF;
--color-defaut-dark: #444; --color-defaut-dark: #444;
--color-default-text: #1F1F1F;
--motif-justi: repeating-linear-gradient(135deg, transparent, transparent 4px, var(--color-justi) 4px, var(--color-justi) 8px); --motif-justi: repeating-linear-gradient(135deg, transparent, transparent 4px, var(--color-justi) 4px, var(--color-justi) 8px);

View File

@ -953,6 +953,89 @@ function createAssiduite(etat, etudid) {
); );
return !with_errors; return !with_errors;
} }
/**
* Création d'une assiduité pour un étudiant
* @param {String} etat l'état de l'étudiant
* @param {Number | String} etudid l'identifiant de l'étudiant
*
* TODO : Rendre asynchrone
*/
function createAssiduiteComplete(assiduite, etudid) {
if (!hasModuleImpl(assiduite) && window.forceModule) {
const html = `
<h3>Aucun module n'a été spécifié</h3>
`;
const div = document.createElement("div");
div.innerHTML = html;
openAlertModal("Erreur Module", div);
return false;
}
const path = getUrl() + `/api/assiduite/${etudid}/create`;
let with_errors = false;
sync_post(
path,
[assiduite],
(data, status) => {
//success
if (data.success.length > 0) {
let obj = data.success["0"].message.assiduite_id;
}
if (data.errors.length > 0) {
console.error(data.errors["0"].message);
if (data.errors["0"].message == "Module non renseigné") {
const HTML = `
<p>Attention, le module doit obligatoirement être renseigné.</p>
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
`;
const content = document.createElement("div");
content.innerHTML = HTML;
openAlertModal("Sélection du module", content);
}
if (
data.errors["0"].message == "L'étudiant n'est pas inscrit au module"
) {
const HTML = `
<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>
`;
const content = document.createElement("div");
content.innerHTML = HTML;
openAlertModal("Sélection du module", content);
}
if (
data.errors["0"].message ==
"Duplication: la période rentre en conflit avec une plage enregistrée"
) {
const HTML = `
<p>L'assiduité n'a pas pu être enregistrée car une autre assiduité existe sur la période sélectionnée</p>
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
`;
const content = document.createElement("div");
content.innerHTML = HTML;
openAlertModal("Période conflictuelle", content);
}
with_errors = true;
}
},
(data, status) => {
//error
console.error(data, status);
errorAlert();
with_errors = true;
}
);
return !with_errors;
}
/** /**
* Suppression d'une assiduité * Suppression d'une assiduité

View File

@ -0,0 +1,234 @@
{% include "assiduites/widgets/toast.j2" %}
{% block pageContent %}
<div class="pageContent">
<h3>Ajouter une assiduité</h3>
{% include "assiduites/widgets/tableau_base.j2" %}
{% if saisie_eval %}
<div id="saisie_eval">
<br>
<h3>
La saisie de l'assiduité a été préconfigurée en fonction de l'évaluation. <br>
Une fois la saisie finie, cliquez sur le lien si dessous pour revenir sur la gestion de l'évaluation
</h3>
<a href="{{redirect_url}}">retourner sur la page de l'évaluation</a>
</div>
{% endif %}
<section class="assi-form page">
<fieldset>
<div class="assi-row">
<div class="assi-label">
<legend for="assi_date_debut" required>Date de début</legend>
<scodoc-datetime name="assi_date_debut" id="assi_date_debut"> </scodoc-datetime>
<span>Journée entière</span> <input type="checkbox" name="assi_journee" id="assi_journee">
</div>
<div class="assi-label" id="date_fin">
<legend for="assi_date_fin" required>Date de fin</legend>
<scodoc-datetime name="assi_date_fin" id="assi_date_fin"></scodoc-datetime>
</div>
</div>
<div class="assi-row">
<div class="assi-label">
<legend for="assi_etat" required>Etat de l'assiduité</legend>
<select name="assi_etat" id="assi_etat">
<option value="absent" selected>Absent</option>
<option value="retard">Retard</option>
<option value="present">Présent</option>
</select>
</div>
</div>
<div class="assi-row">
<div class="assi-label">
<legend for="assi_module" required>Module</legend>
{% with moduleid="ajout_assiduite_module_impl",label=false %}
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
{% endwith %}
</div>
</div>
<div class="assi-row">
<div class="assi-label">
<legend for="assi_raison">Raison</legend>
<textarea name="assi_raison" id="assi_raison" cols="50" rows="10" maxlength="500"></textarea>
</div>
</div>
<div class="assi-row">
<button onclick="validerFormulaire(this)">Créer l'assiduité</button>
<button onclick="effacerFormulaire()">Remettre à zero</button>
</div>
</fieldset>
</section>
<section class="liste">
<a class="icon filter" onclick="filterAssi()"></a>
{% include "assiduites/widgets/tableau_assi.j2" %}
</section>
</div>
<style>
.assi-row {
margin: 5px 0;
}
.assi-form fieldset {
display: flex;
flex-direction: column;
justify-content: space-evenly;
}
.pageContent {
max-width: var(--sco-content-max-width);
margin-top: 15px;
}
.assi-label {
margin: 0 10px;
}
[required]::after {
content: "*";
color: var(--color-error);
}
</style>
<script>
function validateFields() {
const field = document.querySelector('.assi-form')
const { deb, fin } = getDates()
const date_debut = new Date(deb);
const date_fin = new Date(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;
}
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;
}
return true
}
function fieldsToAssiduite() {
const field = document.querySelector('.assi-form.page')
const { deb, fin } = getDates()
const etat = field.querySelector('#assi_etat').value;
const raison = field.querySelector('#assi_raison').value;
const module = field.querySelector("#ajout_assiduite_module_impl").value;
return {
date_debut: new Date(deb).toFakeIso(),
date_fin: new Date(fin).toFakeIso(),
etat: etat,
description: raison,
moduleimpl_id: module,
}
}
function validerFormulaire(btn) {
if (!validateFields()) return
const assiduite = fieldsToAssiduite();
let assiduite_id = null;
createAssiduiteComplete(assiduite, etudid);
loadAll();
btn.disabled = true;
setTimeout(() => {
btn.disabled = false;
}, 1000)
}
function effacerFormulaire() {
const field = document.querySelector('.assi-form')
field.querySelector('#assi_date_debut').value = "";
field.querySelector('#assi_date_fin').value = "";
field.querySelector('#assi_etat').value = "attente";
field.querySelector('#assi_raison').value = "";
}
function dayOnly() {
const date_deb = document.getElementById("assi_date_debut");
const date_fin = document.getElementById("assi_date_fin");
if (document.getElementById('assi_journee').checked) {
date_deb.setAttribute("show", "date")
date_fin.setAttribute("show", "date")
document.querySelector(`legend[for="assi_date_fin"]`).removeAttribute("required")
} else {
date_deb.removeAttribute("show")
date_fin.removeAttribute("show")
document.querySelector(`legend[for="assi_date_fin"]`).setAttribute("required", "")
}
}
function getDates() {
const date_deb = document.querySelector(".page #assi_date_debut")
const date_fin = document.querySelector(".page #assi_date_fin")
const journee = document.querySelector('.page #assi_journee').checked
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
if (journee) {
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
} else {
fin = date_fin.valueAsObject.date + fin
}
return {
"deb": deb,
"fin": fin,
}
}
const etudid = {{ sco.etud.id }};
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
const assi_morning = '{{assi_morning}}';
const assi_evening = '{{assi_evening}}';
{% if saisie_eval %}
const saisie_eval = true;
const date_deb = "{{date_deb}}";
const date_fin = "{{date_fin}}";
const moduleimpl = {{ moduleimpl_id }};
{% else %}
const saisie_eval = false;
{% endif %}
window.addEventListener("load", () => {
loadAll();
document.getElementById('assi_journee').addEventListener('click', () => { dayOnly() });
dayOnly()
if (saisie_eval) {
document.getElementById("assi_date_debut").value = Date.removeUTC(date_deb);
document.getElementById("assi_date_fin").value = Date.removeUTC(date_fin);
} else {
const today = (new Date()).format("YYYY-MM-DD");
document.getElementById("assi_date_debut").valueAsObject = { date: today, time: assi_morning }
document.getElementById("assi_date_fin").valueAsObject = { time: assi_evening }
}
document.getElementById("assi_date_debut").addEventListener("blur", (event) => {
updateSelect(null, "#ajout_assiduite_module_impl", event.target.valueAsObject.date)
})
updateSelect(saisie_eval ? moduleimpl : "", "#ajout_assiduite_module_impl", document.getElementById("assi_date_debut").valueAsObject.date);
});
</script>
{% endblock pageContent %}

View File

@ -3,10 +3,7 @@
<div class="pageContent"> <div class="pageContent">
<h3>Justifier des absences ou retards</h3> <h3>Justifier des absences ou retards</h3>
{% include "assiduites/widgets/tableau_base.j2" %} {% include "assiduites/widgets/tableau_base.j2" %}
<section class="liste">
<a class="icon filter" onclick="filterJusti()"></a>
{% include "assiduites/widgets/tableau_justi.j2" %}
</section>
<section class="justi-form page"> <section class="justi-form page">
@ -60,6 +57,10 @@
</fieldset> </fieldset>
</section> </section>
<section class="liste">
<a class="icon filter" onclick="filterJusti()"></a>
{% include "assiduites/widgets/tableau_justi.j2" %}
</section>
<div class="legende"> <div class="legende">
@ -224,12 +225,12 @@
if (document.getElementById('justi_journee').checked) { if (document.getElementById('justi_journee').checked) {
date_deb.setAttribute("show", "date") date_deb.setAttribute("show", "date")
date_fin.setAttribute("show", "date") date_fin.setAttribute("show", "date")
document.getElementById("date_fin").classList.add("hidden"); document.querySelector(`legend[for="justi_date_fin"]`).removeAttribute("required")
} else { } else {
date_deb.removeAttribute("show") date_deb.removeAttribute("show")
date_fin.removeAttribute("show") date_fin.removeAttribute("show")
document.getElementById("date_fin").classList.remove("hidden"); document.querySelector(`legend[for="justi_date_fin"]`).setAttribute("required", "")
} }
} }
@ -238,8 +239,12 @@
const date_fin = document.querySelector(".page #justi_date_fin") const date_fin = document.querySelector(".page #justi_date_fin")
const journee = document.querySelector('.page #justi_journee').checked const journee = document.querySelector('.page #justi_journee').checked
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time) 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) let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
if (journee) {
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
} else {
fin = date_fin.valueAsObject.date + fin
}
return { return {
"deb": deb, "deb": deb,
"fin": fin, "fin": fin,

View File

@ -343,6 +343,9 @@
</style> </style>
<script> <script>
const datePivot = "{{scu.get_assiduites_time_config("pivot")}}".split(":").map((el) => Number(el))
function getDaysBetweenDates(start, end) { function getDaysBetweenDates(start, end) {
let now = new Date(start); let now = new Date(start);
end = new Date(end); end = new Date(end);
@ -476,7 +479,7 @@
const matin = [new Date(date), new Date(date)] const matin = [new Date(date), new Date(date)]
color = "sans_etat" color = "sans_etat"
matin[0].setHours(0, 0, 0, 0) matin[0].setHours(0, 0, 0, 0)
matin[1].setHours(12, 59, 59) matin[1].setHours(...datePivot)
@ -515,7 +518,8 @@
span_aprem.classList.add("color"); span_aprem.classList.add("color");
const aprem = [new Date(date), new Date(date)] const aprem = [new Date(date), new Date(date)]
color = "sans_etat" color = "sans_etat"
aprem[0].setHours(13, 0, 0, 0) aprem[0].setHours(...datePivot)
aprem[0].add(1, "seconds")
aprem[1].setHours(23, 59, 59) aprem[1].setHours(23, 59, 59)

View File

@ -1,13 +1,24 @@
<label for="moduleimpl_select"> <div>
Module {% if label != false%}
<label for="moduleimpl_select">
Module
</label>
{% else %}
{% endif %}
{% if moduleid %}
<select id="{{moduleid}}" class="dynaSelect">
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
</select>
{% else %}
<select id="moduleimpl_select" class="dynaSelect"> <select id="moduleimpl_select" class="dynaSelect">
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %} {% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
</select> </select>
{% endif %}
<div id="saved" style="display: none;"> <div id="saved" style="display: none;">
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %} {% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
</div> </div>
</label> </div>
<script> <script>

View File

@ -88,6 +88,7 @@
td.textContent = getModuleImpl(assiduite); td.textContent = getModuleImpl(assiduite);
} else if (k.indexOf('est_just') != -1) { } else if (k.indexOf('est_just') != -1) {
td.textContent = assiduite[k] ? "Oui" : "Non" td.textContent = assiduite[k] ? "Oui" : "Non"
if (assiduite[k]) row.classList.add("est_just")
} else if (k.indexOf('etudid') != -1) { } else if (k.indexOf('etudid') != -1) {
const e = getEtudiant(assiduite.etudid); const e = getEtudiant(assiduite.etudid);

View File

@ -456,6 +456,7 @@
td { td {
border: 1px solid #dddddd; border: 1px solid #dddddd;
padding: 8px; padding: 8px;
color: var(--color-default-text);
} }
th { th {
@ -498,17 +499,25 @@
.l-absent, .l-absent,
.l-invalid { .l-invalid {
background-color: var(--color-absent); background-color: var(--color-absent-clair);
} }
.l-valid { .l-valid {
background-color: var(--color-primary); background-color: var(--color-justi-clair);
} }
.l-retard { .l-retard {
background-color: var(--color-retard); background-color: var(--color-retard);
} }
.l-absent.est_just {
background-color: var(--color-absent-justi);
}
.l-retard.est_just {
background-color: var(--color-retard-justi);
}
/* Ajoutez des styles pour le conteneur de pagination et les boutons */ /* Ajoutez des styles pour le conteneur de pagination et les boutons */
.pagination-container { .pagination-container {
display: flex; display: flex;

View File

@ -293,7 +293,8 @@ def signal_assiduites_etud():
"js/date_utils.js", "js/date_utils.js",
"js/etud_info.js", "js/etud_info.js",
], ],
cssstyles=[ cssstyles=CSSSTYLES
+ [
"css/assiduites.css", "css/assiduites.css",
], ],
) )
@ -318,27 +319,43 @@ def signal_assiduites_etud():
header, header,
_mini_timeline(), _mini_timeline(),
render_template( render_template(
"assiduites/pages/signal_assiduites_etud.j2", "assiduites/pages/ajout_assiduites.j2",
sco=ScoData(etud), sco=ScoData(etud),
date=_dateiso_to_datefr(date), assi_limit_annee=sco_preferences.get_preference(
morning=morning, "assi_limit_annee",
lunch=lunch, dept_id=g.scodoc_dept_id,
timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
afternoon=afternoon,
nonworkdays=_non_work_days(),
forcer_module=sco_preferences.get_preference(
"forcer_module", dept_id=g.scodoc_dept_id
),
diff=_differee(
etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
moduleimpl_select=select,
), ),
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
saisie_eval=saisie_eval, saisie_eval=saisie_eval,
date_deb=date_deb, date_deb=date_deb,
date_fin=date_fin, date_fin=date_fin,
redirect_url=redirect_url, redirect_url=redirect_url,
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
), ),
# render_template(
# "assiduites/pages/signal_assiduites_etud.j2",
# sco=ScoData(etud),
# date=_dateiso_to_datefr(date),
# morning=morning,
# lunch=lunch,
# timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
# afternoon=afternoon,
# nonworkdays=_non_work_days(),
# forcer_module=sco_preferences.get_preference(
# "forcer_module", dept_id=g.scodoc_dept_id
# ),
# diff=_differee(
# etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
# moduleimpl_select=select,
# ),
# saisie_eval=saisie_eval,
# date_deb=date_deb,
# date_fin=date_fin,
# redirect_url=redirect_url,
# moduleimpl_id=moduleimpl_id,
# ),
).build() ).build()