const compareHours = (hora1, hora2) => { const [h1, m1] = hora1.split(":").map(Number); const [h2, m2] = hora2.split(":").map(Number); if (h1 !== h2) { return h1 > h2 ? 1 : -1; } if (m1 !== m2) { return m1 > m2 ? 1 : -1; } return 0; }; let horarios = []; const table = document.querySelector("table"); if (!(table instanceof HTMLTableElement)) { triggerMessage("No se ha encontrado la tabla", "Error", "error"); throw new Error("No se ha encontrado la tabla"); } [...Array(16).keys()].map(x => x + 7).forEach(hora => { // add 7 rows for each hour [0, 15, 30, 45].map((minute) => `${minute}`.padStart(2, '0')).forEach((minute) => { const tr = document.createElement("tr"); tr.id = `hora-${hora}:${minute}`; tr.classList.add(hora > 13 ? "tarde" : "mañana"); if (minute == "00") { const th = document.createElement("th"); th.classList.add("text-center"); th.scope = "row"; th.rowSpan = 4; th.innerText = `${hora}:00`; th.style.verticalAlign = "middle"; tr.appendChild(th); } ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado"].forEach(día => { const td = document.createElement("td"); td.id = `hora-${hora}:${minute}-${día}`; tr.appendChild(td); }); const tbody = document.querySelector("tbody#horario"); if (!(tbody instanceof HTMLTableSectionElement)) { throw new Error("No se ha encontrado el tbody"); } tbody.appendChild(tr); }); }); const empty_table = table.cloneNode(true); document.querySelectorAll('.hidden').forEach((element) => { element.style.display = "none"; }); // hide the table table.style.display = "none"; function moveHorario(id, día, hora) { const formData = new FormData(); formData.append("id", id); formData.append("hora", hora); formData.append("día", día); fetch("action/action_horario_update.php", { method: "POST", body: formData }).then(res => res.json()).then(response => { if (response.status == "success") { triggerMessage("Horario movido", "Éxito", "success"); } else { triggerMessage(response.message, "Error"); } }).then(() => { renderHorario(); }).catch(err => { triggerMessage(err, "Error"); }); } function renderHorario() { if (horarios.length == 0) { triggerMessage("Este profesor hay horarios para mostrar", "Error", "info"); table.style.display = "none"; document.querySelectorAll('.hidden').forEach((element) => element.style.display = "none"); return; } // show the table table.style.display = "table"; document.querySelectorAll('.hidden').forEach((element) => element.style.display = "block"); // clear the table table.innerHTML = empty_table.outerHTML; function conflicts(horario1, horario2) { const { hora: hora_inicio1, hora_final: hora_final1, dia: dia1 } = horario1; const { hora: hora_inicio2, hora_final: hora_final2, dia: dia2 } = horario2; if (dia1 !== dia2) { return false; } const compareInicios = compareHours(hora_inicio1, hora_inicio2); const compareFinales = compareHours(hora_final1, hora_final2); if (compareInicios >= 0 && compareInicios <= compareFinales || compareFinales >= 0 && compareFinales <= -compareInicios) { return true; } return false; } // remove the next 5 cells function removeNextCells(horas, minutos, dia, cells = 5) { for (let i = 1; i <= cells; i++) { const minute = minutos + i * 15; const nextMinute = (minute % 60).toString().padStart(2, "0"); const nextHour = horas + Math.floor(minute / 60); const cellId = `hora-${nextHour}:${nextMinute}-${dia}`; const cellElement = document.getElementById(cellId); if (cellElement) { cellElement.remove(); } else { console.log(`No se ha encontrado la celda ${cellId}`); break; } } } function newBlock(horario, edit = false) { function move(horario, cells = 5) { const [horas, minutos] = horario.hora.split(":").map(Number); const cell = document.getElementById(`hora-${horas}:${minutos.toString().padStart(2, "0")}-${horario.dia}`); const { top, left } = cell.getBoundingClientRect(); const block = document.getElementById(`block-${horario.id}`); block.style.top = `${top}px`; block.style.left = `${left}px`; removeNextCells(horas, minutos, horario.dia, cells); } const [horas, minutos] = horario.hora.split(":").map(x => parseInt(x)); const hora = `${horas}:${minutos.toString().padStart(2, "0")}`; horario.hora = hora; const cell = document.getElementById(`hora-${horario.hora}-${horario.dia}`); if (!cell) return; cell.dataset.ids = `${horario.id}`; const float_menu = edit ? `` : ''; cell.innerHTML = `
${horario.hora} ${horario.materia}

Salón: ${horario.salon}
${horario.profesores.map((profesor) => ` ${profesor.grado ?? ''} ${profesor.profesor}`).join("
")}
${float_menu}`; cell.classList.add("bloque-clase", "position-relative"); cell.rowSpan = horario.bloques; // draggable cell.draggable = write; if (horario.bloques > 0) { removeNextCells(horas, minutos, horario.dia, horario.bloques - 1); } } function newConflictBlock(horarios, edit = false) { const first_horario = horarios[0]; const [horas, minutos] = first_horario.hora.split(":").map(x => parseInt(x)); const hora = `${horas}:${minutos.toString().padStart(2, "0")}`; const ids = horarios.map(horario => horario.id); const cell = document.getElementById(`hora-${hora}-${first_horario.dia}`); if (cell == null) { console.error(`Error: No se encontró la celda: hora-${hora}-${first_horario.dia}`); return; } cell.dataset.ids = ids.join(","); // replace the content of the cell cell.innerHTML = ` ${hora}
Empalme de ${ids.length} horarios
Ver horarios …
`; // Add classes and attributes cell.classList.add("conflict", "bloque-clase"); cell.setAttribute("role", "button"); // Add event listener for the cell cell.addEventListener("click", () => { $("#modal-choose").modal("show"); const ids = cell.getAttribute("data-ids").split(",").map(x => parseInt(x)); const tbody = document.querySelector("#modal-choose tbody"); tbody.innerHTML = ""; horarios.filter(horario => ids.includes(horario.id)).sort((a, b) => compareHours(a.hora, b.hora)).forEach(horario => { tbody.innerHTML += ` ${horario.hora.slice(0, -3)}-${horario.hora_final.slice(0, -3)} ${horario.materia} ${horario.profesores.map(({ grado, profesor }) => `${grado ?? ''} ${profesor}`).join(", ")} ${horario.salon} ${edit ? ` ` : ""} `; }); document.querySelectorAll(".dismiss-editar").forEach(btn => { btn.addEventListener("click", () => $("#modal-choose").modal("hide")); }); }); function getDuration(hora_i, hora_f) { const [horas_i, minutos_i] = hora_i.split(":").map(x => parseInt(x)); const [horas_f, minutos_f] = hora_f.split(":").map(x => parseInt(x)); const date_i = new Date(0, 0, 0, horas_i, minutos_i); const date_f = new Date(0, 0, 0, horas_f, minutos_f); const diffInMilliseconds = date_f.getTime() - date_i.getTime(); const diffInMinutes = diffInMilliseconds / (1000 * 60); const diffIn15MinuteIntervals = diffInMinutes / 15; return Math.floor(diffIn15MinuteIntervals); } const maxHoraFinal = horarios.reduce((max, horario) => { const [horas, minutos] = horario.hora_final.split(":").map(x => parseInt(x)); const date = new Date(0, 0, 0, horas, minutos); return date > max ? date : max; }, new Date(0, 0, 0, 0, 0)); const horaFinalMax = new Date(0, 0, 0, maxHoraFinal.getHours(), maxHoraFinal.getMinutes()); const blocks = getDuration(first_horario.hora, `${horaFinalMax.getHours()}:${horaFinalMax.getMinutes()}`); cell.setAttribute("rowSpan", blocks.toString()); removeNextCells(horas, minutos, first_horario.dia, blocks - 1); } const conflictBlocks = horarios.filter((horario, index, arrayHorario) => arrayHorario.filter((_, i) => i != index).some(horario2 => conflicts(horario, horario2))) .sort((a, b) => compareHours(a.hora, b.hora)); const classes = horarios.filter(horario => !conflictBlocks.includes(horario)); const conflictBlocksPacked = []; // array of sets conflictBlocks.forEach(horario => { const setIndex = conflictBlocksPacked.findIndex(set => set.some(horario2 => conflicts(horario, horario2))); if (setIndex === -1) { conflictBlocksPacked.push([horario]); } else { conflictBlocksPacked[setIndex].push(horario); } }); classes.forEach(horario => newBlock(horario, write)); conflictBlocksPacked.forEach(horarios => newConflictBlock(horarios, write)); // remove the elements that are not in the limits let max_hour = Math.max(...horarios.map(horario => { const lastMoment = moment(horario.hora, "HH:mm").add(horario.bloques * 15, "minutes"); const lastHour = moment(`${lastMoment.hours()}:00`, "HH:mm"); const hourInt = parseInt(lastMoment.format("HH")); return lastMoment.isSame(lastHour) ? hourInt - 1 : hourInt; })); let min_hour = Math.min(...horarios.map(horario => parseInt(horario.hora.split(":")[0]))); document.querySelectorAll("tbody#horario tr").forEach(hora => { const hora_id = parseInt(hora.id.split("-")[1].split(":")[0]); (hora_id < min_hour || hora_id > max_hour) ? hora.remove() : null; }); // if there is no sábado, remove the column if (!horarios.some(horario => horario.dia == "sábado")) { document.querySelectorAll("tbody#horario td").forEach(td => { if (td.id.split("-")[2] == "sábado") { td.remove(); } }); // remove the header (the last) document.querySelector("#headers").lastElementChild.remove(); } // adjust width const ths = document.querySelectorAll("tr#headers th"); ths.forEach((th, key) => th.style.width = (key == 0) ? "5%" : `${95 / (ths.length - 1)}%`); // search item animation const menúFlontantes = document.querySelectorAll(".menu-flotante"); menúFlontantes.forEach((element) => { element.classList.add("d-none"); element.parentElement.addEventListener("mouseover", () => element.classList.remove("d-none")); element.parentElement.addEventListener("mouseout", (e) => element.classList.add("d-none")); }); // droppables // forall the .bloque-elements add the event listeners for drag and drop document.querySelectorAll(".bloque-clase").forEach(element => { function dragStart() { this.classList.add("dragging"); } function dragEnd() { this.classList.remove("dragging"); } element.addEventListener("dragstart", dragStart); element.addEventListener("dragend", dragEnd); }); // forall the cells that are not .bloque-clase add the event listeners for drag and drop document.querySelectorAll("td:not(.bloque-clase)").forEach(element => { function dragOver(e) { e.preventDefault(); this.classList.add("dragging-over"); } function dragLeave() { this.classList.remove("dragging-over"); } function drop() { this.classList.remove("dragging-over"); const dragging = document.querySelector(".dragging"); const id = dragging.getAttribute("data-ids"); const hora = this.id.split("-")[1]; const días = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado"]; let día = this.id.split("-")[2]; día = días.indexOf(día) + 1; // rowspan const bloques = parseInt(dragging.getAttribute("rowspan")); const horaMoment = moment(hora, "HH:mm"); const horaFin = horaMoment.add(bloques * 15, "minutes"); const limit = moment('22:00', 'HH:mm'); if (horaFin.isAfter(limit)) { triggerMessage("No se puede mover el bloque a esa hora", "Error"); // scroll to the top window.scrollTo(0, 0); return; } // get the horario // remove the horario const bloque = document.querySelector(`.bloque-clase[data-ids="${id}"]`); // remove all children while (bloque.firstChild) { bloque.removeChild(bloque.firstChild); } // prepend a loading child const loading = `
Loading...
`; bloque.insertAdjacentHTML("afterbegin", loading); // add style vertical-align: middle bloque.style.verticalAlign = "middle"; bloque.classList.add("text-center"); // remove draggable bloque.removeAttribute("draggable"); moveHorario(id, día, hora); } element.addEventListener("dragover", dragOver); element.addEventListener("dragleave", dragLeave); element.addEventListener("drop", drop); }); } const form = document.getElementById('form'); if (!(form instanceof HTMLFormElement)) { triggerMessage('No se ha encontrado el formulario', 'Error', 'danger'); throw new Error("No se ha encontrado el formulario"); } form.querySelector('#clave_profesor').addEventListener('input', function (e) { const input = form.querySelector('#clave_profesor'); const option = form.querySelector(`option[value="${input.value}"]`); if (input.value == "") { input.classList.remove("is-invalid", "is-valid"); return; } if (!option) { input.classList.remove("is-valid"); input.classList.add("is-invalid"); } else { const profesor_id = form.querySelector('#profesor_id'); profesor_id.value = option.dataset.id; input.classList.remove("is-invalid"); input.classList.add("is-valid"); } }); form.addEventListener('submit', async function (e) { e.preventDefault(); const input = form.querySelector('#clave_profesor'); if (input.classList.contains("is-invalid")) { triggerMessage('El profesor no se encuentra registrado', 'Error', 'danger'); return; } const formData = new FormData(form); try { const buttons = document.querySelectorAll("button"); buttons.forEach(button => { button.disabled = true; button.classList.add("disabled"); }); const response = await fetch('action/action_horario_profesor.php', { method: 'POST', body: formData, }); const data = await response.json(); buttons.forEach(button => { button.disabled = false; button.classList.remove("disabled"); }); if (data.status == 'success') { horarios = data.data; renderHorario(); } else { triggerMessage(data.message, 'Error en la consulta', 'warning'); } } catch (error) { triggerMessage('Fallo al consutar los datos ', 'Error', 'danger'); console.log(error); } }); const input = form.querySelector('#clave_profesor'); const option = form.querySelector(`option[value="${input.value}"]`);