[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 { 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 (
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
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