diff --git a/backend/src/controller/person/mod.rs b/backend/src/controller/person/mod.rs index f65bcf1..c4c9ee5 100644 --- a/backend/src/controller/person/mod.rs +++ b/backend/src/controller/person/mod.rs @@ -2,6 +2,7 @@ use reqwest::Client; use rocket::http::Status; use rocket::serde::json::Json; +use crate::model::person::PersonCreate; use crate::model::reniec_person::ReniecPerson; use crate::{db, model::person::Person}; @@ -94,3 +95,20 @@ pub async fn get_by_dni(dni: i32) -> (Status, Json) { // Return error (Status::NotFound, Json(Person::default())) } + +#[options("/person")] +pub fn options() -> Status { + Status::Ok +} + +#[post("/person", format = "json", data = "")] +pub async fn create_person(person: Json) -> Status { + match person.create().await { + Ok(_) => Status::Created, + Err(reason) => { + eprintln!("Error creating person: {:?}", reason); + + Status::InternalServerError + } + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs index e44114f..2ca2c20 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -40,6 +40,8 @@ async fn rocket() -> _ { "/api", routes![ controller::person::get_by_dni, + controller::person::options, + controller::person::create_person, controller::course::get_all, controller::register::insert_all, controller::register::options, diff --git a/backend/src/model/person.rs b/backend/src/model/person.rs index 1254fad..ae6990f 100644 --- a/backend/src/model/person.rs +++ b/backend/src/model/person.rs @@ -1,4 +1,6 @@ -use serde::Serialize; +use serde::{Serialize, Deserialize}; + +use crate::db; #[derive(Serialize, Clone)] pub struct Person { @@ -33,3 +35,30 @@ impl Person { } } } + + + + +#[derive(Deserialize)] +pub struct PersonCreate { + pub person_dni: String, + pub person_names: String, + pub person_paternal_surname: String, + pub person_maternal_surname: String, +} + +impl PersonCreate { + pub async fn create(&self) -> Result<(), sqlx::Error> { + let db = db(); + + sqlx::query!("INSERT INTO person (person_dni, person_names, person_paternal_surname, person_maternal_surname) VALUES (?, ?, ?, ?)", + self.person_dni, + self.person_names, + self.person_paternal_surname, + self.person_maternal_surname) + .execute(db) + .await?; + + Ok(()) + } +} diff --git a/frontend/src/certs/Search.tsx b/frontend/src/certs/Search.tsx deleted file mode 100644 index c649778..0000000 --- a/frontend/src/certs/Search.tsx +++ /dev/null @@ -1,287 +0,0 @@ -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"; -import { Person } from "../types/Person"; - -type HTMLButtonEvent = JSX.EventHandlerUnion; - -/* - 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(null); - const [person, setPerson] = createSignal(null); - - // Update QR and automatically search when DNI is changed - createEffect(() => { - 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); - }); - } else { - setQrBase64(null); - setPerson(null); - props.setPerson(null); - } - }); - - /* - Get the user data from the DB - */ - const search = async() => { - setLoading(true); - setError(""); - - try { - 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); - } else if (response.status === 404) { - console.error(body); - - setError("No encontrado. Ingresar datos manualmente."); - props.setPerson(null); - } else { - setError(body); - } - } catch (e) { - setError(JSON.stringify(e)); - } - - setLoading(false); - }; - - const namesAndSurnames = () => { - const p = person(); - if (p === null) { - return ""; - } - return `${p.person_names} ${p.person_paternal_surname} ${p.person_maternal_surname}`; - }; - - const surnamesAndNames = () => { - const p = person(); - if (p === null) { - return ""; - } - return `${p.person_paternal_surname} ${p.person_maternal_surname} ${p.person_names}`; - }; - - const surnames = () => { - const p = person(); - if (p === null) { - return ""; - } - return `${p.person_paternal_surname} ${p.person_maternal_surname}`; - }; - - const personNames = () => { - const p = person(); - if (p === null) { - return ""; - } - return p.person_names; - }; - - return ( -
-
-
- -
-
-
-
ev.preventDefault()} class="px-4"> - - - -

- Error: {error()} -

- - -
- - - - - -
- - -   - Nombres y Apellidos - - - - -   - Apellidos y Nombres - - -
- - -   - Apellidos - - - -   - Nombres - -
-
-
-
- - {/* - - - - */} -
-
- ); -} - -function InputBox(props: { - loading: boolean, - dni: string, - setDni: (v: string) => void, -}) { - 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 ( -
- props.setDni(e.target.value)} - disabled={props.loading} - /> - - - - - -
- ); -} - - -function MaterialLabel(props: {text: string, resource: string}) { - const copyToClipboard: HTMLButtonEvent = (ev) => { - ev.preventDefault(); - - if (props.text !== "") { - 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/frontend/src/certs/Search/CopyButton.tsx b/frontend/src/certs/Search/CopyButton.tsx new file mode 100644 index 0000000..6a04826 --- /dev/null +++ b/frontend/src/certs/Search/CopyButton.tsx @@ -0,0 +1,25 @@ +import { JSX, createSignal } from "solid-js"; + +export 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/frontend/src/certs/Search/PersonDisplay.tsx b/frontend/src/certs/Search/PersonDisplay.tsx new file mode 100644 index 0000000..ab49f46 --- /dev/null +++ b/frontend/src/certs/Search/PersonDisplay.tsx @@ -0,0 +1,102 @@ +import { JSX } from "solid-js"; +import { CopyIcon } from "../../icons/CopyIcon"; +import { Person } from "../../types/Person"; +import { CopyButton } from "./CopyButton"; + +type HTMLButtonEvent = JSX.EventHandlerUnion; + +export function PersonDisplay(props: {person: Person}) { + const namesAndSurnames = () => { + const p = props.person; + return `${p.person_names} ${p.person_paternal_surname} ${p.person_maternal_surname}`; + }; + + const surnamesAndNames = () => { + const p = props.person; + return `${p.person_paternal_surname} ${p.person_maternal_surname} ${p.person_names}`; + }; + + const surnames = () => { + const p = props.person; + return `${p.person_paternal_surname} ${p.person_maternal_surname}`; + }; + + const personNames = () => { + const p = props.person; + return p.person_names; + }; + + return ( +
+ + + + + +
+ + +   + Nombres y Apellidos + + + + +   + Apellidos y Nombres + + +
+ + +   + Apellidos + + + +   + Nombres + +
+
+
+ ); +} + + +function MaterialLabel(props: {text: string, resource: string}) { + const copyToClipboard: HTMLButtonEvent = (ev) => { + ev.preventDefault(); + + if (props.text !== "") { + navigator.clipboard.writeText(props.text); + } + }; + + return ( +
+ + + {props.text} +   + + + +
+ ); +} + + + diff --git a/frontend/src/certs/Search/PersonRegister.tsx b/frontend/src/certs/Search/PersonRegister.tsx new file mode 100644 index 0000000..3ad0a42 --- /dev/null +++ b/frontend/src/certs/Search/PersonRegister.tsx @@ -0,0 +1,111 @@ +import { createSignal } from "solid-js"; + +export function PersonRegister(props: {dni: string, onRegister: () => void}) { + const [paternalSurname, setPaternalSurname] = createSignal(""); + const [maternalSurname, setMaternalSurname] = createSignal(""); + const [names, setNames] = createSignal(""); + const [loading, setLoading] = createSignal(false); + + const register = async() => { + setLoading(true); + + const paternal = paternalSurname(); + const maternal = maternalSurname(); + const name = names(); + + if (paternal === "" || maternal === "" || name === "") { + return; + } + + const data = { + person_dni: props.dni, + person_names: name.toUpperCase(), + person_paternal_surname: paternal.toUpperCase(), + person_maternal_surname: maternal.toUpperCase(), + }; + + const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/person`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + if (response.ok) { + props.onRegister(); + } + + setLoading(false); + }; + + return ( +
+
{ + ev.preventDefault(); + register(); + }} + > + setPaternalSurname(v.toUpperCase())} + loading={loading()} + /> + setMaternalSurname(v.toUpperCase())} + loading={loading()} + /> + setNames(v.toUpperCase())} + loading={loading()} + /> + + + +
+ ); +} + +function MaterialInput(props: { + name: string, + loading: boolean, + value: string, + setValue: (v: string) => void, +}) { + let inputElement: HTMLInputElement | undefined; + + return ( +
+ props.setValue(e.target.value)} + disabled={props.loading} + /> + + + +
+ ); +} diff --git a/frontend/src/certs/Search/index.tsx b/frontend/src/certs/Search/index.tsx new file mode 100644 index 0000000..31dbf27 --- /dev/null +++ b/frontend/src/certs/Search/index.tsx @@ -0,0 +1,173 @@ +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"; +import { Person } from "../../types/Person"; +import { PersonDisplay } from "./PersonDisplay"; +import { PersonRegister } from "./PersonRegister"; + +type HTMLButtonEvent = JSX.EventHandlerUnion; + +/* + 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(null); + const [person, setPerson] = createSignal(null); + const [manualCreate, setManualCreate] = createSignal(false); + + // Update QR and automatically search when DNI is changed + createEffect(() => { + 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); + }); + } else { + setQrBase64(null); + setPerson(null); + props.setPerson(null); + } + }); + + /* + Get the user data from the DB + */ + const search = async() => { + setLoading(true); + setError(""); + + try { + 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); + setManualCreate(false); + } else if (response.status === 404) { + console.error(body); + + setError("No encontrado. Ingresar datos manualmente."); + setManualCreate(true); + props.setPerson(null); + } else { + setError(body); + setManualCreate(false); + } + } catch (e) { + setError(JSON.stringify(e)); + } + + setLoading(false); + }; + + + + return ( +
+
+
+ +
+
+
+
ev.preventDefault()} class="px-4"> + + + +

+ Error: {error()} +

+ + + + + + + + + +
+
+ ); +} + + +function InputBox(props: { + loading: boolean, + value: string, + setValue: (v: string) => void, +}) { + let inputElement: HTMLInputElement | undefined; + + const copyToClipboard: HTMLButtonEvent = (ev) => { + ev.preventDefault(); + + if (props.value.length === 8) { + navigator.clipboard.writeText(props.value); + } + }; + + const clearDni: HTMLButtonEvent = (ev) => { + ev.preventDefault(); + + props.setValue(""); + (inputElement as HTMLInputElement).focus(); + }; + + return ( +
+ props.setValue(e.target.value)} + disabled={props.loading} + /> + + + + + +
+ ); +} + +