diff --git a/app/api/assiduites.py b/app/api/assiduites.py
index 044931b68d..3c5699c3b6 100644
--- a/app/api/assiduites.py
+++ b/app/api/assiduites.py
@@ -599,7 +599,7 @@ def assiduite_edit(assiduite_id: int):
moduleimpl: ModuleImpl = None
if moduleimpl_id is not False:
- if moduleimpl_id is not None:
+ if moduleimpl_id is not None and moduleimpl_id != "":
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
if moduleimpl is None:
errors.append("param 'moduleimpl_id': invalide")
@@ -611,7 +611,7 @@ def assiduite_edit(assiduite_id: int):
else:
assiduite_unique.moduleimpl_id = moduleimpl_id
else:
- assiduite_unique.moduleimpl_id = moduleimpl_id
+ assiduite_unique.moduleimpl_id = None
# Cas 3 : desc
desc = data.get("desc", False)
diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py
index e2cfe61ebd..d1b7862d31 100644
--- a/app/api/justificatifs.py
+++ b/app/api/justificatifs.py
@@ -272,9 +272,6 @@ def justif_edit(justif_id: int):
if deb is None:
errors.append("param 'date_debut': format invalide")
- if justificatif_unique.date_fin >= deb:
- errors.append("param 'date_debut': date de début située après date de fin ")
-
# cas 4 : date_fin
date_fin = data.get("date_fin", False)
if date_fin is not False:
@@ -283,13 +280,14 @@ def justif_edit(justif_id: int):
fin = scu.is_iso_formated(date_fin.replace(" ", "+"), convert=True)
if fin is None:
errors.append("param 'date_fin': format invalide")
- if justificatif_unique.date_debut <= fin:
- errors.append("param 'date_fin': date de fin située avant date de début ")
# Mise à jour des dates
deb = deb if deb is not None else justificatif_unique.date_debut
fin = fin if fin is not None else justificatif_unique.date_fin
+ if fin <= deb:
+ errors.append("param 'dates' : Date de début après date de fin")
+
justificatif_unique.date_debut = deb
justificatif_unique.date_fin = fin
diff --git a/app/scodoc/sco_archives_justificatifs.py b/app/scodoc/sco_archives_justificatifs.py
index 3cad18e96e..576b39473e 100644
--- a/app/scodoc/sco_archives_justificatifs.py
+++ b/app/scodoc/sco_archives_justificatifs.py
@@ -54,8 +54,8 @@ class Trace:
lines: list[str] = []
for fname, traced in self.content.items():
date_fin: datetime or None = traced[1].isoformat() if traced[1] else "None"
-
- lines.append(f"{fname},{traced[0].isoformat()},{date_fin}")
+ if traced[0] is not None:
+ lines.append(f"{fname},{traced[0].isoformat()},{date_fin}")
with open(self.path, "w", encoding="utf-8") as file:
file.write("\n".join(lines))
diff --git a/app/static/css/assiduites.css b/app/static/css/assiduites.css
index 56cef447c7..45b5bcc204 100644
--- a/app/static/css/assiduites.css
+++ b/app/static/css/assiduites.css
@@ -517,14 +517,25 @@
background-image: url(../icons/filter.svg);
}
+[name='destroyFile'] {
+ -webkit-appearance: none;
+ appearance: none;
+ cursor: pointer;
+ background-image: url(../icons/trash.svg);
+}
+
+[name='destroyFile']:checked {
+ background-image: url(../icons/remove_circle.svg);
+}
+
.icon {
display: block;
width: 24px;
height: 24px;
- outline: none;
- border: none;
+ outline: none !important;
+ border: none !important;
cursor: pointer;
- margin: 0 2px;
+ margin: 0 2px !important;
}
.icon:focus {
diff --git a/app/static/icons/remove_circle.svg b/app/static/icons/remove_circle.svg
new file mode 100644
index 0000000000..e0c6e0d7b9
--- /dev/null
+++ b/app/static/icons/remove_circle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/static/icons/trash.svg b/app/static/icons/trash.svg
new file mode 100644
index 0000000000..f8aa78561e
--- /dev/null
+++ b/app/static/icons/trash.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/templates/assiduites/pages/ajout_justificatif.j2 b/app/templates/assiduites/pages/ajout_justificatif.j2
index e10efcaf8b..5c001790f7 100644
--- a/app/templates/assiduites/pages/ajout_justificatif.j2
+++ b/app/templates/assiduites/pages/ajout_justificatif.j2
@@ -159,6 +159,7 @@
$.when(
requests
).done(() => {
+ loadAll();
})
}
@@ -171,7 +172,6 @@
let couverture = null;
createJustificatif(justificatif, (data) => {
- console.log(data);
if (Object.keys(data.errors).length > 0) {
console.error(data.errors);
}
@@ -179,7 +179,6 @@
couverture = data.success[0].couverture
justif_id = data.success[0].justif_id;
importFiles(justif_id);
- loadAll();
return;
}
})
diff --git a/app/templates/assiduites/pages/calendrier.j2 b/app/templates/assiduites/pages/calendrier.j2
index cf1ce4618b..73a3cbf425 100644
--- a/app/templates/assiduites/pages/calendrier.j2
+++ b/app/templates/assiduites/pages/calendrier.j2
@@ -246,7 +246,7 @@
day.textContent = `${dayOfWeek} ${dayOfMonth}`;
- if (!nonWorkdays.includes(dayOfWeek.toLowerCase())) {
+ if (!nonWorkdays.includes(dayOfWeek.toLowerCase()) && dayAssiduities.length > 0) {
const cache = document.createElement('div')
cache.classList.add('dayline');
cache.appendChild(
@@ -304,7 +304,6 @@
let dates = getDaysBetweenDates(bornes.deb, bornes.fin);
let datesByMonth = organizeByMonth(dates);
const justifs = getEtudJustificatifs(bornes.deb, bornes.fin);
- console.log(justifs)
let assiduitiesByDay = organizeAssiduitiesByDay(datesByMonth, data, justifs);
generateCalendar(assiduitiesByDay, nonwork);
});
diff --git a/app/templates/assiduites/widgets/alert.j2 b/app/templates/assiduites/widgets/alert.j2
index 55d82aa87c..d0ce3add8f 100644
--- a/app/templates/assiduites/widgets/alert.j2
+++ b/app/templates/assiduites/widgets/alert.j2
@@ -24,7 +24,7 @@
/* Hidden by default */
position: fixed;
/* Stay in place */
- z-index: 750;
+ z-index: 850;
/* Sit on top */
padding-top: 100px;
/* Location of the box */
diff --git a/app/templates/assiduites/widgets/prompt.j2 b/app/templates/assiduites/widgets/prompt.j2
index 8ef15c16b7..58c2784ec5 100644
--- a/app/templates/assiduites/widgets/prompt.j2
+++ b/app/templates/assiduites/widgets/prompt.j2
@@ -26,7 +26,7 @@
/* Stay in place */
z-index: 750;
/* Sit on top */
- padding-top: 100px;
+ padding-top: 3vh;
/* Location of the box */
left: 0;
top: 0;
@@ -181,8 +181,10 @@
succBtn.classList.add("btnPrompt")
succBtn.textContent = "Valider"
succBtn.addEventListener('click', () => {
- success();
- closePromptModal();
+ const retour = success();
+ if (retour == null || retour == false || retour == undefined) {
+ closePromptModal();
+ }
})
const cancelBtn = document.createElement('button')
cancelBtn.classList.add("btnPrompt")
diff --git a/app/templates/assiduites/widgets/tableau_assi.j2 b/app/templates/assiduites/widgets/tableau_assi.j2
index 2f995b2ce5..52114f24d3 100644
--- a/app/templates/assiduites/widgets/tableau_assi.j2
+++ b/app/templates/assiduites/widgets/tableau_assi.j2
@@ -39,6 +39,10 @@
+
+ {% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
+
+
\ No newline at end of file
diff --git a/app/templates/assiduites/widgets/tableau_base.j2 b/app/templates/assiduites/widgets/tableau_base.j2
index 6e7520b215..5121cb9acc 100644
--- a/app/templates/assiduites/widgets/tableau_base.j2
+++ b/app/templates/assiduites/widgets/tableau_base.j2
@@ -11,6 +11,7 @@
const itemsPerPage = 10;
const contextMenu = document.getElementById("contextMenu");
const editOption = document.getElementById("editOption");
+ const detailOption = document.getElementById("detailOption");
const deleteOption = document.getElementById("deleteOption");
let selectedRow;
@@ -21,14 +22,32 @@
editOption.addEventListener("click", () => {
if (selectedRow) {
- // Code pour éditer la ligne sélectionnée
- console.debug("Éditer :", selectedRow);
+ const type = selectedRow.getAttribute('type');
+ const obj_id = selectedRow.getAttribute('obj_id');
+
+ if (type == "assiduite") {
+ editionAssiduites(obj_id);
+ } else {
+ editionJustificatifs(obj_id);
+ }
+ }
+ });
+
+ detailOption.addEventListener("click", () => {
+ if (selectedRow) {
+ const type = selectedRow.getAttribute('type');
+ const obj_id = selectedRow.getAttribute('obj_id');
+
+ if (type == "assiduite") {
+ detailAssiduites(obj_id);
+ } else {
+ detailJustificatifs(obj_id);
+ }
}
});
deleteOption.addEventListener("click", () => {
if (selectedRow) {
- // Code pour supprimer la ligne sélectionnée
const type = selectedRow.getAttribute('type');
const obj_id = selectedRow.getAttribute('obj_id');
if (type == "assiduite") {
@@ -112,33 +131,70 @@
function renderPaginationButtons(array, assi = true) {
const totalPages = Math.ceil(array.length / itemsPerPage);
-
- if (assi) {
- paginationContainerAssiduites.innerHTML = ""
- } else {
- paginationContainerJustificatifs.innerHTML = ""
- }
-
- if (totalPages == 1) {
+ if (totalPages <= 1) {
return;
}
- for (let i = 1; i <= totalPages; i++) {
- const paginationButton = document.createElement("a");
- paginationButton.textContent = i;
- paginationButton.classList.add("pagination-button");
- if (assi) {
- paginationButton.addEventListener("click", () => {
- currentPageAssiduites = i;
+ if (assi) {
+ paginationContainerAssiduites.innerHTML = ""
+ paginationContainerAssiduites.querySelector('#paginationAssi')?.addEventListener('change', (e) => {
+ currentPageAssiduites = e.target.value;
+ renderTableAssiduites(currentPageAssiduites, array);
+ })
+
+ paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
+ if (currentPageAssiduites > 1) {
+ currentPageAssiduites--;
+ paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
renderTableAssiduites(currentPageAssiduites, array);
- });
- paginationContainerAssiduites.appendChild(paginationButton);
+ }
+ })
+
+ paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => {
+ if (currentPageAssiduites < totalPages) {
+ currentPageAssiduites++;
+ paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
+ renderTableAssiduites(currentPageAssiduites, array);
+ }
+ })
+ } else {
+ paginationContainerJustificatifs.innerHTML = ""
+ paginationContainerJustificatifs.querySelector('#paginationJusti')?.addEventListener('change', (e) => {
+ currentPageJustificatifs = e.target.value;
+ renderTableJustificatifs(currentPageJustificatifs, array);
+ })
+
+ paginationContainerJustificatifs.querySelector('.pagination_moins').addEventListener('click', () => {
+ if (currentPageJustificatifs > 1) {
+ currentPageJustificatifs--;
+ paginationContainerJustificatifs.querySelector('#paginationJusti').value = currentPageAssiduites
+ renderTableJustificatifs(currentPageJustificatifs, array);
+ }
+ })
+
+ paginationContainerJustificatifs.querySelector('.pagination_plus').addEventListener('click', () => {
+ if (currentPageJustificatifs < totalPages) {
+ currentPageJustificatifs++;
+ paginationContainerJustificatifs.querySelector('#paginationJusti').value = currentPageAssiduites
+ renderTableJustificatifs(currentPageJustificatifs, array);
+ }
+ })
+ }
+
+
+
+
+
+
+ for (let i = 1; i <= totalPages; i++) {
+ const paginationButton = document.createElement("option");
+ paginationButton.textContent = i;
+ paginationButton.value = i;
+
+ if (assi) {
+ paginationContainerAssiduites.querySelector('#paginationAssi').appendChild(paginationButton)
} else {
- paginationButton.addEventListener("click", () => {
- currentPageJustificatifs = i;
- renderTableAssiduites(currentPageJustificatifs, array);
- });
- paginationContainerJustificatifs.appendChild(paginationButton);
+ paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton)
}
}
updateActivePaginationButton(assi);
@@ -571,7 +627,13 @@
}
}
-
+ function openContext(e) {
+ e.preventDefault();
+ selectedRow = e.target.parentElement;
+ contextMenu.style.top = `${e.clientY - contextMenu.offsetHeight}px`;
+ contextMenu.style.left = `${e.clientX}px`;
+ contextMenu.style.display = "block";
+ }
@@ -608,7 +670,7 @@
.context-menu {
display: none;
- position: absolute;
+ position: fixed;
list-style-type: none;
padding: 10px 0;
background-color: #f9f9f9;
@@ -721,4 +783,34 @@
padding: 0;
margin: 0;
}
+
+ .obj-title {
+ text-decoration: underline #bbb;
+ font-weight: bold;
+ }
+
+ .obj-part {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 33%;
+ padding: 5px;
+ border: 1px solid #bbb;
+ }
+
+ .obj-dates,
+ .obj-mod,
+ .obj-rest {
+ display: flex;
+ justify-content: space-evenly;
+ margin: 2px;
+ }
+
+ .liste_pagination {
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ gap: 5px;
+ }
\ No newline at end of file
diff --git a/app/templates/assiduites/widgets/tableau_justi.j2 b/app/templates/assiduites/widgets/tableau_justi.j2
index 27dd12e2a5..c1808640ec 100644
--- a/app/templates/assiduites/widgets/tableau_justi.j2
+++ b/app/templates/assiduites/widgets/tableau_justi.j2
@@ -93,19 +93,360 @@
row.appendChild(td)
})
- row.addEventListener("contextmenu", (e) => {
- e.preventDefault();
- selectedRow = e.target.parentElement;
- contextMenu.style.top = `${e.clientY}px`;
- contextMenu.style.left = `${e.clientX}px`;
- contextMenu.style.display = "block";
- console.log(selectedRow);
- });
-
+ row.addEventListener("contextmenu", openContext);
tableBodyJustificatifs.appendChild(row);
});
updateActivePaginationButton(false);
}
+ function detailJustificatifs(justi_id) {
+ const path = getUrl() + `/api/justificatif/${justi_id}`;
+ async_get(
+ path,
+ (data) => {
+ const user = getUserFromId(data.user_id);
+ 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");
-
\ No newline at end of file
+ const etat = data.etat.capitalize();
+ const desc = data.raison == null ? "" : data.raison;
+ const id = data.justif_id;
+ const fichier = data.fichier != null ? "Oui" : "Non";
+ let filenames = []
+ if (fichier) {
+ sync_get(path + "/list", (data2) => {
+ filenames = data2;
+ })
+ }
+
+ const html = `
+
+
+
+ Date de début
+ ${date_debut}
+
+
+ Date de fin
+ ${date_fin}
+
+
+ Date de saisie
+ ${entry_date}
+
+
+
+
+
+ Raison
+ ${desc}
+
+
+ Etat
+ ${etat}
+
+
+ Créer par
+ ${user}
+
+
+
+
+
+ Identifiant du justificatif
+ ${id}
+
+
+
+
+ `
+
+ const el = document.createElement('div');
+ el.innerHTML = html;
+
+ const fichContent = el.querySelector('#fich-content');
+
+ filenames.forEach((name) => {
+ const a = document.createElement('a');
+ a.textContent = name
+ a.classList.add("fich-file")
+
+ a.onclick = () => { downloadFile(id, name) };
+
+ fichContent.appendChild(a);
+ })
+
+ openAlertModal("Détails", el.firstElementChild, null, "green")
+ }
+ )
+ }
+
+ function downloadFile(id, name) {
+ const path = getUrl() + `/api/justificatif/${id}/export/${name}`;
+
+ fetch(path, {
+ method: "POST"
+
+ })
+ // This returns a promise inside of which we are checking for errors from the server.
+ // The catch promise at the end of the call does not getting called when the server returns an error.
+ // More information about the error catching can be found here: https://www.tjvantoll.com/2015/09/13/fetch-and-errors/.
+ .then((result) => {
+ if (!result.ok) {
+ throw Error(result.statusText);
+ }
+
+ // We are reading the *Content-Disposition* header for getting the original filename given from the server
+ const header = result.headers.get('Content-Disposition');
+ const parts = header.split(';');
+ filename = parts[1].split('=')[1].replaceAll("\"", "");
+
+ return result.blob();
+ })
+ // We use the download property for triggering the download of the file from our browser.
+ // More information about the following code can be found here: https://stackoverflow.com/questions/32545632/how-can-i-download-a-file-using-window-fetch.
+ // The filename from the first promise is used as name of the file.
+ .then((blob) => {
+ if (blob != null) {
+ var url = window.URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ }
+ })
+ // The catch is getting called only for client-side errors.
+ // For example the throw in the first then-promise, which is the error that came from the server.
+ .catch((err) => {
+ console.log(err);
+ });
+ }
+
+ function editionJustificatifs(justif_id) {
+ const path = getUrl() + `/api/justificatif/${justif_id}`;
+ async_get(
+ path,
+ (data) => {
+ const html = `
+
+
+
+
+ Etat du justificatif
+
+ En Attente de validation
+ Non Valide
+ Modifié
+ Valide
+
+
+
+
+
+
+
+
+
+ Importer un fichier
+
+
+
+
+ `
+
+ const desc = data.raison
+ const fichier = data.fichier != null ? "Oui" : "Non";
+
+
+ const el = document.createElement('div')
+ el.innerHTML = html;
+ const assiEdit = el.firstElementChild;
+
+ assiEdit.querySelector('#justi_etat').value = data.etat.toLowerCase();
+ assiEdit.querySelector('#justi_raison').value = desc != null ? desc : "";
+
+ assiEdit.querySelector('#justi_date_debut').value = moment.tz(data.date_debut, TIMEZONE).format("YYYY-MM-DDTHH:MM")
+ assiEdit.querySelector('#justi_date_fin').value = moment.tz(data.date_fin, TIMEZONE).format("YYYY-MM-DDTHH:MM")
+
+ const fichContent = assiEdit.querySelector('.justi-sect');
+
+ let filenames = []
+ if (data.fichier) {
+ sync_get(path + "/list", (data2) => {
+ filenames = data2;
+ })
+
+ fichContent.insertAdjacentHTML('beforeend', "Fichier(s) ")
+ }
+
+
+ filenames.forEach((name) => {
+ const a = document.createElement('a');
+ a.textContent = name
+ a.classList.add("fich-file")
+
+ a.onclick = () => { downloadFile(id, name) };
+
+ const input = document.createElement('input')
+ input.type = "checkbox"
+ input.name = "destroyFile";
+ input.classList.add('icon')
+
+ const span = document.createElement('span');
+ span.classList.add('file-line')
+ span.appendChild(input)
+ span.appendChild(a)
+
+
+ fichContent.appendChild(span);
+ })
+
+ openPromptModal("Modification du justificatif", assiEdit, () => {
+ const prompt = document.querySelector('.assi-edit');
+
+ let date_debut = prompt.querySelector('#justi_date_debut').value;
+ let date_fin = prompt.querySelector('#justi_date_fin').value;
+
+ if (date_debut == "" || date_fin == "") {
+ 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)
+
+ if (date_debut >= date_fin) {
+ openAlertModal("Dates erronées", document.createTextNode('La date de fin doit être après la date de début'));
+ return true
+ }
+
+ const edit = {
+ date_debut: date_debut.format(),
+ date_fin: date_fin.format(),
+ raison: prompt.querySelector('#justi_raison').value,
+ etat: prompt.querySelector('#justi_etat').value,
+ }
+
+ const toRemoveFiles = [...prompt.querySelectorAll('[name="destroyFile"]:checked')]
+
+ if (toRemoveFiles.length > 0) {
+ removeFiles(justif_id, toRemoveFiles);
+ }
+
+ const in_files = prompt.querySelector('#justi_fich');
+
+ if (in_files.files.length > 0) {
+ importNewFiles(justif_id, in_files);
+ }
+
+ fullEditJustificatifs(data.justif_id, edit, () => {
+ loadAll();
+ })
+
+
+ }, () => { }, "green");
+ }
+ );
+ }
+
+ function fullEditJustificatifs(justif_id, obj, call = () => { }) {
+ const path = getUrl() + `/api/justificatif/${justif_id}/edit`;
+ async_post(
+ path,
+ obj,
+ call,
+ (data, status) => {
+ //error
+ console.error(data, status);
+ }
+ );
+ }
+
+ function removeFiles(justif_id, files = []) {
+ const path = getUrl() + `/api/justificatif/${justif_id}/remove`;
+ files = files.map((el) => {
+ return el.parentElement.querySelector('a').textContent;
+ });
+
+ console.log(justif_id, files);
+ sync_post(
+ path,
+ {
+ "remove": "list",
+ "filenames": files,
+ },
+ );
+ }
+
+ function importNewFiles(justif_id, in_files) {
+ const path = getUrl() + `/api/justificatif/${justif_id}/import`;
+
+ const requests = []
+ Array.from(in_files.files).forEach((f) => {
+ const fd = new FormData();
+ fd.append('file', f);
+ requests.push(
+ $.ajax(
+ {
+ url: path,
+ type: 'POST',
+ data: fd,
+ dateType: 'json',
+ contentType: false,
+ processData: false,
+ success: () => { },
+ }
+ )
+ )
+
+ });
+
+ $.when(
+ requests
+ ).done(() => {
+ })
+
+ }
+
+
+
\ No newline at end of file