eeg_certs/frontend/src/certs/Search.tsx

287 lines
9.5 KiB
TypeScript
Raw Normal View History

2023-08-25 15:34:33 +00:00
import { Show, createEffect, createSignal } from "solid-js";
import { JSX } from "solid-js/jsx-runtime";
import QR from "qrcode";
import { CopyIcon } from "../icons/CopyIcon";
import { XIcon } from "../icons/XIcon";
2023-08-25 15:34:33 +00:00
import { Person } from "../types/Person";
type HTMLButtonEvent = JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>;
/*
Form that retrieves a user from the DB given an ID
*/
export function Search(props: {setPerson: (p: Person | null) => void}) {
const [dni, setDni] = createSignal("");
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal("");
const [qrBase64, setQrBase64] = createSignal<string | null>(null);
2023-08-25 15:34:33 +00:00
const [person, setPerson] = createSignal<Person | null>(null);
2023-08-25 15:34:33 +00:00
// Update QR and automatically search when DNI is changed
createEffect(() => {
2023-08-25 15:34:33 +00:00
const dniT = dni();
if (dniT.length >= 8) {
search();
QR.toDataURL(`https://eegsac.com/certificado/${dniT}`, {margin: 1}, (err, res) => {
if (err) {
console.error("Error creating QR code");
return;
}
setQrBase64(res);
});
2023-08-25 15:34:33 +00:00
} else {
setQrBase64(null);
2023-08-25 22:54:30 +00:00
setPerson(null);
}
});
/*
Get the user data from the DB
*/
const search = async() => {
setLoading(true);
setError("");
try {
2023-08-25 22:54:30 +00:00
const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/person/${dni()}`);
const body = await response.json();
2023-08-25 22:54:30 +00:00
if (response.ok) {
2023-08-25 15:34:33 +00:00
setPerson(body);
props.setPerson(body);
} else if (response.status === 404) {
console.error(body);
2023-08-25 15:34:33 +00:00
setError("No encontrado. Ingresar datos manualmente.");
props.setPerson(null);
} else {
setError(body);
}
} catch (e) {
setError(JSON.stringify(e));
}
setLoading(false);
};
2023-08-25 15:34:33 +00:00
const namesAndSurnames = () => {
const p = person();
if (p === null) {
return "";
}
2023-08-25 15:34:33 +00:00
return `${p.person_names} ${p.person_paternal_surname} ${p.person_maternal_surname}`;
};
2023-08-25 15:34:33 +00:00
const surnamesAndNames = () => {
const p = person();
if (p === null) {
return "";
}
2023-08-25 15:34:33 +00:00
return `${p.person_paternal_surname} ${p.person_maternal_surname} ${p.person_names}`;
};
2023-08-25 15:34:33 +00:00
const surnames = () => {
const p = person();
if (p === null) {
return "";
}
2023-08-25 15:34:33 +00:00
return `${p.person_paternal_surname} ${p.person_maternal_surname}`;
};
2023-08-25 15:34:33 +00:00
const personNames = () => {
const p = person();
if (p === null) {
return "";
}
2023-08-25 15:34:33 +00:00
return p.person_names;
};
return (
<div>
<div class="text-center">
<div class="my-4 inline-block w-[10rem] h-[10rem] rounded-lg bg-c-surface-variant overflow-hidden">
<img class={`${qrBase64() === null ? "hidden" : ""} inline-block w-[10rem] h-[10rem]`} src={qrBase64() ?? ""} />
</div>
</div>
<div>
2023-08-25 15:34:33 +00:00
<form onSubmit={(ev) => ev.preventDefault()} class="px-4">
<InputBox dni={dni()} setDni={setDni} loading={loading()} />
</form>
<p
2023-08-25 15:34:33 +00:00
class="relative max-w-[14rem] mx-auto p-1 text-c-error text-sm select-none"
style={{opacity: error() === "" ? "0" : "1"}}
>
2023-08-25 15:34:33 +00:00
Error: {error()}
</p>
2023-08-25 15:34:33 +00:00
<div class={`${person() === null ? "opacity-50 cursor-not-allowed" : ""}`}>
<MaterialLabel text={person()?.person_paternal_surname ?? ""} resource="Apellido Paterno" />
<MaterialLabel text={person()?.person_maternal_surname ?? ""} resource="Apellido Materno" />
<MaterialLabel text={personNames()} resource="Nombres" />
2023-08-25 15:34:33 +00:00
<Show when={person() !== null}>
<div class={"relative max-w-[14rem] mx-auto my-6"}>
<CopyButton copyText={namesAndSurnames()}>
<CopyIcon fill="var(--c-on-primary)" />
&nbsp;
2023-08-25 15:34:33 +00:00
Nombres y <b>Apellidos</b>
</CopyButton>
2023-08-25 15:34:33 +00:00
<CopyButton copyText={surnamesAndNames()}>
<CopyIcon fill="var(--c-on-primary)" />
&nbsp;
2023-08-25 15:34:33 +00:00
<b>Apellidos</b> y Nombres
</CopyButton>
2023-08-25 15:34:33 +00:00
<div class="grid grid-cols-2 gap-2">
<CopyButton copyText={surnames()}>
<CopyIcon fill="var(--c-on-primary)" />
&nbsp;
<b>Apellidos</b>
</CopyButton>
<CopyButton copyText={personNames()}>
<CopyIcon fill="var(--c-on-primary)" />
&nbsp;
Nombres
</CopyButton>
</div>
</div>
2023-08-25 15:34:33 +00:00
</Show>
</div>
2023-08-25 15:34:33 +00:00
{/*
<Show when={warning() !== ""}>
<RegisterPerson dni={dni()} onSuccess={search} />
</Show>
2023-08-25 15:34:33 +00:00
*/}
</div>
</div>
);
}
function InputBox(props: {
loading: boolean,
dni: string,
setDni: (v: string) => void,
}) {
2023-08-25 15:34:33 +00:00
let inputElement: HTMLInputElement | undefined;
const copyToClipboard: HTMLButtonEvent = (ev) => {
ev.preventDefault();
if (props.dni.length === 8) {
navigator.clipboard.writeText(props.dni);
}
};
const clearDni: HTMLButtonEvent = (ev) => {
ev.preventDefault();
props.setDni("");
(inputElement as HTMLInputElement).focus();
};
return (
<div class="relative max-w-[14rem] mx-auto">
2023-08-25 15:34:33 +00:00
<input
ref={inputElement}
id="search-dni"
class="bg-c-background text-c-on-background border-c-outline border-2 rounded px-2 py-1 w-full
invalid:border-c-error invalid:text-c-error
focus:border-c-primary outline-none font-mono
disabled:opacity-50 disabled:cursor-not-allowed"
type="text"
minLength={8}
maxLength={8}
pattern="[0-9]{8}"
placeholder="Número de DNI"
value={props.dni}
required
onInput={(e) => props.setDni(e.target.value)}
disabled={props.loading}
/>
<label for="search-dni" class="absolute -top-2 left-2 text-xs bg-c-surface px-1 select-none">DNI</label>
<button
type="button"
class="absolute top-1 right-8 rounded hover:bg-c-surface-variant"
onclick={copyToClipboard}
>
<CopyIcon fill="var(--c-on-surface)" />
</button>
<button
type="button"
class="absolute top-1 right-1 rounded hover:bg-c-surface-variant"
onclick={clearDni}
>
<XIcon fill="var(--c-on-surface)" />
</button>
</div>
);
}
2023-08-25 15:34:33 +00:00
function MaterialLabel(props: {text: string, resource: string}) {
const copyToClipboard: HTMLButtonEvent = (ev) => {
ev.preventDefault();
2023-08-25 15:34:33 +00:00
if (props.text !== "") {
navigator.clipboard.writeText(props.text);
}
};
return (
<div class="relative max-w-[14rem] mx-auto my-6">
2023-08-25 15:34:33 +00:00
<label for="search-dni" class="absolute -top-2 left-2 text-xs bg-c-surface px-1 select-none">
{props.resource}
</label>
<span
class="bg-c-background text-c-on-background border-c-outline
border-2 rounded px-2 py-1 w-full inline-block font-mono
disabled:opacity-50 disabled:cursor-not-allowed"
>
2023-08-25 15:34:33 +00:00
{props.text}
<span class="select-none">&nbsp;</span>
</span>
<button
type="button"
class="absolute top-1 right-1 rounded hover:bg-c-surface-variant"
onclick={copyToClipboard}
>
<CopyIcon fill="var(--c-on-surface)" />
</button>
</div>
);
}
function CopyButton(props: {copyText: string, children: Array<JSX.Element> | JSX.Element}) {
const [successAnimation, setSuccessAnimation] = createSignal(false);
const onclick = () => {
if (props.copyText !== "") {
navigator.clipboard.writeText(props.copyText);
setSuccessAnimation(true);
setTimeout(() => setSuccessAnimation(false), 500);
}
};
return (
<button
onclick={onclick}
class={
2023-08-25 15:34:33 +00:00
`${successAnimation() ? "bg-c-success text-c-on-success" : "bg-c-primary-container text-c-on-primary-container"
} rounded-lg transition-colors py-1 my-1 relative overflow-hidden inline-block w-full`
}
>
{props.children}
</button>
);
}