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}"]`);