ScoDoc/app/templates/assiduites/widgets/conflict.j2

393 lines
12 KiB
Django/Jinja

<script>
/**
* Transformation d'une date de début en position sur la timeline
* @param {String} start
* @returns {String} un déplacement par rapport à la gauche en %
*/
function getLeftPosition(start) {
const startTime = new Date(start);
const startMins =
(startTime.getHours() - t_start) * 60 + startTime.getMinutes();
return (startMins / (t_end * 60 - t_start * 60)) * 100 + "%";
}
/**
* Ajustement de l'espacement vertical entre les assiduités superposées
* @param {HTMLElement} container le conteneur des assiduités
* @param {String} start la date début de l'assiduité à placer
* @param {String} end la date de fin de l'assiduité à placer
* @returns {String} La position en px
*/
function getTopPosition(container, start, end) {
const overlaps = (a, b) => {
return a.start < b.end && a.end > b.start;
};
const startTime = new Date(start);
const endTime = new Date(end);
const assiduiteDuration = { start: startTime, end: endTime };
let position = 0;
let hasOverlap = true;
while (hasOverlap) {
hasOverlap = false;
Array.from(container.children).some((el) => {
const elStart = new Date(el.getAttribute("data-start"));
const elEnd = new Date(el.getAttribute("data-end"));
const elDuration = { start: elStart, end: elEnd };
if (overlaps(assiduiteDuration, elDuration)) {
position += 25; // Pour ajuster l'espacement vertical entre les assiduités superposées
hasOverlap = true;
return true;
}
return false;
});
}
return position + "px";
}
/**
* Calcule de la largeur de l'assiduité sur la timeline
* @param {String} start date iso de début
* @param {String} end date iso de fin
* @returns {String} la taille en %
*/
function getWidth(start, end) {
const startTime = new Date(start);
const endTime = new Date(end);
const duration = (endTime - startTime) / 1000 / 60;
const percent = (duration / (t_end * 60 - t_start * 60)) * 100;
return percent + "%";
}
function formatDateModal(date) {
return new Date(Date.removeUTC(date)).format("DD/MM/Y HH:mm");
}
class ConflitResolver {
constructor(assiduitesList, conflictPeriod, interval) {
this.list = assiduitesList;
this.conflictPeriod = conflictPeriod;
this.interval = interval;
this.selectedAssiduite = null;
this.element = undefined;
this.callbacks = {
delete: () => {},
split: () => {},
edit: () => {},
};
}
refresh(assiduitesList, periode) {
this.list = assiduitesList;
if (periode) {
this.conflictPeriod = periode;
}
this.render();
}
open() {
const html = `
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2>Veuillez régler le conflit pour poursuivre</h2>
<!-- Ajout de la frise chronologique -->
<div class="modal-timeline">
<div class="time-labels"></div>
<div class="assiduites-container"></div>
</div>
<div class="modal-buttons">
<button id="finish" class="btnPrompt">Quitter</button>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML("afterbegin", html);
this.element = document.getElementById("myModal");
document.querySelector("#myModal #finish").addEventListener("click", () => {
this.close();
});
document.querySelector("#myModal .close").addEventListener("click", () => {
this.close();
});
// fermeture du modal en appuyant sur echap
document.addEventListener(
"keydown",
(e) => {
if (e.key === "Escape") {
this.close();
}
},
{ once: true }
);
this.render();
}
close() {
if (this.element) {
this.element.remove();
}
}
/**
* Génération du modal
*/
render() {
const timeLabels = document.querySelector(".time-labels");
const assiduitesContainer = document.querySelector(".assiduites-container");
timeLabels.innerHTML = "";
assiduitesContainer.innerHTML = '<div class="assiduite-special"></div>';
// Ajout des labels d'heure sur la frise chronologique
for (let i = t_start; i <= t_end; i++) {
const timeLabel = document.createElement("div");
timeLabel.className = "time-label";
timeLabel.textContent = numberToTime(i);
timeLabels.appendChild(timeLabel);
}
//Placement de la période conflictuelle sur la timeline
const specialAssiduiteEl = document.querySelector(".assiduite-special");
specialAssiduiteEl.style.width = getWidth(
this.conflictPeriod.deb,
this.conflictPeriod.fin
);
specialAssiduiteEl.style.left = getLeftPosition(this.conflictPeriod.deb);
specialAssiduiteEl.style.top = "0";
specialAssiduiteEl.style.zIndex = "0"; // Place l'assiduité spéciale en arrière-plan
assiduitesContainer.appendChild(specialAssiduiteEl);
//Placement des assiduités sur la timeline
this.list.forEach((assiduite) => {
const period = {
deb: new Date(assiduite.date_debut),
fin: new Date(assiduite.date_fin),
};
if (!hasTimeConflict(period, this.interval)) {
return;
}
const el = document.createElement("div");
el.classList.add("assiduite", "color", assiduite.etat.toLowerCase());
el.style.width = getWidth(assiduite.date_debut, assiduite.date_fin);
el.style.left = getLeftPosition(assiduite.date_debut);
el.style.top = "10px";
el.setAttribute("data-id", assiduite.assiduite_id);
el.addEventListener("click", () => {});
// Génération des boutons d'action (supprimer, éditer, diviser)
const actionButtons = document.createElement("div");
actionButtons.className = "action-buttons";
const deleteButton = document.createElement("button");
deleteButton.textContent = "🗑️";
deleteButton.addEventListener("click", () => {
this.supprimerAssiduite(assiduite);
});
const editButton = document.createElement("button");
editButton.textContent = "📝";
editButton.addEventListener("click", () => {
this.editerAssiduite(assiduite);
});
const splitButton = document.createElement("button");
splitButton.textContent = "✂️";
splitButton.addEventListener("click", () => {
this.spliterAssiduite(assiduite);
});
actionButtons.appendChild(editButton);
actionButtons.appendChild(splitButton);
actionButtons.appendChild(deleteButton);
el.appendChild(actionButtons);
setupAssiduiteBubble(el, assiduite);
assiduitesContainer.appendChild(el);
});
}
supprimerAssiduite(assiduite) {
const html = `
<p>Êtes-vous sûr de vouloir supprimer cette assiduité ?</p>
`;
const div = document.createElement("div");
div.innerHTML = html;
openPromptModal(
"Suppression de l'assiduité",
div,
async (closePromptModal) => {
await async_post(
`../../api/assiduite/delete`,
[assiduite.assiduite_id],
async (data) => {
if (data.success.length > 0) {
const etud = etuds.get(Number(assiduite.etudid));
await MiseAJourLigneEtud(etud);
this.refresh(etud.assiduites, this.conflictPeriod);
closePromptModal();
} else {
console.error(data.errors["0"].message);
}
},
(error) => {
console.error(
"Erreur lors de la suppression de l'assiduité",
error
);
}
);
},
() => {},
"var(--color-error)"
);
}
editerAssiduite(assiduite) {
// Select pour choisir l'état de l'assiduité
const html = `
<select id="etat" name="etat">
<option disabled>Choisir un état</option>
<option value="present">Présent</option>
<option value="absent">Absent</option>
<option value="retard">Retard</option>
</select>
`;
const div = document.createElement("div");
div.innerHTML = html;
div.style.display = "flex";
div.style.justifyContent = "center";
openPromptModal(
"Modifier l'état de l'assiduité",
div,
async (closePromptModal) => {
const etatAssi = etat.value;
if (!etat) return true;
await async_post(
`../../api/assiduite/${assiduite.assiduite_id}/edit`,
{
etat: etatAssi,
},
async (data) => {
const etud = etuds.get(Number(assiduite.etudid));
await MiseAJourLigneEtud(etud);
this.refresh(etud.assiduites, this.conflictPeriod);
closePromptModal();
},
(error) => {
console.error("Erreur lors de la modification de l'assiduité", error);
}
);
},
() => {},
"var(--color-present)"
);
}
spliterAssiduite(assiduite) {
// Select pour choisir l'état de l'assiduité
const creneau = getPeriodAsDate()
creneau.deb = creneau.deb.format().substring(11,16)
creneau.fin = creneau.fin.format().substring(11,16)
const html = `
<p>La période conflictuelle s'étend de ${creneau.deb} à ${creneau.fin}</p>
<br>
<input type="text" id="promptTime" name="promptTime" class="timepicker"
placeholder="Cliquez pour choisir un horaire" required>
`;
const div = document.createElement("div");
div.innerHTML = html;
div.style.display = "flex";
div.style.justifyContent = "center";
div.style.flexDirection = "column";
openPromptModal(
"Séparer l'assiduité",
div,
async (closePromptModal) => {
const separateur = promptTime.value;
if (separateur === "") return true;
const assiduiteAvant = {...assiduite};
const assiduiteAprès = {...assiduite};
assiduiteAvant.date_fin = assiduite.date_fin.substring(0,11) + separateur;
assiduiteAprès.date_debut = assiduite.date_debut.substring(0,11) + separateur;
// On supprime l'assiduité actuelle
await async_post(
"../../api/assiduite/delete",
[assiduite.assiduite_id],
(data)=>{console.log(data)},
()=>{},
)
// On ajoute les deux nouvelles assiduités
await async_post(
"../../api/assiduites/create",
[assiduiteAvant, assiduiteAprès],
async (data)=>{
console.log(data);
const etud = etuds.get(Number(assiduite.etudid));
await MiseAJourLigneEtud(etud);
this.refresh(etud.assiduites, this.conflictPeriod);
closePromptModal();
},
()=>{},
)
},
() => {},
"var(--color-retard)"
);
// Initialisation du timepicker
const deb = assiduite.date_debut.substring(11,16);
const fin = assiduite.date_fin.substring(11,16);
setTimeout(()=>{
$('#promptTime').timepicker({
timeFormat: 'HH:mm',
interval: 60 * tick_delay,
minTime: deb,
startTime: deb,
maxTime: fin,
dynamic: false,
dropdown: true,
scrollbar: false,
});
}, 100
);
}
}
</script>
<style>
.ui-timepicker-container {
z-index: 100000 !important;
}
</style>