My changes

This commit is contained in:
Cloud User
2024-03-06 18:11:42 -06:00
45 changed files with 4876 additions and 272 deletions

View File

@@ -77,6 +77,10 @@ $('div.modal#cargando').modal({
const store = reactive({
loading: false,
<<<<<<< HEAD
=======
perido: null as Periodo | null,
>>>>>>> 7688f1aac1824c234bc5f19b154e9ad1f4808d4f
current: {
comentario: '',
clase_vista: null,
@@ -216,8 +220,13 @@ const store = reactive({
registro_fecha_justificacion: Date;
};
try {
<<<<<<< HEAD
const res = await fetch('action/action_justificar.php', {
method: 'PUT',
=======
const res = await fetch('action/justificar.php', {
method: 'POST',
>>>>>>> 7688f1aac1824c234bc5f19b154e9ad1f4808d4f
headers: {
'Content-Type': 'application/json'
},
@@ -237,6 +246,44 @@ const store = reactive({
store.current.justificada.justificador_rol = data.justificador_rol
store.current.justificada.registro_fecha_justificacion = data.registro_fecha_justificacion
},
<<<<<<< HEAD
=======
async justificarBloque(fecha: Date, bloques: Array<number>, justificacion: string) {
if (bloques.length === 0) {
alert('No se ha seleccionado ningún bloque');
return;
}
if (!justificacion) {
alert('No se ha ingresado ninguna observación');
return;
}
try {
const res = await fetch('action/action_justificar.php', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fecha,
bloques,
justificacion,
})
})
const resData = await res.json();
if (resData.status === 'success') {
alert('Se ha justificado el bloque');
store.current.modal_state = 'Cargando datos...';
$('div.modal#cargando').modal('show');
await store.registros.fetch();
$('div.modal#cargando').modal('hide');
} else {
alert('No se ha podido justificar el bloque');
}
} catch (error) {
alert('Error al justificar');
}
},
>>>>>>> 7688f1aac1824c234bc5f19b154e9ad1f4808d4f
registros: {
data: [] as Registro[],
async fetch(fecha?: Date, fecha_inicio?: Date, fecha_fin?: Date) {
@@ -284,7 +331,11 @@ const store = reactive({
if one of the filters is null, then it is not relevant
*/
<<<<<<< HEAD
const filters = Object.keys(store.filters).filter((filtro) => store.filters[filtro] !== null || store.filters[filtro]?.length > 0 )
=======
const filters = Object.keys(store.filters).filter((filtro) => store.filters[filtro] !== null || store.filters[filtro]?.length > 0)
>>>>>>> 7688f1aac1824c234bc5f19b154e9ad1f4808d4f
return this.data.filter((registro: Registro) => {
return filters.every((filtro) => {
switch (filtro) {
@@ -365,6 +416,10 @@ type Profesor = {
}
createApp({
store,
<<<<<<< HEAD
=======
messages: [] as Array<{ title: string, text: string, type: string, timestamp: string }>,
>>>>>>> 7688f1aac1824c234bc5f19b154e9ad1f4808d4f
get clase_vista() {
return store.current.clase_vista
},
@@ -381,6 +436,7 @@ createApp({
profesores: [] as Profesor[],
async mounted() {
$('div.modal#cargando').modal('show');
<<<<<<< HEAD
// await store.registros.fetch()
await store.facultades.fetch()
@@ -390,5 +446,24 @@ createApp({
this.profesores = await (await fetch('action/action_profesor.php')).json() as Profesor[];
$('div.modal#cargando').modal('hide');
=======
try {
// await store.registros.fetch()
await store.facultades.fetch()
await store.estados.fetch()
await store.bloques_horario.fetch()
await store.filters.switchFechas();
store.periodo = await fetch('action/periodo_datos.php').then(res => res.json()) as Periodo;
this.profesores = await (await fetch('action/action_profesor.php')).json() as Profesor[];
this.messages.push({ title: 'Datos cargados', text: 'Los datos se han cargado correctamente', type: 'success', timestamp: new Date() })
} catch (error) {
this.messages.push({ title: 'Error al cargar datos', text: 'No se pudieron cargar los datos', type: 'danger', timestamp: new Date() })
}
finally {
$('div.modal#cargando').modal('hide');
}
>>>>>>> 7688f1aac1824c234bc5f19b154e9ad1f4808d4f
}
}).mount('#app')

207
ts/avisos.ts Normal file
View File

@@ -0,0 +1,207 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
type Profesor = {
profesor_clave: string;
profesor_correo: null | string;
profesor_grado: null | string;
profesor_id: number;
profesor_nombre: string;
}
type Carrera = {
carrera_activa: boolean;
carrera_comun: boolean;
carrera_id: number;
carrera_nombre: string;
clave_carrera: string;
facultad_id: number | null;
nivel_id: number;
}
type Periodo = {
created_at: Date;
estado_id: number;
fecha_final: Date;
id_periodo_sgu: number;
nivel_id: number;
periodo_clave: string;
periodo_fecha_fin: Date;
periodo_fecha_inicio: Date;
periodo_id: number;
periodo_nombre: string;
}
type Aviso = {
aviso_estado: boolean;
aviso_titulo: string;
aviso_fecha_final: Date;
aviso_fecha_inicial: Date;
aviso_id: number;
aviso_texto: string;
carreras: Carrera[];
facultad_id: null;
profesores: Profesor[];
}
const new_aviso = reactive({
titulo: '',
descripcion: '',
fechaInicio: '',
fechaFin: '',
profesores: [] as Array<Profesor>,
carreras: [] as Array<Carrera>,
reset() {
this.titulo = ''
this.descripcion = ''
this.fechaInicio = ''
this.fechaFin = ''
this.profesores = []
this.carreras = []
},
get isValid() {
return this.titulo !== '' && this.descripcion !== '' && this.fechaInicio !== '' && this.fechaFin !== '' && (this.profesores.length > 0 || this.carreras.length > 0) && this.facultad_id !== null
},
})
// define datepicker method
const app = createApp({
new_aviso,
profesores: [] as Array<Profesor>,
carreras: [] as Array<Carrera>,
avisos: [] as Array<Aviso>,
profesor: null as String | null,
formatProfesor(profesor: Profesor) {
return `(${profesor.profesor_clave}) ${profesor.profesor_nombre}`
},
addProfesor() {
const profesorObj = this.profesores.find((profesor: Profesor) => this.profesor === this.formatProfesor(profesor))
if (profesorObj) {
this.new_aviso.profesores.push(profesorObj)
this.profesor = null
}
},
aviso_shown: null as Aviso | null,
// int?
aviso_suspendido: null as number | null,
suspenderAviso() {
if (this.aviso_suspendido) {
const aviso = this.avisos.find((aviso: Aviso) => aviso.aviso_id === this.aviso_suspendido)
if (aviso) {
this.deleteAviso(aviso)
}
}
},
get relevant_profesores() {
// not in array new_aviso.profesores
const relevant = this.profesores.filter((profesor: Profesor) => !this.new_aviso.profesores.map((profesor: Profesor) => profesor.profesor_id).includes(profesor.profesor_id))
// console.log('profesores:', this.profesores.map((profesor: Profesor) => profesor.profesor_nombre), 'relevant:', relevant.map((profesor: Profesor) => profesor.profesor_nombre), 'new_aviso:', this.new_aviso.profesores.map((profesor: Profesor) => profesor.profesor_nombre))
return relevant
},
get relevant_carreras() {
// not in array new_aviso.carreras
return this.carreras.filter((carrera: Carrera) => !this.new_aviso.carreras.includes(carrera))
},
createAviso() {
const data = {
aviso_titulo: this.new_aviso.titulo,
aviso_texto: this.new_aviso.descripcion,
aviso_fecha_inicial: this.new_aviso.fechaInicio,
aviso_fecha_final: this.new_aviso.fechaFin,
profesores: this.new_aviso.profesores.map((profesor: Profesor) => profesor.profesor_id),
carreras: this.new_aviso.carreras.map((carrera: Carrera) => carrera.carrera_id),
}
fetch('/action/avisos.php', {
method: 'POST',
body: JSON.stringify(data)
}).then(res => res.json()).then(res => {
if (res.success) {
// hydrate with carreras and profesores
this.avisos.push({
...data,
carreras: this.carreras.filter((carrera: Carrera) => data.carreras.includes(carrera.carrera_id)),
profesores: this.profesores.filter((profesor: Profesor) => data.profesores.includes(profesor.profesor_id)),
aviso_estado: true,
aviso_id: res.aviso_id,
})
this.new_aviso.reset()
}
else {
alert(res.error)
console.log(res.errors)
}
})
},
deleteAviso(aviso: Aviso) {
fetch(`/action/avisos.php`, {
method: 'DELETE',
body: JSON.stringify({ aviso_id: aviso.aviso_id })
}).then(res => res.json()).then(res => {
if (res.success) {
this.avisos = this.avisos.filter((aviso: Aviso) => aviso.aviso_id !== this.aviso_suspendido)
this.aviso_suspendido = null
}
else {
alert(res.error)
console.log(res.errors)
}
})
},
updateAviso() {
fetch(`/action/avisos.php`, {
method: 'PUT',
body: JSON.stringify({
aviso_id: this.aviso_shown.aviso_id,
aviso_fecha_final: this.aviso_shown.aviso_fecha_final,
})
}).then(res => res.json()).then(res => {
if (res.success) {
}
else {
alert(res.error)
console.log(res.errors)
}
})
},
async initializeDatepickers($el: HTMLElement) {
const periodo = await fetch('action/periodo_datos.php');
const periodo_data = await periodo.json() as Periodo;
$('.date-picker').datepicker({
dateFormat: 'yy-mm-dd',
maxDate: periodo_data.periodo_fecha_fin,
minDate: 0,
});
$($el).on('change', () => {
this.aviso_shown.aviso_fecha_final = $($el).val() as string;
});
},
async mounted() {
this.avisos = await fetch("/action/avisos.php").then(res => res.json()) as Array<Aviso>
this.profesores = await fetch('/action/action_profesor.php').then(res => res.json()) as Array<Profesor>
this.carreras = await fetch('/action/action_carreras.php').then(res => res.json()) as Array<Carrera>
await this.initializeDatepickers()
const fechaInicio = $('#fechaInicio.date-picker')
const fechaFin = $('#fechaFin.date-picker')
fechaInicio.on("change", function () {
new_aviso.fechaInicio = fechaInicio.val() as string
fechaFin.datepicker("option", "minDate", fechaInicio.val());
});
fechaFin.on("change", function () {
new_aviso.fechaFin = fechaFin.val() as string
fechaInicio.datepicker("option", "maxDate", fechaFin.val());
});
}
}).mount('#app')

63
ts/carreras.ts Normal file
View File

@@ -0,0 +1,63 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
type Carrera = {
carrera_id: number;
carrera_nombre: string;
clave_carrera: string;
facultad_id: number;
facultad_nombre: string;
nivel_id: number;
nivel_nombre: string;
}
type Nivel = {
nivel_id: number;
nivel_nombre: string;
}
const app = createApp({
carreras: [] as Carrera[],
niveles: [] as Nivel[],
message: {} as Record<string, string>,
async setNivel(carrera: Carrera, nivel: Nivel) {
if (carrera.nivel_id === nivel.nivel_id) {
return
}
carrera.nivel_id = nivel.nivel_id
carrera.nivel_nombre = nivel.nivel_nombre
await fetch('action/carrera.php', {
method: 'PUT',
body: JSON.stringify({
carrera_id: carrera.carrera_id,
nivel_id: nivel.nivel_id
})
})
.then(res => res.json())
.then(res => {
this.message.title = "Actualización"
this.message.text = res.error ?? res.success
this.message.type = res.error ? 'danger' : 'success'
this.message.timestamp = new Date().toLocaleTimeString()
})
},
async mounted() {
this.carreras = await fetch('action/carrera.php').then(res => res.json())
this.niveles = await fetch('action/nivel.php').then(res => res.json())
// group by facultad_id
const carreras = this.carreras.reduce((acc, cur) => {
const { facultad_nombre } = cur
if (!acc[facultad_nombre]) {
acc[facultad_nombre] = []
}
acc[facultad_nombre].push(cur)
return acc
}, {} as Record<number, Carrera[]>)
this.carreras = Object.entries(carreras).map(([facultad_nombre, carreras]) => ({
facultad_nombre: facultad_nombre,
carreras
}))
}
}).mount('#app')

149
ts/client.ts Normal file
View File

@@ -0,0 +1,149 @@
// @ts-ignore Import module
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
export interface PeridoV1 {
IdNivel: number;
IdPeriodo: number;
NombreNivel: string;
NombrePeriodo: string;
in_db: boolean;
linked: boolean;
}
export interface PeridoV2 {
ClaveCarrera: string;
FechaFin: string;
FechaInicio: string;
IdPeriodo: number;
NombreCarrera: string;
}
const webServices = {
getPeriodosV1: async (): Promise<PeridoV1[]> => {
try {
const response = await fetch('periodos.v1.php');
return await response.json();
} catch (error) {
console.log(error);
return [];
}
},
getPeriodosV2: async (): Promise<PeridoV2[]> => {
try {
const response = await fetch('periodos.v2.php');
return await response.json();
} catch (error) {
console.log(error);
return [];
}
}
}
const store = reactive({
periodosV1: [] as PeridoV1[],
periodosV2: [] as PeridoV2[],
errors: [] as string[],
fechas(idPeriodo: number): { inicio: string, fin: string } {
const periodo = this.periodosV2.find((periodo: PeridoV2) => periodo.IdPeriodo === idPeriodo);
return {
inicio: periodo ? periodo.FechaInicio : '',
fin: periodo ? periodo.FechaFin : ''
}
},
periodov1(idPeriodo: number): PeridoV1 | undefined {
return this.periodosV1.find((periodo: PeridoV1) => periodo.IdPeriodo === idPeriodo);
},
periodov2(idPeriodo: number): PeridoV2[] {
return this.periodosV2.filter((periodo: PeridoV2) => periodo.IdPeriodo === idPeriodo);
},
async addPeriodo(periodo: PeridoV1 | PeridoV2) {
try {
const result = await fetch('backend/periodos.php', {
method: 'POST',
body: JSON.stringify({
...periodo,
...this.fechas(periodo.IdPeriodo)
}),
headers: {
'Content-Type': 'application/json'
}
}).then((response) => response.json());
if (result.success) {
this.periodosV1 = this.periodosV1.map((periodoV1: PeridoV1) => {
if (periodoV1.IdPeriodo === periodo.IdPeriodo) {
periodoV1.in_db = true;
}
return periodoV1;
});
return result;
}
else {
this.errors.push(result.message);
}
} catch (error) {
this.errors.push(error);
}
},
async addCarreras(idPeriodo: number) {
try {
const periodoV1 = this.periodov1(idPeriodo) as PeridoV1;
const periodoV2 = this.periodov2(idPeriodo) as PeridoV2[];
const data = periodoV2.map(({ ClaveCarrera, NombreCarrera }: PeridoV2) =>
({
ClaveCarrera: ClaveCarrera,
NombreCarrera: NombreCarrera,
IdNivel: periodoV1.IdNivel,
})
);
const result = await fetch('backend/carreras.php', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
}).then((response) => response.json());
if (result.success) {
await webServices.getPeriodosV1().then((periodosV1) => {
this.periodosV1 = periodosV1;
});
await webServices.getPeriodosV2().then((periodosV2) => {
this.periodosV2 = periodosV2;
});
}
} catch (error) {
this.errors.push(error);
}
}
})
createApp({
store,
info(IdPeriodo: number): PeridoV2 {
const periodo = store.periodosV2.find((periodo: PeridoV2) => periodo.IdPeriodo === IdPeriodo &&
periodo.FechaInicio != '' && periodo.FechaFin != '');
return periodo
},
complete(IdPeriodo: number): boolean {
const info = this.info(IdPeriodo);
return info !== undefined;
},
mounted: async () => {
await webServices.getPeriodosV1().then((periodosV1) => {
store.periodosV1 = periodosV1;
});
await webServices.getPeriodosV2().then((periodosV2) => {
store.periodosV2 = periodosV2;
});
}
}).mount()

1
ts/declaration.ts Normal file
View File

@@ -0,0 +1 @@
declare module 'https://*'

View File

@@ -23,8 +23,12 @@ const app = createApp({
},
async refresh() {
<<<<<<< HEAD
alert(`Facultad: ${filter.facultad} - Profesor: ${filter.profesor} - Porcentaje: ${filter.porcentaje}%`
if(filter.facultad == -1 || filter.porcetaje < 10) {
=======
if(filter.facultad == -1 || filter.porcentaje < 10) {
>>>>>>> 7688f1aac1824c234bc5f19b154e9ad1f4808d4f
return;
}

181
ts/horario.ts Normal file
View File

@@ -0,0 +1,181 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
type Profesor = {
profesor_clave: string;
profesor_correo: string;
profesor_grado: null | string;
profesor_id: number;
profesor_nombre: string;
}
type Horario = {
carrera: string;
carrera_id: number;
dia: string;
duracion: string;
duracion_id: number;
facultad: string;
facultad_id: number;
fecha_carga: Date;
horario_dia: number;
horario_fecha_fin: null;
horario_fecha_inicio: Date;
horario_fin: string;
horario_grupo: string;
horario_hora: string;
horario_id: number;
limite: null;
materia: string;
materia_id: number;
nivel: string;
nivel_id: number;
periodo: string;
periodo_fecha_fin: Date;
periodo_fecha_inicio: Date;
periodo_id: number;
profesor_id: number;
salon: string;
salon_id: number;
bloques: number;
}
type Facultad = {
clave_dependencia: string;
facultad_id: number;
facultad_nombre: string;
carreras: Carrera[];
}
type Carrera = {
carrera_id: number;
carrera_nombre: string;
clave_carrera: string;
facultad_id: number;
}
const profesores = reactive({
data: [] as Profesor[],
search: null as null | string,
fetch: async function () {
const response = await fetch('action/action_profesor.php')
this.data = await response.json() as Profesor[]
},
get clave() {
const match = this.search.match(/^\((.+)\)/)
return match ? match[1] : ''
},
get current() {
return this.data.find((profesor: Profesor) => profesor.profesor_clave === profesores.clave)
},
})
const facultades = reactive({
data: [] as Facultad[],
fetch: async function () {
const facultades = await fetch('action/action_facultad.php').then(response => response.json()) as Facultad[]
const carreras = await fetch(`action/carrera.php`).then(response => response.json()) as Carrera[]
this.data = await Promise.all(facultades.map(async facultad => ({
...facultad,
carreras: await Promise.all(carreras.filter((carrera: Carrera) => carrera.facultad_id === facultad.facultad_id).map(async (carrera: Carrera) => {
const grupos = await fetch(`action/action_grupo.php?carrera_id=${carrera.carrera_id}`).then(response => response.json())
return {
...carrera,
grupos,
}
})),
})))
this.data = this.data.filter((facultad: Facultad) => facultad.carreras.length > 0)
}
})
type Structure = {
sábado: boolean;
hora_mínima: number;
hora_máxima: number;
horas_totales: number;
}
const horarios = reactive({
data: [] as Horario[],
fetch: async function (grupo: number | null = null, carrera_id: number | null = null) {
if (grupo && carrera_id) {
const response = await fetch(`action/action_horario.php?grupo=${grupo}&carrera_id=${carrera_id}`)
this.data = await response.json()
}
else if (profesores.current) {
const response = await fetch(`action/action_horario.php?profesor_id=${profesores.current.profesor_id}`)
this.data = await response.json()
}
},
get structure() {
if (this.data.length === 0) return null;
const structure: Structure = {
sábado: this.data.some((horario: Horario) => horario.horario_dia === 6),
hora_mínima: Math.min(...this.data.map((horario: Horario) => parseInt(horario.horario_hora.split(':')[0]))),
hora_máxima: Math.max(...this.data.map((horario: Horario) => {
const [hour, minute] = horario.horario_fin.split(':').map(Number);
return hour + Math.ceil(minute / 60);
})),
horas_totales: 0
};
structure.horas_totales = structure.hora_máxima - structure.hora_mínima;
return structure;
},
get blocks() {
if (this.data.length === 0) return null;
return [...Array(this.structure.horas_totales).keys()].flatMap(hora => {
const baseHour = hora + this.structure.hora_mínima;
return [0, 15, 30, 45].map(block => ({ hour: baseHour, block }));
});
},
getHorarioData(hour: number, block: number, día: number) {
const foundHorario = this.data.find((horario: Horario) =>
parseInt(horario.horario_hora.split(':')[0]) === hour &&
parseInt(horario.horario_hora.split(':')[1]) === block &&
horario.horario_dia === día
);
return foundHorario;
},
isOccupied(hora: number, bloque: number, day: number) {
if (this.getHorarioData(hora, bloque, day)) {
return false;
}
const currentTimeInMinutes = hora * 60 + bloque;
for (const item of this.data) {
if (item.horario_dia !== day) {
continue; // Skip items that are not on the specified day
}
// Split the hour and minute from horario_hora
const [startHour, startMinute] = item.horario_hora.split(":").map(Number);
const startTimeInMinutes = startHour * 60 + startMinute;
// Calculate end time using duracion
const [durationHours, durationMinutes] = item.duracion.split(":").map(Number);
const endTimeInMinutes = startTimeInMinutes + (durationHours * 60) + durationMinutes;
if (currentTimeInMinutes >= startTimeInMinutes && currentTimeInMinutes < endTimeInMinutes) {
return true; // The block is occupied
}
}
return false; // The block is not occupied by any class
}
})
const app = createApp({
profesores,
horarios,
facultades,
mounted: async function () {
await profesores.fetch()
await facultades.fetch()
}
}).mount('#app')

145
ts/periodos.ts Normal file
View File

@@ -0,0 +1,145 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
interface Periodo {
created_at: Date;
estado_id: number;
id_periodo_sgu: number;
nivel: string;
nivel_id: number | '';
periodo_clave: string;
periodo_fecha_fin: Date;
periodo_fecha_inicio: Date;
periodo_id: number;
periodo_nombre: string;
}
interface Nivel {
nivel_id: number;
nivel_nombre: string;
}
const app = createApp({
periodos: [] as Array<Periodo>,
niveles: [] as Array<Nivel>,
messages: [] as Array<{ title: string, text: string, type: string, timestamp: string }>,
addMessage(title: string, text: string, type: string) {
this.messages.push({ title, text, type, timestamp: new Date() });
},
async sendRequest(action: string, periodo_id: number, data: any) {
const response = await fetch('action/periodos.php', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: action,
periodo_id: periodo_id,
...data
})
})
return await response.json()
},
async changeNivel(periodo: Periodo, nivel_id: number) {
if (periodo.nivel_id === nivel_id) return
const result = await this.sendRequest('changeNivel', periodo.periodo_id, { nivel_id: nivel_id })
if (result.success) {
this.addMessage('Nivel cambiado', `El nivel del periodo ${periodo.periodo_nombre} ha sido cambiado a ${this.niveles.find((nivel: Nivel) => nivel.nivel_id === nivel_id)?.nivel_nombre}`, 'success')
periodo.nivel_id = nivel_id
periodo.nivel = this.niveles.find((nivel: Nivel) => nivel.nivel_id === nivel_id)?.nivel_nombre || ''
} else {
this.addMessage('Error al cambiar nivel', `No se pudo cambiar el nivel del periodo ${periodo.periodo_nombre}`, 'danger')
}
},
async changeFechaInicio(periodo: Periodo, fecha_inicio: Date) {
const result = await this.sendRequest('changeFechaInicio', periodo.periodo_id, { periodo_fecha_inicio: fecha_inicio })
if (result.success) {
this.addMessage('Fecha de inicio cambiada', `La fecha de inicio del periodo ${periodo.periodo_nombre} ha sido cambiada a ${fecha_inicio}`, 'success')
periodo.periodo_fecha_inicio = fecha_inicio
} else {
this.addMessage('Error al cambiar fecha de inicio', `No se pudo cambiar la fecha de inicio del periodo ${periodo.periodo_nombre}`, 'danger')
}
},
async changeFechaFin(periodo: Periodo, fecha_fin: Date) {
const result = await this.sendRequest('changeFechaFin', periodo.periodo_id, { periodo_fecha_fin: fecha_fin })
if (result.success) {
this.addMessage('Fecha de fin cambiada', `La fecha de fin del periodo ${periodo.periodo_nombre} ha sido cambiada a ${fecha_fin}`, 'success')
periodo.periodo_fecha_fin = fecha_fin
} else {
this.addMessage('Error al cambiar fecha de fin', `No se pudo cambiar la fecha de fin del periodo ${periodo.periodo_nombre}`, 'danger')
}
},
async updatePeriodo(periodo: Periodo) {
const result = await this.sendRequest('updatePeriodo', periodo.periodo_id, {
periodo_nombre: periodo.periodo_nombre,
id_periodo_sgu: periodo.id_periodo_sgu,
periodo_clave: periodo.periodo_clave,
})
if (result.success) {
this.addMessage('Periodo actualizado', `El periodo ${periodo.periodo_nombre} ha sido actualizado`, 'success')
} else {
this.addMessage('Error al actualizar periodo', `No se pudo actualizar el periodo ${periodo.periodo_nombre}`, 'danger')
}
},
async createPeriodo(newPeriodo: Periodo) {
if (newPeriodo.periodo_nombre === null || newPeriodo.nivel_id === null || newPeriodo.periodo_fecha_inicio === null || newPeriodo.periodo_fecha_fin === null) {
this.addMessage('Error al crear periodo', `No se pudo crear el periodo ${newPeriodo.periodo_nombre}`, 'danger')
return
}
const result = await fetch('action/periodos.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newPeriodo)
}).then(res => res.json())
if (result.success) {
this.addMessage('Periodo creado', `El periodo ${newPeriodo.periodo_nombre} ha sido creado`, 'success')
this.periodos
Object.keys(newPeriodo).forEach(key => newPeriodo[key] = null);
newPeriodo.nivel_id = '';
this.periodos = await fetch('action/periodos.php').then(res => res.json())
} else {
this.addMessage('Error al crear periodo', `No se pudo crear el periodo ${newPeriodo.periodo_nombre}`, 'danger')
}
},
async deletePeriodo(periodo: Periodo) {
const response = await fetch('action/periodos.php', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
periodo_id: periodo.periodo_id,
})
})
const result = await response.json()
if (result.success) {
this.addMessage('Periodo eliminado', `El periodo ${periodo.periodo_nombre} ha sido eliminado`, 'success')
this.periodos = this.periodos.filter((p: Periodo) => p.periodo_id !== periodo.periodo_id)
} else {
this.addMessage('Error al eliminar periodo', `No se pudo eliminar el periodo ${periodo.periodo_nombre}`, 'danger')
}
},
async mounted() {
this.periodos = await fetch('action/periodos.php').then(res => res.json())
this.niveles = await fetch('action/nivel.php').then(res => res.json())
}
}).mount('#app')

106
ts/puestos.ts Normal file
View File

@@ -0,0 +1,106 @@
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
type Puesto = {
puesto_id: number,
nombre: string,
facultad_id: number,
}
type Carrera = {
carrera_id: number;
carrera_nombre: string;
clave_carrera: string;
}
type Materia = {
carrera_id: number;
clave_materia: string;
materia_id: number;
materia_nombre: string;
}
type Usuario = {
usuario_clave: string;
usuario_id: number;
usuario_nombre: string;
}
const app = createApp({
message: null,
puestos: [] as Puesto[],
carreras: [] as Carrera[],
materias: [] as Materia[],
usuarios: [] as Usuario[],
async nuevoPuesto(nuevoPuesto: string) {
try {
const res = await fetch('action/puesto.php', {
method: 'POST',
body: JSON.stringify({
puesto_nombre: nuevoPuesto
})
})
const data = await res.json()
this.puestos.push(data)
// order by puesto.nombre
this.puestos.sort((a: Puesto, b: Puesto) => a.nombre.localeCompare(b.nombre))
} catch (error) {
alert(`Error: ${error}`)
}
},
to_delete: null as Puesto | null,
async eliminarPuesto(puesto_id: number) {
try {
const res = await fetch('action/puesto.php', {
method: 'DELETE',
body: JSON.stringify({
puesto_id
})
})
const data = await res.json()
this.message = data.msg;
// after 3 seconds, remove the message
setTimeout(() => {
this.message = null
}, 3000)
this.puestos = this.puestos.filter((p: Puesto) => p.puesto_id !== puesto_id)
// order by puesto.nombre
this.puestos.sort((a: Puesto, b: Puesto) => a.nombre.localeCompare(b.nombre))
} catch (error) {
alert(`Error: ${error}`)
}
},
async actualizarPuesto(puesto_id: number, materias: Materia[], usuario_id: number | null) {
try {
const res = await fetch('action/puesto.php', {
method: 'PUT',
body: JSON.stringify({
puesto_id,
materias: materias.map(m => m.materia_id),
usuario_id
})
})
const data = await res.json()
this.message = data.msg;
// after 3 seconds, remove the message
setTimeout(() => {
this.message = null
}, 3000)
} catch (error) {
alert(`Error: ${error}`)
}
},
async mounted() {
this.puestos = await fetch('action/puesto.php').then(res => res.json())
this.carreras = await fetch('action/action_carreras.php').then(res => res.json())
this.materias = await fetch('action/action_materias.php').then(res => res.json())
this.usuarios = await fetch('action/usuarios.php').then(res => res.json())
}
}).mount('#app')

272
ts/reposiciones.ts Normal file
View File

@@ -0,0 +1,272 @@
import { type } from "os";
declare function triggerMessage(message: string, title: string, type?: string): void;
declare const write: boolean;
declare const moment: any;
// from this 'horario_id', 'fecha', 'hora', 'duracion_id', 'descripcion', 'profesor_id', 'salon', 'unidad', 'periodo_id', 'fecha_clase' make a type of ReposicionParams
export interface ReposicionParams {
horario_id: number;
fecha: string;
hora: string;
duracion_id: number;
descripcion: string;
profesor_id: number;
salon: string;
unidad: number;
periodo_id: number;
fecha_clase: string;
}
type Horario = {
id: number;
carrera_id: number;
materia_id: number;
grupo: string;
profesores: Profesor[];
dia: string;
hora: string;
hora_final: string;
salon: string;
fecha_inicio: string;
fecha_final: string;
fecha_carga: string;
nivel_id: number;
periodo_id: number;
facultad_id: number;
materia: string;
horas: number;
minutos: number;
duracion: number;
retardo: boolean;
original_id: number;
last: boolean;
bloques: number;
};
type Profesor = {
id: number;
clave: string;
grado: string;
profesor: string;
nombre: string;
facultad_id: number;
};
// Get references to the HTML elements
const form = document.getElementById('form') as HTMLFormElement;
const steps = Array.from(form.querySelectorAll('.step')) as HTMLElement[];
const nextButton = document.getElementById('next-button') as HTMLButtonElement;
const prevButton = document.getElementById('prev-button') as HTMLButtonElement;
let currentStep = 0;
// #clave_profesor on change => show step 2
const clave_profesor = document.getElementById('clave_profesor') as HTMLInputElement;
const horario_reponer = document.getElementById('horario_reponer') as HTMLInputElement;
const fechas_clase = document.getElementById('fechas_clase') as HTMLInputElement;
const fecha_reponer = $('#fecha_reponer') as JQuery<HTMLElement>;
const hora_reponer = $('#hora_reponer') as JQuery<HTMLElement>;
const minutos_reponer = $('#minutos_reponer') as JQuery<HTMLElement>;
clave_profesor.addEventListener('change', async () => {
const step2 = document.getElementById('step-2') as HTMLElement;
clave_profesor.disabled = true;
// get option which value is the same as clave_profesor.value
const option = document.querySelector(`option[value="${clave_profesor.value}"]`) as HTMLOptionElement;
// make a form data with #form
const profesor_id = document.getElementById('profesor_id') as HTMLInputElement;
profesor_id.value = option.dataset.id;
const formData = new FormData(form);
const response = await fetch(`./action/action_horario_profesor.php`, {
method: 'POST',
body: formData
});
const data = await response.json();
if (data['success'] === false) {
const message = "Hubo un error al obtener los horarios del profesor."
const title = 'Error';
const color = 'danger';
triggerMessage(message, title, color);
return;
}
const horarios = data.data as Horario[];
const initial = document.createElement('option');
initial.value = '';
initial.textContent = 'Seleccione un horario';
initial.selected = true;
initial.disabled = true;
horario_reponer.innerHTML = '';
horario_reponer.appendChild(initial);
horarios.forEach((horario) => {
const dias = ['Lunes', 'Martes', 'Miercoles', 'Jueves', 'Viernes', 'Sabado', 'Domingo'];
const option = document.createElement('option');
option.value = `${horario.id}`;
// materia máx 25 caracteres, if materia.length > 25 then slice(0, 20)
const max = 25;
option.textContent = `${horario.materia.slice(0, max) + (horario.materia.length > max ? '...' : '')} - Grupo: ${horario.grupo} - ${horario.hora.slice(0, 5)}-${horario.hora_final.slice(0, 5)} - Salon: ${horario.salon} - ${horario.dia}`;
option.dataset.materia = `${horario.materia}`;
option.dataset.grupo = `${horario.grupo}`;
option.dataset.hora = `${horario.hora.slice(0, 5)}`; // slice(0, 5) => HH:MM
option.dataset.hora_final = `${horario.hora_final.slice(0, 5)}`;
option.dataset.salon = `${horario.salon}`;
option.dataset.dia = `${horario.dia}`;
option.dataset.id = `${horario.id}`;
horario_reponer.appendChild(option);
});
currentStep = 1;
step2.style.display = 'block';
prevButton.disabled = false;
});
// disable clave_profesor
// from second step to first step
prevButton.addEventListener('click', () => {
const inputs = [clave_profesor, horario_reponer, fechas_clase, fecha_reponer, hora_reponer] as HTMLInputElement[];
switch (currentStep) {
case 1:
case 2:
case 3:
const step = document.getElementById(`step-${currentStep + 1}`) as HTMLElement;
step.style.display = 'none';
inputs[currentStep - 1].disabled = false;
inputs[currentStep - 1].value = '';
if (--currentStep === 0) {
prevButton.disabled = true;
}
break;
case 4:
const step5 = document.getElementById('step-5') as HTMLElement;
step5.style.display = 'none';
fecha_reponer.prop('disabled', false);
fecha_reponer.val('');
hora_reponer.parent().removeClass('disabled');
hora_reponer.siblings('.datalist-input').text('hh');
hora_reponer.val('');
minutos_reponer.parent().removeClass('disabled');
minutos_reponer.siblings('.datalist-input').text('mm');
minutos_reponer.val('');
currentStep--;
break;
}
nextButton.disabled = true;
});
// #horario_reponer on change => show step 3
horario_reponer.addEventListener('change', async () => {
const selected = horario_reponer.querySelector(`option[value="${horario_reponer.value}"]`) as HTMLOptionElement;
horario_reponer.title = `Materia: ${selected.dataset.materia} - Grupo: ${selected.dataset.grupo} - Horario: ${selected.dataset.hora}-${selected.dataset.hora_final} - Salon: ${selected.dataset.salon} - Día: ${selected.dataset.dia}`;
const step3 = document.getElementById('step-3') as HTMLElement;
horario_reponer.disabled = true;
// make a form data with #form
const response = await fetch(`./action/action_fechas_clase.php?horario_id=${horario_reponer.value}`, {
method: 'GET',
});
const data = await response.json();
if (data['success'] === false) {
const message = "Hubo un error al obtener las fechas de clase."
const title = 'Error';
const color = 'danger';
triggerMessage(message, title, color);
return;
}
type Fecha = {
fecha: string;
dia_mes: number;
day: number;
month: number;
year: number;
}
const meses = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
const fechas = data.data as Fecha[];
const initial = document.createElement('option');
initial.value = '';
initial.textContent = 'Seleccione la fecha de la falta';
initial.selected = true;
initial.disabled = true;
fechas_clase.innerHTML = '';
fechas_clase.appendChild(initial);
fechas_clase.title = 'Seleccione la fecha de la falta';
fechas.forEach((fecha) => {
const option = document.createElement('option');
option.value = `${fecha}`;
option.textContent = `${fecha.dia_mes} de ${meses[fecha.month - 1]} de ${fecha.year}`;
fechas_clase.appendChild(option);
});
step3.style.display = 'block';
currentStep = 2;
});
// #fechas_clase on change => show step 4
fechas_clase.addEventListener('change', () => {
const step4 = document.getElementById('step-4') as HTMLElement;
step4.style.display = 'block';
fechas_clase.disabled = true;
currentStep = 3;
});
// when both #fecha_reponer and #hora_reponer are selected => show step 5
const lastStep = () => {
// timeout to wait for the value to be set
setTimeout(() => {
if (fecha_reponer.val() !== '' && hora_reponer.val() !== '' && minutos_reponer.val() !== '') {
const step5 = document.getElementById('step-5') as HTMLElement;
step5.style.display = 'block';
// disable both
fecha_reponer.prop('disabled', true);
hora_reponer.parent().addClass('disabled');
minutos_reponer.parent().addClass('disabled');
const nextButton = document.getElementById('next-button') as HTMLButtonElement;
// remove property disabled
nextButton.removeAttribute('disabled');
currentStep = 4;
}
}, 100);
}
fecha_reponer.on('change', lastStep);
// on click on the sibling ul>li of #hora_reponer and #minutos_reponer
hora_reponer.siblings('ul').children('li').on('click', lastStep);
minutos_reponer.siblings('ul').children('li').on('click', lastStep);
// Initialize the form
hideSteps();
showCurrentStep();
function hideSteps() {
steps.forEach((step) => {
step.style.display = 'none';
});
}
function showCurrentStep() {
steps[currentStep].style.display = 'block';
prevButton.disabled = currentStep === 0;
}
function handleSubmit(event: Event) {
event.preventDefault();
// Handle form submission
// You can access the form data using the FormData API or serialize it manually
}