1
0
forked from ScoDoc/ScoDoc
ScoDoc/app/templates/assiduites/widgets/tableau_justi.j2

479 lines
18 KiB
Django/Jinja

<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 {
td.textContent = `${justificatif[k]}`.capitalize()
}
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 = 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");
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
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 = []
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(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>