diff --git a/API.md b/API.md new file mode 100644 index 0000000..e5c66cf --- /dev/null +++ b/API.md @@ -0,0 +1,217 @@ +# 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 // 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 GET +// Url: /horarios?cursos=... + +// El frontend envia una lista de cursos, de los cuales recuperar sus datos +{ + cursos: Array // 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, + id_horario: number, + grupo: string, + docente: string, + // Array de objetos de la entidad Horario + horario: [ + { + id_horario: 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 // 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 // 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 +} +``` + + + + + diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..b0b26c2 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", +}; diff --git a/src/API/ListaCursos.ts b/src/API/ListaCursos.ts new file mode 100644 index 0000000..9b154f6 --- /dev/null +++ b/src/API/ListaCursos.ts @@ -0,0 +1,82 @@ +/* +// 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} +type ListaCursosFn = () => Promise + +export const getAllListaCursos: ListaCursosFn = async() => { + const response = await fetch(`${SERVER_PATH}/cursos`); + const data = await response.json() as Array; + + 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 = [ + { + 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 = [ + { + 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, + }; +}; diff --git a/src/App.tsx b/src/App.tsx index 3d88fcc..2631c7e 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ 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"; function App() { const route = useRouter(); @@ -22,6 +23,9 @@ function App() { + + + diff --git a/src/Store.ts b/src/Store.ts index 38eb32f..37caceb 100755 --- a/src/Store.ts +++ b/src/Store.ts @@ -28,6 +28,7 @@ export const horasDescanso = [ "15:40 - 15:50", "17:30 - 17:40", ]; +export const SERVER_PATH = ""; const numImgGuardado = Number(localStorage.getItem("num-img") ?? "0"); const tamanoLetraGuardado = Number(/* localStorage.getItem("tamano-letra") ?? */ "16"); diff --git a/src/Views/Index.tsx b/src/Views/Index.tsx index 3eeeeb8..a4433fb 100644 --- a/src/Views/Index.tsx +++ b/src/Views/Index.tsx @@ -3,6 +3,7 @@ import { StyleSheet, css } from "aphrodite/no-important"; import { RouterLink } from "../Router"; import { Show } from "solid-js"; import { isMobile } from "../Store"; +import { MobileIndex } from "./MobileIndex"; const e = StyleSheet.create({ contenedorGlobal: { @@ -31,46 +32,7 @@ const e = StyleSheet.create({ }, }); -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 inputElement = ; - - const login = () => { - console.log((inputElement as HTMLInputElement).value); - window.location.href = "#/sistemas-movil/"; - }; - - return ( -
-
-

Iniciar sesión

-
-
- {inputElement} -
- -
-
- ); -} export function Index() { return ( diff --git a/src/Views/MobileIndex.tsx b/src/Views/MobileIndex.tsx new file mode 100644 index 0000000..8dc15d7 --- /dev/null +++ b/src/Views/MobileIndex.tsx @@ -0,0 +1,113 @@ +import { css, StyleSheet } from "aphrodite/no-important"; +import { createSignal } from "solid-js"; +import { SERVER_PATH } 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", + }, +}); + +type IdLaboratorio = number; +type LoginData = {correo_usuario: string}; +type LoginResponse = Promise<{matriculas: Array} | null>; +type LoginFunction = (data: LoginData) => LoginResponse; + +// Mock for a login without courses +const mockLoginEmpty: LoginFunction = async(data) => ({matriculas: []}); + +// Mock for a login with courses +const mockLoginNotEmpty: LoginFunction = async(_) => ({ + matriculas: [0, 1, 2, 3], +}); + +// Error login mock +const mockLoginWithError: LoginFunction = async(_) => null; + +// Standard login +const loginFn: LoginFunction = async(data) => { + const petition = await fetch(`${SERVER_PATH}/login`, { + method: "POST", + body: JSON.stringify({ + correo_usuario: data.correo_usuario, + }), + }); + if (!petition.ok) return null; + return await petition.json() as {matriculas: Array}; +}; + +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 = ; + + const login = async(ev: Event) => { + ev.preventDefault(); + const email = (inputElement as HTMLInputElement).value; + const response = await mockLoginEmpty({correo_usuario: email}); + + if (response === null) { + setMsgErrorVisible(true); + setTimeout(() => setMsgErrorVisible(false), 2500); + } else if (response.matriculas.length === 0) { + window.location.href = "#/seleccion-cursos/"; + } else if (response.matriculas.length > 0) { + alert("TODO"); + } + }; + + return ( +
+
+

Iniciar sesión

+
+
+
login(ev)}> + {inputElement} +
+ +
+ El correo es invalido +
+
+ ); +} diff --git a/src/Views/SeleccionCursos.tsx b/src/Views/SeleccionCursos.tsx new file mode 100644 index 0000000..f960712 --- /dev/null +++ b/src/Views/SeleccionCursos.tsx @@ -0,0 +1,79 @@ +import { TopBar } from "./SistemasMovil/TopBar"; +import { StyleSheet, css } from "aphrodite/no-important"; +import { Card } from "../components/Card"; +import { createSignal, For } from "solid-js"; +import { getAllListaCursosMock, 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({}); + + // Recuperar cursos de back + (async() => setCursos(await getAllListaCursosMock()))(); + + const submit = (ev: Event) => { + ev.preventDefault(); + const form = ev.target as HTMLFormElement; + // Los checkboxes + const elements = form.elements; + const idsAEnviar: Array = []; + for (let i = 0; i < elements.length; i += 1) { + const inputBox = elements[i] as HTMLInputElement; + if (inputBox.checked) { + idsAEnviar.push(parseInt(inputBox.value, 10)); + } + } + console.log(idsAEnviar); + }; + + return ( +
+ + + +

Escoge los cursos en los que matricularte

+
+
submit(ev)}> + + {([nombreAnio, infoCurso]) => ( + +

{nombreAnio} año

+
+ + {(curso) => ( + <> + + {curso.nombre_curso} + + )} + +
+
+ )} +
+
+
+
+
+ ); +} + + diff --git a/src/Views/SistemasMovil.tsx b/src/Views/SistemasMovil.tsx index be5cccd..6813f06 100644 --- a/src/Views/SistemasMovil.tsx +++ b/src/Views/SistemasMovil.tsx @@ -4,7 +4,7 @@ import { Table } from "./SistemasMovil/Table"; export function SistemasMovil() { return (
- + ); diff --git a/src/Views/SistemasMovil/Table.tsx b/src/Views/SistemasMovil/Table.tsx index b13bf9f..582bd54 100644 --- a/src/Views/SistemasMovil/Table.tsx +++ b/src/Views/SistemasMovil/Table.tsx @@ -1,11 +1,11 @@ import {StyleSheet, css} from "aphrodite/no-important"; -import { createSignal, For, JSX } from "solid-js"; +import { createSignal, For } from "solid-js"; import { horas } from "../../Store"; const s = StyleSheet.create({ container: { display: "grid", - gridTemplateColumns: "14vw 1fr 1fr", + gridTemplateColumns: "13vw 1fr 1fr", textAlign: "center", fontSize: "0.9rem", }, diff --git a/src/Views/SistemasMovil/TopBar.tsx b/src/Views/SistemasMovil/TopBar.tsx index 3d719a6..531363f 100644 --- a/src/Views/SistemasMovil/TopBar.tsx +++ b/src/Views/SistemasMovil/TopBar.tsx @@ -25,7 +25,7 @@ const s = StyleSheet.create({ }, }); -export function TopBar() { +export function TopBar(props: {tituloBarra: string}) { return ( ); } diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..5bed0e1 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,20 @@ +import { StyleSheet, css } from "aphrodite/no-important"; + +export function Button(props: {texto: string}) { + 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 ( + + ); +} diff --git a/src/components/Card.tsx b/src/components/Card.tsx new file mode 100644 index 0000000..7cc6ebd --- /dev/null +++ b/src/components/Card.tsx @@ -0,0 +1,19 @@ +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 ( +
+ {props.children} +
+ ); +}