<table id="justificatifTable"> <thead> <tr> <th> <div> <span>Début</span> <a class="icon order" onclick="order('date_debut', justificatifCallBack, this, false)"></a> </div> </th> <th> <div> <span>Fin</span> <a class="icon order" onclick="order('date_fin', justificatifCallBack, this, false)"></a> </div> </th> <th> <div> <span>État</span> <a class="icon order" onclick="order('etat', justificatifCallBack, this, false)"></a> </div> </th> <th> <div> <span>Raison</span> <a class="icon order" onclick="order('raison', justificatifCallBack, this, false)"></a> </div> </th> <th> <div> <span>Fichier</span> <a class="icon order" onclick="order('fichier', justificatifCallBack, this, false)"></a> </div> </th> </tr> </thead> <tbody id="tableBodyJustificatifs"> </tbody> </table> <div id="paginationContainerJustificatifs" class="pagination-container"> </div> <script> const paginationContainerJustificatifs = document.getElementById("paginationContainerJustificatifs"); let currentPageJustificatifs = 1; let orderJustificatifs = true; let filterJustificatifs = { columns: [ "entry_date", "date_debut", "date_fin", "etat", "raison", "fichier" ], filters: {} } const tableBodyJustificatifs = document.getElementById("tableBodyJustificatifs"); function justificatifCallBack(justi) { justi = filterArray(justi, filterJustificatifs.filters) renderTableJustificatifs(currentPageJustificatifs, justi); renderPaginationButtons(justi, false); } function renderTableJustificatifs(page, justificatifs) { generateTableHead(filterJustificatifs.columns, false) tableBodyJustificatifs.innerHTML = ""; const start = (page - 1) * itemsPerPage; const end = start + itemsPerPage; justificatifs.slice(start, end).forEach((justificatif) => { const row = document.createElement("tr"); row.setAttribute('type', "justificatif"); row.setAttribute('obj_id', justificatif.justif_id); const etat = justificatif.etat.toLowerCase(); if (etat == "valide") { row.classList.add(`l-valid`); } else { row.classList.add(`l-invalid`); } filterJustificatifs.columns.forEach((k) => { const td = document.createElement('td'); if (k.indexOf('date') != -1) { td.textContent = new Date(Date.removeUTC(justificatif[k])).format(`DD/MM/Y HH:mm`) } else if (k.indexOf('fichier') != -1) { td.textContent = justificatif.fichier ? "Oui" : "Non"; } else if (k.indexOf('etudid') != -1) { const e = getEtudiant(justificatif.etudid); td.innerHTML = `<a class="etudinfo" id="line-${justificatif.etudid}" href="bilan_etud?etudid=${justificatif.etudid}">${e.prenom.capitalize()} ${e.nom.toUpperCase()}</a>`; } else if (k == "formsemestre") { if (justificatif.hasOwnProperty("formsemestre")) { td.textContent = justificatif.formsemestre.title.replaceAll('-', ' '); } else { td.textContent = `Pas de Semestre`; } } else { if (justificatif[k] != null) { td.textContent = `${justificatif[k]}`.capitalize() } else { td.textContent = ""; } } row.appendChild(td) }) 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 = getUser(data); const date_debut = new Date(Date.removeUTC(data.date_debut)).format("DD/MM/YYYY HH:mm"); const date_fin = new Date(Date.removeUTC(data.date_fin)).format("DD/MM/YYYY HH:mm"); const entry_date = new Date(Date.removeUTC(data.entry_date)).format("DD/MM/YYYY HH:mm"); 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 = [] let totalFiles = 0; if (fichier) { sync_get(path + "/list", (data2) => { filenames = data2.filenames; totalFiles = data2.total; }) } const html = ` <div class="obj-detail"> <div class="obj-dates"> <div id="date_debut" class="obj-part"> <span class="obj-title">Date de début</span> <span class="obj-content">${date_debut}</span> </div> <div id="date_fin" class="obj-part"> <span class="obj-title">Date de fin</span> <span class="obj-content">${date_fin}</span> </div> <div id="entry_date" class="obj-part"> <span class="obj-title">Date de saisie</span> <span class="obj-content">${entry_date}</span> </div> </div> <div class="obj-mod"> <div id="module" class="obj-part"> <span class="obj-title">Raison</span> <span class="obj-content">${desc}</span> </div> <div id="etat" class="obj-part"> <span class="obj-title">Etat</span> <span class="obj-content">${etat}</span> </div> <div id="user" class="obj-part"> <span class="obj-title">Créé par</span> <span class="obj-content">${user}</span> </div> </div> <div class="obj-rest"> <div id="est_just" class="obj-part obj-66"> <span class="obj-title">Fichier(s)</span> <div class="obj-content" id="fich-content"></div> </div> <div id="id" class="obj-part"> <span class="obj-title">Identifiant du justificatif</span> <span class="obj-content">${id}</span> </div> </div> </div> ` const el = document.createElement('div'); el.innerHTML = html; const fichContent = el.querySelector('#fich-content'); const s = document.createElement('span') s.textContent = `${totalFiles} fichier(s) dont ${filenames.length} visible(s)` fichContent.appendChild(s) 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, "var(--color-information)") } ) } 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.error(err); }); } function editionJustificatifs(justif_id) { const path = getUrl() + `/api/justificatif/${justif_id}`; async_get( path, (data) => { const html = ` <div class="assi-edit"> <div class="justi-row"> <div class="justi-label"> <legend for="justi_date_debut">Date de début</legend> <input type="datetime-local" name="justi_date_debut" id="justi_date_debut"> </div> <div class="justi-label"> <legend for="justi_date_fin">Date de fin</legend> <input type="datetime-local" name="justi_date_fin" id="justi_date_fin"> </div> </div> <div class="justi-row"> <div class="justi-label"> <legend for="justi_etat">Etat du justificatif</legend> <select name="justi_etat" id="justi_etat"> <option value="attente" selected>En Attente de validation</option> <option value="non_valide">Non Valide</option> <option value="modifie">Modifié</option> <option value="valide">Valide</option> </select> </div> </div> <div class="justi-row"> <div class="justi-label"> <legend for="justi_raison">Raison</legend> <textarea name="justi_raison" id="justi_raison" cols="50" rows="10" maxlength="500"></textarea> </div> </div> <div class="justi-row"> <div class="justi-sect"> </div> <div class="justi-label"> <legend for="justi_fich">Importer un fichier</legend> <input type="file" name="justi_fich" id="justi_fich" multiple> </div> </div> </div> ` const desc = data.raison == null ? "" : 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; const d_deb = new Date(Date.removeUTC(data.date_debut)).format("YYYY-MM-DDTHH:mm") const d_fin = new Date(Date.removeUTC(data.date_fin)).format("YYYY-MM-DDTHH:mm") assiEdit.querySelector('#justi_date_debut').value = d_deb assiEdit.querySelector('#justi_date_fin').value = d_fin const fichContent = assiEdit.querySelector('.justi-sect'); let filenames = [] let totalFiles = 0; if (data.fichier) { sync_get(path + "/list", (data2) => { filenames = data2.filenames; totalFiles = data2.total; }) let html = "<legend>Fichier(s)</legend>" html += `<span>${totalFiles} fichier(s) dont ${filenames.length} visible(s)</span>` fichContent.insertAdjacentHTML('beforeend', html) } filenames.forEach((name) => { const a = document.createElement('a'); a.textContent = name a.classList.add("fich-file") a.onclick = () => { downloadFile(justif_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 = new Date(Date.removeUTC(date_debut)) date_fin = new Date(Date.removeUTC(date_fin)) if (date_debut >= date_fin) { openAlertModal("Dates erronées", document.createTextNode('La date de fin doit être après la date de début')); 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(); }) }, () => { }, "var(--color-information)"); } ); } 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); errorAlert(); } ); } function removeFiles(justif_id, files = []) { const path = getUrl() + `/api/justificatif/${justif_id}/remove`; files = files.map((el) => { return el.parentElement.querySelector('a').textContent; }); 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(() => { }) } function filterJusti(dept = false) { let dept_html_head = ` <label> Semestre <input class="chk" type="checkbox" name="formsemestre" id="formsemestre" checked> </label> <label> Etudiant <input class="chk" type="checkbox" name="etudid" id="etudid" checked> </label> ` let dept_html_body = ` <span class="filter-line"> <span class="filter-title" for="formsemestre">Recherche dans les semestre</span> <input type="text" name="formsemestre" id="formsemestre" placeholder="S1 2023" > </span> ` let html = ` <div class="filter-body"> <h3>Affichage des colonnes:</h3> <div class="filter-head"> ${dept ? dept_html_head : ""} <label> Date de saisie <input class="chk" type="checkbox" name="entry_date" id="entry_date"> </label> <label> Date de Début <input class="chk" type="checkbox" name="date_debut" id="date_debut" checked> </label> <label> Date de Fin <input class="chk" type="checkbox" name="date_fin" id="date_fin" checked> </label> <label> Etat <input class="chk" type="checkbox" name="etat" id="etat" checked> </label> <label> Raison <input class="chk" type="checkbox" name="raison" id="raison" checked> </label> <label> Fichier <input class="chk" type="checkbox" name="fichier" id="fichier" checked> </label> </div> <hr> <h3>Filtrage des colonnes:</h3> <span class="filter-line"> <span class="filter-title" for="entry_date">Date de saisie</span> <select name="entry_date_pref" id="entry_date_pref"> <option value="-1">Avant</option> <option value="0">Égal</option> <option value="1">Après</option> </select> <input type="datetime-local" name="entry_date_time" id="entry_date_time"> </span> <span class="filter-line"> <span class="filter-title" for="date_debut">Date de début</span> <select name="date_debut_pref" id="date_debut_pref"> <option value="-1">Avant</option> <option value="0">Égal</option> <option value="1">Après</option> </select> <input type="datetime-local" name="date_debut_time" id="date_debut_time"> </span> <span class="filter-line"> <span class="filter-title" for="date_fin">Date de fin</span> <select name="date_fin_pref" id="date_fin_pref"> <option value="-1">Avant</option> <option value="0">Égal</option> <option value="1">Après</option> </select> <input type="datetime-local" name="date_fin_time" id="date_fin_time"> </span> <span class="filter-line"> <span class="filter-title" for="etat">Etat</span> <label> Valide <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="valide"> </label> <label> Non Valide <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="non_valide"> </label> <label> En Attente <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="attente"> </label> <label> Modifié <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="modifie"> </label> </span> <span class="filter-line"> <span class="filter-title" for="etud">Rechercher dans les étudiants</span> <input type="text" name="etud" id="etud" placeholder="Anne Onymous" > </span> ${dept ? dept_html_body : ""} </div> `; const span = document.createElement('span'); span.innerHTML = html html = span.firstElementChild const filterHead = html.querySelector('.filter-head'); filterHead.innerHTML = "" let cols = ["etudid", "entry_date", "date_debut", "date_fin", "etat", "raison", "fichier"]; if (dept) { cols.push("formsemestre") } cols.forEach((k) => { const label = document.createElement('label') label.classList.add('f-label') const s = document.createElement('span'); s.textContent = columnTranslator(k); const input = document.createElement('input'); input.classList.add('chk') input.type = "checkbox" input.name = k input.id = k; input.checked = filterJustificatifs.columns.includes(k) label.appendChild(s) label.appendChild(input) filterHead.appendChild(label) }) // Mise à jour des filtres Object.keys(filterJustificatifs.filters).forEach((key) => { const l = html.querySelector(`.filter-title[for="${key}"]`).parentElement; if (key.indexOf('date') != -1) { l.querySelector(`#${key}_pref`).value = filterJustificatifs.filters[key].pref; l.querySelector(`#${key}_time`).value = filterJustificatifs.filters[key].time.format("YYYY-MM-DDTHH:mm"); } else if (key.indexOf('etat') != -1) { l.querySelectorAll('input').forEach((e) => { e.checked = filterJustificatifs.filters[key].includes(e.value) }) } else if (key == "formsemestre") { l.querySelector('#formsemestre').value = filterJustificatifs.filters["formsemestre"]; } else if (key == "etudid") { l.querySelector('#etudid').value = filterJustificatifs.filters["etud"]; } }) openPromptModal("Filtrage des Justificatifs", html, () => { const columns = [...document.querySelectorAll('.chk')] .map((el) => { if (el.checked) return el.id }) .filter((el) => el) filterJustificatifs.columns = columns filterJustificatifs.filters = {} //reste des filtres const lines = [...document.querySelectorAll('.filter-line')]; lines.forEach((l) => { const key = l.querySelector('.filter-title').getAttribute('for'); if (key.indexOf('date') != -1) { const pref = l.querySelector(`#${key}_pref`).value; const time = l.querySelector(`#${key}_time`).value; if (l.querySelector(`#${key}_time`).value != "") { filterJustificatifs.filters[key] = { pref: pref, time: new Date(Date.removeUTC(time)) } } } else if (key.indexOf('etat') != -1) { filterJustificatifs.filters[key] = [...l.querySelectorAll("input:checked")].map((e) => e.value); } else if (key == "formsemestre") { filterJustificatifs.filters["formsemestre"] = l.querySelector('#formsemestre').value; } else if (key == "etud") { filterJustificatifs.filters["etud"] = l.querySelector('#etud').value; } }) loadAll(); }, () => { }, "var(--color-primary)"); } function downloadJusti() { getJusti((d) => { toCSV(d, filterJustificatifs) }) } function getJusti(action) { try { getAllJustificatifsFromEtud(etudid, action, true, assi_limit_annee) } catch (_) { } } </script> <style> .fich-file { cursor: pointer; margin: 2px; } #fich-content { display: flex; flex-wrap: wrap; flex-direction: column; align-items: center; } .obj-66 { width: 66%; } .file-line { display: flex; justify-content: flex-start; align-items: center; gap: 5px; } </style>