[FE] Use resources & context to load courses, instead of global state

This commit is contained in:
Araozu 2023-12-14 17:38:06 -05:00
parent 1a236dc505
commit 16efa06639
7 changed files with 173 additions and 72 deletions

View File

@ -1,53 +1,11 @@
import { Accessor, For, createSignal } from "solid-js"; import { For, createMemo, createSignal } from "solid-js";
import { Chip } from "../../components/Chip"; import { Chip } from "../../components/Chip";
import { getCourseMemo } from "../../utils/allCourses";
import { Course } from "../../types/Course"; import { Course } from "../../types/Course";
import { RegistrationPreview } from "."; import { RegistrationPreview } from ".";
import { useCourses } from "..";
type PresetName = "None" | "2 Matpel" | "3 Matpel" | "4 Escolta" | "MD, 2 Matpel" | "3 4x4" | "2 4x4" | "6 Sibia"; type PresetName = "None" | "2 Matpel" | "3 Matpel" | "4 Escolta" | "MD, 2 Matpel" | "3 4x4" | "2 4x4" | "6 Sibia";
function genPresets(): {[k: string]: Array<Accessor<Course | null>>} {
return {
"2 Matpel": [
getCourseMemo("Matpel 2"),
getCourseMemo("Matpel 1"),
],
"3 Matpel": [
getCourseMemo("Matpel 3"),
getCourseMemo("Matpel 2"),
getCourseMemo("Matpel 1"),
],
"4 Escolta": [
getCourseMemo("Sup. Escolta"),
getCourseMemo("Matpel 3"),
getCourseMemo("Matpel 2"),
getCourseMemo("Matpel 1"),
],
"MD, 2 Matpel": [
getCourseMemo("Matpel 2"),
getCourseMemo("Matpel 1"),
getCourseMemo("Manejo Defensivo"),
],
"3 4x4": [
getCourseMemo("4x4"),
getCourseMemo("Mecanica Basica"),
getCourseMemo("Manejo Defensivo"),
],
"2 4x4": [
getCourseMemo("4x4"),
getCourseMemo("Manejo Defensivo"),
],
"6 Sibia": [
getCourseMemo("Matpel 2"),
getCourseMemo("Matpel 1"),
getCourseMemo("Manejo Defensivo"),
getCourseMemo("Primeros Auxilios"),
getCourseMemo("IPERC"),
getCourseMemo("Lucha contra Incendios"),
],
};
}
export function RegisterPresets(props: { export function RegisterPresets(props: {
disableCreation: boolean, disableCreation: boolean,
onAdd: (v: RegistrationPreview) => void onAdd: (v: RegistrationPreview) => void
@ -56,7 +14,46 @@ export function RegisterPresets(props: {
const [selectedPreset, setSelectedPreset] = createSignal<PresetName>("None"); const [selectedPreset, setSelectedPreset] = createSignal<PresetName>("None");
const [isPreview, setIsPreview] = createSignal(false); const [isPreview, setIsPreview] = createSignal(false);
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
const presets = genPresets(); const courses = useCourses();
const presets = createMemo<{[key: string]: Array<Course>}>(() => {
if (courses === undefined) {
return {};
}
const coursesMap = courses.map();
const matpel1 = coursesMap["Matpel 1"];
const matpel2 = coursesMap["Matpel 2"];
const matpel3 = coursesMap["Matpel 3"];
const escolta = coursesMap["Sup. Escolta"];
const mecanica = coursesMap["Mecanica Basica"];
const manejo = coursesMap["Manejo Defensivo"];
const primerosAuxilios = coursesMap["Primeros Auxilios"];
const iperc = coursesMap.IPERC;
const luchaIncendios = coursesMap["Lucha contra Incendios"];
const cuatroXcuatro = coursesMap["4x4"];
if (matpel1 === undefined || matpel2 === undefined || matpel3 === undefined || escolta === undefined ||
mecanica === undefined || manejo === undefined || primerosAuxilios === undefined || iperc === undefined ||
luchaIncendios === undefined || cuatroXcuatro === undefined
) {
setError("Un curso de los presets no existe. Error fatal.");
return {};
}
const data: {[key: string]: Array<Course>} = {
"2 Matpel": [matpel2, matpel1],
"3 Matpel": [matpel3,matpel2, matpel1],
"4 Escolta": [escolta, matpel3,matpel2, matpel1],
"MD, 2 Matpel": [matpel2, matpel1, manejo],
"3 4x4": [cuatroXcuatro, mecanica, manejo],
"2 4x4": [cuatroXcuatro, manejo],
"6 Sibia": [matpel2, matpel1, manejo, primerosAuxilios, iperc, luchaIncendios],
};
return data;
});
const add = () => { const add = () => {
if (datePicker === undefined) { if (datePicker === undefined) {
@ -76,11 +73,9 @@ export function RegisterPresets(props: {
return; return;
} }
const presetIds = presets[preset]; const presetIds = presets()[preset];
const currentDate = new Date(date); const currentDate = new Date(date);
for (const courseMemo of presetIds) { for (const course of presetIds) {
// Get course and course duration from the memo
const course = courseMemo();
if (course === null) { if (course === null) {
setError(`Un curso no existe. Error fatal. (${preset})`); setError(`Un curso no existe. Error fatal. (${preset})`);
return; return;
@ -112,7 +107,7 @@ export function RegisterPresets(props: {
<p>Las fechas se colocan automáticamente según la duración del curso y su jeraquía.</p> <p>Las fechas se colocan automáticamente según la duración del curso y su jeraquía.</p>
<br /> <br />
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<For each={Object.keys(presets)}> <For each={Object.keys(presets())}>
{(key) => ( {(key) => (
<Chip <Chip
selected={selectedPreset() === key} selected={selectedPreset() === key}

View File

@ -1,12 +1,12 @@
import { FilledCard } from "../../components/FilledCard"; import { FilledCard } from "../../components/FilledCard";
import { For, Show, createSignal } from "solid-js"; import { For, Show, createSignal } from "solid-js";
import { XIcon } from "../../icons/XIcon"; import { XIcon } from "../../icons/XIcon";
import { allCourses } from "../../utils/allCourses";
import { RegisterBatchCreate } from "../../types/Register"; import { RegisterBatchCreate } from "../../types/Register";
import { RegistrationPreview } from "."; import { RegistrationPreview } from ".";
import { loadCustomLabels } from "../../utils/allCustomLabels"; import { loadCustomLabels } from "../../utils/allCustomLabels";
import { FileDashedIcon } from "../../icons/FileDashedIcon"; import { FileDashedIcon } from "../../icons/FileDashedIcon";
import { LoadingIcon } from "../../icons/LoadingIcon"; import { LoadingIcon } from "../../icons/LoadingIcon";
import { useCourses } from "..";
function isoDateToLocalDate(date: string): string { function isoDateToLocalDate(date: string): string {
@ -101,9 +101,18 @@ function Register(props: {
onDelete: (v: number) => void, onDelete: (v: number) => void,
isPreview: boolean, isPreview: boolean,
}) { }) {
const courseName = () => { const courses = useCourses();
const courses = allCourses();
return courses.find((course) => course.course_id === props.courseId)?.course_name ?? `Curso invalido! (${props.courseId})`; const courseName = (): string => {
if (courses === undefined || courses?.data.state !== "ready") {
return "~~Cursos cargando~~";
}
return courses
.data()
.find((course) => course.course_id === props.courseId)
?.course_name ??
`~~Curso invalido! (${props.courseId})~~`;
}; };
return ( return (

View File

@ -1,7 +1,5 @@
import { createEffect, createSignal, For } from "solid-js"; import { createEffect, createSignal, For, onMount } from "solid-js";
// import type {CursoGIE} from "../../../model/CursoGIE/cursoGIE.entity"; import { useCourses } from "..";
import { isServer } from "solid-js/web";
import { allCourses } from "../../utils/allCourses";
export function SearchableSelect(props: { export function SearchableSelect(props: {
onChange: (id: number | null) => void, onChange: (id: number | null) => void,
@ -11,6 +9,8 @@ export function SearchableSelect(props: {
const [selected, setSelected] = createSignal<number | null>(null); const [selected, setSelected] = createSignal<number | null>(null);
const [inputValue, setInputValue] = createSignal(""); const [inputValue, setInputValue] = createSignal("");
const courses = useCourses();
const iHandler = (ev: KeyboardEvent & {currentTarget: HTMLInputElement, target: Element}) => { const iHandler = (ev: KeyboardEvent & {currentTarget: HTMLInputElement, target: Element}) => {
const inputEl = ev.target as HTMLInputElement; const inputEl = ev.target as HTMLInputElement;
// Clear current selection // Clear current selection
@ -58,18 +58,22 @@ export function SearchableSelect(props: {
/> />
); );
if (!isServer) { onMount(() => {
(inputElement as HTMLInputElement).addEventListener("keydown", (ev) => { (inputElement as HTMLInputElement).addEventListener("keydown", (ev) => {
if (ev.code === "Enter") { if (ev.code === "Enter") {
ev.preventDefault(); ev.preventDefault();
} }
}); });
} });
const filteredOptions = () => { const filteredOptions = () => {
const filterText = filter(); const filterText = filter();
return allCourses().filter((course) => { if (courses === undefined || courses?.data.state !== "ready") {
return [];
}
return courses.data().filter((course) => {
let courseText = course.course_name.toLowerCase(); let courseText = course.course_name.toLowerCase();
courseText = courseText.replace("á", "a"); courseText = courseText.replace("á", "a");
courseText = courseText.replace("é", "e"); courseText = courseText.replace("é", "e");

View File

@ -1,21 +1,80 @@
import { createSignal } from "solid-js"; import { JSX, Resource, createContext, createMemo, createResource, createSignal, useContext } from "solid-js";
import { NewRegister } from "./NewRegister"; import { NewRegister } from "./NewRegister";
import { Registers } from "./Registers"; import { Registers } from "./Registers";
import { Search } from "./Search"; import { Search } from "./Search";
import { Person } from "../types/Person"; import { Person } from "../types/Person";
import axios from "axios";
import { Course } from "../types/Course";
export function Certs() { export function Certs() {
const [person, setPerson] = createSignal<Person | null>(null); const [person, setPerson] = createSignal<Person | null>(null);
const [count, setCount] = createSignal(0); const [count, setCount] = createSignal(0);
const [courses] = createResource(fetchAllCourses);
const coursesReady = createMemo(() => courses.state === "ready");
return ( return (
<div class="grid grid-cols-[16rem_25rem_1fr]"> <>
<Search setPerson={setPerson} /> <div class={`top-0 left-0 w-screen h-1 ${coursesReady() ? "hidden" : "fixed"}`}>
<NewRegister <div class='h-1 w-full bg-c-on-primary overflow-hidden'>
personId={person()?.person_id ?? null} <div class='progress w-full h-full bg-c-primary left-right' />
onSuccess={() => setCount((x) => x + 1)} </div>
/> </div>
<Registers person={person()} count={count()} /> <CoursesProvider courses={courses}>
</div> <div class={`grid grid-cols-[16rem_25rem_1fr] ${coursesReady() ? "" : "opacity-25"}`}>
<Search setPerson={setPerson} />
<NewRegister
personId={person()?.person_id ?? null}
onSuccess={() => setCount((x) => x + 1)}
/>
<Registers person={person()} count={count()} />
</div>
</CoursesProvider>
</>
); );
} }
const CoursesContext = createContext<{
data: Resource<Course[]>,
map: () => {[course_name: string]: Course},
}>();
function CoursesProvider(props: {courses: Resource<Course[]>, children: JSX.Element}) {
const courseMapMemo = createMemo(() => {
if (props.courses === undefined || props.courses?.state !== "ready") {
return {};
}
const data = props.courses();
type CourseMap = {[k: string]: Course};
const map: CourseMap = {};
for (const course of data) {
map[course.course_name] = course;
}
return map;
});
const coursesData = {
data: props.courses,
map: courseMapMemo,
};
return (
<CoursesContext.Provider value={coursesData}>
{props.children}
</CoursesContext.Provider>
);
}
async function fetchAllCourses(): Promise<Array<Course>> {
const result = await axios.get<Array<Course>>(`${import.meta.env.VITE_BACKEND_URL}/api/course`);
return result.data;
}
export function useCourses() {
return useContext(CoursesContext);
}

View File

@ -29,3 +29,23 @@ body {
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
.progress {
animation: progress 1s infinite linear;
}
.left-right {
transform-origin: 0% 50%;
}
@keyframes progress {
0% {
transform: translateX(0) scaleX(0);
}
40% {
transform: translateX(0) scaleX(0.4);
}
100% {
transform: translateX(100%) scaleX(0.5);
}
}

View File

@ -3,7 +3,7 @@ import { Course } from "../types/Course";
type CourseMap = {[k: number]: Course}; type CourseMap = {[k: number]: Course};
export const [allCourses, setAllCourses] = createSignal<Array<Course>>([]); const [allCourses, setAllCourses] = createSignal<Array<Course>>([]);
export const [courseMap, setCourseMap] = createSignal<CourseMap>({}); export const [courseMap, setCourseMap] = createSignal<CourseMap>({});
(() => { (() => {

View File

@ -8,6 +8,20 @@ module.exports = {
fontFamily: { fontFamily: {
"mono": ["Inconsolata", "monospace"], "mono": ["Inconsolata", "monospace"],
}, },
animation: {
progress: "progress 1s infinite linear",
},
keyframes: {
progress: {
"0%": { transform: " translateX(0) scaleX(0)" },
"40%": { transform: "translateX(0) scaleX(0.4)" },
"100%": { transform: "translateX(100%) scaleX(0.5)" },
},
},
transformOrigin: {
"left-right": "0% 50%",
},
}, },
colors: { colors: {
"c-primary": "var(--c-primary)", "c-primary": "var(--c-primary)",