[FE][Certs] UI for certificate presets
This commit is contained in:
parent
a4f46c75cf
commit
5dc64e217e
18
frontend/src/certs/NewRegister/RegisterPresets.tsx
Normal file
18
frontend/src/certs/NewRegister/RegisterPresets.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
import { FilledCard } from "../../components/FilledCard";
|
||||
import { For } from "solid-js";
|
||||
// import { subjects } from "src/views/subjects";
|
||||
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 {
|
||||
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() => {
|
||||
console.log("Submit...");
|
||||
|
||||
// TODO: Send all register requests at once
|
||||
for (const [courseId, date] of props.selections) {
|
||||
const result = await defaultNewRegisterFn(
|
||||
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}) {
|
||||
const courseName = () => {
|
||||
const courses = subjects();
|
||||
return courses.find((c) => c.id === props.courseId)?.nombre ?? "!";
|
||||
const courses = allCourses();
|
||||
return courses.find((course) => course.course_id === props.courseId)?.course_name ?? "!";
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,9 +1,9 @@
|
||||
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 { allCourses } from "../../utils/allCourses";
|
||||
|
||||
export function SearchableSelect(props: {
|
||||
subjects: Array<CursoGIE>,
|
||||
onChange: (id: number | null) => void,
|
||||
count: number
|
||||
}) {
|
||||
@ -68,22 +68,22 @@ export function SearchableSelect(props: {
|
||||
const filteredOptions = () => {
|
||||
const filterText = filter();
|
||||
|
||||
return props.subjects.filter((subject) => {
|
||||
let subjectText = subject.nombre.toLowerCase();
|
||||
subjectText = subjectText.replace("á", "a");
|
||||
subjectText = subjectText.replace("é", "e");
|
||||
subjectText = subjectText.replace("í", "i");
|
||||
subjectText = subjectText.replace("ó", "o");
|
||||
subjectText = subjectText.replace("ú", "u");
|
||||
return allCourses().filter((course) => {
|
||||
let courseText = course.course_name.toLowerCase();
|
||||
courseText = courseText.replace("á", "a");
|
||||
courseText = courseText.replace("é", "e");
|
||||
courseText = courseText.replace("í", "i");
|
||||
courseText = courseText.replace("ó", "o");
|
||||
courseText = courseText.replace("ú", "u");
|
||||
|
||||
return selected() === null && subjectText.indexOf(filterText) !== -1;
|
||||
return selected() === null && courseText.indexOf(filterText) !== -1;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{inputElement}
|
||||
<br/>
|
||||
<br />
|
||||
<div
|
||||
class="border-c-outline border-2 rounded overflow-y-scroll h-[10rem]"
|
||||
>
|
||||
@ -95,11 +95,11 @@ export function SearchableSelect(props: {
|
||||
onclick={(ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
setSelected(s.id);
|
||||
setInputValue(s.nombre);
|
||||
setSelected(s.course_id);
|
||||
setInputValue(s.course_name);
|
||||
}}
|
||||
>
|
||||
{s.nombre}
|
||||
{s.course_name}
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { createSignal, Show } from "solid-js";
|
||||
import { SearchableSelect } from "./SearchableSelect";
|
||||
import { JSX } from "solid-js/jsx-runtime";
|
||||
// import { subjects } from "../subjects";
|
||||
import { FilledCard } from "../../components/FilledCard";
|
||||
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 & {
|
||||
submitter: HTMLElement;
|
||||
@ -30,13 +28,13 @@ export function NewRegister(props: {
|
||||
return (
|
||||
<div class="h-screen overflow-y-scroll">
|
||||
<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} />
|
||||
|
||||
<div class="bg-c-surface p-4 h-[22rem]">
|
||||
<Show when={active() === "Presets"}>
|
||||
<p>Proximamente...</p>
|
||||
<RegisterPresets />
|
||||
</Show>
|
||||
<Show when={active() === "Manual"}>
|
||||
<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 [error, setError] = createSignal("");
|
||||
|
||||
const [selectedSubject, setSelectedSubject] = createSignal<number | null>(null);
|
||||
const [selectedCourseId, seSelectedCourseId] = createSignal<number | null>(null);
|
||||
|
||||
const datePicker = (
|
||||
<input
|
||||
id="create-date"
|
||||
class="bg-c-surface text-c-on-surface border border-c-outline rounded-lg p-2"
|
||||
type="date"
|
||||
/>
|
||||
);
|
||||
let datePicker: HTMLInputElement | undefined;
|
||||
|
||||
const register: HTMLEventFn = async(ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
const subject = selectedSubject();
|
||||
const date = (datePicker as HTMLInputElement).value;
|
||||
const subject = selectedCourseId();
|
||||
|
||||
if (datePicker === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const date = datePicker.value;
|
||||
|
||||
if (subject === null) {
|
||||
setError("Selecciona un curso");
|
||||
@ -123,15 +120,34 @@ function ManualCerts(props: {personId: number | null, onAdd: (v: [number, string
|
||||
<form onsubmit={register}>
|
||||
<div>
|
||||
<SearchableSelect
|
||||
subjects={subjects()}
|
||||
onChange={setSelectedSubject}
|
||||
onChange={seSelectedCourseId}
|
||||
count={count()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="relative my-4">
|
||||
{datePicker}
|
||||
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">Fecha</label>
|
||||
<div class="my-4 grid grid-cols-[9.5rem_auto] gap-2">
|
||||
<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"
|
||||
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>
|
||||
|
||||
<input
|
||||
|
@ -33,6 +33,7 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
||||
});
|
||||
} else {
|
||||
setQrBase64(null);
|
||||
setPerson(null);
|
||||
}
|
||||
});
|
||||
|
||||
@ -44,8 +45,9 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
||||
setError("");
|
||||
|
||||
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();
|
||||
|
||||
if (response.ok) {
|
||||
setPerson(body);
|
||||
props.setPerson(body);
|
||||
|
9
frontend/src/components/Chip.tsx
Normal file
9
frontend/src/components/Chip.tsx
Normal 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>
|
||||
);
|
||||
}
|
8
frontend/src/types/Course.ts
Normal file
8
frontend/src/types/Course.ts
Normal 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,
|
||||
}
|
12
frontend/src/utils/allCourses.ts
Normal file
12
frontend/src/utils/allCourses.ts
Normal 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));
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user