Reestructurar codigo de vista de pc

master
Araozu 2022-10-14 17:20:21 -05:00
parent 314a57d01a
commit 678f266946
19 changed files with 479 additions and 340 deletions

33
src/API/Login.ts Normal file
View File

@ -0,0 +1,33 @@
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: {
"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,4 +1,4 @@
import { Main } from "./Views/Main";
import { Sistemas } from "./Views/pc/Sistemas";
import { Index } from "./Views/Index";
import { Editor } from "./Views/Editor";
import { useRouter } from "./Router";
@ -7,6 +7,7 @@ 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";
function App() {
const route = useRouter();
@ -33,8 +34,12 @@ function App() {
<Match when={route() === "/ver-matricula/"}>
<VerMatricula />
</Match>
<Match when={route() === "/sistemas/"}>
<Main />
<Match when={route() === "/pc/seleccion-cursos/"}>
<SeleccionCursosPC />
</Match>
<Match when={route() === "/pc/sistemas/"}>
<Sistemas />
</Match>
</Switch>
</div>

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 "../ContenedorHorarios/Tabla";
import { TablaObserver } from "../ContenedorHorarios/TablaObserver";
import { Tabla } from "./pc/Sistemas/ContenedorHorarios/Tabla";
import { TablaObserver } from "./pc/Sistemas/ContenedorHorarios/TablaObserver";
import { Curso, Cursos } from "../types/DatosHorario";
import { For, createMemo } from "solid-js";
import {createStore} from "solid-js/store";

View File

@ -1,9 +1,10 @@
import { estilosGlobales } from "../Estilos";
import { StyleSheet, css } from "aphrodite/no-important";
import { RouterLink } from "../Router";
import { Show } from "solid-js";
import { isMobile } from "../Store";
import { batch, createSignal, Show } from "solid-js";
import { isMobile, setGruposSeleccionados } from "../Store";
import { MobileIndex } from "./MobileIndex";
import { mockLoginEmpty, mockLoginNotEmpty, mockLoginWithError } from "../API/Login";
const e = StyleSheet.create({
contenedorGlobal: {
@ -30,11 +31,45 @@ 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",
},
});
export function Index() {
const [msgErrorVisible, setMsgErrorVisible] = createSignal(false);
const inputElement = <input className={css(e.inputCorreo)} type="email" required placeholder="correo@unsa.edu.pe" />;
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) {
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/";
}
};
return (
<>
<Show when={!isMobile()}>
@ -49,36 +84,19 @@ export function Index() {
Horarios UNSA
</h1>
<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).
Inicia sesión con tu correo institucional.
<br />
{inputElement}
</p>
<span style={msgErrorVisible() ? "opacity: 1; color: red;" : "opacity: 0;"}>
El correo es invalido
</span>
</div>
<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 onClick={login} className={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}>
Iniciar sesion
</button>
*/}
<RouterLink
to={"/editor/"}
className={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}
>
<i className={`${css(e.iconoGitHub)} ph-pencil`} />
Editor
</RouterLink>
<br />
<br />
<a
className={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}
href="https://github.com/Araozu/horarios-unsa-2/"

View File

@ -1,49 +0,0 @@
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,6 +1,7 @@
import { css, StyleSheet } from "aphrodite/no-important";
import { batch, createSignal } from "solid-js";
import { SERVER_PATH, setGruposSeleccionados } from "../Store";
import { mockLoginEmpty } from "../API/Login";
const e = StyleSheet.create({
contenedorGlobal: {
@ -29,34 +30,6 @@ const e = StyleSheet.create({
},
});
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: {
@ -78,7 +51,7 @@ export function MobileIndex() {
});
const [msgErrorVisible, setMsgErrorVisible] = createSignal(false);
const inputElement = <input required type="email" placeholder="Correo electronico" className={css(s.entrada)} />;
const inputElement = <input required type="email" placeholder="correo@unsa.edu.pe" className={css(s.entrada)} />;
const login = async(ev: Event) => {
ev.preventDefault();
@ -89,13 +62,16 @@ export function MobileIndex() {
setMsgErrorVisible(true);
setTimeout(() => setMsgErrorVisible(false), 2500);
} else if (response.matriculas.length === 0) {
localStorage.setItem("correo", email);
window.location.href = "#/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 = "#/ver-matricula/";
}
};

View File

@ -0,0 +1,135 @@
import { css, StyleSheet } from "aphrodite/no-important";
import { estilosGlobales } from "../../Estilos";
import { createSignal, For } from "solid-js";
import { getAllListaCursosMock, 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 getAllListaCursosMock()))();
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 className={css(e.contenedorGlobal)}>
<div className={css(e.cont)}>
<form onSubmit={submit}>
<div className={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 className={css(e.grid)}>
<For each={infoCurso}>
{(curso) => (
<>
<input
type="checkbox"
value={curso.id_curso}
className={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"
className={css(estilosGlobales.contenedor, estilosGlobales.contenedorCursor, e.botonAccion)}
>
Iniciar sesion
</button >
</form>
</div>
</div>
);
}

18
src/Views/pc/Sistemas.tsx Normal file
View File

@ -0,0 +1,18 @@
import { BarraSuperior } from "../../BarraSuperior";
import { ContenedorHorarios } from "./Sistemas/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 Sistemas() {
return (
<div>
<BarraSuperior />
<Separador />
<ContenedorHorarios />
<Creditos />
</div>
);
}

View File

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

@ -1,9 +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 { 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: {
@ -33,14 +33,14 @@ 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,
@ -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,31 +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,
() => {
setCursosUsuarios("cursos", Number(indiceCurso), "Teoria", produce<{ [p: string]: DatosGrupo }>((x) => {
const grupoActualSeleccionado = x[grupo].seleccionado
const grupoActualSeleccionado = x[grupo].seleccionado;
if (grupoActualSeleccionado) {
x[grupo].seleccionado = false
x[grupo].seleccionado = false;
} else {
for (const xKey in x) {
x[xKey].seleccionado = xKey === grupo
x[xKey].seleccionado = xKey === grupo;
}
}
}))
}));
},
])
}
return profesores
]);
}
return profesores;
};
function CursoE(
indiceCurso: string,
@ -117,7 +117,7 @@ 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,
@ -126,33 +126,33 @@ function CursoE(
equals:
(x, y) => x === y,
},
)
);
const tituloMemo = createMemo(() => (cursoAgregadoMemo()
? "Remover de mi horario"
: "Agregar a mi horario"))
: "Agregar a mi horario"));
const claseMemo = createMemo(() => {
if (props.esCursoMiHorario && datosCurso.oculto) {
return claseCursoOculto
return claseCursoOculto;
}
return cursoAgregadoMemo()
? claseCursoAgregado
: claseCursoNoAgregado
})
: 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"}}>
@ -172,7 +172,7 @@ function CursoE(
}
</For>
</td>
)
);
return (
<div className={claseMemo()}>
@ -204,17 +204,17 @@ function CursoE(
{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 (
<>
@ -222,5 +222,5 @@ export function CursosElem(props: Props) {
{([indiceCurso, datosCurso]) => CursoE(indiceCurso, datosCurso, anio, claseCursoAgregado, props)}
</For>
</>
)
);
}

View File

@ -1,14 +1,14 @@
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"
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,
@ -23,59 +23,59 @@ const {
setListaCursos,
agregarCursoALista,
eliminarCursosDeLista,
} = useListaCursos()
} = useListaCursos();
export function Horarios(props: HorariosProps) {
const [anioActual, setAnioActual] = createSignal("1er año")
const [anioActual, setAnioActual] = createSignal("1er año");
const tablaObserver = new TablaObserver()
const tablaObserver = new TablaObserver();
const elAnios = (
<For each={Object.entries(props.data.años)}>
{([nombre]) => {
const clases = createMemo(() => {
const vAnio = anioActual()
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 = {}
const anio = anioActual();
const obj: Cursos = {};
untrack(() => {
const cursos = props.data.años[anio]
const cursos = props.data.años[anio];
batch(() => {
eliminarCursosDeLista()
eliminarCursosDeLista();
let i = 0
let i = 0;
for (const [, curso] of Object.entries(cursos)) {
// El curso devuelto por esta fun. es reactivo
obj[i] = agregarCursoALista(curso)
i += 1
obj[i] = agregarCursoALista(curso);
i += 1;
}
})
})
});
});
return obj
})
return obj;
});
const fnMaximizar = () => props.setEstadoLayout("MaxHorarios")
const fnMinimizar = () => props.setEstadoLayout("Normal")
const estadoActualLayout = () => props.estadoLayout
const fnMaximizar = () => props.setEstadoLayout("MaxHorarios");
const fnMinimizar = () => props.setEstadoLayout("Normal");
const estadoActualLayout = () => props.estadoLayout;
return (
<div>
@ -135,5 +135,5 @@ export function Horarios(props: HorariosProps) {
</Switch>
</div>
)
);
}

View File

@ -1,14 +1,14 @@
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"
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,
@ -29,22 +29,22 @@ const e = StyleSheet.create({
textDecoration: "none",
},
},
})
});
export function MiHorario(props: MiHorarioProps) {
const tablaObserver = new TablaObserver()
const tablaObserver = new TablaObserver();
const datosMiHorario = createMemo(() => {
const obj: Cursos = {}
const obj: Cursos = {};
props.cursosUsuario.cursos.forEach((x, i) => {
obj[i] = x
})
return obj
})
obj[i] = x;
});
return obj;
});
const fnMaximizar = () => props.setEstadoLayout("MaxPersonal")
const fnMinimizar = () => props.setEstadoLayout("Normal")
const estadoActualLayout = () => props.estadoLayout
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.
@ -142,5 +142,5 @@ export function MiHorario(props: MiHorarioProps) {
</Match>
</Switch>
</div>
)
);
}

View File

@ -1,9 +1,9 @@
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 {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";

View File

@ -1,8 +1,8 @@
import { StyleSheet, css } from "aphrodite";
import { estilosGlobales } from "../../Estilos";
import { estilosGlobales } from "../../../../../Estilos";
import { For, createSignal, createMemo, createEffect, onCleanup } from "solid-js";
import { Dia } from "../../Store";
import { DatosGrupo } from "../../types/DatosHorario";
import { Dia } from "../../../../../Store";
import { DatosGrupo } from "../../../../../types/DatosHorario";
import {TablaObserver} from "../TablaObserver";
const e = StyleSheet.create({
@ -179,7 +179,9 @@ function RenderFila(datos: DatosProps, props: Props) {
return `${css(...clases)} ${adicional}`;
},
undefined,
(x, y) => x === y,
{
equals: (x, y) => x === y,
},
);
return (

View File

@ -1,10 +1,10 @@
import { StyleSheet, css } from "aphrodite";
import { estilosGlobales } from "../../Estilos";
import { estilosGlobales } from "../../../../../Estilos";
import { For, createMemo } from "solid-js";
import {createStore, Store} from "solid-js/store";
import { Dia, dias } from "../../Store";
import { Dia, dias } from "../../../../../Store";
import { CeldaFila } from "./CeldaFila";
import { DataProcesada } from "../../types/DatosHorario";
import { DataProcesada } from "../../../../../types/DatosHorario";
import { coloresBorde, diaANum } from "../Tabla";
import { TablaObserver } from "../TablaObserver";

View File

@ -1,6 +1,6 @@
import { createMemo, createEffect, untrack } from "solid-js"
import {createStore, SetStoreFunction, Store, produce} from "solid-js/store"
import { DatosGrupo } from "../types/DatosHorario"
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,
@ -8,7 +8,7 @@ const createMemoDefault = <T>(f: () => T) => createMemo<T>(
{
equals: (x, y) => x === y,
},
)
);
/**
* - Normal
@ -62,13 +62,13 @@ 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;
}
/**
@ -86,83 +86,83 @@ export class TablaObserver {
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>(() => {
const gruposSeleccionados = this.seleccionado[anio][curso][esLab ? "Laboratorio" : "Teoria"]
const gruposSeleccionados = this.seleccionado[anio][curso][esLab ? "Laboratorio" : "Teoria"];
if (gruposSeleccionados.length > 0) {
return gruposSeleccionados.find((x) => x === grupo) ? "Seleccionado" : "Oculto"
return gruposSeleccionados.find((x) => x === grupo) ? "Seleccionado" : "Oculto";
} else {
return "Normal"
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 _;
}
}
})
});
}
/**
@ -172,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(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;
}
/**
@ -186,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() {
@ -212,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>,
@ -12,30 +12,30 @@ export const useListaCursos = (): ReturnType => {
const [listaCursos, setListaCursos] = createStore<ListaCursosUsuario>({
sigIndice: 0,
cursos: [],
})
});
const agregarCursoALista = (curso: Curso): Curso => {
// Si el horario ya se habia agregado, ocultarlo
const cursoActualIndex = listaCursos.cursos.findIndex((x) => x.nombre === 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]
setListaCursos("cursos", cursoActualIndex, "oculto", (x) => !x);
return listaCursos.cursos[cursoActualIndex];
} else {
setListaCursos("cursos", listaCursos.sigIndice, curso)
setListaCursos("sigIndice", (x) => x + 1)
return listaCursos.cursos[listaCursos.sigIndice - 1]
}
setListaCursos("cursos", listaCursos.sigIndice, curso);
setListaCursos("sigIndice", (x) => x + 1);
return listaCursos.cursos[listaCursos.sigIndice - 1];
}
};
const eliminarCursosDeLista = () => {
setListaCursos("cursos", [])
setListaCursos("sigIndice", 0)
}
setListaCursos("cursos", []);
setListaCursos("sigIndice", 0);
};
return {
listaCursos,
setListaCursos,
agregarCursoALista,
eliminarCursosDeLista,
}
}
};
};