[FE][Certs] UI for certificate presets

master
Araozu 2023-08-25 17:54:30 -05:00
parent a4f46c75cf
commit 5dc64e217e
9 changed files with 104 additions and 40 deletions

View File

@ -0,0 +1,18 @@
import { Chip } from "../../components/Chip";
export function RegisterPresets() {
return (
<div class="h-52">
<p>Las fechas se colocan automáticamente según la duración del curso.</p>
<br />
<div class="flex flex-wrap gap-2">
<Chip text="2 Matpel" />
<Chip text="3 Matpel" />
<Chip text="4 Escolta" />
<Chip text="MD, 2 Matpel" />
<Chip text="3 4x4" />
<Chip text="2 4x4" />
</div>
</div>
);
}

View File

@ -1,10 +1,8 @@
import { FilledCard } from "../../components/FilledCard"; import { FilledCard } from "../../components/FilledCard";
import { For } from "solid-js"; import { For } from "solid-js";
// import { subjects } from "src/views/subjects";
import { XIcon } from "../../icons/XIcon"; import { XIcon } from "../../icons/XIcon";
import { allCourses } from "../../utils/allCourses";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const subjects: () => Array<any> = () => [];
function isoDateToLocalDate(date: string): string { function isoDateToLocalDate(date: string): string {
const [,month, day] = /\d{4}-(\d{2})-(\d{2})/.exec(date) ?? ""; const [,month, day] = /\d{4}-(\d{2})-(\d{2})/.exec(date) ?? "";
@ -15,6 +13,7 @@ export function RegisterPreview(props: {selections: Array<[number, string]>, per
const submit = async() => { const submit = async() => {
console.log("Submit..."); console.log("Submit...");
// TODO: Send all register requests at once
for (const [courseId, date] of props.selections) { for (const [courseId, date] of props.selections) {
const result = await defaultNewRegisterFn( const result = await defaultNewRegisterFn(
props.personId ?? -1, props.personId ?? -1,
@ -56,8 +55,8 @@ export function RegisterPreview(props: {selections: Array<[number, string]>, per
function Register(props: {courseId: number, date: string, onDelete: (v: number) => void}) { function Register(props: {courseId: number, date: string, onDelete: (v: number) => void}) {
const courseName = () => { const courseName = () => {
const courses = subjects(); const courses = allCourses();
return courses.find((c) => c.id === props.courseId)?.nombre ?? "!"; return courses.find((course) => course.course_id === props.courseId)?.course_name ?? "!";
}; };
return ( return (

View File

@ -1,9 +1,9 @@
import { createEffect, createSignal, For } from "solid-js"; import { createEffect, createSignal, For } from "solid-js";
import type {CursoGIE} from "../../../model/CursoGIE/cursoGIE.entity"; // import type {CursoGIE} from "../../../model/CursoGIE/cursoGIE.entity";
import { isServer } from "solid-js/web"; import { isServer } from "solid-js/web";
import { allCourses } from "../../utils/allCourses";
export function SearchableSelect(props: { export function SearchableSelect(props: {
subjects: Array<CursoGIE>,
onChange: (id: number | null) => void, onChange: (id: number | null) => void,
count: number count: number
}) { }) {
@ -68,22 +68,22 @@ export function SearchableSelect(props: {
const filteredOptions = () => { const filteredOptions = () => {
const filterText = filter(); const filterText = filter();
return props.subjects.filter((subject) => { return allCourses().filter((course) => {
let subjectText = subject.nombre.toLowerCase(); let courseText = course.course_name.toLowerCase();
subjectText = subjectText.replace("á", "a"); courseText = courseText.replace("á", "a");
subjectText = subjectText.replace("é", "e"); courseText = courseText.replace("é", "e");
subjectText = subjectText.replace("í", "i"); courseText = courseText.replace("í", "i");
subjectText = subjectText.replace("ó", "o"); courseText = courseText.replace("ó", "o");
subjectText = subjectText.replace("ú", "u"); courseText = courseText.replace("ú", "u");
return selected() === null && subjectText.indexOf(filterText) !== -1; return selected() === null && courseText.indexOf(filterText) !== -1;
}); });
}; };
return ( return (
<> <>
{inputElement} {inputElement}
<br/> <br />
<div <div
class="border-c-outline border-2 rounded overflow-y-scroll h-[10rem]" class="border-c-outline border-2 rounded overflow-y-scroll h-[10rem]"
> >
@ -95,11 +95,11 @@ export function SearchableSelect(props: {
onclick={(ev) => { onclick={(ev) => {
ev.preventDefault(); ev.preventDefault();
setSelected(s.id); setSelected(s.course_id);
setInputValue(s.nombre); setInputValue(s.course_name);
}} }}
> >
{s.nombre} {s.course_name}
</button> </button>
)} )}
</For> </For>

View File

@ -1,12 +1,10 @@
import { createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
import { SearchableSelect } from "./SearchableSelect"; import { SearchableSelect } from "./SearchableSelect";
import { JSX } from "solid-js/jsx-runtime"; import { JSX } from "solid-js/jsx-runtime";
// import { subjects } from "../subjects";
import { FilledCard } from "../../components/FilledCard"; import { FilledCard } from "../../components/FilledCard";
import { RegisterPreview } from "./RegisterPreview"; import { RegisterPreview } from "./RegisterPreview";
import { RegisterPresets } from "./RegisterPresets";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const subjects: () => Array<any> = () => [];
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & { type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
submitter: HTMLElement; submitter: HTMLElement;
@ -30,13 +28,13 @@ export function NewRegister(props: {
return ( return (
<div class="h-screen overflow-y-scroll"> <div class="h-screen overflow-y-scroll">
<FilledCard class="border border-c-outline overflow-hidden"> <FilledCard class="border border-c-outline overflow-hidden">
<h2 class="p-4 font-bold text-xl">Registrar certs</h2> <h2 class="p-3 font-bold text-xl">Agregar certs</h2>
<RegisterTabs active={active()} setActive={setActive} /> <RegisterTabs active={active()} setActive={setActive} />
<div class="bg-c-surface p-4 h-[22rem]"> <div class="bg-c-surface p-4 h-[22rem]">
<Show when={active() === "Presets"}> <Show when={active() === "Presets"}>
<p>Proximamente...</p> <RegisterPresets />
</Show> </Show>
<Show when={active() === "Manual"}> <Show when={active() === "Manual"}>
<ManualCerts personId={props.personId} onAdd={(v) => setSelections((x) => [...x, v])} /> <ManualCerts personId={props.personId} onAdd={(v) => setSelections((x) => [...x, v])} />
@ -82,21 +80,20 @@ function ManualCerts(props: {personId: number | null, onAdd: (v: [number, string
const [count, setCount] = createSignal(0); const [count, setCount] = createSignal(0);
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
const [selectedSubject, setSelectedSubject] = createSignal<number | null>(null); const [selectedCourseId, seSelectedCourseId] = createSignal<number | null>(null);
const datePicker = ( let datePicker: HTMLInputElement | undefined;
<input
id="create-date"
class="bg-c-surface text-c-on-surface border border-c-outline rounded-lg p-2"
type="date"
/>
);
const register: HTMLEventFn = async(ev) => { const register: HTMLEventFn = async(ev) => {
ev.preventDefault(); ev.preventDefault();
const subject = selectedSubject(); const subject = selectedCourseId();
const date = (datePicker as HTMLInputElement).value;
if (datePicker === undefined) {
return;
}
const date = datePicker.value;
if (subject === null) { if (subject === null) {
setError("Selecciona un curso"); setError("Selecciona un curso");
@ -123,15 +120,34 @@ function ManualCerts(props: {personId: number | null, onAdd: (v: [number, string
<form onsubmit={register}> <form onsubmit={register}>
<div> <div>
<SearchableSelect <SearchableSelect
subjects={subjects()} onChange={seSelectedCourseId}
onChange={setSelectedSubject}
count={count()} count={count()}
/> />
</div> </div>
<div class="relative my-4"> <div class="my-4 grid grid-cols-[9.5rem_auto] gap-2">
{datePicker} <div class="relative">
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">Fecha</label> <input
ref={datePicker}
id="create-date"
class="bg-c-surface text-c-on-surface border border-c-outline rounded-lg p-2 font-mono"
type="date"
/>
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">Fecha</label>
</div>
<div class="relative">
<input
ref={datePicker}
id="create-date"
class="bg-c-surface text-c-on-surface border border-c-outline rounded-lg p-2 font-mono w-full
disabled:opacity-50 disabled:cursor-not-allowed"
type="text"
disabled
/>
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">
Denominación
</label>
</div>
</div> </div>
<input <input

View File

@ -33,6 +33,7 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
}); });
} else { } else {
setQrBase64(null); setQrBase64(null);
setPerson(null);
} }
}); });
@ -44,8 +45,9 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
setError(""); setError("");
try { try {
const response = await fetch(`/api/person/${dni()}`); const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/person/${dni()}`);
const body = await response.json(); const body = await response.json();
if (response.ok) { if (response.ok) {
setPerson(body); setPerson(body);
props.setPerson(body); props.setPerson(body);

View File

@ -0,0 +1,9 @@
export function Chip(props: {text: string}) {
return (
<div class="inline-block px-3 py-1 border border-c-outline rounded-md text-sm cursor-pointer
hover:bg-c-surface-variant hover:text-c-on-surface-variant transition-colors"
>
{props.text}
</div>
);
}

View File

@ -0,0 +1,8 @@
export type Course = {
course_id: number,
course_code: number,
course_name: string,
course_display_name: string,
course_days_amount: number,
course_has_custom_label: boolean,
}

View File

@ -0,0 +1,12 @@
import { createSignal } from "solid-js";
import { Course } from "../types/Course";
export const [allCourses, setAllCourses] = createSignal<Array<Course>>([]);
(() => {
// Get all courses from the API
fetch(`${import.meta.env.VITE_BACKEND_URL}/api/course`)
.then((res) => res.json())
.then((data) => setAllCourses(data));
})();