[FE][Certs] Improve UI elements
This commit is contained in:
parent
3f49209c29
commit
acb29940fc
1
backend/.gitignore
vendored
Normal file
1
backend/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
target
|
5
frontend/.vscode/settings.json
vendored
Normal file
5
frontend/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": true
|
||||||
|
}
|
||||||
|
}
|
24
frontend/src/certs/Registers.tsx
Normal file
24
frontend/src/certs/Registers.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { DownloadIcon } from "../icons/DownloadIcon";
|
||||||
|
|
||||||
|
export function Registers() {
|
||||||
|
return (
|
||||||
|
<div class="m-4 p-4 rounded-md">
|
||||||
|
<h3 class="text-xl font-medium py-2">
|
||||||
|
SUMA BERNAL, MAIKOL
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap justify-start gap-2">
|
||||||
|
<div class="inline-block w-[14rem] p-1 border border-c-outline rounded-md">
|
||||||
|
<button class="rounded-full bg-c-primary-container hover:bg-c-primary transition-colors h-12 w-12">
|
||||||
|
<DownloadIcon fill="var(--c-on-primary-container)" />
|
||||||
|
</button>
|
||||||
|
<div class="inline-block h-12 w-40 pl-2 align-middle">
|
||||||
|
<p class="font-bold">Matpel 2</p>
|
||||||
|
<p class="font-mono text-sm">12/08/2023 - 6486</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,20 +1,12 @@
|
|||||||
import { createEffect, createSignal, Show } from "solid-js";
|
import { Show, createEffect, createSignal } from "solid-js";
|
||||||
import { JSX } from "solid-js/jsx-runtime";
|
import { JSX } from "solid-js/jsx-runtime";
|
||||||
// import { Person } from "../../types/Person";
|
|
||||||
// import { RegisterPerson } from "./Search/RegisterPerson";
|
|
||||||
import QR from "qrcode";
|
import QR from "qrcode";
|
||||||
import { CopyIcon } from "../icons/CopyIcon";
|
import { CopyIcon } from "../icons/CopyIcon";
|
||||||
import { MagnifyingGlassIcon } from "../icons/MagnifyingGlassIcon";
|
|
||||||
import { XIcon } from "../icons/XIcon";
|
import { XIcon } from "../icons/XIcon";
|
||||||
|
import { Person } from "../types/Person";
|
||||||
|
|
||||||
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
|
|
||||||
submitter: HTMLElement;
|
|
||||||
}>;
|
|
||||||
type HTMLButtonEvent = JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>;
|
type HTMLButtonEvent = JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
type Person = any;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Form that retrieves a user from the DB given an ID
|
Form that retrieves a user from the DB given an ID
|
||||||
*/
|
*/
|
||||||
@ -22,16 +14,16 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
|||||||
const [dni, setDni] = createSignal("");
|
const [dni, setDni] = createSignal("");
|
||||||
const [loading, setLoading] = createSignal(false);
|
const [loading, setLoading] = createSignal(false);
|
||||||
const [error, setError] = createSignal("");
|
const [error, setError] = createSignal("");
|
||||||
const [warning, setWarning] = createSignal("");
|
|
||||||
const [qrBase64, setQrBase64] = createSignal<string | null>(null);
|
const [qrBase64, setQrBase64] = createSignal<string | null>(null);
|
||||||
const [persona, setPersona] = createSignal<Person | null>(null);
|
const [person, setPerson] = createSignal<Person | null>(null);
|
||||||
|
|
||||||
// Update QR
|
// Update QR and automatically search when DNI is changed
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (dni() !== "") {
|
const dniT = dni();
|
||||||
// Old URL: https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()}
|
|
||||||
// New URL: https://eegsac.com/certificado/${dni()}
|
if (dniT.length >= 8) {
|
||||||
QR.toDataURL(`https://eegsac.com/certificado/${dni()}`, {margin: 1}, (err, res) => {
|
search();
|
||||||
|
QR.toDataURL(`https://eegsac.com/certificado/${dniT}`, {margin: 1}, (err, res) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Error creating QR code");
|
console.error("Error creating QR code");
|
||||||
return;
|
return;
|
||||||
@ -39,32 +31,28 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
|||||||
|
|
||||||
setQrBase64(res);
|
setQrBase64(res);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setQrBase64(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get the user data from the DB
|
Get the user data from the DB
|
||||||
*/
|
*/
|
||||||
const searchDNI: HTMLEventFn = (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
search();
|
|
||||||
};
|
|
||||||
|
|
||||||
const search = async() => {
|
const search = async() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError("");
|
setError("");
|
||||||
setWarning("");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/person/${dni()}`);
|
const response = await fetch(`/api/person/${dni()}`);
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setPersona(body);
|
setPerson(body);
|
||||||
props.setPerson(body);
|
props.setPerson(body);
|
||||||
} else if (response.status === 404) {
|
} else if (response.status === 404) {
|
||||||
console.error(body);
|
console.error(body);
|
||||||
|
|
||||||
setWarning("Persona no encontrada. Se debe insertar manualmente sus datos.");
|
setError("No encontrado. Ingresar datos manualmente.");
|
||||||
props.setPerson(null);
|
props.setPerson(null);
|
||||||
} else {
|
} else {
|
||||||
setError(body);
|
setError(body);
|
||||||
@ -76,36 +64,36 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const nombresYApellidos = () => {
|
const namesAndSurnames = () => {
|
||||||
const p = persona();
|
const p = person();
|
||||||
if (p === null) {
|
if (p === null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return `${p.nombres} ${p.apellidoPaterno} ${p.apellidoMaterno}`;
|
return `${p.person_names} ${p.person_paternal_surname} ${p.person_maternal_surname}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const apellidosYNombres = () => {
|
const surnamesAndNames = () => {
|
||||||
const p = persona();
|
const p = person();
|
||||||
if (p === null) {
|
if (p === null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return `${p.apellidoPaterno} ${p.apellidoMaterno} ${p.nombres}`;
|
return `${p.person_paternal_surname} ${p.person_maternal_surname} ${p.person_names}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const apellidos = () => {
|
const surnames = () => {
|
||||||
const p = persona();
|
const p = person();
|
||||||
if (p === null) {
|
if (p === null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return `${p.apellidoPaterno} ${p.apellidoMaterno}`;
|
return `${p.person_paternal_surname} ${p.person_maternal_surname}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const nombres = () => {
|
const personNames = () => {
|
||||||
const p = persona();
|
const p = person();
|
||||||
if (p === null) {
|
if (p === null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return p.nombres;
|
return p.person_names;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -116,59 +104,58 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={searchDNI} class="px-4">
|
<form onSubmit={(ev) => ev.preventDefault()} class="px-4">
|
||||||
<InputBox dni={dni()} setDni={setDni} loading={loading()} />
|
<InputBox dni={dni()} setDni={setDni} loading={loading()} />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
class="relative max-w-[14rem] mx-auto p-1 text-c-error text-sm"
|
class="relative max-w-[14rem] mx-auto p-1 text-c-error text-sm select-none"
|
||||||
style={{display: error() === "" ? "none" : "block"}}
|
style={{opacity: error() === "" ? "0" : "1"}}
|
||||||
>
|
>
|
||||||
Error:
|
Error: {error()}
|
||||||
<br />
|
|
||||||
{error()}
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class={`${persona() === null ? "opacity-50 cursor-not-allowed" : ""}`}>
|
<div class={`${person() === null ? "opacity-50 cursor-not-allowed" : ""}`}>
|
||||||
<MaterialLabel text={persona()?.apellidoPaterno ?? null} resource="Apellido Paterno" />
|
<MaterialLabel text={person()?.person_paternal_surname ?? ""} resource="Apellido Paterno" />
|
||||||
<MaterialLabel text={persona()?.apellidoMaterno ?? null} resource="Apellido Materno" />
|
<MaterialLabel text={person()?.person_maternal_surname ?? ""} resource="Apellido Materno" />
|
||||||
<MaterialLabel text={persona()?.nombres ?? null} resource="Nombres" />
|
<MaterialLabel text={personNames()} resource="Nombres" />
|
||||||
|
|
||||||
|
<Show when={person() !== null}>
|
||||||
<div class={"relative max-w-[14rem] mx-auto my-6"}>
|
<div class={"relative max-w-[14rem] mx-auto my-6"}>
|
||||||
<CopyButton copyText={nombresYApellidos()}>
|
<CopyButton copyText={namesAndSurnames()}>
|
||||||
<CopyIcon fill="var(--c-on-primary)" />
|
<CopyIcon fill="var(--c-on-primary)" />
|
||||||
|
|
||||||
Nombres y <b>Apellidos</b>
|
Nombres y <b>Apellidos</b>
|
||||||
</CopyButton>
|
</CopyButton>
|
||||||
|
|
||||||
<CopyButton copyText={apellidosYNombres()}>
|
<CopyButton copyText={surnamesAndNames()}>
|
||||||
<CopyIcon fill="var(--c-on-primary)" />
|
<CopyIcon fill="var(--c-on-primary)" />
|
||||||
|
|
||||||
<b>Apellidos</b> y Nombres
|
<b>Apellidos</b> y Nombres
|
||||||
</CopyButton>
|
</CopyButton>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<CopyButton copyText={apellidos()}>
|
<CopyButton copyText={surnames()}>
|
||||||
<CopyIcon fill="var(--c-on-primary)" />
|
<CopyIcon fill="var(--c-on-primary)" />
|
||||||
|
|
||||||
<b>Apellidos</b>
|
<b>Apellidos</b>
|
||||||
</CopyButton>
|
</CopyButton>
|
||||||
<CopyButton copyText={nombres()}>
|
<CopyButton copyText={personNames()}>
|
||||||
<CopyIcon fill="var(--c-on-primary)" />
|
<CopyIcon fill="var(--c-on-primary)" />
|
||||||
|
|
||||||
Nombres
|
Nombres
|
||||||
</CopyButton>
|
</CopyButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={warning() !== ""}>
|
|
||||||
{/*
|
{/*
|
||||||
|
<Show when={warning() !== ""}>
|
||||||
<RegisterPerson dni={dni()} onSuccess={search} />
|
<RegisterPerson dni={dni()} onSuccess={search} />
|
||||||
*/}
|
|
||||||
</Show>
|
</Show>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -179,24 +166,7 @@ function InputBox(props: {
|
|||||||
dni: string,
|
dni: string,
|
||||||
setDni: (v: string) => void,
|
setDni: (v: string) => void,
|
||||||
}) {
|
}) {
|
||||||
const inputElement = (
|
let inputElement: HTMLInputElement | undefined;
|
||||||
<input
|
|
||||||
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
|
|
||||||
onChange={(e) => props.setDni(e.target.value)}
|
|
||||||
disabled={props.loading}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const copyToClipboard: HTMLButtonEvent = (ev) => {
|
const copyToClipboard: HTMLButtonEvent = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@ -215,13 +185,26 @@ function InputBox(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative max-w-[14rem] mx-auto">
|
<div class="relative max-w-[14rem] mx-auto">
|
||||||
{inputElement}
|
<input
|
||||||
<label for="search-dni" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">DNI</label>
|
ref={inputElement}
|
||||||
<button
|
id="search-dni"
|
||||||
class="absolute top-1 right-[3.75rem] rounded hover:bg-c-surface-variant"
|
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
|
||||||
<MagnifyingGlassIcon fill="var(--c-on-surface)" />
|
focus:border-c-primary outline-none font-mono
|
||||||
</button>
|
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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="absolute top-1 right-8 rounded hover:bg-c-surface-variant"
|
class="absolute top-1 right-8 rounded hover:bg-c-surface-variant"
|
||||||
@ -241,24 +224,26 @@ function InputBox(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function MaterialLabel(props: {text: string | null, resource: string}) {
|
function MaterialLabel(props: {text: string, resource: string}) {
|
||||||
const copyToClipboard: HTMLButtonEvent = (ev) => {
|
const copyToClipboard: HTMLButtonEvent = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
if (props.text !== null) {
|
if (props.text !== "") {
|
||||||
navigator.clipboard.writeText(props.text);
|
navigator.clipboard.writeText(props.text);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative max-w-[14rem] mx-auto my-6">
|
<div class="relative max-w-[14rem] mx-auto my-6">
|
||||||
<label for="search-dni" class="absolute -top-2 left-2 text-xs bg-c-surface px-1 select-none">{props.resource}</label>
|
<label for="search-dni" class="absolute -top-2 left-2 text-xs bg-c-surface px-1 select-none">
|
||||||
|
{props.resource}
|
||||||
|
</label>
|
||||||
<span
|
<span
|
||||||
class="bg-c-background text-c-on-background border-c-outline
|
class="bg-c-background text-c-on-background border-c-outline
|
||||||
border-2 rounded px-2 py-1 w-full inline-block font-mono
|
border-2 rounded px-2 py-1 w-full inline-block font-mono
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{props.text ?? ""}
|
{props.text}
|
||||||
<span class="select-none"> </span>
|
<span class="select-none"> </span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -288,7 +273,7 @@ function CopyButton(props: {copyText: string, children: Array<JSX.Element> | JSX
|
|||||||
<button
|
<button
|
||||||
onclick={onclick}
|
onclick={onclick}
|
||||||
class={
|
class={
|
||||||
`${successAnimation() ? "bg-c-success text-c-on-success" : "bg-c-primary text-c-on-primary"
|
`${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`
|
} rounded-lg transition-colors py-1 my-1 relative overflow-hidden inline-block w-full`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { NewRegister } from "./NewRegister";
|
import { NewRegister } from "./NewRegister";
|
||||||
|
import { Registers } from "./Registers";
|
||||||
import { Search } from "./Search";
|
import { Search } from "./Search";
|
||||||
|
|
||||||
export function Certs() {
|
export function Certs() {
|
||||||
@ -6,7 +7,7 @@ export function Certs() {
|
|||||||
<div class="grid grid-cols-[18rem_25rem_1fr]">
|
<div class="grid grid-cols-[18rem_25rem_1fr]">
|
||||||
<Search setPerson={() => {}} />
|
<Search setPerson={() => {}} />
|
||||||
<NewRegister personId={0} onSuccess={() => {}} />
|
<NewRegister personId={0} onSuccess={() => {}} />
|
||||||
<div>Registers</div>
|
<Registers />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { HomeIcon } from "../icons/HomeIcon";
|
import { HomeIcon } from "../icons/HomeIcon";
|
||||||
import type {JSX} from "solid-js";
|
import type {JSX} from "solid-js";
|
||||||
import { DocxIcon } from "../icons/DocxIcon";
|
import { DocxIcon } from "../icons/DocxIcon";
|
||||||
import { StackIcon } from "../icons/StackIcon";
|
|
||||||
import { ScanIcon } from "../icons/ScanIcon";
|
import { ScanIcon } from "../icons/ScanIcon";
|
||||||
import { KeyIcon } from "../icons/KeyIcon";
|
import { KeyIcon } from "../icons/KeyIcon";
|
||||||
import { PaletteIcon } from "../icons/PaletteIcon";
|
import { PaletteIcon } from "../icons/PaletteIcon";
|
||||||
@ -12,7 +11,6 @@ export function NavRail() {
|
|||||||
<div class="flex flex-col justify-center items-center h-screen overflow-x-hidden">
|
<div class="flex flex-col justify-center items-center h-screen overflow-x-hidden">
|
||||||
<NavRailButton path="/" name="Inicio" icon={<HomeIcon />} />
|
<NavRailButton path="/" name="Inicio" icon={<HomeIcon />} />
|
||||||
<NavRailButton path="/certs" name="Certs" icon={<DocxIcon />} />
|
<NavRailButton path="/certs" name="Certs" icon={<DocxIcon />} />
|
||||||
<NavRailButton path="/batch" name="Batch" icon={<StackIcon />} />
|
|
||||||
<NavRailButton path="/accesos" name="Accesos" icon={<KeyIcon />} />
|
<NavRailButton path="/accesos" name="Accesos" icon={<KeyIcon />} />
|
||||||
<NavRailButton path="/escaneo" name="Escaneo" icon={<ScanIcon />} />
|
<NavRailButton path="/escaneo" name="Escaneo" icon={<ScanIcon />} />
|
||||||
<NavRailButton path="/colores" name="Colores" icon={<PaletteIcon />} />
|
<NavRailButton path="/colores" name="Colores" icon={<PaletteIcon />} />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export function DownloadIcon(props: {fill: string}) {
|
export function DownloadIcon(props: {fill: string}) {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill={props.fill} viewBox="0 0 256 256"><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H72a8,8,0,0,1,0,16H32v64H224V136H184a8,8,0,0,1,0-16h40A16,16,0,0,1,240,136Zm-117.66-2.34a8,8,0,0,0,11.32,0l48-48a8,8,0,0,0-11.32-11.32L136,108.69V24a8,8,0,0,0-16,0v84.69L85.66,74.34A8,8,0,0,0,74.34,85.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill={props.fill} viewBox="0 0 256 256"><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H72a8,8,0,0,1,0,16H32v64H224V136H184a8,8,0,0,1,0-16h40A16,16,0,0,1,240,136Zm-117.66-2.34a8,8,0,0,0,11.32,0l48-48a8,8,0,0,0-11.32-11.32L136,108.69V24a8,8,0,0,0-16,0v84.69L85.66,74.34A8,8,0,0,0,74.34,85.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z" /></svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
9
frontend/src/types/Person.tsx
Normal file
9
frontend/src/types/Person.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
export type Person = {
|
||||||
|
person_id: number
|
||||||
|
person_dni: number
|
||||||
|
person_names: string
|
||||||
|
person_paternal_surname: string
|
||||||
|
person_maternal_surname: string
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user