Vista de seleccion de cursos
This commit is contained in:
parent
16f2e71430
commit
57a96ce28c
217
API.md
Normal file
217
API.md
Normal file
@ -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<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 GET
|
||||
// Url: /horarios?cursos=...
|
||||
|
||||
// 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,
|
||||
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<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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
5
jest.config.js
Normal file
5
jest.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
};
|
82
src/API/ListaCursos.ts
Normal file
82
src/API/ListaCursos.ts
Normal file
@ -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<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,
|
||||
};
|
||||
};
|
@ -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() {
|
||||
<Match when={route() === "/editor/"}>
|
||||
<Editor />
|
||||
</Match>
|
||||
<Match when={route() === "/seleccion-cursos/"}>
|
||||
<SeleccionCursos />
|
||||
</Match>
|
||||
<Match when={route() === "/sistemas-movil/"}>
|
||||
<SistemasMovil />
|
||||
</Match>
|
||||
|
@ -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");
|
||||
|
@ -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 = <input type="email" placeholder="Correo electronico" className={css(s.entrada)} />;
|
||||
|
||||
const login = () => {
|
||||
console.log((inputElement as HTMLInputElement).value);
|
||||
window.location.href = "#/sistemas-movil/";
|
||||
};
|
||||
|
||||
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 (
|
||||
|
113
src/Views/MobileIndex.tsx
Normal file
113
src/Views/MobileIndex.tsx
Normal file
@ -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<IdLaboratorio>} | 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<IdLaboratorio>};
|
||||
};
|
||||
|
||||
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 electronico" className={css(s.entrada)} />;
|
||||
|
||||
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 (
|
||||
<div className={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" className={css(s.boton)}>Iniciar Sesion</button>
|
||||
</form>
|
||||
<span style={{opacity: msgErrorVisible() ? 1 : 0}}>El correo es invalido</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
79
src/Views/SeleccionCursos.tsx
Normal file
79
src/Views/SeleccionCursos.tsx
Normal file
@ -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<RespuestaListaCursos>({});
|
||||
|
||||
// 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<number> = [];
|
||||
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 (
|
||||
<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 className={css(s.grid)}>
|
||||
<For each={infoCurso}>
|
||||
{(curso) => (
|
||||
<>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={curso.id_curso}
|
||||
className={css(s.checkbox)}
|
||||
/>
|
||||
<span>{curso.nombre_curso}</span>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</For>
|
||||
<div style="text-align: center">
|
||||
<Button texto={"Continuar"} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { Table } from "./SistemasMovil/Table";
|
||||
export function SistemasMovil() {
|
||||
return (
|
||||
<div>
|
||||
<TopBar />
|
||||
<TopBar tituloBarra="Mi Horario" />
|
||||
<Table />
|
||||
</div>
|
||||
);
|
||||
|
@ -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",
|
||||
},
|
||||
|
@ -25,7 +25,7 @@ const s = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
export function TopBar() {
|
||||
export function TopBar(props: {tituloBarra: string}) {
|
||||
return (
|
||||
<nav className={css(s.bar)}>
|
||||
<button>
|
||||
@ -34,7 +34,7 @@ export function TopBar() {
|
||||
title={"Cambiar imagen de fondo"}
|
||||
/>
|
||||
</button>
|
||||
<p className={css(s.barLabel)}>Mi horario</p>
|
||||
<p className={css(s.barLabel)}>{props.tituloBarra}</p>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
20
src/components/Button.tsx
Normal file
20
src/components/Button.tsx
Normal file
@ -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 (
|
||||
<button type="submit" className={css(s.boton)}>
|
||||
{props.texto}
|
||||
</button>
|
||||
);
|
||||
}
|
19
src/components/Card.tsx
Normal file
19
src/components/Card.tsx
Normal file
@ -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 (
|
||||
<div className={css(s.card)}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user