From d70d1edbd61206321fb7568a3906299f87676e91 Mon Sep 17 00:00:00 2001 From: Araozu Date: Thu, 3 Aug 2023 17:50:18 -0500 Subject: [PATCH] [Certs] Migrate UI from failed Electron rewrite --- src/views/Certs.tsx | 18 +- src/views/components/FilledCard.tsx | 9 + src/views/components/NewRegister.tsx | 139 +++++++++++++ .../NewRegister/RegisterPreview.tsx | 95 +++++++++ .../NewRegister/SearchableSelect.tsx | 2 +- src/views/components/Registers.tsx | 47 ----- src/views/components/Search.tsx | 190 +++++++++++++++--- src/views/icons/CopyIcon.tsx | 5 + src/views/icons/DocxIcon.tsx | 5 + src/views/icons/DownloadIcon.tsx | 5 + src/views/icons/HomeIcon.tsx | 5 + src/views/icons/KeyIcon.tsx | 5 + src/views/icons/MagnifyingGlassIcon.tsx | 5 + src/views/icons/PaletteIcon.tsx | 5 + src/views/icons/ScanIcon.tsx | 5 + src/views/icons/StackIcon.tsx | 5 + src/views/icons/TrashIcon.tsx | 5 + src/views/icons/XIcon.tsx | 6 + 18 files changed, 464 insertions(+), 92 deletions(-) create mode 100644 src/views/components/FilledCard.tsx create mode 100644 src/views/components/NewRegister/RegisterPreview.tsx create mode 100644 src/views/icons/CopyIcon.tsx create mode 100644 src/views/icons/DocxIcon.tsx create mode 100644 src/views/icons/DownloadIcon.tsx create mode 100644 src/views/icons/HomeIcon.tsx create mode 100644 src/views/icons/KeyIcon.tsx create mode 100644 src/views/icons/MagnifyingGlassIcon.tsx create mode 100644 src/views/icons/PaletteIcon.tsx create mode 100644 src/views/icons/ScanIcon.tsx create mode 100644 src/views/icons/StackIcon.tsx create mode 100644 src/views/icons/TrashIcon.tsx create mode 100644 src/views/icons/XIcon.tsx diff --git a/src/views/Certs.tsx b/src/views/Certs.tsx index a975afb..5bc9be2 100644 --- a/src/views/Certs.tsx +++ b/src/views/Certs.tsx @@ -12,19 +12,13 @@ export function Certs() { onMount(ensureColors); return ( -
-

- Registrar certificado -

+
-
- - setLastUpdate((x) => x + 1)} - /> -
- + setLastUpdate((x) => x + 1)} + /> +
); } diff --git a/src/views/components/FilledCard.tsx b/src/views/components/FilledCard.tsx new file mode 100644 index 0000000..845a2b8 --- /dev/null +++ b/src/views/components/FilledCard.tsx @@ -0,0 +1,9 @@ +import type { JSX } from "solid-js"; + +export function FilledCard(props: {children?: Array | JSX.Element, class?: string}) { + return ( +
+ {props.children} +
+ ); +} diff --git a/src/views/components/NewRegister.tsx b/src/views/components/NewRegister.tsx index 775a96b..c9f463e 100644 --- a/src/views/components/NewRegister.tsx +++ b/src/views/components/NewRegister.tsx @@ -2,6 +2,8 @@ import { createSignal, Show } from "solid-js"; import { SearchableSelect } from "./NewRegister/SearchableSelect"; import { JSX } from "solid-js/jsx-runtime"; import { subjects } from "../subjects"; +import { FilledCard } from "./FilledCard"; +import { RegisterPreview } from "./NewRegister/RegisterPreview"; type HTMLEventFn = JSX.EventHandlerUnion Promise const defaultLabelText = "3. Crear nuevos registros"; +type TabType = "Presets" | "Manual"; + export function NewRegister(props: { personId: number | null, onSuccess: () => void, registerFn?: RegisterFn, labelText?: string, }) { + const [active, setActive] = createSignal("Manual"); + const [selections, setSelections] = createSignal>([]); + + const onRegister = () => { + setSelections([]); + props.onSuccess(); + }; + + return ( +
+ +

{props.labelText ?? defaultLabelText}

+ + + +
+ +

Proximamente...

+
+ + setSelections((x) => [...x, v])} /> + +
+
+ + setSelections((s) => [...s.filter(([id]) => id !== deleteId)])} + onRegister={onRegister} + /> +
+ ); + const [error, setError] = createSignal(""); const [loading, setLoading] = createSignal(false); // Used to update SearchableSelect.tsx manually @@ -138,3 +176,104 @@ export function NewRegister(props: {
); } + + +function RegisterTabs(props: {active: TabType, setActive: (v: TabType) => void}) { + const presetsClasses = () => ((props.active === "Presets") ? "font-bold border-c-primary" : "border-c-transparent"); + const manualClasses = () => ((props.active === "Manual") ? "font-bold border-c-primary" : "border-c-transparent"); + + return ( +
+ + +
+ ); +} + +function ManualCerts(props: {personId: number | null, onAdd: (v: [number, string]) => void}) { + // Used to update SearchableSelect.tsx manually + const [count, setCount] = createSignal(0); + const [error, setError] = createSignal(""); + + const [selectedSubject, setSelectedSubject] = createSignal(null); + + const datePicker = ( + + ); + + const register: HTMLEventFn = async(ev) => { + ev.preventDefault(); + + const subject = selectedSubject(); + const date = (datePicker as HTMLInputElement).value; + + if (subject === null) { + setError("Selecciona un curso"); + + setTimeout(() => setError(""), 5000); + return; + } + if (date === "") { + setError("Selecciona una fecha"); + + setTimeout(() => setError(""), 5000); + return; + } + + props.onAdd([subject, date]); + // This is used to update & refresh the component + setCount((x) => x + 1); + }; + + console.log(`Person ID: ${props.personId}`); + + return ( + <> +
+
+ +
+ +
+ {datePicker} + +
+ + +
+ +

+ {error()}  +

+ + ); +} + + diff --git a/src/views/components/NewRegister/RegisterPreview.tsx b/src/views/components/NewRegister/RegisterPreview.tsx new file mode 100644 index 0000000..1a8a695 --- /dev/null +++ b/src/views/components/NewRegister/RegisterPreview.tsx @@ -0,0 +1,95 @@ + +import { FilledCard } from "../FilledCard"; +import { For } from "solid-js"; +import { XIcon } from "src/views/icons/XIcon"; +import { subjects } from "src/views/subjects"; + +function isoDateToLocalDate(date: string): string { + const [,month, day] = /\d{4}-(\d{2})-(\d{2})/.exec(date) ?? ""; + return `${day}/${month}`; +} + +export function RegisterPreview(props: {selections: Array<[number, string]>, personId: number | null, onDelete: (v: number) => void, onRegister: () => void}) { + const submit = async() => { + console.log("Submit..."); + + for (const [courseId, date] of props.selections) { + const result = await defaultNewRegisterFn( + props.personId ?? -1, + courseId, + date, + ); + + if (result === null) { + console.log("Success"); + } else { + console.log(`error. ${result}`); + } + } + + props.onRegister(); + }; + + return ( + +

Confirmar registro

+
+ + {([courseId, date]) => } + + + +
+
+ ); +} + +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 ?? "!"; + }; + + return ( +
+ {courseName()} + {isoDateToLocalDate(props.date)} + +
+ ); +} + + +async function defaultNewRegisterFn(personId: number, subjectId: number, date: string): Promise { + const response = await fetch("/certificate", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + personId, + subjectId, + date, + }), + }); + + if (response.ok) { + return null; + } else { + const data = await response.json(); + return JSON.stringify(data); + } +} diff --git a/src/views/components/NewRegister/SearchableSelect.tsx b/src/views/components/NewRegister/SearchableSelect.tsx index 9cc7301..c0218e5 100644 --- a/src/views/components/NewRegister/SearchableSelect.tsx +++ b/src/views/components/NewRegister/SearchableSelect.tsx @@ -85,7 +85,7 @@ export function SearchableSelect(props: { {inputElement}
{(s) => ( diff --git a/src/views/components/Registers.tsx b/src/views/components/Registers.tsx index e656c1a..05d3dfd 100644 --- a/src/views/components/Registers.tsx +++ b/src/views/components/Registers.tsx @@ -37,55 +37,8 @@ export function Registers(props: { person: Person | null, lastUpdate: number }) return (
-

2. Revisar registros actuales

-
-
- -
- -
- - NOM AP - - - - AP NOM - - - - NOM - - - AP - - - Ap - - - Am - -
-

void}) { const [error, setError] = createSignal(""); const [warning, setWarning] = createSignal(""); const [qrBase64, setQrBase64] = createSignal(null); + const [persona, setPersona] = createSignal(null); // Update QR createEffect(() => { @@ -53,6 +57,7 @@ export function Search(props: {setPerson: (p: Person | null) => void}) { const response = await fetch(`/person/${dni()}`); const body = await response.json(); if (response.ok) { + setPersona(body); props.setPerson(body); } else if (response.status === 404) { console.error(body); @@ -69,30 +74,55 @@ export function Search(props: {setPerson: (p: Person | null) => void}) { setLoading(false); }; - return ( -

-
-

1. Buscar persona

-
- -
- + const nombresYApellidos = () => { + const p = persona(); + if (p === null) { + return ""; + } + return `${p.nombres} ${p.apellidoPaterno} ${p.apellidoMaterno}`; + }; -
- + const apellidosYNombres = () => { + const p = persona(); + if (p === null) { + return ""; + } + return `${p.apellidoPaterno} ${p.apellidoMaterno} ${p.nombres}`; + }; + + const apellidos = () => { + const p = persona(); + if (p === null) { + return ""; + } + return `${p.apellidoPaterno} ${p.apellidoMaterno}`; + }; + + const nombres = () => { + const p = persona(); + if (p === null) { + return ""; + } + return p.nombres; + }; + + return ( +
+
+
+ +
+
+
+ +

- Error: + Error:
{error()}

@@ -106,13 +136,45 @@ export function Search(props: {setPerson: (p: Person | null) => void}) { {warning()}

+
+ +
+ + + + +
+ + +   + Nombres y Apellidos + + + + +   + Apellidos y Nombres + + +
+ + +   + Apellidos + + + +   + Nombres + +
+
+
+
-
- -
); } @@ -122,12 +184,10 @@ function InputBox(props: { dni: string, setDni: (v: string) => void, }) { - const [successAnimation, setSuccessAnimation] = createSignal(false); - const inputElement = ( setSuccessAnimation(false), 1000); } }; @@ -161,18 +219,86 @@ function InputBox(props: { }; return ( -
+
{inputElement} + - +
); } + + +function MaterialLabel(props: {text: string | null, resource: string}) { + const copyToClipboard: HTMLButtonEvent = (ev) => { + ev.preventDefault(); + + if (props.text !== null) { + navigator.clipboard.writeText(props.text); + } + }; + + return ( +
+ + + {props.text ?? ""} +   + + + +
+ ); +} + +function CopyButton(props: {copyText: string, children: Array | JSX.Element}) { + const [successAnimation, setSuccessAnimation] = createSignal(false); + + const onclick = () => { + if (props.copyText !== "") { + navigator.clipboard.writeText(props.copyText); + setSuccessAnimation(true); + setTimeout(() => setSuccessAnimation(false), 500); + } + }; + + return ( + + ); +} + diff --git a/src/views/icons/CopyIcon.tsx b/src/views/icons/CopyIcon.tsx new file mode 100644 index 0000000..0965d35 --- /dev/null +++ b/src/views/icons/CopyIcon.tsx @@ -0,0 +1,5 @@ +export function CopyIcon(props: {fill: string}) { + return ( + + ); +} diff --git a/src/views/icons/DocxIcon.tsx b/src/views/icons/DocxIcon.tsx new file mode 100644 index 0000000..d8d1818 --- /dev/null +++ b/src/views/icons/DocxIcon.tsx @@ -0,0 +1,5 @@ +export function DocxIcon() { + return ( + + ); +} diff --git a/src/views/icons/DownloadIcon.tsx b/src/views/icons/DownloadIcon.tsx new file mode 100644 index 0000000..51682ed --- /dev/null +++ b/src/views/icons/DownloadIcon.tsx @@ -0,0 +1,5 @@ +export function DownloadIcon(props: {fill: string}) { + return ( + + ); +} diff --git a/src/views/icons/HomeIcon.tsx b/src/views/icons/HomeIcon.tsx new file mode 100644 index 0000000..42ea841 --- /dev/null +++ b/src/views/icons/HomeIcon.tsx @@ -0,0 +1,5 @@ +export function HomeIcon() { + return ( + + ); +} diff --git a/src/views/icons/KeyIcon.tsx b/src/views/icons/KeyIcon.tsx new file mode 100644 index 0000000..ba82e6c --- /dev/null +++ b/src/views/icons/KeyIcon.tsx @@ -0,0 +1,5 @@ +export function KeyIcon() { + return ( + + ); +} diff --git a/src/views/icons/MagnifyingGlassIcon.tsx b/src/views/icons/MagnifyingGlassIcon.tsx new file mode 100644 index 0000000..df3e72b --- /dev/null +++ b/src/views/icons/MagnifyingGlassIcon.tsx @@ -0,0 +1,5 @@ +export function MagnifyingGlassIcon(props: {fill: string}) { + return ( + + ); +} diff --git a/src/views/icons/PaletteIcon.tsx b/src/views/icons/PaletteIcon.tsx new file mode 100644 index 0000000..c491788 --- /dev/null +++ b/src/views/icons/PaletteIcon.tsx @@ -0,0 +1,5 @@ +export function PaletteIcon() { + return ( + + ); +} diff --git a/src/views/icons/ScanIcon.tsx b/src/views/icons/ScanIcon.tsx new file mode 100644 index 0000000..aae8fd1 --- /dev/null +++ b/src/views/icons/ScanIcon.tsx @@ -0,0 +1,5 @@ +export function ScanIcon() { + return ( + + ); +} diff --git a/src/views/icons/StackIcon.tsx b/src/views/icons/StackIcon.tsx new file mode 100644 index 0000000..e32b4be --- /dev/null +++ b/src/views/icons/StackIcon.tsx @@ -0,0 +1,5 @@ +export function StackIcon() { + return ( + + ); +} diff --git a/src/views/icons/TrashIcon.tsx b/src/views/icons/TrashIcon.tsx new file mode 100644 index 0000000..681ab9b --- /dev/null +++ b/src/views/icons/TrashIcon.tsx @@ -0,0 +1,5 @@ +export function TrashIcon(props: {fill: string}) { + return ( + + ); +} diff --git a/src/views/icons/XIcon.tsx b/src/views/icons/XIcon.tsx new file mode 100644 index 0000000..52c716f --- /dev/null +++ b/src/views/icons/XIcon.tsx @@ -0,0 +1,6 @@ +export function XIcon(props: {fill: string}) { + return ( + + ); +} +