<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() - 8) * 60 + startTime.getMinutes(); return (startMins / (18 * 60 - 8 * 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 / (18 * 60 - 8 * 60)) * 100 if (percent > 100) { console.log(start, end); console.log(startTime, endTime) } 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">×</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() }) 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 (HH:mm) :</legend> <input type="time" id="promptTime" name="appt" min="08:00" max="18:00" required>`; const fieldSet = document.createElement("fieldset"); fieldSet.classList.add("fieldsplit"); 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)"); } /** * 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 // TODO permettre la modification des bornes (8 et 18) for (let i = 8; i <= 18; 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>