diff --git a/app/static/css/assiduites.css b/app/static/css/assiduites.css
index da872491b..02730449c 100644
--- a/app/static/css/assiduites.css
+++ b/app/static/css/assiduites.css
@@ -6,7 +6,9 @@
--color-justi: #29b990;
--color-justi-clair: #48f6ff;
--color-justi-attente: yellow;
- --color-justi-attente-stripe: #29b990; /* pink #fa25cb; */ /* #789dbb;*/
+ --color-justi-attente-stripe: #29b990;
+ /* pink #fa25cb; */
+ /* #789dbb;*/
--color-justi-modifie: rgb(255, 230, 0);
--color-justi-invalide: #a84476;
--color-nonwork: #badfff;
@@ -28,27 +30,23 @@
--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-invalide: repeating-linear-gradient(
- -135deg,
- transparent,
- transparent 4px,
- var(--color-justi-invalide) 4px,
- var(--color-justi-invalide) 8px
- );
+ --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;
}
-.selectors > * {
+.selectors>* {
margin: 10px 0;
}
@@ -339,6 +337,11 @@
background-image: url(../icons/retard.svg);
}
+.rbtn.conflit::before {
+ background-color: var(--color-absent);
+ background-image: url(../icons/solveur_conflits.svg);
+}
+
.rbtn:checked:before {
outline: 5px solid var(--color-primary);
border-radius: 50%;
@@ -405,29 +408,11 @@
.assiduite {
position: absolute;
top: 20px;
- cursor: pointer;
border-radius: 4px;
z-index: 10;
height: 100px;
padding: 4px;
-}
-
-.assiduite-info {
- display: flex;
- flex-direction: column;
- height: 100%;
- justify-content: space-between;
-}
-
-.assiduite-id,
-.assiduite-period,
-.assiduite-state,
-.assiduite-user_id {
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-align: center;
+ border: 1px solid #444;
}
.assiduites-container {
@@ -438,7 +423,7 @@
margin-bottom: 10px;
}
-.action-buttons {
+.modal-buttons {
position: absolute;
text-align: center;
display: flex;
@@ -449,48 +434,35 @@
bottom: 5%;
}
-/* Ajout de la classe CSS pour la bordure en pointillés */
-.assiduite.selected {
- border: 2px dashed black;
-}
.assiduite-special {
height: 120px;
position: absolute;
z-index: 5;
- border: 2px solid #000;
- background-color: rgba(36, 36, 36, 0.25);
- background-image: repeating-linear-gradient(
- 135deg,
- transparent,
- transparent 5px,
- rgba(81, 81, 81, 0.61) 5px,
- rgba(81, 81, 81, 0.61) 10px
- );
+ border: 5px solid var(--color-primary);
+ /* background-color: rgba(36, 36, 36, 0.25);
+ background-image: repeating-linear-gradient(135deg,
+ transparent,
+ transparent 5px,
+ rgba(81, 81, 81, 0.61) 5px,
+ rgba(81, 81, 81, 0.61) 10px); */
border-radius: 5px;
}
-/*<== Info sur l'assiduité sélectionnée ==>*/
-.modal-assiduite-content {
- background-color: #fefefe;
- margin: 5% auto;
- padding: 20px;
- border: 1px solid #888;
- width: max-content;
- position: relative;
- border-radius: 10px;
- display: none;
+.assiduite .assiduite-bubble {
+ top: 5px;
+ left: 50%;
+ transform: translateX(-50%);
}
-.modal-assiduite-content.show {
- display: block;
-}
-
-.modal-assiduite-content .infos {
+.action-buttons {
display: flex;
- flex-direction: column;
- justify-content: space-evenly;
- align-items: flex-start;
+ justify-content: center;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 2px;
+ height: 100%;
+
}
/*<=== Mass Action ==>*/
@@ -500,57 +472,16 @@
justify-content: flex-start;
align-items: center;
width: 100%;
- margin: 2% 0;
+ gap: 4px;
}
-.mass-selection span {
- margin: 0 1%;
-}
+
.mass-selection .rbtn {
background-color: transparent;
cursor: pointer;
}
-/*<== Loader ==> */
-
-.loader-container {
- display: none;
- /* Cacher le loader par défaut */
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- /* Fond semi-transparent pour bloquer les clics */
- z-index: 9999;
- /* Placer le loader au-dessus de tout le contenu */
-}
-
-.loader {
- border: 6px solid #f3f3f3;
- border-radius: 50%;
- border-top: 6px solid var(--color-primary);
- width: 60px;
- height: 60px;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- animation: spin 2s linear infinite;
-}
-
-@keyframes spin {
- 0% {
- transform: translate(-50%, -50%) rotate(0deg);
- }
-
- 100% {
- transform: translate(-50%, -50%) rotate(360deg);
- }
-}
-
.fieldsplit {
display: flex;
justify-content: flex-start;
@@ -569,7 +500,7 @@
flex-direction: column;
}
-#page-assiduite-content > * {
+#page-assiduite-content>* {
margin: 1.5% 0;
}
@@ -649,6 +580,7 @@
margin-right: 24px;
padding: 12px;
}
+
#options-tableau label {
font-weight: normal;
margin-right: 12px;
@@ -657,15 +589,20 @@
section.assi-form {
margin-bottom: 12px;
}
+
table.liste_assi td.date {
width: 140px;
}
+
table.liste_assi.dataTable tbody td.date-debut {
padding-left: 12px;
}
+
table.liste_assi td.actions {
- white-space: nowrap; /* boutons horizontalement */
+ white-space: nowrap;
+ /* boutons horizontalement */
}
+
table.liste_assi td.actions a:last-child {
padding-right: 12px;
}
@@ -673,31 +610,154 @@ table.liste_assi td.actions a:last-child {
tr.row-assiduite td {
border-bottom: 1px solid grey;
}
+
table.liste_assi tbody tr td.assi-type {
padding-left: 8px;
padding-right: 4px;
}
+
tr.row-assiduite.absent td.assi-type {
background-color: var(--color-absent-clair);
}
+
tr.row-assiduite.absent.justifiee td.assi-type {
background-color: var(--color-absent-justi);
}
+
tr.row-assiduite.retard td.assi-type {
background-color: var(--color-retard);
}
+
tr.row-assiduite.present td.assi-type {
background-color: var(--color-present);
}
+
tr.row-justificatif.valide td.assi-type {
background-color: var(--color-justi);
}
+
tr.row-justificatif.attente td.assi-type {
background-color: var(--color-justi-attente);
}
+
tr.row-justificatif.modifie td.assi-type {
background-color: var(--color-justi-modifie);
}
+
tr.row-justificatif.non_valide td.assi-type {
background-color: var(--color-justi-invalide);
}
+
+/*
+
+<== Loader ==>
+
+*/
+/* HTML:
*/
+.loader {
+ width: 80px;
+ height: 70px;
+ border: 5px solid #000;
+ padding: 0 8px;
+ box-sizing: border-box;
+ background:
+ linear-gradient(#fff 0 0) 0 0/8px 20px,
+ linear-gradient(#fff 0 0) 100% 0/8px 20px,
+ radial-gradient(farthest-side, #fff 90%, #0000) 0 5px/8px 8px content-box,
+ #000;
+ background-repeat: no-repeat;
+ animation: l3 2s infinite linear;
+}
+
+@keyframes l3 {
+ 25% {
+ background-position: 0 0, 100% 100%, 100% calc(100% - 5px)
+ }
+
+ 50% {
+ background-position: 0 100%, 100% 100%, 0 calc(100% - 5px)
+ }
+
+ 75% {
+ background-position: 0 100%, 100% 0, 100% 5px
+ }
+}
+
+#loader {
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ transform: translate(-50%, -50%);
+ z-index: 1000;
+ background-color: rgba(255, 255, 255, 0.8);
+}
+
+/**
+ * <== Couleurs ==>
+ */
+
+.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;
+}
+
+.color.est_just.sans_etat::before {
+ content: "";
+ position: absolute;
+ width: 25%;
+ height: 100%;
+ background-color: var(--color-justi) !important;
+ right: 0;
+}
+
+.color.invalide::before {
+ content: "";
+ position: absolute;
+ width: 25%;
+ height: 100%;
+ right: 0;
+ background-color: var(--color-justi-invalide) !important;
+}
+
+.color.attente::before,
+.color.modifie::before {
+ content: "";
+ position: absolute;
+ width: 25%;
+ height: 100%;
+ right: 0;
+ background: repeating-linear-gradient(to bottom,
+ var(--color-justi-attente-stripe) 0px,
+ var(--color-justi-attente-stripe) 4px,
+ var(--color-justi-attente) 4px,
+ var(--color-justi-attente) 7px) !important;
+}
\ No newline at end of file
diff --git a/app/static/icons/solveur_conflits.svg b/app/static/icons/solveur_conflits.svg
new file mode 100644
index 000000000..1dadba2df
--- /dev/null
+++ b/app/static/icons/solveur_conflits.svg
@@ -0,0 +1,17 @@
+
+
+
\ No newline at end of file
diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js
index 8141dec33..91fee8cfe 100644
--- a/app/static/js/assiduites.js
+++ b/app/static/js/assiduites.js
@@ -1,30 +1,17 @@
-// TODO : Supprimer les fonctions non utilisées + optimiser les fonctions utilisées
-// <=== CONSTANTS and GLOBALS ===>
+/**
+ *
+ * Ensemble des fonctions liées à la gestion des assiduités
+ * Créé par : HARTMANN Matthias (Iziram)
+ *
+ */
-let url;
-
-function getUrl() {
- if (!url) {
- url = SCO_URL.substring(0, SCO_URL.lastIndexOf("/"));
- }
- return url;
-}
-
-//Les valeurs par défaut de la timeline (8h -> 18h)
-let currentValues = [8.0, 10.0];
-
-//Objet stockant les étudiants et les assiduités
-let etuds = {};
-let assiduites = {};
-let justificatifs = {};
-
-// Variable qui définit si le processus d'action de masse est lancé
-let currentMassAction = false;
-let currentMassActionEtat = undefined;
+/**
+ * <== OUTILS ==>
+ */
/**
* Ajout d'une fonction `capitalize` sur tous les strings
- * alice.capitalize() -> Alice
+ * "alice".capitalize() -> "Alice"
*/
Object.defineProperty(String.prototype, "capitalize", {
value: function () {
@@ -33,179 +20,6 @@ Object.defineProperty(String.prototype, "capitalize", {
enumerable: false,
});
-const DatePrecisions = [
- "year",
- "month",
- "day",
- "hour",
- "minute",
- "second",
- "millisecond",
-];
-
-// <<== Outils ==>>
-Object.defineProperty(Array.prototype, "reversed", {
- value: function () {
- return [...this].map(this.pop, this);
- },
- enumerable: false,
-});
-
-/**
- * Ajout des évents sur les boutons d'assiduité
- * @param {Document | HTMLFieldSetElement} parent par défaut le document, un field sinon
- */
-function setupCheckBox(parent = document) {
- const checkboxes = Array.from(parent.querySelectorAll(".rbtn"));
- checkboxes.forEach((box) => {
- box.addEventListener("click", (event) => {
- if (!uniqueCheckBox(box)) {
- event.preventDefault();
- }
- if (!box.parentElement.classList.contains("mass")) {
- assiduiteAction(box);
- }
- });
- });
-}
-
-function updateEtudList() {
- const group_ids = getGroupIds();
- etuds = {};
- group_ids.forEach((group_id) => {
- sync_get(getUrl() + `/api/group/${group_id}/etudiants`, (data, status) => {
- if (status === "success") {
- data.forEach((etud) => {
- if (!(etud.id in etuds)) {
- etuds[etud.id] = etud;
- }
- });
- }
- });
- });
-
- getAssiduitesFromEtuds(true);
- generateAllEtudRow();
-}
-
-/**
- * Validation préalable puis désactivation des chammps :
- * - Groupe
- * - Module impl
- * - Date
- */
-function validateSelectors(btn) {
- const action = () => {
- const group_ids = getGroupIds();
-
- etuds = {};
- group_ids.forEach((group_id) => {
- sync_get(
- getUrl() + `/api/group/${group_id}/etudiants`,
- (data, status) => {
- if (status === "success") {
- data.forEach((etud) => {
- if (!(etud.id in etuds)) {
- etuds[etud.id] = etud;
- }
- });
- }
- }
- );
- });
-
- if (getModuleImplId() == null && window.forceModule && !readOnly) {
- const HTML = `
- Attention, le module doit obligatoirement être renseigné.
- Cela vient de la configuration du semestre ou plus largement du département.
- Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.
- `;
-
- const content = document.createElement("div");
- content.innerHTML = HTML;
-
- openAlertModal("Sélection du module", content);
- return;
- }
-
- generateMassAssiduites();
-
- getAssiduitesFromEtuds(true);
- generateAllEtudRow();
-
- btn.remove();
- // Auto actualisation
- $("#tl_date").on("change", updateEtudList);
- $("#group_ids_sel").on("change", updateEtudList);
-
- onlyAbs();
- };
-
- if (!verifyDateInSemester()) {
- const HTML = `
- Attention, la date sélectionnée n'est pas comprise dans le semestre.
- Cette page permet l'affichage et la modification des assiduités uniquement pour le semestre sélectionné.
- Vous n'aurez donc pas accès aux assiduités.
- Appuyer sur "Valider" uniquement si vous souhaitez poursuivre sans modifier la date.
- `;
-
- const content = document.createElement("div");
- content.innerHTML = HTML;
-
- openPromptModal("Vérification de la date", content, action);
- return;
- }
-
- action();
-}
-
-function onlyAbs() {
- if (getDate() > Date.now()) {
- document
- .querySelectorAll(".rbtn.present, .rbtn.retard")
- .forEach((el) => el.remove());
- }
-}
-
-/**
- * Limite le nombre de checkbox marquée
- * Vérifie aussi si le cliqué est fait sur des assiduités conflictuelles
- * @param {HTMLInputElement} box la checkbox utilisée
- * @returns {boolean} Faux si il y a un conflit d'assiduité, Vrai sinon
- */
-function uniqueCheckBox(box) {
- const type = box.parentElement.getAttribute("type") === "conflit";
- if (!type) {
- const checkboxs = Array.from(box.parentElement.children);
-
- checkboxs.forEach((chbox) => {
- if (chbox.checked && chbox.value !== box.value) {
- chbox.checked = false;
- }
- });
- return true;
- }
-
- return false;
-}
-
-/**
- * Fait une requête GET de façon synchrone
- * @param {String} path adresse distante
- * @param {CallableFunction} success fonction à effectuer en cas de succès
- * @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,
- type: "GET",
- url: path,
- success: success,
- error: errors,
- });
-}
/**
* Fait une requête GET de façon asynchrone
* @param {String} path adresse distante
@@ -213,43 +27,24 @@ function sync_get(path, success, errors) {
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/
async function async_get(path, success, errors) {
- console.log("async_get " + path);
- let response;
- try {
- 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);
- }
+ const response = fetch(path);
+ response
+ .then((response) => {
+ if (response.ok) {
+ response.json().then((data) => {
+ success(data, "success");
+ });
+ } else {
+ throw new Error("Network response was not ok.");
+ }
+ })
+ .catch((error) => {
+ console.error(error);
+ if (errors) errors(error);
+ });
return response;
}
-
-/**
- * Fait une requête POST de façon synchrone
- * @param {String} path adresse distante
- * @param {object} data données à envoyer (objet js)
- * @param {CallableFunction} success fonction à effectuer en cas de succès
- * @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,
- type: "POST",
- url: path,
- data: JSON.stringify(data),
- success: success,
- error: errors,
- });
-}
/**
* Fait une requête POST de façon asynchrone
* @param {String} path adresse distante
@@ -283,1679 +78,325 @@ async function async_post(path, data, success, errors) {
return response;
}
-// <<== Gestion des actions de masse ==>>
-const massActionQueue = new Map();
-
/**
- * Cette fonction remet à zero la gestion des actions de masse
+ * Récupère les étudiants en fonction des groupes sélectionnés
+ * @param {Array} groupIds - Les identifiants des groupes pour lesquels récupérer les étudiants.
+ * @returns {Promise