<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 getEtudiant(id) { if (id in etuds) { return etuds[id]; } getSingleEtud(id); return etuds[id]; } 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 = moment.tz(justificatif[k], TIMEZONE).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.textContent = `${e.prenom.capitalize()} ${e.nom.toUpperCase()}`; } 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 = 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"); 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éer 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, "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 = ` <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 = moment.tz(data.date_debut, TIMEZONE).format("YYYY-MM-DDTHH:mm") const d_fin = moment.tz(data.date_fin, TIMEZONE).format("YYYY-MM-DDTHH:mm") console.warn(d_deb, d_fin, data.date_debut, data.date_fin) 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 = 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); errorAlert(); } ); } 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(() => { }) } </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>