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

483 lines
19 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";
}
/**
* Transformation d'un état en couleur
* @param {String} state l'état
* @returns {String} la couleur correspondant à l'état
*/
function getColor(state) {
switch (state) {
case "PRESENT":
return "var(--color-present)";
case "ABSENT":
return "var(--color-absent)";
case "RETARD":
return "var(--color-retard)";
default:
return "var(--color-defaut-dark)";
}
}
/**
* 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 + "%";
}
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()
}
selectAssiduite() {
}
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="action-buttons">
<button id="finish" class="btnPrompt">Quitter</button>
<button id="delete" class="btnPrompt" disabled>Supprimer</button>
<button id="split" class="btnPrompt" disabled>Séparer</button>
<button id="edit" class="btnPrompt" disabled>Modifier l'état</button>
</div>
</div>
<div class="modal-assiduite-content">
<h2>Information de l'assiduité sélectionnée</h2>
<div class="infos">
<p>Assiduite id : <span id="modal-assiduite-id">A</span></p>
<p>Etat : <span id="modal-assiduite-etat">B</span></p>
<p>Date de début : <span id="modal-assiduite-deb">C</span></p>
<p>Date de fin: <span id="modal-assiduite-fin">D</span></p>
<p>Module : <span id="modal-assiduite-module">E</span></p>
<p><span id="modal-assiduite-user">F</span></p>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML("afterbegin", html);
this.element = document.getElementById('myModal');
this.deleteBtn = document.querySelector('#myModal #delete');
this.editBtn = document.querySelector('#myModal #edit');
this.splitBtn = document.querySelector('#myModal #split');
this.deleteBtn.addEventListener('click', () => { this.deleteAssiduiteModal() });
this.editBtn.addEventListener('click', () => { this.editAssiduiteModal() });
this.splitBtn.addEventListener('click', () => { this.splitAssiduiteModal() });
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()
}
}
/**
* Sélection d'une assiduité sur la timeline
* @param {Assiduité} assiduite l'assiduité sélectionnée
*/
selectAssiduite(assiduite) {
// Désélectionner l'assiduité précédemment sélectionnée
if (this.selectedAssiduite) {
const prevSelectedEl = document.querySelector(
`.assiduite[data-id="${this.selectedAssiduite.assiduite_id}"]`
);
if (prevSelectedEl) {
prevSelectedEl.classList.remove("selected");
}
}
// Sélectionner la nouvelle assiduité
this.selectedAssiduite = assiduite;
const selectedEl = document.querySelector(
`.assiduite[data-id="${assiduite.assiduite_id}"]`
);
if (selectedEl) {
selectedEl.classList.add("selected");
}
//Mise à jour de la partie information du modal
const selectedModal = document.querySelector(".modal-assiduite-content");
selectedModal.classList.add("show");
document.getElementById("modal-assiduite-id").textContent =
assiduite.assiduite_id;
document.getElementById(
"modal-assiduite-user"
).textContent = `saisie le ${formatDateModal(
assiduite.entry_date,
"à"
)} \npar ${assiduite.user_id}`;
document.getElementById("modal-assiduite-module").textContent =
assiduite.moduleimpl_id;
document.getElementById("modal-assiduite-deb").textContent = formatDateModal(
assiduite.date_debut
);
document.getElementById("modal-assiduite-fin").textContent = formatDateModal(
assiduite.date_fin
);
document.getElementById("modal-assiduite-etat").textContent =
assiduite.etat.capitalize();
//Activation des boutons d'actions de conflit
this.deleteBtn.disabled = false;
this.splitBtn.disabled = false;
this.editBtn.disabled = false;
}
/**
* Suppression de l'assiduité sélectionnée
*/
deleteAssiduiteModal() {
if (!this.selectedAssiduite) return;
deleteAssiduite(this.selectedAssiduite.assiduite_id);
this.callbacks.delete(this.selectedAssiduite)
this.refresh(assiduites[this.selectedAssiduite.etudid]);
// Désélection de l'assiduité
this.resetSelection();
}
/**
* Division d'une assiduité
*/
splitAssiduiteModal() {
//Préparation du prompt
const htmlPrompt = `<legend>Entrez l'heure de séparation</legend>
<input type="text" id="promptTime" name="appt"required style="position: relative; z-index: 100000;">`;
const fieldSet = document.createElement("fieldset");
fieldSet.classList.add("fieldsplit", "timepicker");
fieldSet.innerHTML = htmlPrompt;
//Callback de division
const success = () => {
const separatorTime = document.getElementById("promptTime").value;
const dateString =
getDate().format("YYYY-MM-DD") + `T${separatorTime}`;
const separtorDate = new Date(dateString);
const assiduite_debut = new Date(this.selectedAssiduite.date_debut);
const assiduite_fin = new Date(this.selectedAssiduite.date_fin);
if (
separtorDate.isAfter(assiduite_debut) &&
separtorDate.isBefore(assiduite_fin)
) {
const assiduite_avant = {
etat: this.selectedAssiduite.etat,
date_debut: assiduite_debut.toFakeIso(),
date_fin: separtorDate.toFakeIso(),
};
const assiduite_apres = {
etat: this.selectedAssiduite.etat,
date_debut: separtorDate.toFakeIso(),
date_fin: assiduite_fin.toFakeIso(),
};
if (this.selectedAssiduite.moduleimpl_id) {
assiduite_apres["moduleimpl_id"] = this.selectedAssiduite.moduleimpl_id;
assiduite_avant["moduleimpl_id"] = this.selectedAssiduite.moduleimpl_id;
}
deleteAssiduite(this.selectedAssiduite.assiduite_id);
const path = getUrl() + `/api/assiduite/${this.selectedAssiduite.etudid}/create`;
sync_post(
path,
[assiduite_avant, assiduite_apres],
(data, status) => {
//success
},
(data, status) => {
//error
console.error(data, status);
errorAlert();
}
);
this.callbacks.split(this.selectedAssiduite)
this.refresh(assiduites[this.selectedAssiduite.etudid]);
this.resetSelection();
} else {
const att = document.createTextNode(
"L'heure de séparation doit être compris dans la période de l'assiduité sélectionnée."
);
openAlertModal("Attention", att, "", "var(--color-warning)");
}
};
openPromptModal("Séparation de l'assiduité sélectionnée", fieldSet, success, () => { }, "var(--color-present)");
// Initialisation du timepicker
const deb = this.selectedAssiduite.date_debut.substring(11,16);
const fin = this.selectedAssiduite.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
);
}
/**
* Modification d'une assiduité conflictuelle
*/
editAssiduiteModal() {
if (!this.selectedAssiduite) return;
//Préparation du modal d'édition
const htmlPrompt = `<legend>Entrez l'état de l'assiduité :</legend>
<select name="promptSelect" id="promptSelect" required>
<option value="">Choissez l'état</option>
<option value="present">Présent</option>
<option value="retard">En Retard</option>
<option value="absent">Absent</option>
</select>`;
const fieldSet = document.createElement("fieldset");
fieldSet.classList.add("fieldsplit");
fieldSet.innerHTML = htmlPrompt;
//Callback d'action d'édition
const success = () => {
const newState = document.getElementById("promptSelect").value;
if (!["present", "absent", "retard"].includes(newState.toLowerCase())) {
const att = document.createTextNode(
"L'état doit être 'present', 'absent' ou 'retard'."
);
openAlertModal("Attention", att, "", "var(--color-warning)");
return;
}
// Actualiser l'affichage
editAssiduite(this.selectedAssiduite.assiduite_id, newState, [this.selectedAssiduite]);
this.callbacks.edit(this.selectedAssiduite)
this.refresh(assiduites[this.selectedAssiduite.etudid]);
// Désélection de l'assiduité
this.resetSelection();
};
//Affichage du prompt
openPromptModal("Modification de l'état de l'assiduité sélectionnée", fieldSet, success, () => { }, "var(--color-present)");
}
/**
* 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 = i < 10 ? `0${i}:00` : `${i}:00`;
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.className = "assiduite";
el.style.backgroundColor = getColor(assiduite.etat);
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", () => this.selectAssiduite(assiduite));
// Ajout des informations dans la visualisation d'une assiduité
const infoContainer = document.createElement("div");
infoContainer.className = "assiduite-info";
const idDiv = document.createElement("div");
idDiv.className = "assiduite-id";
idDiv.textContent = `ID: ${assiduite.assiduite_id}`;
infoContainer.appendChild(idDiv);
const periodDivDeb = document.createElement("div");
periodDivDeb.className = "assiduite-period";
periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`;
infoContainer.appendChild(periodDivDeb);
const periodDivFin = document.createElement("div");
periodDivFin.className = "assiduite-period";
periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`;
infoContainer.appendChild(periodDivFin);
const stateDiv = document.createElement("div");
stateDiv.className = "assiduite-state";
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
infoContainer.appendChild(stateDiv);
const userIdDiv = document.createElement("div");
userIdDiv.className = "assiduite-user_id";
userIdDiv.textContent = `saisie le ${formatDateModal(
assiduite.entry_date,
"à"
)} \npar ${assiduite.user_id}`;
infoContainer.appendChild(userIdDiv);
el.appendChild(infoContainer);
assiduitesContainer.appendChild(el);
});
}
/**
* Remise à zéro de la sélection
* Désactivation des boutons d'actions de conflit
*/
resetSelection() {
this.selectedAssiduite = null;
this.deleteBtn.disabled = true;
this.splitBtn.disabled = true;
this.editBtn.disabled = true;
}
}
</script>
<style>
.ui-timepicker-container {
z-index: 100000 !important;
}
</style>