Assiduites : Modif Live + Toasts + filtre Liste

This commit is contained in:
iziram 2023-06-13 16:25:45 +02:00
parent f10fd311e1
commit da8b416785
10 changed files with 413 additions and 71 deletions

View File

@ -10,6 +10,10 @@
opacity: 0.5;
}
#validate_selectors {
margin-top: 5vh;
}
.no-display {
display: none !important;
}
@ -246,6 +250,10 @@
background-image: url(../icons/absent.svg);
}
.rbtn.aucun::before {
background-image: url(../icons/aucun.svg);
}
.rbtn.retard::before {
background-image: url(../icons/retard.svg);
}
@ -497,4 +505,19 @@
border: 1px dashed #333;
width: 75%;
padding: 20px;
}
.order {
display: block;
width: 24px;
height: 24px;
background-image: url(../icons/sort.svg);
outline: none;
border: none;
cursor: pointer;
}
.order:focus {
outline: none;
border: none;
}

8
app/static/icons/aucun.svg Executable file
View File

@ -0,0 +1,8 @@
<svg width="85" height="85" viewBox="0 0 85 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="85" height="85" rx="15" fill="#BBB"/>
<defs>
<clipPath id="clip0_120_4425">
<rect width="56" height="56" fill="white" transform="matrix(1 0 0 -1 15 70)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 291 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10 14H2m6-4H2m4-4H2m10 12H2m17 2V4m0 16l3-3m-3 3l-3-3m3-13l3 3m-3-3l-3 3"/></svg>

After

Width:  |  Height:  |  Size: 274 B

View File

@ -20,17 +20,7 @@ let justificatifs = {};
// Variable qui définit si le processus d'action de masse est lancé
let currentMassAction = false;
/**
* Variable de gestion des conflits
*/
let modal;
let closeBtn;
let timeline;
let deleteBtn;
let splitBtn;
let editBtn;
let selectedAssiduite;
let currentMassActionEtat = undefined;
/**
* Ajout d'une fonction `capitalize` sur tous les strings
@ -74,7 +64,7 @@ function setupCheckBox(parent = document) {
* - Module impl
* - Date
*/
function validateSelectors() {
function validateSelectors(btn) {
const action = () => {
const group_ids = getGroupIds();
@ -113,6 +103,8 @@ function validateSelectors() {
document.querySelector(".selectors").disabled = true;
generateMassAssiduites();
generateAllEtudRow();
btn.remove();
onlyAbs();
};
if (!verifyDateInSemester()) {
@ -133,6 +125,14 @@ function validateSelectors() {
action();
}
function onlyAbs() {
if (getDate() > moment()) {
document
.querySelectorAll(".rbtn.present, .rbtn.retard")
.forEach((el) => el.remove());
}
}
/**
* Limite le nombre de checkbox marquée
* Vérifie aussi si le cliqué est fait sur des assiduités conflictuelles
@ -287,6 +287,7 @@ function executeMassActionQueue() {
console.error(data, status);
}
);
return createQueue.length;
};
//Fonction qui modifie les assiduités de la queue 'edition'
@ -312,6 +313,7 @@ function executeMassActionQueue() {
console.error(data, status);
}
);
return editQueue.length;
};
//Fonction qui supprime les assiduités de la queue 'supprimer'
@ -328,12 +330,44 @@ function executeMassActionQueue() {
console.error(data, status);
}
);
return toDelete.length;
};
//On exécute les fonctions de queue
create();
edit();
supprimer();
let color;
switch (currentMassActionEtat.toUpperCase()) {
case "PRESENT":
color = "#6bdb83";
break;
case "ABSENT":
color = "#F1A69C";
break;
case "RETARD":
color = "#f0c865";
break;
default:
color = "#AAA";
break;
}
let count = 0;
if (currentMassActionEtat == "remove") {
count += supprimer();
const span = document.createElement("span");
span.innerHTML = `${count} assiduités ont été supprimées.`;
pushToast(generateToast(span, color, 5));
} else {
count += create();
count += edit();
const etat =
currentMassActionEtat.toUpperCase() == "RETARD"
? "En retard"
: currentMassActionEtat;
const span = document.createElement("span");
span.innerHTML = `${count} étudiants ont été mis <u><strong>${etat
.capitalize()
.trim()}</strong></u>`;
pushToast(generateToast(span, color, 5));
}
//On récupère les assiduités puis on regénère les lignes d'étudiants
getAssiduitesFromEtuds(true);
generateAllEtudRow();
@ -346,7 +380,10 @@ function massAction() {
//On récupère tous les boutons d'assiduités
const fields = Array.from(document.querySelectorAll(".btns_field.single"));
//On récupère l'état de l'action de masse
const action = getAssiduiteValue(document.querySelector(".btns_field.mass"));
currentMassActionEtat = getAssiduiteValue(
document.querySelector(".btns_field.mass")
);
//On remet à 0 les queues
resetMassActionQueue();
@ -366,7 +403,11 @@ function massAction() {
*/
fields.forEach((field) => {
if (field.getAttribute("type") != "conflit") {
field.querySelector(`.rbtn.${action}`).click();
if (currentMassActionEtat != "remove") {
field.querySelector(`.rbtn.${currentMassActionEtat}`).click();
} else {
field.querySelector(".rbtn.absent").click();
}
} else {
const etudid = field.getAttribute("etudid");
conflicts.push(etuds[parseInt(etudid)]);
@ -379,6 +420,7 @@ function massAction() {
//Fin du processus, on remet à false
currentMassAction = false;
currentMassActionEtat = undefined;
//On remet à zero les boutons d'assiduité de masse
const boxes = Array.from(
@ -423,6 +465,7 @@ function generateMassAssiduites() {
class="rbtn present">
<input type="checkbox" value="retard" name="mass_btn_assiduites" id="mass_rbtn_retard" class="rbtn retard">
<input type="checkbox" value="absent" name="mass_btn_assiduites" id="mass_rbtn_absent" class="rbtn absent">
<input type="checkbox" value="remove" name="mass_btn_assiduites" id="mass_rbtn_aucun" class="rbtn aucun">
</fieldset>`;
content.insertBefore(mass, content.querySelector(".etud_holder"));
@ -953,7 +996,6 @@ function actualizeEtudAssiduite(etudid, has_formsemestre = true) {
getUrl() +
`/api/assiduites/${etudid}/query?${formsemestre_id}date_debut=${date_debut}&date_fin=${date_fin}`;
sync_get(url_api, (data, status) => {
console.error(data, status);
if (status === "success") {
assiduites[etudid] = data;
}
@ -991,20 +1033,22 @@ function assiduiteAction(element) {
// Cas de l'action de masse -> peuplement des queues
if (currentMassAction) {
switch (type) {
case "création":
addToMassActionQueue("creer", { etat: etat, etudid: etudid });
break;
case "édition":
if (etat === "remove") {
addToMassActionQueue("supprimer", assiduite_id);
} else {
addToMassActionQueue("editer", {
etat: etat,
assiduite_id: assiduite_id,
});
}
break;
if (currentMassActionEtat != "remove") {
switch (type) {
case "création":
addToMassActionQueue("creer", { etat: etat, etudid: etudid });
break;
case "édition":
if (etat != "remove") {
addToMassActionQueue("editer", {
etat: etat,
assiduite_id: assiduite_id,
});
}
break;
}
} else if (type == "édition") {
addToMassActionQueue("supprimer", assiduite_id);
}
} else {
// Cas normal -> mise à jour en base
@ -1042,13 +1086,49 @@ function assiduiteAction(element) {
}
if (type != "conflit") {
document
.querySelector(".toast-holder")
.appendChild(
generateToast(
document.createTextNode("L'assiduité a bien été enregistrée.")
)
);
let etatAffiche;
switch (etat.toUpperCase()) {
case "PRESENT":
etatAffiche =
"%etud% a été noté(e) <u><strong>présent(e)</strong></u>";
break;
case "RETARD":
etatAffiche =
"%etud% a été noté(e) <u><strong>en retard</strong></u>";
break;
case "ABSENT":
etatAffiche =
"%etud% a été noté(e) <u><strong>absent(e)</strong></u>";
break;
case "REMOVE":
etatAffiche = "L'assiduité de %etud% a été retirée.";
}
let color;
switch (etat.toUpperCase()) {
case "PRESENT":
color = "#6bdb83";
break;
case "ABSENT":
color = "#F1A69C";
break;
case "RETARD":
color = "#f0c865";
break;
default:
color = "#AAA";
break;
}
const nom_prenom = `${etuds[etudid].nom.toUpperCase()} ${etuds[
etudid
].prenom.capitalize()}`;
const span = document.createElement("span");
span.innerHTML = etatAffiche.replace("%etud%", nom_prenom);
pushToast(generateToast(span, color, 5));
}
actualizeEtud(etudid, !isSingleEtud);

View File

@ -692,6 +692,52 @@
})
}
function launchToast(etudid, etat) {
let etatAffiche;
switch (etat.toUpperCase()) {
case "PRESENT":
etatAffiche =
"%etud% a été noté(e) <u><strong>présent(e)</strong></u>";
break;
case "RETARD":
etatAffiche =
"%etud% a été noté(e) <u><strong>en retard</strong></u>";
break;
case "ABSENT":
etatAffiche =
"%etud% a été noté(e) <u><strong>absent(e)</strong></u>";
break;
case "REMOVE":
etatAffiche = "L'assiduité de %etud% a été retirée.";
}
let color;
switch (etat.toUpperCase()) {
case "PRESENT":
color = "#6bdb83";
break;
case "ABSENT":
color = "#F1A69C";
break;
case "RETARD":
color = "#f0c865";
break;
default:
color = "#AAA";
break;
}
const nom_prenom = `${etuds[etudid].nom.toUpperCase()} ${etuds[
etudid
].prenom.capitalize()}`;
const span = document.createElement("span");
span.innerHTML = etatAffiche.replace("%etud%", nom_prenom);
pushToast(generateToast(span, color, 5));
}
function updateEtudAssiduite(rbtn) {
const [_, colid, etudid] = rbtn.name.split("_");
@ -714,8 +760,8 @@
assiduite["assiduite_id"] = assi_id;
assiduites[etudid].push(assiduite);
updateAllCol()
launchToast(etudid, etat);
// TODO Envoyer toast
}
})
break;
@ -761,7 +807,12 @@
}
asyncEditAssiduite(edit, (data) => {
console.log(data)
const obj = getAssiduite(etudid, assi);
obj.moduleimpl = edit.moduleimpl_id;
obj.etat = edit.etat;
launchToast(etudid, etat);
})
break;
@ -769,6 +820,10 @@
}
function getAssiduite(etudid, id) {
return assiduites[etudid].filter((a) => a.assiduite_id == id)
}
function asyncCreateAssiduite(assi, callback = () => { }) {
const path = getUrl() + `/api/assiduite/${assi.etudid}/create`;
async_post(

View File

@ -10,11 +10,36 @@
<table id="assiduiteTable">
<thead>
<tr>
<th>Début</th>
<th>Fin</th>
<th>État</th>
<th>Module</th>
<th>Justifiée</th>
<th>
<div>
<span>Début</span>
<a class="order" onclick="order('date_debut', assiduiteCallBack, this)"></a>
</div>
</th>
<th>
<div>
<span>Fin</span>
<a class="order" onclick="order('date_fin', assiduiteCallBack, this)"></a>
</div>
</th>
<th>
<div>
<span>État</span>
<a class="order" onclick="order('etat', assiduiteCallBack, this)"></a>
</div>
</th>
<th>
<div>
<span>Module</span>
<a class="order" onclick="order('moduleimpl_id', assiduiteCallBack, this)"></a>
</div>
</th>
<th>
<div>
<span>Justifiée</span>
<a class="order" onclick="order('est_just', assiduiteCallBack, this)"></a>
</div>
</th>
</tr>
</thead>
<tbody id="tableBodyAssiduites">
@ -26,10 +51,30 @@
<table id="justificatifTable">
<thead>
<tr>
<th>Début</th>
<th>Fin</th>
<th>État</th>
<th>Raison</th>
<th>
<div>
<span>Début</span>
<a class="order" onclick="order('date_debut', justificatifCallBack, this, false)"></a>
</div>
</th>
<th>
<div>
<span>Fin</span>
<a class="order" onclick="order('date_fin', justificatifCallBack, this, false)"></a>
</div>
</th>
<th>
<div>
<span>État</span>
<a class="order" onclick="order('etat', justificatifCallBack, this, false)"></a>
</div>
</th>
<th>
<div>
<span>Raison</span>
<a class="order" onclick="order('raison', justificatifCallBack, this, false)"></a>
</div>
</th>
</tr>
</thead>
<tbody id="tableBodyJustificatifs">
@ -70,6 +115,7 @@
th {
background-color: #f2f2f2;
}
tr:hover {
@ -138,6 +184,12 @@
color: #fff;
border-color: #007bff;
}
th>div {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
<script>
@ -147,6 +199,8 @@
const itemsPerPage = 10;
let currentPageAssiduites = 1;
let currentPageJustificatifs = 1;
let orderAssiduites = true;
let orderJustificatifs = true;
const tableBodyAssiduites = document.getElementById("tableBodyAssiduites");
const tableBodyJustificatifs = document.getElementById("tableBodyJustificatifs");
@ -184,6 +238,33 @@
}
});
function assiduiteCallBack(assi) {
renderTableAssiduites(currentPageAssiduites, assi);
renderPaginationButtons(assi);
}
function justificatifCallBack(justi) {
renderTableJustificatifs(currentPageJustificatifs, justi);
renderPaginationButtons(justi, false);
}
const moduleimpls = {}
function getModuleImpl(id) {
if (id == null || id == undefined) {
moduleimpls[id] = "Pas de module"
}
if (id in moduleimpls) {
return moduleimpls[id];
}
const url_api = getUrl() + `/api/moduleimpl/${id}`;
sync_get(url_api, (data) => {
moduleimpls[id] = `${data.module.code} ${data.module.abbrev}`;
}, (data) => { moduleimpls[id] = "Pas de module" });
return moduleimpls[id];
}
function renderTableAssiduites(page, assiduités) {
tableBodyAssiduites.innerHTML = "";
const start = (page - 1) * itemsPerPage;
@ -198,10 +279,10 @@
row.classList.add(etat);
row.innerHTML = `
<td>${new Date(assiduite.date_debut).toLocaleString()}</td>
<td>${new Date(assiduite.date_fin).toLocaleString()}</td>
<td>${moment.tz(assiduite.date_debut, TIMEZONE).format(`DD/MM/Y HH:mm`)}</td>
<td>${moment.tz(assiduite.date_fin, TIMEZONE).format(`DD/MM/Y HH:mm`)}</td>
<td>${etat}</td>
<td>${assiduite.moduleimpl_id}</td> <td>${assiduite.est_just ? "Oui" : "Non"
<td>${getModuleImpl(assiduite.moduleimpl_id)}</td> <td>${assiduite.est_just ? "Oui" : "Non"
}</td>
`;
@ -267,6 +348,10 @@
paginationContainerJustificatifs.innerHTML = ""
}
if (totalPages == 1) {
return;
}
for (let i = 1; i <= totalPages; i++) {
const paginationButton = document.createElement("a");
paginationButton.textContent = i;
@ -312,15 +397,47 @@
}
function loadAll() {
getAllAssiduitesFromEtud(etudid, (assi) => {
renderTableAssiduites(currentPageAssiduites, assi);
renderPaginationButtons(assi), true;
})
getAllAssiduitesFromEtud(etudid, assiduiteCallBack)
getAllJustificatifsFromEtud(etudid, justificatifCallBack)
}
function order(keyword, callback = () => { }, el, assi = true) {
const call = (array) => {
const sorted = array.sort((a, b) => {
let keyValueA = a[keyword];
let keyValueB = b[keyword];
if (keyword.indexOf("date") != -1) {
keyValueA = moment.tz(keyValueA, TIMEZONE)
keyValueB = moment.tz(keyValueB, TIMEZONE)
}
if (keyword.indexOf("module") != -1) {
keyValueA = getModuleImpl(keyValueA);
keyValueB = getModuleImpl(keyValueB);
}
let orderDertermined = keyValueA > keyValueB;
if (el.classList.contains("desc")) {
orderDertermined = keyValueA < keyValueB;
}
return orderDertermined
});
el.classList.toggle("desc");
callback(sorted);
};
if (assi) {
getAllAssiduitesFromEtud(etudid, call)
} else {
getAllJustificatifsFromEtud(etudid, call)
}
getAllJustificatifsFromEtud(etudid, (assi) => {
renderTableJustificatifs(currentPageJustificatifs, assi);
renderPaginationButtons(assi, false);
})
}
window.onload = () => {

View File

@ -71,7 +71,8 @@
setupDate(() => {
if (updateDate()) {
actualizeEtud(etudid);
updateSelect()
updateSelect();
onlyAbs();
}
});
@ -98,11 +99,6 @@
window.forceModule = "{{ forcer_module }}"
window.forceModule = window.forceModule == "True" ? true : false
window.addEventListener('load', function () {
loading();
}, { once: true });
</script>

View File

@ -22,14 +22,14 @@
Date: <span id="datestr"></span>
<input type="date" name="tl_date" id="tl_date" value="{{ date }}" onchange="updateDate()">
</div>
<button id="validate_selectors" onclick="validateSelectors()">
Valider
</button>
</fieldset>
{{timeline|safe}}
<button id="validate_selectors" onclick="validateSelectors(this)">
Faire la saisie
</button>
<div class="etud_holder">
<p class="placeholder">
Veillez à choisir le groupe concerné par la saisie ainsi que la date de la saisie.
@ -77,5 +77,23 @@
window.forceModule = "{{ forcer_module }}"
window.forceModule = window.forceModule == "True" ? true : false
if (window.forceModule) {
const btn = document.getElementById("validate_selectors");
const select = document.getElementById("moduleimpl_select");
if (select.value == "") {
btn.disabled = true;
}
select.addEventListener('change', (e) => {
if (e.target.value != "") {
btn.disabled = false;
} else {
btn.disabled = true;
}
});
}
</script>
</section>

View File

@ -2,6 +2,7 @@
<div class="period" style="left: 0%; width: 20%">
<div class="period-handle left"></div>
<div class="period-handle right"></div>
<div class="period-time">Time</div>
</div>
</div>
<script>
@ -77,6 +78,15 @@
return Math.round(value * tick_time) / tick_time;
}
function updatePeriodTimeLabel() {
const values = getPeriodValues();
const deb = numberToTime(values[0])
const fin = numberToTime(values[1])
const text = `${deb} - ${fin}`
periodTimeLine.querySelector('.period-time').textContent = text;
}
function setupTimeLine(callback) {
const func_call = callback ? callback : () => { };
timelineContainer.addEventListener("mousedown", (event) => {
@ -91,6 +101,8 @@
const newLeft = startLeft + (deltaX / containerWidth) * 100;
adjustPeriodPosition(newLeft, parseFloat(periodTimeLine.style.width));
updatePeriodTimeLabel();
};
document.addEventListener("mousemove", onMouseMove);
@ -121,6 +133,8 @@
} else {
adjustPeriodPosition(parseFloat(periodTimeLine.style.left), newWidth);
}
updatePeriodTimeLabel();
};
document.addEventListener("mousemove", onMouseMove);
@ -187,6 +201,7 @@
snapHandlesToQuarters();
generateAllEtudRow();
updatePeriodTimeLabel()
}
function snapHandlesToQuarters() {
@ -204,6 +219,8 @@
const width = `${wid}%`
periodTimeLine.style.left = left;
periodTimeLine.style.width = width;
updatePeriodTimeLabel()
}
function computePercentage(a, b) {
@ -273,4 +290,23 @@
right: 0;
border-radius: 4px 0 0 4px;
}
.period .period-time {
display: none;
position: absolute;
left: calc(50% - var(--w)/2 - 5px);
justify-content: center;
align-content: center;
top: calc(-60% - 10px);
--w: 10em;
width: var(--w);
}
.period:hover .period-time {
display: flex;
background-color: rgba(0, 183, 255, 1);
border-radius: 15px;
padding: 5px;
}
</style>

View File

@ -83,6 +83,14 @@
return toast
}
function pushToast(toast) {
document
.querySelector(".toast-holder")
.appendChild(
toast
);
}