master
Araozu 2023-10-26 21:43:45 -05:00
parent 0f26ae732d
commit 3fd0baad96
46 changed files with 1610 additions and 2689 deletions

217
API.md
View File

@ -1,217 +0,0 @@
# Inicio de sesion
```ts
// HTTP POST
// Url: /login
// Frontend envia el correo del usuario
{
correo_usuario: string
}
// Backend responde con una lista de matriculas
// Si el correo es valido y el usuario tiene alguna matricula
{
matriculas: Array<number> // Un array de id_laboratorio
}
// Si el correo es valido pero el usuario no tiene matriculas
{
matriculas: [] // Un array vacio
}
// Si el correo es invalido: Se envia HTTP 401
```
# Lista cursos
```ts
// HTTP GET
// Url: /cursos
// El frontend pide una lista de la informacion de todos los cursos
// Backend responde con una lista de todos los cursos
[
{
id_curso: number,
id_datos_carrera: any, // Opcional
nombre_curso: string,
curso_anio: number | string, // Numero o string, dependiendo de como este en DB
abreviado: string,
}
]
```
# Carga de horarios
```ts
// HTTP POST
// Url: /horarios
// El frontend envia una lista de cursos, de los cuales recuperar sus datos
{
cursos: Array<number> // Un array de id_curso
}
// Backend responde con los cursos especificados y sus horarios
[
// Cada objeto dentro del array sera un Curso
{
id_curso: number,
id_datos_carrera: any, // Opcional
nombre_curso: string,
curso_anio: number | string,
abreviado: string,
// Un array de objetos, estos objetos son de la entidad Laboratorio
laboratorios: [
{
id_laboratorio: number,
id_curso: number,
grupo: string,
docente: string,
// Array de objetos de la entidad Horario
horario: [
{
id_horario: number,
id_laboratorio: number,
dia: string,
hora_inicio: string,
hora_fin: string,
}
]
}
]
}
]
```
# Matricula
```ts
// HTTP POST
// Url: /matricula
// Frontend envia una lista de horarios en los cuales se matricula y el usuario
{
correo_usuario: string, // Correo del usuario
horarios: Array<number> // Array de id_laboratorio
}
// Backend devuelve HTTP 200 si exitoso, HTTP 400 si hay error (no hay cupos)
```
# Recuperacion de matricula
```ts
// HTTP POST
// Url: /recuperacion
// Frontend envia una lista de id_laboratorio
{
matriculas: Array<number> // Array de id_laboratorio
}
// Backend devuelve:
// Por cada id_laboratorio: datos del curso y el laboratorio
[
// Un objeto asi por cada id_laboratorio
{
nombre_curso: string, // Este es el campo `nombre_curso` de la entidad Curso
grupo: string, // Campo `grupo` de la entidad Laboratorio
docente: string, // Campo `docente` de la entidad Laboratorio
}
]
```
# Creacion de datos
Endpoints para insertar datos a la db
```ts
// POST /crear-carrera
// Front
{
nombre_carrera: string
}
// Backend
{
id_carrera: number // id de la carrera creada
}
```
```ts
// POST /crear-curso
// Front
{
id_datos_carrera: number,
nombre_curso: string,
curso_anio: string, // Ejm: "1er", "2do", etc
abreviado: string
}
// Backend
{
id_curso: number // id del curso creado
}
```
```ts
// POST /crear-laboratorio
// Front
{
id_curso: number,
grupo: string, // Una letra
docente: string,
max_estudiantes: number
}
// Backend
{
id_laboratorio: number // id del lab creado
}
```
```ts
// POST /crear-horario
// Front
{
id_laboratorio: number,
dia: string, // "Lunes", "Martes", etc
hora_inicio: string, // "07:00"
hora_fin: string, // "08:50"
}
// Backend
{
id_horario: number // id del horario creado
}
```

View File

@ -1,52 +1,171 @@
interface Curso {
// Nombre completo del curso
nombre: string,
// Nombre del curso abreviado
abreviado: string,
// Información de las horas de teoria
Teoria: {
// grupo es una letra: A, B, C, D
[grupo: string]: DatosGrupo,
},
// Información de las horas de laboratorio
Laboratorio?: {
// grupo es una letra: A, B, C, D
[grupo: string]: DatosGrupo,
},
}
interface DatosGrupo {
// Nombre del docente de este grupo
Docente: string,
/*
Las horas del curso en el siguiente formato: DD_HHMM
DD puede ser Lu, Ma, Mi, Ju, Vi
Ejm: Ma0850, Vi1640, Ju1550
*/
Horas: string[]
}
// Exclusivo de un unico dia
import { GrupoDia, TableInput } from "../src/Views/SistemasMovil/Table";
import { generarMapaCeldas, MapaCeldas } from "../src/Views/SistemasMovil/mapaCeldas";
type Input = {
offsetVertical: number,
horaInicio: number,
nroHoras: number,
}
type Output = {
horaInicio: number,
nroHoras: number,
offset: number, // 0, 1, 2
fraccion: number, // por cuanto dividir la celda. 1, 2, 3, ...
}
class MapaCeldas {
// Almacena referencias a input
private mapa: Map<number, Map<number, Input>> = new Map();
private disponible(nroFila: number, nroColumna: number): boolean {
if (!this.mapa.has(nroFila)) return true;
const fila = this.mapa.get(nroFila)!;
return fila.has(nroColumna) === false;
}
private obtenerFilaOCrear(nro: number): Map<number, Input> {
if (!this.mapa.has(nro)) {
const m = new Map<number, Input>();
this.mapa.set(nro, m);
return m;
}
return this.mapa.get(nro)!;
}
// Devuelve el offset
public solicitar(inicio: number, cantidad: number, input: Input): number {
const filas = [];
for (let i = 0; i < cantidad; i += 1) filas.push(inicio + i);
for (let offsetActual = 0; offsetActual < 8; offsetActual += 1) {
let todasCeldasDisponibles = true;
for (const fila of filas) {
if (!this.disponible(fila, offsetActual)) {
todasCeldasDisponibles = false;
break;
}
}
if (todasCeldasDisponibles) {
// Crear estas celdas y almacenar
filas.forEach((nroFila) => {
const fila = this.obtenerFilaOCrear(nroFila);
fila.set(offsetActual, input);
});
// Devolver nro de offset
return offsetActual;
}
}
throw new Error("Limite de celdas alcanzado");
}
public generarFraccion(nroFila: number, nroColumna: number, cantidad: number): number {
let fraccionActual = 1;
for (let i = 0; i < cantidad; i += 1) {
const nroFilaActual = nroFila + i;
const filaActual = this.mapa.get(nroFilaActual)!;
const numeroColumnas = filaActual.size;
if (numeroColumnas > fraccionActual) {
fraccionActual = numeroColumnas;
}
}
return fraccionActual;
}
}
function generarMapaCeldas(entrada: Readonly<Array<Input>>): Array<Output> {
const mapa = new MapaCeldas();
const salida: Array<Output> = [];
// Obtener los offsets de cada curso
for (const input of entrada) {
const offset = mapa.solicitar(input.horaInicio, input.nroHoras, input);
salida.push({
...input,
offset,
fraccion: -1,
});
}
// Generar las fracciones de cada curso
for (const output of salida) {
output.fraccion = mapa.generarFraccion(output.horaInicio, output.offset, output.nroHoras);
}
return salida;
}
describe("generarMapaCeldas", () => {
it("vacio si input es vacio", () => {
const input: Array<GrupoDia> = [];
const input: Array<Input> = [];
const output = generarMapaCeldas(input);
expect(output.length).toBe(0);
});
it("funciona con 1 curso", () => {
const input: Array<any> = [
const input: Array<Input> = [
{
offsetVertical: 0,
horaInicio: 0,
nroHoras: 2,
},
];
const output = generarMapaCeldas(input)[0];
expect(output).not.toBeUndefined();
expect(output.offsetHorizontal).toBe(0);
expect(output.offset).toBe(0);
expect(output.fraccion).toBe(1);
});
it("funciona con 2 cursos", () => {
const input: Array<any> = [
const input: Array<Input> = [
{
offsetVertical: 0,
horaInicio: 0,
nroHoras: 2,
},
{
offsetVertical: 1,
horaInicio: 1,
nroHoras: 3,
},
];
const output1 = generarMapaCeldas(input)[0];
expect(output1.offsetHorizontal).toBe(0);
expect(output1.offset).toBe(0);
expect(output1.fraccion).toBe(2);
const output2 = generarMapaCeldas(input)[1];
expect(output2.offsetHorizontal).toBe(1);
expect(output2.offset).toBe(1);
expect(output2.fraccion).toBe(2);
});
});
@ -55,29 +174,29 @@ describe("MapaCeldas", () => {
it("crea 1", () => {
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
const offset = mapa.solicitar(0, 2);
const offset = mapa.solicitar(0, 2, input);
expect(offset).toBe(0);
});
it("crea varios que no se solapan", () => {
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
let offset = mapa.solicitar(0, 2);
let offset = mapa.solicitar(0, 2, input);
expect(offset).toBe(0);
offset = mapa.solicitar(4, 3);
offset = mapa.solicitar(4, 3, input);
expect(offset).toBe(0);
offset = mapa.solicitar(7, 4);
offset = mapa.solicitar(7, 4, input);
expect(offset).toBe(0);
});
it("crea varios que se solapan", () => {
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
let offset = mapa.solicitar(0, 2);
let offset = mapa.solicitar(0, 2, input);
expect(offset).toBe(0);
offset = mapa.solicitar(0, 2);
offset = mapa.solicitar(0, 2, input);
expect(offset).toBe(1);
offset = mapa.solicitar(0, 4);
offset = mapa.solicitar(0, 4, input);
expect(offset).toBe(2);
});
@ -93,33 +212,33 @@ describe("MapaCeldas", () => {
*/
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
let offset = mapa.solicitar(0, 2);
let offset = mapa.solicitar(0, 2, input);
expect(offset).toBe(0);
offset = mapa.solicitar(1, 3);
offset = mapa.solicitar(1, 3, input);
expect(offset).toBe(1);
offset = mapa.solicitar(1, 4);
offset = mapa.solicitar(1, 4, input);
expect(offset).toBe(2);
offset = mapa.solicitar(2, 3);
offset = mapa.solicitar(2, 3, input);
expect(offset).toBe(0);
offset = mapa.solicitar(4, 2);
offset = mapa.solicitar(4, 2, input);
expect(offset).toBe(1);
});
it("genera offsets", () => {
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
let offset = mapa.solicitar(0, 2);
let offset = mapa.solicitar(0, 2, input);
expect(offset).toBe(0);
let fraccion = mapa.generarFraccion(0, offset, 2);
expect(fraccion).toBe(1);
offset = mapa.solicitar(1, 3);
offset = mapa.solicitar(1, 3, input);
fraccion = mapa.generarFraccion(1, offset, 3);
expect(fraccion).toBe(2);
mapa.solicitar(1, 4);
mapa.solicitar(2, 3);
offset = mapa.solicitar(4, 2);
mapa.solicitar(1, 4, input);
mapa.solicitar(2, 3, input);
offset = mapa.solicitar(4, 2, input);
fraccion = mapa.generarFraccion(4, offset, 2);
expect(fraccion).toBe(3);
});

View File

@ -1,5 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
};

View File

@ -1,6 +0,0 @@
[build.environment]
NPM_FLAGS="--version"
NODE_VERSION="16"
[build]
command = "npx pnpm install --store=node_modules/.pnpm-store && npx pnpm build"

View File

@ -15,7 +15,7 @@
"jest": "^29.1.2",
"normalize.css": "^8.0.1",
"solid-app-router": "^0.3.2",
"solid-js": "1.5.7",
"solid-js": "^1.3.12",
"ts-jest": "^29.0.3",
"typescript": "^4.6.2",
"vite": "^2.8.6",
@ -35,7 +35,6 @@
"Node 10"
],
"dependencies": {
"swiper": "^8.4.4",
"yaml": "^1.10.0"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

View File

@ -1,186 +0,0 @@
/*
// HTTP POST
// Url: /horarios
// El frontend envia una lista de cursos, de los cuales recuperar sus datos
{
cursos: Array<number> // Un array de id_curso
}
// Backend responde con los cursos especificados y sus horarios
[
// Cada objeto dentro del array sera un Curso
{
id_curso: number,
id_datos_carrera: any, // Opcional
nombre_curso: string,
curso_anio: number | string,
abreviado: string,
// Un array de objetos, estos objetos son de la entidad Laboratorio
laboratorios: [
{
id_laboratorio: number,
id_curso: number,
grupo: string,
docente: string,
// Array de objetos de la entidad Horario
horario: [
{
id_horario: number,
dia: string,
hora_inicio: string,
hora_fin: string,
}
]
}
]
}
]
*/
import { SERVER_PATH } from "../Store";
export type Horario = {
id_horario: number,
id_laboratorio: number,
dia: string,
hora_inicio: string,
hora_fin: string,
}
export type Laboratorio = {
id_laboratorio: number,
id_curso: number,
grupo: string,
docente: string,
// Array de objetos de la entidad Horario
horarios: Array<Horario>
}
export type CursoCompleto = {
id_curso: number,
nombre_curso: string,
curso_anio: number | string,
abreviado: string,
// Un array de objetos, estos objetos son de la entidad Laboratorio
laboratorios: Array<Laboratorio>
}
type InputData = {
cursos: Array<number>
}
export type ListaCursosCompleto = Array<CursoCompleto>
type GetHorariosFn = (_: InputData) => Promise<ListaCursosCompleto>
export const getHorarios: GetHorariosFn = async(data) => {
const response = await fetch(`${SERVER_PATH}/horarios`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
return await response.json();
};
export const getHorariosMock: GetHorariosFn = async(_) => {
const c1: CursoCompleto = {
id_curso: 0,
nombre_curso: "Gestion de Sistemas y Tecnologias de Informacion",
curso_anio: "5to",
abreviado: "GSTI",
laboratorios: [
{
id_laboratorio: 0,
id_curso: 0,
grupo: "A",
docente: "Luis Rocha",
horarios: [
{
id_horario: 0,
id_laboratorio: 0,
hora_inicio: "1830",
hora_fin: "1920",
dia: "Jueves",
},
{
id_horario: 1,
id_laboratorio: 0,
hora_inicio: "1550",
hora_fin: "1740",
dia: "Viernes",
},
],
},
{
id_laboratorio: 1,
id_curso: 0,
grupo: "B",
docente: "Luis Rocha",
horarios: [
{
id_horario: 2,
id_laboratorio: 1,
hora_inicio: "0700",
hora_fin: "0850",
dia: "Lunes",
},
{
id_horario: 3,
id_laboratorio: 1,
hora_inicio: "1400",
hora_fin: "1640",
dia: "Miercoles",
},
{
id_horario: 6,
id_laboratorio: 1,
hora_inicio: "1400",
hora_fin: "1640",
dia: "Viernes",
},
],
},
],
};
const c2: CursoCompleto = {
id_curso: 1,
nombre_curso: "Plataformas Emergentes",
curso_anio: "5to",
abreviado: "PE",
laboratorios: [
{
id_laboratorio: 2,
id_curso: 1,
grupo: "A",
docente: "Diego Iquira",
horarios: [
{
id_horario: 4,
id_laboratorio: 2,
hora_inicio: "0850",
hora_fin: "1040",
dia: "Jueves",
},
],
},
{
id_laboratorio: 3,
id_curso: 1,
grupo: "B",
docente: "Diego Iquira",
horarios: [
{
id_horario: 5,
id_laboratorio: 3,
hora_inicio: "1740",
hora_fin: "1920",
dia: "Martes",
},
],
},
],
};
return [c1, c2];
};

View File

@ -1,82 +0,0 @@
/*
// HTTP GET
// Url: /cursos
// El frontend pide una lista de la informacion de todos los cursos
// Backend responde con una lista de todos los cursos
[
{
id_curso: number,
id_datos_carrera: any, // Opcional
nombre_curso: string,
curso_anio: number | string, // Numero o string, dependiendo de como este en DB
abreviado: string,
}
]
*/
import { SERVER_PATH } from "../Store";
export type InfoCurso = {
id_curso: number,
nombre_curso: string,
curso_anio: string,
abreviado: string,
}
// `"1er"`, `"2do"`, etc
type NombreAnio = string
export type RespuestaListaCursos = {[key: NombreAnio]: Array<InfoCurso>}
type ListaCursosFn = () => Promise<RespuestaListaCursos>
export const getAllListaCursos: ListaCursosFn = async() => {
const response = await fetch(`${SERVER_PATH}/cursos`);
const data = await response.json() as Array<InfoCurso>;
const resultMap: RespuestaListaCursos = {};
data.forEach((curso) => {
if (resultMap[curso.curso_anio] === undefined) {
resultMap[curso.curso_anio] = [];
}
resultMap[curso.curso_anio]?.push(curso);
});
return resultMap;
};
export const getAllListaCursosMock: ListaCursosFn = async() => {
const arr5to: Array<InfoCurso> = [
{
id_curso: 0,
nombre_curso: "Gestion de Sistemas y Tecnologias de Informacion",
curso_anio: "5to",
abreviado: "GSTI",
},
{
id_curso: 1,
nombre_curso: "Practicas Pre Profesionales",
curso_anio: "5to",
abreviado: "PPP",
},
];
const arr4to: Array<InfoCurso> = [
{
id_curso: 2,
nombre_curso: "Diseño y Arquitectura de Software",
curso_anio: "4to",
abreviado: "DAS",
},
{
id_curso: 3,
nombre_curso: "Gestion de Proyectos de Software",
curso_anio: "4to",
abreviado: "GPS",
},
];
return {
"5to": arr5to,
"4to": arr4to,
};
};

View File

@ -1,34 +0,0 @@
import { SERVER_PATH } from "../Store";
type IdLaboratorio = number;
type LoginData = {correo_usuario: string};
type LoginResponse = Promise<{matriculas: Array<IdLaboratorio>} | null>;
export type LoginFunction = (data: LoginData) => LoginResponse;
// Mock for a login without courses
export const mockLoginEmpty: LoginFunction = async(data) => ({matriculas: []});
// Mock for a login with courses
export const mockLoginNotEmpty: LoginFunction = async(_) => ({
matriculas: [0, 1, 2, 3],
});
// Error login mock
export const mockLoginWithError: LoginFunction = async(_) => null;
// Standard login
export const loginFn: LoginFunction = async(data) => {
const petition = await fetch(`${SERVER_PATH}/login`, {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
correo_usuario: data.correo_usuario,
}),
});
if (!petition.ok) return null;
return await petition.json() as {matriculas: Array<IdLaboratorio>};
};

View File

@ -1,36 +0,0 @@
import {SERVER_PATH} from "../Store";
type Input = {
matriculas: Array<number>
}
export type InfoMatricula = {
nombre_curso: string,
grupo: string,
docente: string,
}
type VerMatriculaFn = (_: Input) => Promise<Array<InfoMatricula>>;
export const getMatricula: VerMatriculaFn = async(input) => {
const response = await fetch(`${SERVER_PATH}/recuperacion`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
return await response.json();
};
export const getMatriculaMock: VerMatriculaFn = async(_) => [
{
nombre_curso: "Plataformas Emergentes",
grupo: "LA",
docente: "Diego Iquira",
},
{
nombre_curso: "Gestión de Proyectos de Software",
grupo: "LB",
docente: "Luis Rocha",
},
];

View File

@ -1,21 +1,16 @@
import { Sistemas } from "./Views/pc/Sistemas";
import { Main } from "./Views/Main";
import { Index } from "./Views/Index";
import { Editor } from "./Views/Editor";
import { useRouter } from "./Router";
import { Switch, Match, Show } from "solid-js";
import { Wallpaper } from "./Wallpaper";
import { SistemasMovil } from "./Views/SistemasMovil";
import { SeleccionCursos } from "./Views/SeleccionCursos";
import { VerMatricula } from "./Views/VerMatricula";
import {SeleccionCursos as SeleccionCursosPC} from "./Views/pc/SeleccionCursos";
import { VerMatricula as VerMatriculaPC } from "./Views/pc/VerMatricula";
function App() {
const route = useRouter();
const isMobile = screen.width <= 500;
return (
<div class="App" style={isMobile ? "--color-texto: #202020;" : ""}>
<div className="App" style={isMobile ? "--color-texto: #202020;" : ""}>
<Show when={!isMobile}>
<Wallpaper />
</Show>
@ -26,24 +21,8 @@ function App() {
<Match when={route() === "/editor/"}>
<Editor />
</Match>
<Match when={route() === "/seleccion-cursos/"}>
<SeleccionCursos />
</Match>
<Match when={route() === "/sistemas-movil/"}>
<SistemasMovil />
</Match>
<Match when={route() === "/ver-matricula/"}>
<VerMatricula />
</Match>
<Match when={route() === "/pc/seleccion-cursos/"}>
<SeleccionCursosPC />
</Match>
<Match when={route() === "/pc/sistemas/"}>
<Sistemas />
</Match>
<Match when={route() === "/pc/ver-matricula/"}>
<VerMatriculaPC />
<Match when={route() === "/sistemas/"}>
<Main />
</Match>
</Switch>
</div>

View File

@ -1,5 +1,5 @@
import { css } from "aphrodite";
import {estilosGlobales} from "../../../../Estilos";
import { css } from "aphrodite"
import { estilosGlobales } from "../Estilos"
interface BotonMaxMinProps {
icono: string,
@ -21,5 +21,5 @@ export function BotonIcono(props: BotonMaxMinProps) {
>
<i className={`${css(estilosGlobales.botonPhospor)} ${props.icono}`} />
</div>
);
)
}

View File

@ -1,6 +1,6 @@
import { css } from "aphrodite";
import { estilosGlobales } from "../../../../Estilos";
import {EstadoLayout} from "../ContenedorHorarios";
import { css } from "aphrodite"
import { estilosGlobales } from "../Estilos"
import { EstadoLayout } from "./ContenedorHorarios"
interface BotonMaxMinProps {
fnMaximizar: () => void,
@ -10,19 +10,19 @@ interface BotonMaxMinProps {
}
export function BotonMaxMin(props: BotonMaxMinProps) {
const horariosMax = () => props.estadoActualLayout() === props.estadoLayoutMax;
const horariosMax = () => props.estadoActualLayout() === props.estadoLayoutMax
const tituloBoton = () => (horariosMax() ? "Minimizar" : "Maximizar");
const iconoBoton = () => (horariosMax() ? "ph-arrows-in" : "ph-arrows-out");
const tituloBoton = () => (horariosMax() ? "Minimizar" : "Maximizar")
const iconoBoton = () => (horariosMax() ? "ph-arrows-in" : "ph-arrows-out")
const funcionBoton = () => {
const estaMaximizado = horariosMax();
const estaMaximizado = horariosMax()
if (estaMaximizado) {
props.fnMinimizar();
props.fnMinimizar()
} else {
props.fnMaximizar();
props.fnMaximizar()
}
};
}
return (
<button
@ -38,5 +38,5 @@ export function BotonMaxMin(props: BotonMaxMinProps) {
>
<i className={`${css(estilosGlobales.botonPhospor)} ${iconoBoton()}`} />
</button>
);
)
}

View File

@ -0,0 +1,137 @@
import YAML from "yaml"
import { css, StyleSheet } from "aphrodite"
import { MiHorario } from "./MiHorario"
import { Horarios } from "./Horarios"
import {
Anios,
Cursos,
DatosHorario,
DatosHorarioRaw,
DatosGrupo,
} from "../types/DatosHorario"
import { estilosGlobales } from "../Estilos"
import { batch, createEffect, createMemo, createSignal, Show } from "solid-js"
import { useListaCursos } from "./useListaCursos"
const datosPromise = (async() => {
const file = await fetch("/horarios/2022_2_fps_ingenieriadesistemas.yaml")
const text = await file.text()
const datosRaw = YAML.parse(text) as DatosHorarioRaw
// Agregar los campos faltantes a DatosHorarioRaw para que sea DatosHorario
const datos: DatosHorario = {
...datosRaw,
años: {},
}
const anios: Anios = {}
for (const [nombreAnio, anio] of Object.entries(datosRaw.años)) {
const anioData: Cursos = {}
for (const [nombreCurso, curso] of Object.entries(anio)) {
const gruposTeoria: { [k: string]: DatosGrupo } = {}
for (const [key, data] of Object.entries(curso.Teoria)) {
gruposTeoria[key] = Object.assign({seleccionado: false}, data)
}
const gruposLab: { [k: string]: DatosGrupo } = {}
for (const [key, data] of Object.entries(curso.Laboratorio ?? {})) {
gruposLab[key] = Object.assign({seleccionado: false}, data)
}
anioData[nombreCurso] = {
...curso,
oculto: false,
Teoria: gruposTeoria,
Laboratorio: gruposLab,
}
}
anios[nombreAnio] = anioData
}
datos.años = anios
return datos
})()
const ElemCargando = () => (
<div className={css(estilosGlobales.contenedor, estilosGlobales.inlineBlock)}>
Recuperando horarios...
</div>
)
export type EstadoLayout = "MaxPersonal" | "Normal" | "MaxHorarios";
const {
listaCursos: cursosUsuario,
setListaCursos: setCursosUsuarios,
agregarCursoALista: agregarCursoUsuario,
} = useListaCursos()
export function ContenedorHorarios() {
const [datosCargados, setDatosCargados] = createSignal(false)
const [datos, setDatos] = createSignal<DatosHorario | null>(null)
const [estadoLayout, setEstadoLayout] = (
createSignal<EstadoLayout>(localStorage.getItem("estadoLayout") as EstadoLayout || "Normal")
)
const e = createMemo(() => {
let templateColumns = ""
switch (estadoLayout()) {
case "MaxHorarios": {
templateColumns = "0 auto"
break
}
case "MaxPersonal": {
templateColumns = "auto 0m"
break
}
case "Normal": {
templateColumns = "50% 50%"
}
}
localStorage.setItem("estadoLayout", estadoLayout())
return StyleSheet.create({
contenedor: {
display: "grid",
gridTemplateColumns: templateColumns,
},
})
})
createEffect(async() => {
const datos = await datosPromise
batch(() => {
setDatos(datos)
setDatosCargados(true)
})
})
return (
<div className={css(e().contenedor)}>
<div>
<MiHorario
estadoLayout={estadoLayout()}
setEstadoLayout={setEstadoLayout}
cursosUsuario={cursosUsuario}
fnAgregarCurso={agregarCursoUsuario}
setCursosUsuarios={setCursosUsuarios}
/>
</div>
<div>
<Show when={datosCargados()}>
<Horarios
data={datos()!}
estadoLayout={estadoLayout()}
setEstadoLayout={setEstadoLayout}
fnAgregarCurso={(c) => agregarCursoUsuario(JSON.parse(JSON.stringify(c)))}
listaCursosUsuario={cursosUsuario}
setCursosUsuarios={setCursosUsuarios}
/>
</Show>
</div>
</div>
)
}

View File

@ -1,10 +1,9 @@
import { Cursos, DatosGrupo, ListaCursosUsuario, Curso } from "../../../../types/DatosHorario";
import { createMemo, For } from "solid-js";
import { produce, SetStoreFunction } from "solid-js/store";
import { StyleSheet, css } from "aphrodite";
import { estilosGlobales } from "../../../../Estilos";
import { TablaObserver } from "./TablaObserver";
import { setGruposSeleccionados } from "../../../../Store";
import { Cursos, DatosGrupo, ListaCursosUsuario, Curso } from "../types/DatosHorario"
import { createMemo, For } from "solid-js"
import { produce, SetStoreFunction } from "solid-js/store"
import { StyleSheet, css } from "aphrodite"
import { estilosGlobales } from "../Estilos"
import { TablaObserver } from "./TablaObserver"
const e = StyleSheet.create({
inline: {
@ -34,20 +33,21 @@ const e = StyleSheet.create({
border: "none",
color: "var(--color)",
},
});
})
const claseCursoNoAgregado = css(
e.contenedorCurso,
estilosGlobales.contenedor,
);
)
const claseCursoOculto = css(e.cursoOculto);
const claseCursoOculto = css(e.cursoOculto)
interface Props {
version: number,
dataAnio: Cursos,
anioActual: () => string,
fnAgregarCurso: (c: Curso) => void,
listaCursosUsuario: ListaCursosUsuario,
esCursoMiHorario: boolean,
setCursosUsuarios: SetStoreFunction<ListaCursosUsuario>,
tablaObserver: TablaObserver,
@ -64,7 +64,7 @@ interface PropsIndicadorGrupo {
}
function IndicadorGrupo(props: PropsIndicadorGrupo) {
const id = `${props.idParcial}_${props.esLab ? "L" : "T"}_${props.nombre}`;
const id = `${props.idParcial}_${props.esLab ? "L" : "T"}_${props.nombre}`
return (
<span
className={css(e.botonTexto, estilosGlobales.contenedorCursor, estilosGlobales.contenedorCursorSoft)}
@ -75,7 +75,7 @@ function IndicadorGrupo(props: PropsIndicadorGrupo) {
>
{props.esLab ? "L" : ""}{props.nombre}
</span>
);
)
}
const agruparProfesores = (
@ -84,21 +84,31 @@ const agruparProfesores = (
esLab: boolean,
setCursosUsuarios: FnSetCursosUsuarios,
) => {
const profesores: { [k: string]: [string, () => void][] } = {};
const profesores: { [k: string]: [string, () => void][] } = {}
for (const [grupo, datosGrupo] of Object.entries(datos)) {
const nombreProfesor = datosGrupo.Docente;
const nombreProfesor = datosGrupo.Docente
if (!profesores[nombreProfesor]) {
profesores[nombreProfesor] = [];
profesores[nombreProfesor] = []
}
profesores[nombreProfesor].push([
grupo,
() => {
setGruposSeleccionados(datosGrupo.id_laboratorio, (x) => !x);
setCursosUsuarios("cursos", Number(indiceCurso), "Teoria", produce<{ [p: string]: DatosGrupo }>((x) => {
const grupoActualSeleccionado = x[grupo].seleccionado
if (grupoActualSeleccionado) {
x[grupo].seleccionado = false
} else {
for (const xKey in x) {
x[xKey].seleccionado = xKey === grupo
}
}
}))
},
]);
])
}
return profesores;
};
return profesores
}
function CursoE(
indiceCurso: string,
@ -107,27 +117,42 @@ function CursoE(
claseCursoAgregado: string,
props: Props,
) {
const idCurso = `${props.version}_${anio()}_${datosCurso.abreviado}`;
const idCurso = `${props.version}_${anio()}_${datosCurso.abreviado}`
const cursoAgregadoMemo = createMemo(
() => props.listaCursosUsuario.cursos.find((x) => x.nombre === datosCurso.nombre && !x.oculto) !== undefined,
undefined,
{
equals:
(x, y) => x === y,
},
)
const tituloMemo = createMemo(() => (cursoAgregadoMemo()
? "Remover de mi horario"
: "Agregar a mi horario"))
const claseMemo = createMemo(() => {
if (props.esCursoMiHorario && datosCurso.oculto) {
return claseCursoOculto;
return claseCursoOculto
}
return claseCursoNoAgregado;
});
return cursoAgregadoMemo()
? claseCursoAgregado
: claseCursoNoAgregado
})
const profesoresTeoria = createMemo(() => agruparProfesores(
datosCurso.Teoria,
Number(indiceCurso),
false,
props.setCursosUsuarios,
));
))
const profesoresLab = createMemo(() => agruparProfesores(
datosCurso.Laboratorio ?? {},
Number(indiceCurso),
true,
props.setCursosUsuarios,
));
))
const IndicadorGrupos = (profesor: string, grupos: [string, () => void][], esLab: boolean) => (
<td style={{"padding-bottom": "0.5rem", "padding-right": "0.75rem"}}>
@ -147,7 +172,7 @@ function CursoE(
}
</For>
</td>
);
)
return (
<div className={claseMemo()}>
@ -172,18 +197,24 @@ function CursoE(
</tr>
</tbody>
</table>
<button
className={css(e.botonTexto, estilosGlobales.contenedorCursor, estilosGlobales.contenedorCursorSoft)}
onClick={() => props.fnAgregarCurso(datosCurso)}
>
{tituloMemo}
</button>
</div>
);
)
}
export function CursosElem(props: Props) {
const anio = () => props.anioActual().substring(0, props.anioActual().indexOf(" "));
const anio = () => props.anioActual().substring(0, props.anioActual().indexOf(" "))
const claseCursoAgregado = css(
e.contenedorCurso,
estilosGlobales.contenedor,
!props.esCursoMiHorario && estilosGlobales.contenedorCursorActivo,
);
)
return (
<>
@ -191,5 +222,5 @@ export function CursosElem(props: Props) {
{([indiceCurso, datosCurso]) => CursoE(indiceCurso, datosCurso, anio, claseCursoAgregado, props)}
</For>
</>
);
)
}

View File

@ -0,0 +1,139 @@
import { Curso, Cursos, DatosHorario, ListaCursosUsuario } from "../types/DatosHorario"
import { batch, createMemo, createSignal, For, Match, Switch, untrack } from "solid-js"
import {SetStoreFunction} from "solid-js/store"
import { css } from "aphrodite"
import { estilosGlobales } from "../Estilos"
import { Tabla } from "./Tabla"
import { CursosElem } from "./CursosElem"
import { EstadoLayout } from "./ContenedorHorarios"
import { BotonMaxMin } from "./BotonMaxMin"
import { useListaCursos } from "./useListaCursos"
import { TablaObserver } from "./TablaObserver"
interface HorariosProps {
data: DatosHorario,
estadoLayout: EstadoLayout,
setEstadoLayout: (v: EstadoLayout) => EstadoLayout,
fnAgregarCurso: (c: Curso) => void,
listaCursosUsuario: ListaCursosUsuario,
setCursosUsuarios: SetStoreFunction<ListaCursosUsuario>
}
const {
setListaCursos,
agregarCursoALista,
eliminarCursosDeLista,
} = useListaCursos()
export function Horarios(props: HorariosProps) {
const [anioActual, setAnioActual] = createSignal("1er año")
const tablaObserver = new TablaObserver()
const elAnios = (
<For each={Object.entries(props.data.años)}>
{([nombre]) => {
const clases = createMemo(() => {
const vAnio = anioActual()
return css(
estilosGlobales.contenedor,
estilosGlobales.inlineBlock,
estilosGlobales.contenedorCursor,
estilosGlobales.contenedorCursorSoft,
nombre === vAnio && estilosGlobales.contenedorCursorActivo,
)
})
return (
<button className={clases()} title={`Cambiar a ${nombre}`} onClick={() => setAnioActual(nombre)}>
{nombre}
</button>
)
}}
</For>
)
const dataTabla = createMemo(() => {
const anio = anioActual()
const obj: Cursos = {}
untrack(() => {
const cursos = props.data.años[anio]
batch(() => {
eliminarCursosDeLista()
let i = 0
for (const [, curso] of Object.entries(cursos)) {
// El curso devuelto por esta fun. es reactivo
obj[i] = agregarCursoALista(curso)
i += 1
}
})
})
return obj
})
const fnMaximizar = () => props.setEstadoLayout("MaxHorarios")
const fnMinimizar = () => props.setEstadoLayout("Normal")
const estadoActualLayout = () => props.estadoLayout
return (
<div>
<Switch>
<Match when={props.estadoLayout === "Normal" || props.estadoLayout === "MaxHorarios"}>
<div>
<div className={css(
estilosGlobales.inlineBlock,
estilosGlobales.contenedor,
)}
>
Horarios disponibles
</div>
</div>
{elAnios}
|
<BotonMaxMin
fnMaximizar={fnMaximizar}
fnMinimizar={fnMinimizar}
estadoActualLayout={estadoActualLayout}
estadoLayoutMax={"MaxHorarios"}
/>
<br />
<div className={css(estilosGlobales.contenedor)}>
<Tabla
data={dataTabla()}
version={props.data.version}
anio={anioActual()}
setCursosUsuarios={setListaCursos}
tablaObserver={tablaObserver}
/>
</div>
<div>
<CursosElem
version={props.data.version}
dataAnio={dataTabla()}
anioActual={anioActual}
fnAgregarCurso={props.fnAgregarCurso}
listaCursosUsuario={props.listaCursosUsuario}
esCursoMiHorario={false}
setCursosUsuarios={setListaCursos}
tablaObserver={tablaObserver}
/>
</div>
</Match>
<Match when={props.estadoLayout === "MaxPersonal"}>
{/*
<BotonMaxMin
fnMaximizar={fnMaximizar}
fnMinimizar={fnMinimizar}
estadoActualLayout={estadoActualLayout}
estadoLayoutMax={"MaxHorarios"}
/>
*/}
<div />
</Match>
</Switch>
</div>
)
}

View File

@ -0,0 +1,146 @@
import { estilosGlobales } from "../Estilos"
import { StyleSheet, css } from "aphrodite"
import { Tabla } from "./Tabla"
import { EstadoLayout } from "./ContenedorHorarios"
import { Switch, Match, createMemo } from "solid-js"
import {SetStoreFunction} from "solid-js/store"
import { BotonMaxMin } from "./BotonMaxMin"
import { BotonIcono } from "./BotonIcono"
import { Curso, Cursos, ListaCursosUsuario } from "../types/DatosHorario"
import { CursosElem } from "./CursosElem"
import { TablaObserver } from "./TablaObserver"
interface MiHorarioProps {
estadoLayout: EstadoLayout,
setEstadoLayout: (v: EstadoLayout) => EstadoLayout,
cursosUsuario: ListaCursosUsuario,
fnAgregarCurso: (c: Curso) => void,
setCursosUsuarios: SetStoreFunction<ListaCursosUsuario>
}
const e = StyleSheet.create({
horario: {},
boton: {
textDecoration: "none",
// paddingRight: "0.5rem",
"::before": {
fontSize: "1rem",
// transform: "translateY(0.2rem)",
textDecoration: "none",
},
},
})
export function MiHorario(props: MiHorarioProps) {
const tablaObserver = new TablaObserver()
const datosMiHorario = createMemo(() => {
const obj: Cursos = {}
props.cursosUsuario.cursos.forEach((x, i) => {
obj[i] = x
})
return obj
})
const fnMaximizar = () => props.setEstadoLayout("MaxPersonal")
const fnMinimizar = () => props.setEstadoLayout("Normal")
const estadoActualLayout = () => props.estadoLayout
/* TODO: En barra superior colocar todos los horarios. En barra inferior el horario
actual.
Al hacer click en un horario de la barra superior, llevarlo al inicio de la lista.
*/
return (
<div>
<Switch>
<Match when={props.estadoLayout === "Normal" || props.estadoLayout === "MaxPersonal"}>
<div>
<div className={css(
estilosGlobales.inlineBlock,
estilosGlobales.contenedor,
)}
>
Mi horario
</div>
</div>
<div>
<div className={css(
estilosGlobales.inlineBlock,
estilosGlobales.contenedor,
)}
>
Mi horario
</div>
|
{/*
<BotonIcono
titulo={"Nuevo horario en blanco"}
icono={"ph-plus"}
onClick={() => {}}
/>
<BotonIcono
titulo={"Reiniciar horario"}
icono={"ph-arrow-counter-clockwise"}
onClick={() => {}}
/>
<BotonIcono
titulo={"Duplicar horario"}
icono={"ph-copy"}
onClick={() => {}}
/>
<BotonIcono titulo={"Eliminar horario"}
icono={"ph-trash"}
onClick={() => {}}
/>
|
*/}
<BotonMaxMin
fnMaximizar={fnMaximizar}
fnMinimizar={fnMinimizar}
estadoActualLayout={estadoActualLayout}
estadoLayoutMax={"MaxPersonal"}
/>
</div>
<div className={css(
e.horario,
estilosGlobales.contenedor,
)}
>
<Tabla
data={datosMiHorario()}
anio={"Mi horario"}
version={1}
setCursosUsuarios={props.setCursosUsuarios}
tablaObserver={tablaObserver}
/>
</div>
<CursosElem
version={Math.floor(Math.random() * 1_000_000)}
anioActual={() => "Mi horario"}
dataAnio={datosMiHorario()}
fnAgregarCurso={props.fnAgregarCurso}
listaCursosUsuario={props.cursosUsuario}
esCursoMiHorario
setCursosUsuarios={props.setCursosUsuarios}
tablaObserver={tablaObserver}
/>
</Match>
<Match when={props.estadoLayout === "MaxHorarios"}>
{/*
<BotonMaxMin
fnMaximizar={fnMaximizar}
fnMinimizar={fnMinimizar}
estadoActualLayout={estadoActualLayout}
estadoLayoutMax={"MaxPersonal"}
/>
*/}
<div />
</Match>
</Switch>
</div>
)
}

View File

@ -1,11 +1,11 @@
import { StyleSheet, css } from "aphrodite";
import { batch, createMemo, For } from "solid-js";
import { produce, SetStoreFunction } from "solid-js/store";
import {estilosGlobales} from "../../../../Estilos";
import { Cursos, ListaCursosUsuario, DataProcesada, DatosGrupo } from "../../../../types/DatosHorario";
import { Dia, dias, gruposSeleccionados, horas, setGruposSeleccionados } from "../../../../Store";
import { FilaTabla } from "./Tabla/FilaTabla";
import { TablaObserver } from "./TablaObserver";
import { StyleSheet, css } from "aphrodite"
import { batch, createMemo, For } from "solid-js"
import { produce, SetStoreFunction } from "solid-js/store"
import { estilosGlobales } from "../Estilos"
import { Cursos, ListaCursosUsuario, DataProcesada, DatosGrupo } from "../types/DatosHorario"
import { Dia, dias, horas } from "../Store"
import { FilaTabla } from "./Tabla/FilaTabla"
import { TablaObserver } from "./TablaObserver"
export const coloresBorde = Object.freeze([
"rgba(33,150,243,1)",
@ -13,22 +13,22 @@ export const coloresBorde = Object.freeze([
"rgba(236,64,122 ,1)",
"rgba(29,233,182 ,1)",
"rgba(244,67,54,1)",
]);
])
export const diaANum = (d: Dia) => {
switch (d) {
case "Lunes":
return 0;
return 0
case "Martes":
return 1;
return 1
case "Miercoles":
return 2;
return 2
case "Jueves":
return 3;
return 3
case "Viernes":
return 4;
return 4
}
};
}
const e = StyleSheet.create({
fila: {
@ -81,34 +81,34 @@ const e = StyleSheet.create({
celdaCursoTeoria: {
fontWeight: "bold",
},
});
})
type FnSetCursosUsuarios = SetStoreFunction<ListaCursosUsuario>;
const procesarAnio = (data: Cursos, anio: string, version: number, setCursosUsuarios: FnSetCursosUsuarios) => {
const obj: DataProcesada = {};
const obj: DataProcesada = {}
for (const [indiceCurso, curso] of Object.entries(data)) {
if (curso.oculto) continue;
if (curso.oculto) continue
const nombreAbreviado = curso.abreviado;
const nombreAbreviado = curso.abreviado
for (const [grupoStr, grupo] of Object.entries(curso.Teoria)) {
for (const hora of grupo.Horas) {
const dia = hora.substring(0, 2);
const horas = hora.substring(2, 4);
const minutos = hora.substr(4);
const dia = hora.substring(0, 2)
const horas = hora.substring(2, 4)
const minutos = hora.substr(4)
const horaCompleta = `${horas}:${minutos}`;
const horaCompleta = `${horas}:${minutos}`
const id = `${version}_${anio}_${nombreAbreviado}_T_${grupoStr}`;
const id = `${version}_${anio}_${nombreAbreviado}_T_${grupoStr}`
if (!(horaCompleta in obj)) {
obj[horaCompleta] = {};
obj[horaCompleta] = {}
}
if (!(dia in obj[horaCompleta])) {
obj[horaCompleta][dia] = [];
obj[horaCompleta][dia] = []
}
obj[horaCompleta][dia].push({
@ -117,28 +117,38 @@ const procesarAnio = (data: Cursos, anio: string, version: number, setCursosUsua
esLab: false,
datosGrupo: grupo,
fnSeleccionar: () => {
setCursosUsuarios("cursos", Number(indiceCurso), "Teoria", produce<{ [p: string]: DatosGrupo }>((x) => {
const grupoActualSeleccionado = x[grupoStr].seleccionado
if (grupoActualSeleccionado) {
x[grupoStr].seleccionado = false
} else {
for (const xKey in x) {
x[xKey].seleccionado = xKey === grupoStr
}
}
}))
},
});
})
}
}
for (const [grupoStr, grupo] of Object.entries(curso.Laboratorio ?? {})) {
for (const hora of grupo.Horas) {
const dia = hora.substring(0, 2);
const horas = hora.substring(2, 4);
const minutos = hora.substr(4);
const dia = hora.substring(0, 2)
const horas = hora.substring(2, 4)
const minutos = hora.substr(4)
const horaCompleta = `${horas}:${minutos}`;
const horaCompleta = `${horas}:${minutos}`
const id = `${version}_${anio}_${nombreAbreviado}_L_${grupoStr}`;
const id = `${version}_${anio}_${nombreAbreviado}_L_${grupoStr}`
if (!(horaCompleta in obj)) {
obj[horaCompleta] = {};
obj[horaCompleta] = {}
}
if (!(dia in obj[horaCompleta])) {
obj[horaCompleta][dia] = [];
obj[horaCompleta][dia] = []
}
obj[horaCompleta][dia].push({
@ -147,15 +157,30 @@ const procesarAnio = (data: Cursos, anio: string, version: number, setCursosUsua
esLab: true,
datosGrupo: grupo,
fnSeleccionar: () => {
setGruposSeleccionados(grupo.id_laboratorio, (x) => !x);
setCursosUsuarios(
"cursos",
Number(indiceCurso),
"Laboratorio",
produce<{ [p: string]: DatosGrupo }>((x) => {
const grupoActualSeleccionado = x[grupoStr].seleccionado
if (grupoActualSeleccionado) {
x[grupoStr].seleccionado = false
} else {
for (const xKey in x) {
x[xKey].seleccionado = xKey === grupoStr
}
}
}),
)
},
});
})
}
}
}
return obj;
};
return obj
}
interface Props {
data: Cursos,
@ -166,12 +191,12 @@ interface Props {
}
export function Tabla(props: Props) {
const anio = () => props.anio.substring(0, props.anio.indexOf(" "));
const data = createMemo(() => procesarAnio(props.data, anio(), props.version, props.setCursosUsuarios));
const anio = () => props.anio.substring(0, props.anio.indexOf(" "))
const data = createMemo(() => procesarAnio(props.data, anio(), props.version, props.setCursosUsuarios))
const celdas = createMemo(() => {
// Hace reaccionar a la reactividad de Solid
const d = data();
const d = data()
return (
<For each={horas}>
{(hora) => (
@ -182,8 +207,8 @@ export function Tabla(props: Props) {
/>
)}
</For>
);
});
)
})
return (
<div>
@ -198,5 +223,5 @@ export function Tabla(props: Props) {
</div>
{celdas()}
</div>
);
)
}

View File

@ -1,9 +1,9 @@
import { StyleSheet, css } from "aphrodite";
import { estilosGlobales } from "../../../../../Estilos";
import { For, createSignal, createMemo, createEffect, onCleanup } from "solid-js";
import { Dia } from "../../../../../Store";
import { DatosGrupo } from "../../../../../types/DatosHorario";
import {TablaObserver} from "../TablaObserver";
import { StyleSheet, css } from "aphrodite"
import { estilosGlobales } from "../../Estilos"
import { For, createSignal, createMemo, createEffect, onCleanup } from "solid-js"
import { Dia } from "../../Store"
import { DatosGrupo } from "../../types/DatosHorario"
import { TablaObserver } from "../TablaObserver"
const e = StyleSheet.create({
celdaComun: {
@ -42,7 +42,7 @@ const e = StyleSheet.create({
celdaResaltadoSeleccionado: {
textDecoration: "underline",
},
});
})
const eColores = StyleSheet.create({
lunes: {
@ -62,7 +62,7 @@ const eColores = StyleSheet.create({
viernes: {
backgroundColor: "rgba(244,67,54,1)",
},
});
})
const clasesColores = {
Lunes: css(eColores.lunes),
@ -70,7 +70,7 @@ const clasesColores = {
Miercoles: css(eColores.miercoles),
Jueves: css(eColores.jueves),
Viernes: css(eColores.viernes),
};
}
interface DatosProps {
id: string,
@ -97,110 +97,108 @@ interface Props {
tablaObserver: TablaObserver,
}
const claseSeldaSeleccionada = css(e.celdaSeleccionado);
const claseSeldaSeleccionada = css(e.celdaSeleccionado)
function RenderFila(datos: DatosProps, props: Props) {
const id = datos.id;
const txt = datos.txt;
const esLab = datos.esLab;
const fnSeleccionar = datos.fnSeleccionar;
const id = datos.id
const txt = datos.txt
const esLab = datos.esLab
const fnSeleccionar = datos.fnSeleccionar
const estadoCeldaMemo = props.tablaObserver.registrarConId(id, datos.datosGrupo);
const estadoCeldaMemo = props.tablaObserver.registrarConId(id, datos.datosGrupo)
const [estabaResaltado, setEstabaResaltado] = createSignal(false);
const [estabaResaltado, setEstabaResaltado] = createSignal(false)
// Limpiar los memos, porque cuando se desmonta la celda esos memos quedan sin efecto
onCleanup(() => {
props.tablaObserver.limpiar(id);
});
props.tablaObserver.limpiar(id)
})
const clases = createMemo(
() => {
const clases = [
e.celdaCurso,
esLab ? e.celdaCursoLab : e.celdaCursoTeoria,
];
let adicional = "";
]
let adicional = ""
const estadoCelda = estadoCeldaMemo();
const estadoCelda = estadoCeldaMemo()
switch (estadoCelda) {
case "Normal": {
if (estabaResaltado()) {
props.fnDesresaltarFila();
setEstabaResaltado(false);
props.fnDesresaltarFila()
setEstabaResaltado(false)
}
break;
break
}
case "Oculto": {
if (estabaResaltado()) {
props.fnDesresaltarFila();
setEstabaResaltado(false);
props.fnDesresaltarFila()
setEstabaResaltado(false)
}
clases.push(e.celdaOculto);
break;
clases.push(e.celdaOculto)
break
}
case "Resaltado": {
props.fnResaltarFila();
setEstabaResaltado(true);
clases.push(e.celdaResaltado);
adicional = clasesColores[props.dia];
break;
props.fnResaltarFila()
setEstabaResaltado(true)
clases.push(e.celdaResaltado)
adicional = clasesColores[props.dia]
break
}
case "Seleccionado": {
if (estabaResaltado()) {
props.fnDesresaltarFila();
setEstabaResaltado(false);
props.fnDesresaltarFila()
setEstabaResaltado(false)
}
clases.push(e.celdaSeleccionado);
break;
clases.push(e.celdaSeleccionado)
break
}
case "ResaltadoOculto": {
props.fnResaltarFila();
setEstabaResaltado(true);
props.fnResaltarFila()
setEstabaResaltado(true)
clases.push(e.celdaResaltadoOculto);
adicional = clasesColores[props.dia];
break;
clases.push(e.celdaResaltadoOculto)
adicional = clasesColores[props.dia]
break
}
case "ResaltadoSeleccionado": {
props.fnResaltarFila();
setEstabaResaltado(true);
props.fnResaltarFila()
setEstabaResaltado(true)
clases.push(e.celdaResaltadoSeleccionado);
adicional = clasesColores[props.dia];
break;
clases.push(e.celdaResaltadoSeleccionado)
adicional = clasesColores[props.dia]
break
}
}
return `${css(...clases)} ${adicional}`;
return `${css(...clases)} ${adicional}`
},
undefined,
{
equals: (x, y) => x === y,
},
);
(x, y) => x === y,
)
return (
<button className={clases()}
onMouseEnter={() => {
props.tablaObserver.resaltar(id);
props.tablaObserver.resaltar(id)
}}
onMouseLeave={() => {
props.tablaObserver.quitarResaltado();
props.tablaObserver.quitarResaltado()
}}
onClick={fnSeleccionar}
>
{txt}
</button>
);
)
}
export function CeldaFila(props: Props) {
const datos = props.datos;
const datos = props.datos
return (
<div className={css(e.celdaComun, estilosGlobales.inlineBlock)}>
@ -208,5 +206,5 @@ export function CeldaFila(props: Props) {
{(datos) => RenderFila(datos, props)}
</For>
</div>
);
)
}

View File

@ -1,12 +1,12 @@
import { StyleSheet, css } from "aphrodite";
import { estilosGlobales } from "../../../../../Estilos";
import { For, createMemo } from "solid-js";
import {createStore, Store} from "solid-js/store";
import { Dia, dias } from "../../../../../Store";
import { CeldaFila } from "./CeldaFila";
import { DataProcesada } from "../../../../../types/DatosHorario";
import { coloresBorde, diaANum } from "../Tabla";
import { TablaObserver } from "../TablaObserver";
import { StyleSheet, css } from "aphrodite"
import { estilosGlobales } from "../../Estilos"
import { For, createMemo } from "solid-js"
import {createStore, Store} from "solid-js/store"
import { Dia, dias } from "../../Store"
import { CeldaFila } from "./CeldaFila"
import { DataProcesada } from "../../types/DatosHorario"
import { coloresBorde, diaANum } from "../Tabla"
import { TablaObserver } from "../TablaObserver"
const e = StyleSheet.create({
celdaHora: {
@ -50,7 +50,7 @@ const e = StyleSheet.create({
celdaResaltadoTransparente: {
backgroundColor: "transparent",
},
});
})
const [diasResaltados, setDiasResaltados] = createStore({
Lunes: 0,
@ -58,7 +58,7 @@ const [diasResaltados, setDiasResaltados] = createStore({
Miercoles: 0,
Jueves: 0,
Viernes: 0,
} as { [k: string]: number });
} as { [k: string]: number })
interface Props {
hora: string,
@ -69,7 +69,7 @@ interface Props {
const diasFilter = createMemo(() => Object.entries(diasResaltados)
.filter((x) => x[1] > 0)
.map((x) => x[0] as Dia)
.sort((x, y) => (diaANum(x) > diaANum(y) ? 1 : -1)));
.sort((x, y) => (diaANum(x) > diaANum(y) ? 1 : -1)))
const useDiasResaltados: () => [
Store<{ [k: string]: boolean }>,
@ -82,26 +82,26 @@ const useDiasResaltados: () => [
Miercoles: false,
Jueves: false,
Viernes: false,
} as { [k: string]: boolean });
} as { [k: string]: boolean })
const fnResaltar = (d: Dia) => {
setDiasResaltadosLocal(d, true);
setDiasResaltados(d, (v) => v + 1);
};
setDiasResaltadosLocal(d, true)
setDiasResaltados(d, (v) => v + 1)
}
const fnDesresaltar = (d: Dia) => {
setDiasResaltadosLocal(d, false);
setDiasResaltados(d, (v) => v - 1);
};
setDiasResaltadosLocal(d, false)
setDiasResaltados(d, (v) => v - 1)
}
return [diasResaltadosLocal, fnResaltar, fnDesresaltar];
};
return [diasResaltadosLocal, fnResaltar, fnDesresaltar]
}
export function FilaTabla(props: Props) {
const [diasResaltadosLocal, fnResaltar, fnDesresaltar] = useDiasResaltados();
const [diasResaltadosLocal, fnResaltar, fnDesresaltar] = useDiasResaltados()
const hora = props.hora;
const data = props.data;
const hora = props.hora
const data = props.data
return (
<div style={{position: "relative"}}>
@ -133,10 +133,10 @@ export function FilaTabla(props: Props) {
</div>
<For each={dias}>
{(dia) => {
const diaStr = dia.substring(0, 2);
const horaStr = hora.substring(0, 5);
const diaStr = dia.substring(0, 2)
const horaStr = hora.substring(0, 5)
const datos = data?.[horaStr]?.[diaStr] ?? [];
const datos = data?.[horaStr]?.[diaStr] ?? []
return (
<CeldaFila
@ -146,11 +146,11 @@ export function FilaTabla(props: Props) {
dia={dia}
tablaObserver={props.tablaObserver}
/>
);
)
}}
</For>
<div className={css(e.filaBorde)} />
</div>
</div>
);
)
}

View File

@ -1,7 +1,6 @@
import { createMemo, createEffect, untrack } from "solid-js";
import {createStore, SetStoreFunction, Store, produce} from "solid-js/store";
import { DatosGrupo } from "../../../../types/DatosHorario";
import { gruposSeleccionados } from "../../../../Store";
import { createMemo, createEffect, untrack } from "solid-js"
import {createStore, SetStoreFunction, Store, produce} from "solid-js/store"
import { DatosGrupo } from "../types/DatosHorario"
const createMemoDefault = <T>(f: () => T) => createMemo<T>(
f,
@ -9,7 +8,7 @@ const createMemoDefault = <T>(f: () => T) => createMemo<T>(
{
equals: (x, y) => x === y,
},
);
)
/**
* - Normal
@ -63,18 +62,17 @@ export class TablaObserver {
curso: undefined,
esLab: undefined,
grupo: undefined,
});
this.resaltado = resaltado;
this.setResaltado = setResaltado;
})
this.resaltado = resaltado
this.setResaltado = setResaltado
const [seleccionado, setSeleccionado] = createStore<ISeleccionado>({});
this.seleccionado = seleccionado;
this.setSeleccionado = setSeleccionado;
const [seleccionado, setSeleccionado] = createStore<ISeleccionado>({})
this.seleccionado = seleccionado
this.setSeleccionado = setSeleccionado
}
/**
* Crea un memo que indica el estado de la celda
* @param id_laboratorio Id del laboratorio
* @param anio El año
* @param curso Curso abreviado
* @param esLab Si es laboratorio
@ -82,82 +80,89 @@ export class TablaObserver {
* @param datosGrupo Contiene `seleccionado`, se usa ese valor reactivo
*/
private registrar(
id_laboratorio: number,
anio: string,
curso: string,
esLab: boolean,
grupo: string,
datosGrupo: DatosGrupo,
): () => EstadoCelda {
const resaltado = this.resaltado;
const resaltado = this.resaltado
const resaltadoMemo = createMemoDefault(() => {
if (resaltado.anio === anio && resaltado.curso === curso) {
if (resaltado.esLab === undefined) {
return true;
return true
} else if (resaltado.esLab !== esLab) {
return false;
return false
} else {
if (resaltado.grupo === undefined) {
return true;
} else return resaltado.grupo === grupo;
return true
} else return resaltado.grupo === grupo
}
} else {
return false;
return false
}
});
})
// Registrar curso en `seleccionado`
this.setSeleccionado((obj: Store<ISeleccionado>) => {
const nuevoObj = {...obj};
const nuevoObj = {...obj}
if (!nuevoObj[anio]) {
nuevoObj[anio] = {};
nuevoObj[anio] = {}
}
if (!nuevoObj[anio][curso]) {
nuevoObj[anio][curso] = {
Laboratorio: [],
Teoria: [],
};
}
}
return nuevoObj;
});
return nuevoObj
})
// Crear un effect para que cada vez que la celda se seleccione se actualize `seleccionado`
createEffect(() => {
const seleccionado = datosGrupo.seleccionado;
const seleccionado = datosGrupo.seleccionado
if (seleccionado) {
this.setSeleccionado(anio, curso, esLab ? "Laboratorio" : "Teoria", (x) => [...x, grupo]);
this.setSeleccionado(anio, curso, esLab ? "Laboratorio" : "Teoria", (x) => [...x, grupo])
} else {
this.setSeleccionado(anio, curso, esLab ? "Laboratorio" : "Teoria", (x) => x.filter((x) => x !== grupo));
this.setSeleccionado(anio, curso, esLab ? "Laboratorio" : "Teoria", (x) => x.filter((x) => x !== grupo))
}
});
})
const seleccionadoMemo = createMemoDefault<EstadoSeleccionado>(() => ((gruposSeleccionados[id_laboratorio]) ? "Seleccionado" : "Normal"));
const seleccionadoMemo = createMemoDefault<EstadoSeleccionado>(() => {
const gruposSeleccionados = this.seleccionado[anio][curso][esLab ? "Laboratorio" : "Teoria"]
if (gruposSeleccionados.length > 0) {
return gruposSeleccionados.find((x) => x === grupo) ? "Seleccionado" : "Oculto"
} else {
return "Normal"
}
})
return createMemoDefault((): EstadoCelda => {
const resaltado = resaltadoMemo();
const seleccionado = seleccionadoMemo();
const resaltado = resaltadoMemo()
const seleccionado = seleccionadoMemo()
switch (seleccionado) {
case "Normal": {
return resaltado ? "Resaltado" : "Normal";
return resaltado ? "Resaltado" : "Normal"
}
case "Oculto": {
return resaltado ? "ResaltadoOculto" : "Oculto";
return resaltado ? "ResaltadoOculto" : "Oculto"
}
case "Seleccionado": {
return resaltado ? "ResaltadoSeleccionado" : "Seleccionado";
return resaltado ? "ResaltadoSeleccionado" : "Seleccionado"
}
default: {
let _: never;
let _: never
// eslint-disable-next-line prefer-const
_ = seleccionado;
return _;
_ = seleccionado
return _
}
}
});
})
}
/**
@ -167,13 +172,13 @@ export class TablaObserver {
*/
registrarConId(id: string, datosGrupo: DatosGrupo): () => EstadoCelda {
if (this.memos[id]) {
return this.memos[id];
return this.memos[id]
}
const [, anio, curso, lab, grupo] = id.split("_");
const memo = this.registrar(datosGrupo.id_laboratorio, anio, curso, lab === "L", grupo, datosGrupo);
this.memos[id] = memo;
return memo;
const [, anio, curso, lab, grupo] = id.split("_")
const memo = this.registrar(anio, curso, lab === "L", grupo, datosGrupo)
this.memos[id] = memo
return memo
}
/**
@ -181,24 +186,24 @@ export class TablaObserver {
* @param id Id a resaltar - YYYYMMDD_Año_Curso[\_Lab[_Grupo]]
*/
resaltar(id: string) {
const [, anio, curso, lab, grupo] = id.split("_");
const [, anio, curso, lab, grupo] = id.split("_")
if (anio === undefined || curso === undefined) {
console.error("Error al intentar resaltar celda: anio o curso son undefined:", anio, curso);
return;
console.error("Error al intentar resaltar celda: anio o curso son undefined:", anio, curso)
return
}
let esLab: boolean | undefined;
let esLab: boolean | undefined
if (lab === undefined) {
esLab = undefined;
esLab = undefined
} else {
esLab = lab === "L";
esLab = lab === "L"
}
this.setResaltado({
anio,
curso,
esLab,
grupo,
});
})
}
quitarResaltado() {
@ -207,10 +212,10 @@ export class TablaObserver {
curso: undefined,
esLab: undefined,
grupo: undefined,
});
})
}
limpiar(id: string) {
delete this.memos[id];
delete this.memos[id]
}
}

View File

@ -1,5 +1,5 @@
import {createStore, SetStoreFunction, Store} from "solid-js/store";
import { Curso, ListaCursosUsuario } from "../../../../types/DatosHorario";
import {createStore, SetStoreFunction, Store} from "solid-js/store"
import { Curso, ListaCursosUsuario } from "../types/DatosHorario"
interface ReturnType {
listaCursos: Store<ListaCursosUsuario>,
@ -9,27 +9,33 @@ interface ReturnType {
}
export const useListaCursos = (): ReturnType => {
const [listaCursos, setListaCursos] = createStore<ListaCursosUsuario>({});
const [listaCursos, setListaCursos] = createStore<ListaCursosUsuario>({
sigIndice: 0,
cursos: [],
})
const agregarCursoALista = (curso: Curso): Curso => {
// Si el horario ya se habia agregado, ocultarlo
if (listaCursos[curso.nombre]) {
setListaCursos(curso.nombre, "oculto", (x) => !x);
return listaCursos[curso.nombre];
const cursoActualIndex = listaCursos.cursos.findIndex((x) => x.nombre === curso.nombre)
if (cursoActualIndex !== -1) {
setListaCursos("cursos", cursoActualIndex, "oculto", (x) => !x)
return listaCursos.cursos[cursoActualIndex]
} else {
setListaCursos(curso.nombre, curso);
return listaCursos[curso.nombre];
setListaCursos("cursos", listaCursos.sigIndice, curso)
setListaCursos("sigIndice", (x) => x + 1)
return listaCursos.cursos[listaCursos.sigIndice - 1]
}
};
}
const eliminarCursosDeLista = () => {
setListaCursos({});
};
setListaCursos("cursos", [])
setListaCursos("sigIndice", 0)
}
return {
listaCursos,
setListaCursos,
agregarCursoALista,
eliminarCursosDeLista,
};
};
}
}

View File

@ -1,4 +1,4 @@
import { StyleSheet } from "aphrodite";
import { StyleSheet } from "aphrodite"
export const estilosGlobales = StyleSheet.create({
contenedor: {
@ -58,4 +58,4 @@ export const estilosGlobales = StyleSheet.create({
padding: "0.25rem 0.35rem",
borderRadius: "5px",
},
});
})

View File

@ -1,31 +1,31 @@
import { createSignal, JSX } from "solid-js";
import { createSignal, JSX } from "solid-js"
export const useRouter = (): () => string => {
let rutaPrevia = window.location.hash;
let rutaPrevia = window.location.hash
if (rutaPrevia === "") {
window.history.pushState({}, "Horarios UNSA", "#/");
rutaPrevia = "/";
window.history.pushState({}, "Horarios UNSA", "#/")
rutaPrevia = "/"
} else {
rutaPrevia = rutaPrevia.substr(1);
rutaPrevia = rutaPrevia.substr(1)
}
const [rutaActual, setRutaActual] = createSignal(rutaPrevia);
const [rutaActual, setRutaActual] = createSignal(rutaPrevia)
const fnEffect = () => {
const nuevaRuta = window.location.hash.substr(1);
setRutaActual(nuevaRuta);
};
const nuevaRuta = window.location.hash.substr(1)
setRutaActual(nuevaRuta)
}
window.addEventListener("hashchange", fnEffect);
window.addEventListener("hashchange", fnEffect)
return rutaActual;
};
return rutaActual
}
export function RouterLink(props: { to: string, className?: string, children: JSX.Element }) {
return (
<a className={props.className} href={`/#${props.to}`}>
{props.children}
</a>
);
)
}

View File

@ -1,5 +1,4 @@
import { createSignal } from "solid-js";
import { createStore } from "solid-js/store";
export type Dia = "Lunes" | "Martes" | "Miercoles" | "Jueves" | "Viernes";
@ -29,7 +28,6 @@ export const horasDescanso = [
"15:40 - 15:50",
"17:30 - 17:40",
];
export const SERVER_PATH = "https://matriculas.fly.dev/sistema";
const numImgGuardado = Number(localStorage.getItem("num-img") ?? "0");
const tamanoLetraGuardado = Number(/* localStorage.getItem("tamano-letra") ?? */ "16");
@ -37,5 +35,3 @@ const tamanoLetraGuardado = Number(/* localStorage.getItem("tamano-letra") ?? */
export const [numWallpaper, setNumWallpaper] = createSignal(numImgGuardado);
export const [tamanoLetra, setTamanoLetra] = createSignal(tamanoLetraGuardado);
export const [isMobile, setIsMobile] = createSignal(screen.width < 500);
export const [gruposSeleccionados, setGruposSeleccionados] = createStore<{[k: number]: boolean}>({});

View File

@ -2,8 +2,8 @@ import { BarraSuperior } from "../BarraSuperior";
import { estilosGlobales } from "../Estilos";
import { StyleSheet, css } from "aphrodite";
import { Separador } from "../Separador";
import { Tabla } from "./pc/Sistemas/ContenedorHorarios/Tabla";
import { TablaObserver } from "./pc/Sistemas/ContenedorHorarios/TablaObserver";
import { Tabla } from "../ContenedorHorarios/Tabla";
import { TablaObserver } from "../ContenedorHorarios/TablaObserver";
import { Curso, Cursos } from "../types/DatosHorario";
import { For, createMemo } from "solid-js";
import {createStore} from "solid-js/store";

View File

@ -1,10 +1,8 @@
import { estilosGlobales } from "../Estilos";
import { StyleSheet, css } from "aphrodite/no-important";
import { RouterLink } from "../Router";
import { batch, createSignal, Show } from "solid-js";
import { isMobile, setGruposSeleccionados } from "../Store";
import { MobileIndex } from "./MobileIndex";
import { loginFn } from "../API/Login";
import { Show } from "solid-js";
import { isMobile } from "../Store";
const e = StyleSheet.create({
contenedorGlobal: {
@ -31,51 +29,55 @@ const e = StyleSheet.create({
verticalAlign: "bottom",
marginRight: "0.5rem",
},
inputCorreo: {
width: "100%",
backgroundColor: "rgba(159,159,159,0.44)",
border: "none",
borderBottom: "solid 2px var(--color-texto)",
padding: "0.5rem 1rem",
boxSizing: "border-box",
marginTop: "1rem",
borderRadius: "5px",
},
});
function MobileIndex() {
const s = StyleSheet.create({
boton: {
backgroundColor: "var(--color-primario)",
color: "white",
padding: "1rem 5rem",
borderRadius: "25px",
margin: "1.5rem 0",
boxShadow: "2px 2px 2px 0 gray",
cursor: "pointer",
},
entrada: {
borderTop: "none",
borderRight: "none",
borderLeft: "none",
borderBottom: "solid 2px gray",
padding: "0.75rem 1rem",
},
});
export function Index() {
const [msgErrorVisible, setMsgErrorVisible] = createSignal(false);
const inputElement = <input class={css(e.inputCorreo)} type="email" required placeholder="correo@unsa.edu.pe" />;
const inputElement = <input type="email" placeholder="Correo electronico" className={css(s.entrada)} />;
const login = async(ev: Event) => {
ev.preventDefault();
const email = (inputElement as HTMLInputElement).value;
const response = await loginFn({correo_usuario: email});
if (response === null) {
setMsgErrorVisible(true);
setTimeout(() => setMsgErrorVisible(false), 2500);
} else if (!response.matriculas || response.matriculas.length === 0) {
localStorage.setItem("correo", email);
window.location.href = "#/pc/seleccion-cursos/";
} else if (response.matriculas.length > 0) {
localStorage.setItem("correo", email);
batch(() => {
for (const id_lab of response.matriculas) {
setGruposSeleccionados(id_lab, true);
}
});
window.location.href = "#/pc/ver-matricula/";
}
const login = () => {
console.log((inputElement as HTMLInputElement).value);
};
return (
<div className={css(e.contenedorGlobal)}>
<div style="text-align: center;">
<h1>Iniciar sesión</h1>
<br />
<br />
{inputElement}
<br />
<button className={css(s.boton)} onClick={login}>Iniciar Sesion</button>
</div>
</div>
);
}
export function Index() {
return (
<>
<Show when={!isMobile()}>
<div class={css(e.contenedorGlobal)}>
<div class={css(e.cont)}>
<div class={css(estilosGlobales.contenedor, estilosGlobales.inlineBlock, e.cont)}>
<div className={css(e.contenedorGlobal)}>
<div className={css(e.cont)}>
<div className={css(estilosGlobales.contenedor, estilosGlobales.inlineBlock, e.cont)}>
<h1 style={{
"text-align": "center",
"font-size": "1.75rem",
@ -83,26 +85,43 @@ export function Index() {
>
Horarios UNSA
</h1>
<p class={css(e.parrafo)}>
Inicia sesión con tu correo institucional.
<br />
{inputElement}
<p className={css(e.parrafo)}>
Esta página te permite crear tu horario fácilmente, sin importar de que
año son los cursos.
</p>
<p className={css(e.parrafo)}>
Por ahora solo está disponible para ing. de sistemas. Proximamente se habilitarán
otras carreras.
</p>
<p className={css(e.parrafo)}>
Se recomienda usar un computador/laptop y un navegador actualizado (Firefox, Chrome,
Qutebrowser).
</p>
<span style={msgErrorVisible() ? "opacity: 1; color: red;" : "opacity: 0;"}>
El correo es invalido
</span>
</div>
<button onClick={login} class={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}>
Iniciar sesion
</button>
<br />
<br />
<RouterLink
to={"/sistemas/"}
className={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}
>
Ing. de Sistemas
</RouterLink>
{/*
<button disabled className={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}>
Otras carreras
</button>
*/}
<RouterLink
to={"/editor/"}
className={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}
>
<i className={`${css(e.iconoGitHub)} ph-pencil`} />
Editor
</RouterLink>
<a
class={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}
className={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}
href="https://github.com/Araozu/horarios-unsa-2/"
target="_blank"
>
<i class={`${css(e.iconoGitHub)} ph-code`} />
<i className={`${css(e.iconoGitHub)} ph-code`} />
Código fuente en GitHub
</a>
</div>

49
src/Views/Main.tsx Normal file
View File

@ -0,0 +1,49 @@
import { BarraSuperior } from "../BarraSuperior"
import { ContenedorHorarios } from "../ContenedorHorarios/ContenedorHorarios"
import { Show, createSignal } from "solid-js"
import { css } from "aphrodite"
import { estilosGlobales } from "../Estilos"
import { Creditos } from "../Creditos"
import { Separador } from "../Separador"
export function Main() {
/// @ts-ignore
const soportaBackdropFilter = document.body.style.backdropFilter !== undefined
const mostrarMensajeBackdropFilterRaw = !localStorage.getItem("mensaje-backdrop-filter-oculto")
const [mostrarMensajeBackdropFilter, setMostrarMensaje] = createSignal(mostrarMensajeBackdropFilterRaw)
const ocultarMensajeBackdropFilter = () => {
setMostrarMensaje(false)
localStorage.setItem("mensaje-backdrop-filter-oculto", "true")
}
return (
<div>
<BarraSuperior />
<Show when={!soportaBackdropFilter && mostrarMensajeBackdropFilter()}>
<div className={css(estilosGlobales.contenedor)}>
Tu navegador no soporta "backdrop-filter". Este es solo un efecto
visual, no afecta la funcionalidad de la página.&nbsp;
<span
className={css(estilosGlobales.contenedorCursor, estilosGlobales.contenedorCursorSoft)}
onClick={ocultarMensajeBackdropFilter}
>
No volver a mostrar.
</span>
</div>
</Show>
{/*
<div className={css(estilosGlobales.contenedor)}>
Solo teoria por ahora. Actualizado el 2021/03/28. Fuente:&nbsp;
<a className={css(estilosGlobales.linkGenerico)} target="_blank" href="https://www.facebook.com/Escuela-Profesional-de-Ingenieria-de-Sistemas-171720913528/photos/pcb.10159175240878529/10159175239403529">
Página de Facebook de la escuela.
</a>
</div>
*/}
<Separador />
<ContenedorHorarios />
<Creditos />
</div>
)
}

View File

@ -1,85 +0,0 @@
import { css, StyleSheet } from "aphrodite/no-important";
import { batch, createSignal } from "solid-js";
import { SERVER_PATH, setGruposSeleccionados } from "../Store";
import { loginFn } from "../API/Login";
const e = StyleSheet.create({
contenedorGlobal: {
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
cont: {
width: "30rem",
},
parrafo: {
textAlign: "justify",
lineHeight: "1.4rem",
},
botonAccion: {
width: "30rem",
display: "inline-block",
textAlign: "center",
},
iconoGitHub: {
fontSize: "1.25rem",
verticalAlign: "bottom",
marginRight: "0.5rem",
},
});
export function MobileIndex() {
const s = StyleSheet.create({
boton: {
backgroundColor: "var(--color-primario)",
color: "white",
padding: "1rem 5rem",
borderRadius: "25px",
margin: "1.5rem 0",
boxShadow: "2px 2px 2px 0 gray",
cursor: "pointer",
},
entrada: {
borderTop: "none",
borderRight: "none",
borderLeft: "none",
borderBottom: "solid 2px gray",
padding: "0.75rem 1rem",
},
});
const [msgErrorVisible, setMsgErrorVisible] = createSignal(false);
const inputElement = <input required type="email" placeholder="correo@unsa.edu.pe" class={css(s.entrada)} />;
const login = async(ev: Event) => {
ev.preventDefault();
const email = (inputElement as HTMLInputElement).value;
const response = await loginFn({correo_usuario: email});
if (response === null) {
setMsgErrorVisible(true);
setTimeout(() => setMsgErrorVisible(false), 2500);
} else {
localStorage.setItem("correo", email);
window.location.href = "#/seleccion-cursos/";
}
};
return (
<div class={css(e.contenedorGlobal)}>
<div style="text-align: center;">
<h1>Iniciar sesión</h1>
<br />
<br />
<form onSubmit={(ev) => login(ev)}>
{inputElement}
<br />
<button type="submit" class={css(s.boton)}>Iniciar Sesion</button>
</form>
<span style={{opacity: msgErrorVisible() ? 1 : 0}}>El correo es invalido</span>
</div>
</div>
);
}

View File

@ -1,92 +0,0 @@
import { TopBar } from "./SistemasMovil/TopBar";
import { StyleSheet, css } from "aphrodite/no-important";
import { Card } from "../components/Card";
import { createSignal, For } from "solid-js";
import { getAllListaCursos, RespuestaListaCursos } from "../API/ListaCursos";
import { Button } from "../components/Button";
const s = StyleSheet.create({
checkbox: {
width: "1.25rem",
height: "1.25rem",
margin: "0 0.5rem",
},
grid: {
display: "grid",
gridTemplateColumns: "3rem auto",
gridRowGap: "1rem",
},
});
export function SeleccionCursos() {
const [cursos, setCursos] = createSignal<RespuestaListaCursos>({});
const [msjErr, setMsjError] = createSignal(false);
// Recuperar cursos de back
(async() => setCursos(await getAllListaCursos()))();
const submit = (ev: Event) => {
ev.preventDefault();
const form = ev.target as HTMLFormElement;
// Los checkboxes
const elements = form.elements;
const idsAEnviar: Array<string> = [];
for (let i = 0; i < elements.length; i += 1) {
const inputBox = elements[i] as HTMLInputElement;
if (inputBox.checked) {
idsAEnviar.push(inputBox.value);
}
}
if (idsAEnviar.length === 0) {
setMsjError(true);
setTimeout(() => setMsjError(false), 2500);
return;
}
// Almacenar en localStorage
localStorage.setItem("cursos-seleccionados", JSON.stringify(idsAEnviar));
// Ir a sig pantalla
window.location.href = "#/sistemas-movil/";
};
return (
<div>
<TopBar tituloBarra="Selección de Cursos" />
<Card>
<p>Escoge los cursos en los que matricularte</p>
</Card>
<form onSubmit={(ev) => submit(ev)}>
<For each={Object.entries(cursos())}>
{([nombreAnio, infoCurso]) => (
<Card>
<h2>{nombreAnio} año</h2>
<div class={css(s.grid)}>
<For each={infoCurso}>
{(curso) => (
<>
<input
type="checkbox"
value={curso.id_curso}
class={css(s.checkbox)}
/>
<span>{curso.nombre_curso}</span>
</>
)}
</For>
</div>
</Card>
)}
</For>
<div style="text-align: center">
<span style={msjErr() ? "opacity: 1; color: red" : "opacity: 0"}>Selecciona al menos 1 curso</span>
<br />
<Button texto={"Continuar"} />
</div>
</form>
</div>
);
}

View File

@ -1,167 +0,0 @@
import { TopBar } from "./SistemasMovil/TopBar";
import { GrupoDia, Table, TableInput } from "./SistemasMovil/Table";
import { getHorarios, Horario, ListaCursosCompleto } from "../API/CargaHorarios";
import { createSignal } from "solid-js";
import { generarMapaCeldas } from "./SistemasMovil/mapaCeldas";
import { Button } from "../components/Button";
import { gruposSeleccionados, SERVER_PATH } from "../Store";
export function SistemasMovil() {
const [rawData, setRawData] = createSignal<ListaCursosCompleto>([]);
// Obtener cursos seleccionados del servidor
(async() => {
const cursos: Array<string> = JSON.parse(localStorage.getItem("cursos-seleccionados") ?? "[]");
const data = await getHorarios({
cursos: cursos.map((x) => parseInt(x, 10)),
});
setRawData(data);
})();
const matricular = async() => {
const laboratoriosAMatricular = Object.entries(gruposSeleccionados)
.filter((x) => x[1] === true)
.map((x) => x[0]);
const response = await fetch(`${SERVER_PATH}/matricula`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
correo_usuario: localStorage.getItem("correo"),
horarios: laboratoriosAMatricular,
}),
});
if (response.ok) {
window.location.href = "#/ver-matricula/";
} else {
alert("No se pudo procesar la matricula");
}
};
return (
<div>
<TopBar tituloBarra="Mi Horario" />
<Table datos={transformar(rawData())} />
<hr />
<div style="text-align: center;">
<Button texto={"Matricular"} onClick={matricular} />
</div>
</div>
);
}
function transformar(input: ListaCursosCompleto): TableInput {
const data: TableInput = {
lunes: [],
martes: [],
miercoles: [],
jueves: [],
viernes: [],
};
// Organizar por dias
for (const curso of input) {
for (const lab of curso.laboratorios) {
for (const horas of lab.horarios) {
const dia = horas.dia;
const [idx, nroHoras] = infoDiaAOffsets(horas.hora_inicio, horas.hora_fin);
const datos = {
id_horario: horas.id_horario,
id_laboratorio: lab.id_laboratorio,
abreviado: curso.abreviado,
grupo: lab.grupo,
offsetVertical: idx,
nroHoras: nroHoras,
offsetHorizontal: 0,
fraccion: 0,
};
if (dia === "Lunes") {
data.lunes.push(datos);
} else if (dia === "Martes") {
data.martes.push(datos);
} else if (dia === "Miercoles") {
data.miercoles.push(datos);
} else if (dia === "Jueves") {
data.jueves.push(datos);
} else if (dia === "Viernes") {
data.viernes.push(datos);
}
}
}
}
// Procesar cada dia y devolver
return {
lunes: generarMapaCeldas(data.lunes),
martes: generarMapaCeldas(data.martes),
miercoles: generarMapaCeldas(data.miercoles),
jueves: generarMapaCeldas(data.jueves),
viernes: generarMapaCeldas(data.viernes),
};
}
const horasStr = ["0700","0750","0850","0940","1040","1130","1220","1310","1400",
"1450","1550","1640","1740","1830","1920","2010","2100","2150"];
const horas = [
700,
750,
850,
940,
1040,
1130,
1220,
1310,
1400,
1450,
1550,
1640,
1740,
1830,
1920,
2010,
2100,
2150,
];
/**
* Convierte horas en texto a offsets
*/
// Ejm: 0700, 0850 -> 0, 2
function infoDiaAOffsets(horaInicio: string, horaFinal: string): [number, number] {
const inicio = parseInt(horaInicio, 10);
const final = parseInt(horaFinal, 10);
const idxInicio = horas.findIndex((x) => x === inicio);
let nroHoras = 0;
for (let i = idxInicio; i < horas.length; i += 1) {
if (final > horas[i]) {
nroHoras += 1;
} else {
break;
}
}
return [idxInicio, nroHoras];
}
// inicio: 1740 fin 2010 -> 1740,1830,1920
export function infoDiaAListaHoras(horas: Array<Horario>): Array<string> {
const horasFin: Array<string> = [];
for (const grupoHoras of horas) {
const [idx, cantidad] = infoDiaAOffsets(grupoHoras.hora_inicio, grupoHoras.hora_fin);
const strDia = grupoHoras.dia.substring(0, 2);
for (let i = 0; i < cantidad; i += 1) {
horasFin.push(`${strDia}${horasStr[idx + i]}`);
}
}
return horasFin;
}

View File

@ -1,64 +0,0 @@
import { css, StyleSheet } from "aphrodite/no-important";
import { GrupoDia } from "./Table";
import { gruposSeleccionados, setGruposSeleccionados } from "../../Store";
const colores: Array<[string, string]> = [
["#FFEBEE", "#F44336"],
["#F3E5F5", "#9C27B0"],
["#E8EAF6", "#3F51B5"],
["#E1F5FE", "#03A9F4"],
["#E0F2F1", "#009688"],
["#F1F8E9", "#689F38"],
["#FFF9C4", "#FBC02D"],
["#FBE9E7", "#F4511E"],
["#EFEBE9", "#795548"],
];
export function Grupo(props: { data: GrupoDia }) {
const [colorDesactivado, colorActivado] = colores[props.data.id_laboratorio % 9];
const ss = StyleSheet.create({
button: {
display: "inline-block",
padding: "0.2rem 1rem",
textAlign: "left",
borderRadius: "10px",
border: `solid 2px ${colorActivado}`,
position: "absolute",
},
});
const estiloFondo = () => {
if (gruposSeleccionados[props.data.id_laboratorio]) {
return `background-color: ${colorActivado}; color: white; font-weight: 600;`;
} else {
return `background-color: ${colorDesactivado};`;
}
};
const estilo = () => {
const fraccion = props.data.fraccion;
const offsetHorizontal = props.data.offsetHorizontal;
const offsetVertical = props.data.offsetVertical;
const nroHoras = props.data.nroHoras;
return `left: calc((43vw / ${fraccion}) * ${offsetHorizontal}); top: ${offsetVertical * 3}rem;` +
`height: ${nroHoras * 3}rem; width: calc(100% / ${fraccion});`;
};
const handleClick = () => {
setGruposSeleccionados(props.data.id_laboratorio, (x) => !x);
};
return (
<button
type="button"
className={css(ss.button)}
style={`${estilo()}${estiloFondo()}`}
onClick={handleClick}
>
{props.data.abreviado}
<br />
{props.data.grupo}
</button>
);
}

View File

@ -1,100 +0,0 @@
import { StyleSheet, css } from "aphrodite/no-important";
import { createSignal, For } from "solid-js";
import { Swiper, SwiperSlide } from "swiper/solid";
import { horas } from "../../Store";
import "swiper/css";
import { Grupo } from "./Grupo";
const s = StyleSheet.create({
container: {
display: "grid",
gridTemplateColumns: "13vw 1fr",
textAlign: "center",
fontSize: "0.9rem",
},
tableIndex: {
backgroundColor: "rgb(108,67,75)",
color: "white",
padding: "0.5rem 0.25rem",
textAlign: "center",
width: "42vw",
},
columna: {
borderRight: "solid 2px var(--color-borde)",
},
celdaHora: {
position: "relative",
top: "-0.75rem",
height: "3rem",
},
});
export type GrupoDia = {
id_horario: number,
id_laboratorio: number,
abreviado: string,
grupo: string,
offsetVertical: number, // 07:00 -> 0, 07:50 -> 1
nroHoras: number,
offsetHorizontal: number, // 0, 1, 2
fraccion: number, // por cuanto dividir la celda. 1, 2, 3, ...
}
function Dia(props: { dia: string, grupos: Array<GrupoDia> }) {
const ss = StyleSheet.create({
contenedorDia: {
position: "relative",
width: "42vw",
},
});
return (
<div className={css(s.columna)}>
<div className={css(s.tableIndex)}>{props.dia}</div>
<div className={css(ss.contenedorDia)}>
<For each={props.grupos}>
{(grupo) => (
<Grupo data={grupo} />
)}
</For>
</div>
</div>
);
}
export type TableInput = {
lunes: Array<GrupoDia>,
martes: Array<GrupoDia>,
miercoles: Array<GrupoDia>,
jueves: Array<GrupoDia>,
viernes: Array<GrupoDia>,
}
export function Table(props: { datos: TableInput }) {
const lunes = <Dia dia={"Lunes"} grupos={props.datos.lunes} />;
const martes = <Dia dia={"Martes"} grupos={props.datos.martes} />;
const miercoles = <Dia dia={"Miercoles"} grupos={props.datos.miercoles} />;
const jueves = <Dia dia={"Jueves"} grupos={props.datos.jueves} />;
const viernes = <Dia dia={"Viernes"} grupos={props.datos.viernes} />;
return (
<div className={css(s.container)}>
<div className={css(s.columna)}>
<div className={css(s.tableIndex)} style="border: none; background: transparent;">&nbsp;</div>
<For each={horas}>
{(hora) => <div className={css(s.celdaHora)}>{hora.substring(0, 5)}</div>}
</For>
</div>
<Swiper
slidesPerView={2}
style="width: 86vw"
>
<SwiperSlide>{lunes}</SwiperSlide>
<SwiperSlide>{martes}</SwiperSlide>
<SwiperSlide>{miercoles}</SwiperSlide>
<SwiperSlide>{jueves}</SwiperSlide>
<SwiperSlide>{viernes}</SwiperSlide>
</Swiper>
</div>
);
}

View File

@ -1,43 +0,0 @@
import { StyleSheet, css } from "aphrodite/no-important";
const s = StyleSheet.create({
bar: {
backgroundColor: "var(--color-primario)",
color: "white",
height: "3.5rem",
display: "flex",
alignItems: "center",
position: "sticky",
top: 0,
zIndex: 100,
},
icon: {
display: "inline-block",
color: "white",
fontSize: "1.5rem",
verticalAlign: "bottom",
cursor: "pointer",
height: "1.5rem",
padding: "0 0.5rem",
},
barLabel: {
color: "white",
padding: "0 1rem",
fontWeight: 500,
fontSize: "1.25rem",
},
});
export function TopBar(props: {tituloBarra: string}) {
return (
<nav className={css(s.bar)}>
<button>
<i
className={`ph-list ${css(s.icon)}`}
title={"Cambiar imagen de fondo"}
/>
</button>
<p className={css(s.barLabel)}>{props.tituloBarra}</p>
</nav>
);
}

View File

@ -1,89 +0,0 @@
import { GrupoDia } from "./Table";
export class MapaCeldas {
// Almacena referencias a input
private mapa: Map<number, Map<number, null>> = new Map();
private disponible(nroFila: number, nroColumna: number): boolean {
if (!this.mapa.has(nroFila)) return true;
const fila = this.mapa.get(nroFila)!;
return fila.has(nroColumna) === false;
}
private obtenerFilaOCrear(nro: number): Map<number, null> {
if (!this.mapa.has(nro)) {
const m = new Map<number, null>();
this.mapa.set(nro, m);
return m;
}
return this.mapa.get(nro)!;
}
// Devuelve el offset
public solicitar(inicio: number, cantidad: number): number {
const filas = [];
for (let i = 0; i < cantidad; i += 1) filas.push(inicio + i);
for (let offsetActual = 0; offsetActual < 8; offsetActual += 1) {
let todasCeldasDisponibles = true;
for (const fila of filas) {
if (!this.disponible(fila, offsetActual)) {
todasCeldasDisponibles = false;
break;
}
}
if (todasCeldasDisponibles) {
// Crear estas celdas y almacenar
filas.forEach((nroFila) => {
const fila = this.obtenerFilaOCrear(nroFila);
fila.set(offsetActual, null);
});
// Devolver nro de offset
return offsetActual;
}
}
throw new Error("Limite de celdas alcanzado");
}
public generarFraccion(nroFila: number, nroColumna: number, cantidad: number): number {
let fraccionActual = 1;
for (let i = 0; i < cantidad; i += 1) {
const nroFilaActual = nroFila + i;
const filaActual = this.mapa.get(nroFilaActual)!;
const numeroColumnas = filaActual.size;
if (numeroColumnas > fraccionActual) {
fraccionActual = numeroColumnas;
}
}
return fraccionActual;
}
}
export function generarMapaCeldas(entrada: Readonly<Array<GrupoDia>>): Array<GrupoDia> {
const mapa = new MapaCeldas();
const salida: Array<GrupoDia> = [];
// Obtener los offsets de cada curso
for (const input of entrada) {
const offset = mapa.solicitar(input.offsetVertical, input.nroHoras);
salida.push({
...input,
offsetHorizontal: offset,
fraccion: -1,
});
}
// Generar las fracciones de cada curso
for (const output of salida) {
output.fraccion = mapa.generarFraccion(output.offsetVertical, output.offsetHorizontal, output.nroHoras);
}
return salida;
}

View File

@ -1,34 +0,0 @@
import { TopBar } from "./SistemasMovil/TopBar";
import { Card } from "../components/Card";
import { createSignal, For } from "solid-js";
import { getMatricula, InfoMatricula } from "../API/VerMatricula";
import { gruposSeleccionados } from "../Store";
export function VerMatricula() {
const [infoMatriculas, setInfoMatriculas] = createSignal<Array<InfoMatricula>>([]);
(async() => {
const laboratorios = Object.entries(gruposSeleccionados)
.filter((x) => x[1] === true)
.map((x) => parseInt(x[0], 10));
setInfoMatriculas(await getMatricula({matriculas: laboratorios}));
})();
return (
<div>
<TopBar tituloBarra={"Ver Matricula"} />
<Card>
<h2>Tu matrícula</h2>
<For each={infoMatriculas()}>
{(matricula) => (
<div>
<h3>{matricula.nombre_curso}</h3>
<p>Grupo: {matricula.grupo}</p>
<p>Docente: {matricula.docente}</p>
</div>
)}
</For>
</Card>
</div>
);
}

View File

@ -1,135 +0,0 @@
import { css, StyleSheet } from "aphrodite/no-important";
import { estilosGlobales } from "../../Estilos";
import { createSignal, For } from "solid-js";
import { getAllListaCursos, RespuestaListaCursos } from "../../API/ListaCursos";
const e = StyleSheet.create({
contenedorGlobal: {
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
cont: {
width: "30rem",
},
parrafo: {
textAlign: "justify",
lineHeight: "1.4rem",
},
botonAccion: {
width: "30rem",
display: "inline-block",
textAlign: "center",
},
iconoGitHub: {
fontSize: "1.25rem",
verticalAlign: "bottom",
marginRight: "0.5rem",
},
inputCorreo: {
width: "100%",
backgroundColor: "rgba(159,159,159,0.44)",
border: "none",
borderBottom: "solid 2px var(--color-texto)",
padding: "0.5rem 1rem",
boxSizing: "border-box",
marginTop: "1rem",
borderRadius: "5px",
},
checkbox: {
width: "1.25rem",
height: "1.25rem",
margin: "0 0.5rem",
},
grid: {
display: "grid",
gridTemplateColumns: "3rem auto",
gridRowGap: "1rem",
},
});
export function SeleccionCursos() {
const [cursos, setCursos] = createSignal<RespuestaListaCursos>({});
const [msgErr, setMsgError] = createSignal(false);
// Recuperar cursos de back
(async() => setCursos(await getAllListaCursos()))();
const submit = (ev: Event) => {
ev.preventDefault();
const form = ev.target as HTMLFormElement;
// Los checkboxes
const elements = form.elements;
const idsAEnviar: Array<string> = [];
for (let i = 0; i < elements.length; i += 1) {
const inputBox = elements[i] as HTMLInputElement;
if (inputBox.checked) {
idsAEnviar.push(inputBox.value);
}
}
if (idsAEnviar.length === 0) {
setMsgError(true);
setTimeout(() => setMsgError(false), 2500);
return;
}
// Almacenar en localStorage
localStorage.setItem("cursos-seleccionados", JSON.stringify(idsAEnviar));
// Ir a sig pantalla
window.location.href = "#/pc/sistemas/";
};
return (
<div class={css(e.contenedorGlobal)}>
<div class={css(e.cont)}>
<form onSubmit={submit}>
<div class={css(estilosGlobales.contenedor, estilosGlobales.inlineBlock, e.cont)}>
<h1 style={{
"text-align": "center",
"font-size": "1.75rem",
}}
>
Seleccion de cursos
</h1>
<p>Selecciona los cursos en los que matricularte</p>
<For each={Object.entries(cursos())}>
{([nombreAnio, infoCurso]) => (
<>
<h2>{nombreAnio} año</h2>
<div class={css(e.grid)}>
<For each={infoCurso}>
{(curso) => (
<>
<input
type="checkbox"
value={curso.id_curso}
class={css(e.checkbox)}
/>
<span>{curso.nombre_curso}</span>
</>
)}
</For>
</div>
</>
)}
</For>
<br />
<span style={msgErr() ? "opacity: 1; color: red;" : "opacity: 0;"}>
Selecciona al menos un curso
</span>
</div>
<button
type="submit"
class={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}
>
Continuar
</button>
</form>
</div>
</div>
);
}

View File

@ -1,101 +0,0 @@
import { BarraSuperior } from "../../BarraSuperior";
import { ContenedorHorarios } from "./Sistemas/ContenedorHorarios";
import { Creditos } from "../../Creditos";
import { Separador } from "../../Separador";
import { createSignal } from "solid-js";
import { getHorarios, ListaCursosCompleto } from "../../API/CargaHorarios";
import { Cursos, DatosGrupo } from "../../types/DatosHorario";
import { infoDiaAListaHoras } from "../SistemasMovil";
import { StyleSheet, css } from "aphrodite/no-important";
import { estilosGlobales } from "../../Estilos";
import { gruposSeleccionados, SERVER_PATH } from "../../Store";
const s = StyleSheet.create({
botonAccion: {
width: "50%",
display: "inline-block",
textAlign: "center",
backgroundColor: "var(--color-primario)",
},
});
export function Sistemas() {
const [data, setData] = createSignal<Cursos>({});
// Obtener cursos seleccionados del servidor
(async() => {
const cursos: Array<string> = JSON.parse(localStorage.getItem("cursos-seleccionados") ?? "[]");
const data = await getHorarios({
cursos: cursos.map((x) => parseInt(x, 10)),
});
setData(listaCursosADatos(data));
})();
const matricular = async() => {
const laboratoriosAMatricular = Object.entries(gruposSeleccionados)
.filter((x) => x[1] === true)
.map((x) => x[0]);
const response = await fetch(`${SERVER_PATH}/matricula`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
correo_usuario: localStorage.getItem("correo"),
horarios: laboratoriosAMatricular,
}),
});
if (response.ok) {
window.location.href = "#/pc/ver-matricula/";
} else {
alert("No se pudo procesar la matricula");
}
};
return (
<div>
<BarraSuperior />
<Separador />
<Separador />
<ContenedorHorarios datos={data()} />
<Separador />
<div style="text-align: center;">
<button
class={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, s.botonAccion)}
onclick={matricular}
>
Matricular
</button>
</div>
<Creditos />
</div>
);
}
function listaCursosADatos(cursosEntrada: ListaCursosCompleto): Cursos {
const result: Cursos = {};
for (const curso of cursosEntrada) {
const gruposLab: {[grupo: string]: DatosGrupo} = {};
for (const lab of curso.laboratorios) {
gruposLab[lab.grupo] = {
id_laboratorio: lab.id_laboratorio,
Docente: lab.docente,
Horas: infoDiaAListaHoras(lab.horarios),
seleccionado: false,
};
}
result[curso.nombre_curso] = {
nombre: curso.nombre_curso,
abreviado: curso.abreviado,
oculto: false,
Teoria: {},
Laboratorio: gruposLab,
};
}
return result;
}

View File

@ -1,37 +0,0 @@
import YAML from "yaml";
import { css, StyleSheet } from "aphrodite";
import { MiHorario } from "./ContenedorHorarios/MiHorario";
import {
Anios,
Cursos,
DatosHorario,
DatosGrupo,
} from "../../../types/DatosHorario";
import { batch, createEffect, createMemo, createSignal, Show } from "solid-js";
import { useListaCursos } from "./ContenedorHorarios/useListaCursos";
export type EstadoLayout = "MaxPersonal" | "Normal" | "MaxHorarios";
const {
listaCursos: cursosUsuario,
setListaCursos: setCursosUsuarios,
agregarCursoALista: agregarCursoUsuario,
} = useListaCursos();
export function ContenedorHorarios(props: {datos: Cursos}) {
createEffect(async() => {
const d2 = props.datos;
batch(() => {
Object.entries(d2).forEach(([_, curso]) => agregarCursoUsuario(curso));
});
});
return (
<MiHorario
cursos={props.datos}
fnAgregarCurso={agregarCursoUsuario}
setCursosUsuarios={setCursosUsuarios}
/>
);
}

View File

@ -1,78 +0,0 @@
import { estilosGlobales } from "../../../../Estilos";
import { StyleSheet, css } from "aphrodite";
import {Tabla} from "./Tabla";
import { EstadoLayout } from "../ContenedorHorarios";
import { Switch, Match, createMemo } from "solid-js";
import {SetStoreFunction} from "solid-js/store";
import { BotonMaxMin } from "./BotonMaxMin";
import { BotonIcono } from "./BotonIcono";
import { Curso, Cursos, ListaCursosUsuario } from "../../../../types/DatosHorario";
import { CursosElem } from "./CursosElem";
import { TablaObserver } from "./TablaObserver";
interface MiHorarioProps {
cursos: Cursos,
fnAgregarCurso: (c: Curso) => void,
setCursosUsuarios: SetStoreFunction<ListaCursosUsuario>
}
const e = StyleSheet.create({
horario: {},
boton: {
textDecoration: "none",
// paddingRight: "0.5rem",
"::before": {
fontSize: "1rem",
// transform: "translateY(0.2rem)",
textDecoration: "none",
},
},
});
export function MiHorario(props: MiHorarioProps) {
const tablaObserver = new TablaObserver();
const datosMiHorario = createMemo(() => {
const obj: Cursos = {};
Object.entries(props.cursos).forEach(([_, x], i) => {
obj[i] = x;
});
return obj;
});
return (
<div>
<div className={css(
estilosGlobales.inlineBlock,
estilosGlobales.contenedor,
)}
>
Mi horario
</div>
<div className={css(
e.horario,
estilosGlobales.contenedor,
)}
>
<Tabla
data={datosMiHorario()}
anio={"Mi horario"}
version={1}
setCursosUsuarios={props.setCursosUsuarios}
tablaObserver={tablaObserver}
/>
</div>
<CursosElem
version={Math.floor(Math.random() * 1_000_000)}
anioActual={() => "Mi horario"}
dataAnio={datosMiHorario()}
fnAgregarCurso={props.fnAgregarCurso}
esCursoMiHorario
setCursosUsuarios={props.setCursosUsuarios}
tablaObserver={tablaObserver}
/>
</div>
);
}

View File

@ -1,90 +0,0 @@
import { css, StyleSheet } from "aphrodite/no-important";
import { estilosGlobales } from "../../Estilos";
import { createSignal, For } from "solid-js";
import { getMatricula, InfoMatricula } from "../../API/VerMatricula";
import { gruposSeleccionados } from "../../Store";
const e = StyleSheet.create({
contenedorGlobal: {
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
cont: {
width: "30rem",
},
parrafo: {
textAlign: "justify",
lineHeight: "1.4rem",
},
botonAccion: {
width: "30rem",
display: "inline-block",
textAlign: "center",
},
iconoGitHub: {
fontSize: "1.25rem",
verticalAlign: "bottom",
marginRight: "0.5rem",
},
inputCorreo: {
width: "100%",
backgroundColor: "rgba(159,159,159,0.44)",
border: "none",
borderBottom: "solid 2px var(--color-texto)",
padding: "0.5rem 1rem",
boxSizing: "border-box",
marginTop: "1rem",
borderRadius: "5px",
},
checkbox: {
width: "1.25rem",
height: "1.25rem",
margin: "0 0.5rem",
},
grid: {
display: "grid",
gridTemplateColumns: "3rem auto",
gridRowGap: "1rem",
},
});
export function VerMatricula() {
const [infoMatriculas, setInfoMatriculas] = createSignal<Array<InfoMatricula>>([]);
(async() => {
const laboratorios = Object.entries(gruposSeleccionados)
.filter((x) => x[1] === true)
.map((x) => parseInt(x[0], 10));
setInfoMatriculas(await getMatricula({matriculas: laboratorios}));
})();
return (
<div class={css(e.contenedorGlobal)}>
<div class={css(e.cont)}>
<div class={css(estilosGlobales.contenedor, estilosGlobales.inlineBlock, e.cont)}>
<h1 style={{
"text-align": "center",
"font-size": "1.75rem",
}}
>
Matricula realizada
</h1>
<For each={infoMatriculas()}>
{(matricula) => (
<div>
<h3>{matricula.nombre_curso}</h3>
<p>Grupo: {matricula.grupo}</p>
<p>Docente: {matricula.docente}</p>
</div>
)}
</For>
</div>
</div>
</div>
);
}

View File

@ -1,20 +0,0 @@
import { StyleSheet, css } from "aphrodite/no-important";
export function Button(props: {texto: string, onClick?: () => void}) {
const s = StyleSheet.create({
boton: {
backgroundColor: "var(--color-primario)",
color: "white",
padding: "1rem 5rem",
borderRadius: "25px",
margin: "1.5rem 0",
boxShadow: "2px 2px 2px 0 gray",
cursor: "pointer",
},
});
return (
<button type="submit" className={css(s.boton)} onClick={() => props.onClick?.()}>
{props.texto}
</button>
);
}

View File

@ -1,19 +0,0 @@
import { JSX } from "solid-js";
import { StyleSheet, css } from "aphrodite/no-important";
const s = StyleSheet.create({
card: {
padding: "0.5rem",
border: "solid 2px var(--color-borde)",
borderRadius: "10px",
margin: "0.5rem",
},
});
export function Card(props: {children?: JSX.Element}) {
return (
<div className={css(s.card)}>
{props.children}
</div>
);
}

View File

@ -1,7 +1,6 @@
:root {
--color-texto: white;
--color-primario: #531925;
--color-borde: rgba(83, 25, 37, 0.49);
}
body {

View File

@ -27,7 +27,6 @@ export interface DatosHorarioRaw {
}
export interface DatosGrupo {
id_laboratorio: number,
Docente: string,
Horas: string[]
seleccionado: boolean
@ -45,8 +44,9 @@ export interface Curso {
}
}
export type ListaCursosUsuario = {
[key: string]: Curso
export interface ListaCursosUsuario {
sigIndice: number,
cursos: Curso[]
}
export interface Cursos {