forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -66,7 +66,6 @@ Bilan assiduité de {{sco.etud.nomprenom}}
|
||||
{% endblock styles %}
|
||||
|
||||
{% block app_content %}
|
||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||
<div class="pageContent">
|
||||
|
||||
<h2>Bilan de l'assiduité de {{sco.etud.html_link_fiche()|safe}}</span></h2>
|
||||
|
@ -1,624 +0,0 @@
|
||||
<ul id="contextMenu" class="context-menu">
|
||||
<li id="detailOption">Détails</li>
|
||||
<li id="editOption">Éditer</li>
|
||||
<li id="deleteOption">Supprimer</li>
|
||||
</ul>
|
||||
|
||||
{% include "assiduites/widgets/alert.j2" %}
|
||||
{% include "assiduites/widgets/prompt.j2" %}
|
||||
|
||||
<script>
|
||||
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;
|
||||
|
||||
document.addEventListener("click", () => {
|
||||
contextMenu.style.display = "none";
|
||||
if (contextMenu.childElementCount > 3) {
|
||||
contextMenu.removeChild(contextMenu.lastElementChild)
|
||||
}
|
||||
});
|
||||
|
||||
editOption.addEventListener("click", () => {
|
||||
if (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) {
|
||||
const type = selectedRow.getAttribute('type');
|
||||
const obj_id = selectedRow.getAttribute('obj_id');
|
||||
if (type == "assiduite") {
|
||||
deleteAssiduite(obj_id);
|
||||
|
||||
} else {
|
||||
deleteJustificatif(obj_id);
|
||||
}
|
||||
loadAll();
|
||||
}
|
||||
});
|
||||
|
||||
function filterArray(array, f) {
|
||||
return array.filter((el) => {
|
||||
let t = Object.keys(f).every((k) => {
|
||||
if (k == "etat") {
|
||||
return f.etat.includes(el.etat.toLowerCase())
|
||||
};
|
||||
if (k == "est_just") {
|
||||
if (f.est_just != "") {
|
||||
return `${el.est_just}` == f.est_just;
|
||||
}
|
||||
}
|
||||
if (k.indexOf('date') != -1) {
|
||||
const assi_time = new Date(Date.removeUTC(el[k]));
|
||||
const filter_time = f[k].time;
|
||||
switch (f[k].pref) {
|
||||
|
||||
case "0":
|
||||
return assi_time.isSame(filter_time, 'minute');
|
||||
case "-1":
|
||||
return assi_time.isBefore(filter_time, 'minutes');
|
||||
case "1":
|
||||
return assi_time.isAfter(filter_time, 'minutes');
|
||||
}
|
||||
}
|
||||
|
||||
if (k == "moduleimpl_id") {
|
||||
const m = el[k] == undefined || el[k] == null ? "null" : el[k];
|
||||
if (f.moduleimpl_id != '') {
|
||||
return m == f.moduleimpl_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (k == "obj_id") {
|
||||
const obj_id = el.assiduite_id || el.justif_id;
|
||||
return f.obj_id.includes(obj_id)
|
||||
}
|
||||
|
||||
if (k == "formsemestre") {
|
||||
return f.formsemestre === "" || (el.hasOwnProperty("formsemestre") && el.formsemestre.title.replaceAll('-', ' ').indexOf(f.formsemestre) != -1);
|
||||
}
|
||||
if (k == "etud") {
|
||||
|
||||
const e = getEtudiant(el.etudid);
|
||||
const str = `${e.prenom.capitalize()} ${e.nom.toUpperCase()}`
|
||||
|
||||
return f.etud === "" || str.indexOf(f.etud) != -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
|
||||
return t;
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function generateTableHead(columns, assi = true) {
|
||||
const table = assi ? "#assiduiteTable" : "#justificatifTable"
|
||||
const call = assi ? [assiduiteCallBack, true] : [justificatifCallBack, false]
|
||||
const tr = document.querySelector(`${table} thead tr`);
|
||||
|
||||
tr.innerHTML = ""
|
||||
|
||||
columns.forEach((c) => {
|
||||
const th = document.createElement('th');
|
||||
const div = document.createElement('div');
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.textContent = columnTranslator(c);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.classList.add('icon', "order");
|
||||
a.onclick = () => { order(c, call[0], a, call[1]) }
|
||||
|
||||
div.appendChild(span)
|
||||
div.appendChild(a)
|
||||
|
||||
th.appendChild(div);
|
||||
|
||||
tr.appendChild(th);
|
||||
})
|
||||
}
|
||||
|
||||
function renderPaginationButtons(array, assi = true) {
|
||||
const totalPages = Math.ceil(array.length / itemsPerPage);
|
||||
if (totalPages <= 1) {
|
||||
if (assi) {
|
||||
paginationContainerAssiduites.innerHTML = ""
|
||||
} else {
|
||||
paginationContainerJustificatifs.innerHTML = ""
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (assi) {
|
||||
paginationContainerAssiduites.innerHTML = "<span class='liste_pagination'><button class='pagination_moins'><</button><select id='paginationAssi'></select><button class='pagination_plus'>></button></span>"
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi')?.addEventListener('change', (e) => {
|
||||
currentPageAssiduites = e.target.value;
|
||||
assiduiteCallBack(array);
|
||||
})
|
||||
|
||||
paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
|
||||
if (currentPageAssiduites > 1) {
|
||||
currentPageAssiduites--;
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
|
||||
assiduiteCallBack(array);
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => {
|
||||
if (currentPageAssiduites < totalPages) {
|
||||
currentPageAssiduites++;
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
|
||||
assiduiteCallBack(array);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
paginationContainerJustificatifs.innerHTML = "<span class='liste_pagination'><button class='pagination_moins'><</button><select id='paginationJusti'></select><button class='pagination_plus'>></button></span>"
|
||||
paginationContainerJustificatifs.querySelector('#paginationJusti')?.addEventListener('change', (e) => {
|
||||
currentPageJustificatifs = e.target.value;
|
||||
justificatifCallBack(array);
|
||||
})
|
||||
|
||||
paginationContainerJustificatifs.querySelector('.pagination_moins').addEventListener('click', () => {
|
||||
if (currentPageJustificatifs > 1) {
|
||||
currentPageJustificatifs--;
|
||||
paginationContainerJustificatifs.querySelector('#paginationJusti').value = currentPageJustificatifs
|
||||
justificatifCallBack(array);
|
||||
}
|
||||
})
|
||||
|
||||
paginationContainerJustificatifs.querySelector('.pagination_plus').addEventListener('click', () => {
|
||||
if (currentPageJustificatifs < totalPages) {
|
||||
currentPageJustificatifs++;
|
||||
paginationContainerJustificatifs.querySelector('#paginationJusti').value = currentPageJustificatifs
|
||||
justificatifCallBack(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)
|
||||
if (i == currentPageAssiduites)
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = i + "";
|
||||
} else {
|
||||
paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton)
|
||||
if (i == currentPageJustificatifs)
|
||||
paginationContainerJustificatifs.querySelector('#paginationJusti').value = i + "";
|
||||
}
|
||||
}
|
||||
updateActivePaginationButton(assi);
|
||||
}
|
||||
function updateActivePaginationButton(assi = true) {
|
||||
if (assi) {
|
||||
const paginationButtons =
|
||||
paginationContainerAssiduites.querySelectorAll("#paginationContainerAssiduites .pagination-button");
|
||||
paginationButtons.forEach((button) => {
|
||||
if (parseInt(button.textContent) === currentPageAssiduites) {
|
||||
button.classList.add("active");
|
||||
} else {
|
||||
button.classList.remove("active");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const paginationButtons =
|
||||
paginationContainerJustificatifs.querySelectorAll("#paginationContainerJustificatifs .pagination-button");
|
||||
paginationButtons.forEach((button) => {
|
||||
if (parseInt(button.textContent) === currentPageJustificatifs) {
|
||||
button.classList.add("active");
|
||||
} else {
|
||||
button.classList.remove("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadAll() {
|
||||
try { getAssi(assiduiteCallBack) } catch { }
|
||||
try { getJusti(justificatifCallBack) } catch { }
|
||||
}
|
||||
|
||||
function order(keyword, callback = () => { }, el, assi = true) {
|
||||
const call = (array, ordered) => {
|
||||
const sorted = array.sort((a, b) => {
|
||||
let keyValueA = a[keyword];
|
||||
let keyValueB = b[keyword];
|
||||
|
||||
if (keyword.indexOf("date") != -1) {
|
||||
keyValueA = new Date(Date.removeUTC(keyValueA))
|
||||
keyValueB = new Date(Date.removeUTC(keyValueB))
|
||||
}
|
||||
|
||||
if (keyword.indexOf("module") != -1) {
|
||||
keyValueA = getModuleImpl(a);
|
||||
keyValueB = getModuleImpl(b);
|
||||
}
|
||||
if (keyword.indexOf("etudid") != -1) {
|
||||
keyValueA = getEtudiant(a.etudid);
|
||||
keyValueB = getEtudiant(b.etudid);
|
||||
|
||||
keyValueA = `${keyValueA.prenom.capitalize()} ${keyValueA.nom.toUpperCase()}`
|
||||
keyValueB = `${keyValueB.prenom.capitalize()} ${keyValueB.nom.toUpperCase()}`
|
||||
}
|
||||
|
||||
let orderDertermined = keyValueA > keyValueB;
|
||||
|
||||
if (!ordered) {
|
||||
orderDertermined = keyValueA < keyValueB;
|
||||
}
|
||||
return orderDertermined
|
||||
});
|
||||
|
||||
|
||||
|
||||
callback(sorted);
|
||||
|
||||
};
|
||||
|
||||
if (assi) {
|
||||
orderAssiduites = !orderAssiduites;
|
||||
getAssi((a) => { call(a, orderAssiduites) });
|
||||
} else {
|
||||
orderJustificatifs = !orderJustificatifs;
|
||||
getJusti((a) => { call(a, orderJustificatifs) });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function columnTranslator(colName) {
|
||||
switch (colName) {
|
||||
case "date_debut":
|
||||
return "Début";
|
||||
case "entry_date":
|
||||
return "Saisie le";
|
||||
case "date_fin":
|
||||
return "Fin";
|
||||
case "etat":
|
||||
return "État";
|
||||
case "moduleimpl_id":
|
||||
return "Module";
|
||||
case "est_just":
|
||||
return "Justifiée";
|
||||
case "raison":
|
||||
return "Raison";
|
||||
case "fichier":
|
||||
return "Fichier";
|
||||
case "etudid":
|
||||
return "Etudiant";
|
||||
case "formsemestre":
|
||||
return "Semestre";
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
if (contextMenu.childElementCount > 3) {
|
||||
contextMenu.removeChild(contextMenu.lastElementChild)
|
||||
}
|
||||
if (selectedRow.getAttribute('type') == "assiduite") {
|
||||
|
||||
const li = document.createElement('li')
|
||||
li.textContent = "Justifier"
|
||||
let obj_id = selectedRow.getAttribute('obj_id');
|
||||
let assi = Object.values(assiduites).flat().filter((a) => { return a.assiduite_id == obj_id })[0]
|
||||
li.addEventListener('click', () => {
|
||||
|
||||
if (assi && !assi.est_just && assi.etat != "PRESENT") {
|
||||
fastJustify(assi)
|
||||
} else {
|
||||
openAlertModal("Erreur", document.createTextNode("L'assiduité est déjà justifiée."))
|
||||
}
|
||||
})
|
||||
if (assi && assi.etat != "PRESENT") {
|
||||
contextMenu.appendChild(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadStr(data, name) {
|
||||
const blob = new Blob([data], { type: 'text/csv' });
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', name);
|
||||
a.click()
|
||||
|
||||
a.remove()
|
||||
}
|
||||
|
||||
function askDownload(data) {
|
||||
|
||||
const div = document.createElement('div');
|
||||
const head = document.createElement('h3');
|
||||
const input = document.createElement('input');
|
||||
head.textContent = "Veuillez nommer le fichier qui sera téléchargé (sera au format CSV)"
|
||||
|
||||
input.type = "text";
|
||||
input.placeholder = "liste.csv"
|
||||
|
||||
div.appendChild(head)
|
||||
div.appendChild(input)
|
||||
|
||||
openPromptModal("Préparation du téléchargement", div, () => {
|
||||
|
||||
downloadStr(data, input.value ? input.value : "download.csv")
|
||||
|
||||
}, () => { }, "var(--color-present)");
|
||||
}
|
||||
|
||||
function toCSV(array, filters) {
|
||||
array = filterArray(array, filters.filters)
|
||||
|
||||
let csv = filters.columns.map((c) => columnTranslator(c)).join(',') + "\n";
|
||||
array.forEach((a) => {
|
||||
let line = ""
|
||||
filters.columns.forEach((c) => {
|
||||
switch (c) {
|
||||
case "fichier":
|
||||
line += a[c] ? "Oui," : "Non,"
|
||||
break;
|
||||
case "etudid":
|
||||
const e = getEtudiant(a.etudid);
|
||||
line += `${e.nom.toUpperCase()} ${e.prenom.capitalize()},`
|
||||
break;
|
||||
case "formsemestre":
|
||||
line += a.hasOwnProperty("formsemestre") ? a.formsemestre.title : ""
|
||||
line += ","
|
||||
break;
|
||||
case "est_just":
|
||||
line += a[c] ? "Oui," : "Non,"
|
||||
break;
|
||||
case "moduleimpl_id":
|
||||
line += `${getModuleImpl(a)},`
|
||||
break;
|
||||
default:
|
||||
line += `${a[c]},`;
|
||||
break;
|
||||
}
|
||||
})
|
||||
line = line.substring(0, line.lastIndexOf(',')) + "\n"
|
||||
csv += line;
|
||||
})
|
||||
askDownload(csv);
|
||||
}
|
||||
|
||||
function getEtudiant(id) {
|
||||
if (id in etuds) {
|
||||
return etuds[id];
|
||||
}
|
||||
getSingleEtud(id);
|
||||
|
||||
return etuds[id];
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.pageContent {
|
||||
width: 100%;
|
||||
max-width: var(--sco-content-max-width);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
|
||||
text-align: left;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #dddddd;
|
||||
padding: 8px;
|
||||
color: var(--color-default-text);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
filter: brightness(1.2)
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
display: none;
|
||||
position: fixed;
|
||||
list-style-type: none;
|
||||
padding: 10px 0;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
z-index: 45;
|
||||
}
|
||||
|
||||
.context-menu li {
|
||||
padding: 8px 16px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.context-menu li:hover {
|
||||
filter: brightness(0.7);
|
||||
}
|
||||
|
||||
#deleteOption {
|
||||
background-color: var(--color-absent);
|
||||
}
|
||||
|
||||
.l-present {
|
||||
background-color: var(--color-present)
|
||||
}
|
||||
|
||||
.l-absent,
|
||||
.l-invalid {
|
||||
background-color: var(--color-absent-clair);
|
||||
}
|
||||
|
||||
.l-valid {
|
||||
background-color: var(--color-justi-clair);
|
||||
}
|
||||
|
||||
.l-retard {
|
||||
background-color: var(--color-retard);
|
||||
}
|
||||
|
||||
.l-absent.est_just {
|
||||
background-color: var(--color-absent-justi);
|
||||
}
|
||||
|
||||
.l-retard.est_just {
|
||||
background-color: var(--color-retard-justi);
|
||||
}
|
||||
|
||||
/* Ajoutez des styles pour le conteneur de pagination et les boutons */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.pagination-button {
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: pointer;
|
||||
background-color: #f9f9f9;
|
||||
margin: 0 5px;
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.pagination-button:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.pagination-button.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
th>div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.filter-line {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.filter-line>* {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.rbtn {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
margin: 0 5px !important;
|
||||
}
|
||||
|
||||
.f-label {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.chk {
|
||||
margin-left: 2px !important;
|
||||
}
|
||||
|
||||
.filter-body label {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
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;
|
||||
}
|
||||
</style>
|
@ -1,679 +0,0 @@
|
||||
<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>
|
Loading…
Reference in New Issue
Block a user