Compare commits
No commits in common. "246bec540cc002593f34a2f690ab8b01e2442545" and "40914aab1ae6cf60d3421d0b825c4625351c5e3d" have entirely different histories.
246bec540c
...
40914aab1a
10464
sql/backup/educa7ls_plataforma_2023_05_07.sql
Normal file
10464
sql/backup/educa7ls_plataforma_2023_05_07.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@ import {
|
||||
AlignmentType,
|
||||
BorderStyle,
|
||||
} from "docx";
|
||||
import { cmText, createSimpleTextP, getImage, getQR } from "./utils";
|
||||
import { cmText, createSimpleText, createSimpleTextP, getImage, getQR } from "./utils";
|
||||
import { CertData } from "./CertData";
|
||||
|
||||
const imgFondoDoc = getImage({
|
||||
@ -30,7 +30,7 @@ const imgEATE = getImage({
|
||||
height: 2.28,
|
||||
width: 3.39,
|
||||
horizontalOffset: 0.94,
|
||||
verticalOffset: 15.67,
|
||||
verticalOffset: 13.77,
|
||||
});
|
||||
|
||||
const imgMTC = getImage({
|
||||
@ -38,7 +38,7 @@ const imgMTC = getImage({
|
||||
height: 1.6,
|
||||
width: 5,
|
||||
horizontalOffset: 0.9,
|
||||
verticalOffset: 18.3,
|
||||
verticalOffset: 17.22,
|
||||
});
|
||||
|
||||
const imgOSHA = getImage({
|
||||
@ -73,7 +73,7 @@ const tCertificate = createSimpleTextP({
|
||||
// Se expide el presente
|
||||
const tLabel3 = createSimpleTextP({
|
||||
xPosition: 11.08,
|
||||
yPosition: 5.43,
|
||||
yPosition: 4.62,
|
||||
width: 7.74,
|
||||
height: 0.5,
|
||||
text: "Se expide el presente a:",
|
||||
@ -84,7 +84,7 @@ const tLabel3 = createSimpleTextP({
|
||||
// SUPERVISOR ESCOLTA MATPEL
|
||||
const tCourse = createSimpleTextP({
|
||||
xPosition: 7.28,
|
||||
yPosition: 8.75,
|
||||
yPosition: 7.94,
|
||||
width: 15.42,
|
||||
height: 1.5,
|
||||
text: "SUPERVISOR ESCOLTA MATPEL",
|
||||
@ -95,7 +95,7 @@ const tCourse = createSimpleTextP({
|
||||
// Por haber aprobado la dormacion...
|
||||
const tLabel2 = createSimpleTextP({
|
||||
xPosition: 10.93,
|
||||
yPosition: 7.95,
|
||||
yPosition: 7.14,
|
||||
width: 7.74,
|
||||
height: 0.6,
|
||||
text: "Por haber aprobado la formación en el curso",
|
||||
@ -106,7 +106,7 @@ const tLabel2 = createSimpleTextP({
|
||||
// Temas tratados...
|
||||
const tTopics = createSimpleTextP({
|
||||
xPosition: 5.04,
|
||||
yPosition: 10.3,
|
||||
yPosition: 9.49,
|
||||
width: 19.34,
|
||||
height: 1.5,
|
||||
text: "Temas tratados: Inspección de Unidades y Llenado de Herramientas de Gestión Check List, IPERC, Inspección básica de Kit de Emergencias, Parada de Controles e Inspección, Delimitación y Evaluación del Evento, Sistemas de Comando de Incidentes, Practicas Reales en caso de Derrames de MATPEL con Trajes, Evacuación de Pacientes Afectados con Camioneta, Primeros Auxilios básico, Con una duración de 36 horas lectivas.",
|
||||
@ -118,7 +118,7 @@ const tTopics = createSimpleTextP({
|
||||
// Respaldado por:
|
||||
const tHours = createSimpleTextP({
|
||||
xPosition: 1.15,
|
||||
yPosition: 15,
|
||||
yPosition: 13.15,
|
||||
width: 3.07,
|
||||
height: 0.5,
|
||||
text: "Respaldado por:",
|
||||
@ -131,7 +131,7 @@ const tHours = createSimpleTextP({
|
||||
// Chile
|
||||
const tChile = createSimpleTextP({
|
||||
xPosition: 3.07,
|
||||
yPosition: 17.83,
|
||||
yPosition: 15.98,
|
||||
width: 1.43,
|
||||
height: 0.5,
|
||||
text: "CHILE",
|
||||
@ -144,7 +144,7 @@ const tChile = createSimpleTextP({
|
||||
// Se expide certificado...
|
||||
const tFinishLabel = createSimpleTextP({
|
||||
xPosition: 8.22,
|
||||
yPosition: 12.68,
|
||||
yPosition: 11.87,
|
||||
width: 13.15,
|
||||
height: 0.5,
|
||||
text: "Se expide el presente certificado para los fines que se estime conveniente",
|
||||
@ -160,7 +160,7 @@ const photoSection = new Paragraph({
|
||||
frame: {
|
||||
position: {
|
||||
x: cmText(25),
|
||||
y: cmText(5.2),
|
||||
y: cmText(4.37),
|
||||
},
|
||||
height: cmText(3.57),
|
||||
width: cmText(2.81),
|
||||
@ -199,13 +199,13 @@ export async function supervisorEscolta(props: CertData<null>): Promise<Buffer>
|
||||
height: 2.5,
|
||||
width: 2.5,
|
||||
horizontalOffset: 25.85,
|
||||
verticalOffset: 17.49,
|
||||
verticalOffset: 15.88,
|
||||
});
|
||||
|
||||
// FERNANDO ARAOZ
|
||||
const tName = createSimpleTextP({
|
||||
xPosition: 3.78,
|
||||
yPosition: 6.02,
|
||||
yPosition: 5.21,
|
||||
width: 22.07,
|
||||
height: 1.5,
|
||||
text: props.personFullName,
|
||||
@ -219,7 +219,7 @@ export async function supervisorEscolta(props: CertData<null>): Promise<Buffer>
|
||||
frame: {
|
||||
position: {
|
||||
x: cmText(11.84),
|
||||
y: cmText(7.35),
|
||||
y: cmText(6.54),
|
||||
},
|
||||
width: cmText(6.02),
|
||||
height: cmText(0.6),
|
||||
@ -249,7 +249,7 @@ export async function supervisorEscolta(props: CertData<null>): Promise<Buffer>
|
||||
frame: {
|
||||
position: {
|
||||
x: cmText(19.62),
|
||||
y: cmText(19.2),
|
||||
y: cmText(17.58),
|
||||
},
|
||||
width: cmText(5.87),
|
||||
height: cmText(0.75),
|
||||
@ -277,7 +277,7 @@ export async function supervisorEscolta(props: CertData<null>): Promise<Buffer>
|
||||
// N° XXXX-20XX-EEG
|
||||
const tCertCode = createSimpleTextP({
|
||||
xPosition: 24.59,
|
||||
yPosition: 9,
|
||||
yPosition: 8.07,
|
||||
width: 3.67,
|
||||
height: 0.5,
|
||||
text: `N° ${props.certCode}-${props.certYear}-EEG`,
|
||||
|
@ -61,9 +61,7 @@ export async function getQR(data : {
|
||||
verticalOffset: number,
|
||||
behindDocument?: boolean,
|
||||
}): Promise<ImageRun> {
|
||||
// Old URL: https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()}
|
||||
// New URL: https://eegsac.com/certificado/${dni()}
|
||||
const qr = await QR.toDataURL(`https://eegsac.com/certificado/${data.dni}?iid=${data.iid}`, {margin: 1});
|
||||
const qr = await QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${data.dni}&iid=${data.iid}`, {margin: 1});
|
||||
|
||||
return new ImageRun({
|
||||
data: qr,
|
||||
|
@ -3,7 +3,7 @@ import { renderToString } from "solid-js/web";
|
||||
import { CertsBatch } from "src/views/BatchCerts";
|
||||
import { template } from "./BatchCerts.template";
|
||||
|
||||
@Controller("batch-mode")
|
||||
@Controller("batch-certs")
|
||||
export class BatchCertController {
|
||||
@Get()
|
||||
entry(): string {
|
||||
|
@ -9,7 +9,6 @@ export function template(ssr: string): string {
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="/static/styles.css?t=${Date.now()}" />
|
||||
<link rel="icon" type="image/png" href="/static/favicon.png">
|
||||
<!-- Phosphor icons -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
|
@ -184,7 +184,7 @@ export class CertificateService {
|
||||
certificate.curso = data.subjectId;
|
||||
certificate.codigo = await this.getNextRegisterCode(data.subjectId);
|
||||
certificate.fecha_actual = new Date();
|
||||
certificate.fecha_inscripcion = data.date as unknown as Date ; // new Date(data.date);
|
||||
certificate.fecha_inscripcion = new Date(data.date);
|
||||
certificate.curso_nombre = subject.nombre;
|
||||
|
||||
certificate.persona = person;
|
||||
|
@ -8,7 +8,6 @@ export function template(ssr: string): string {
|
||||
<title>Registrar certificados - EEGSAC</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/png" href="/static/favicon.png">
|
||||
<link rel="stylesheet" href="/static/styles.css?t=${Date.now()}" />
|
||||
<!-- Phosphor icons -->
|
||||
<link
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { createMemo, createSignal, For, Match, onMount, Show, Switch } from "solid-js";
|
||||
import { createSignal, Match, onMount, Show, Switch } from "solid-js";
|
||||
import { Person } from "src/types/Person";
|
||||
import { DniRegister } from "./DniEntry/DniRegister";
|
||||
import { RegisterReturn } from "src/types/RegisterReturn";
|
||||
|
||||
/**
|
||||
* Sample data
|
||||
@ -28,11 +27,9 @@ enum Status {
|
||||
Error,
|
||||
}
|
||||
|
||||
export function DniEntry(props: {dni: string, remove: (_: string) => void}) {
|
||||
export function DniEntry(props: {dni: string}) {
|
||||
const [person, setPerson] = createSignal<Person | null>(null);
|
||||
const [status, setStatus] = createSignal<Status>(Status.Empty);
|
||||
const [certificates, setCertificates] = createSignal<Array<RegisterReturn>>([]);
|
||||
const [certStatus, setCertStatus] = createSignal<Status>(Status.Empty);
|
||||
|
||||
const loadPerson = async() => {
|
||||
setStatus(Status.Loading);
|
||||
@ -43,7 +40,7 @@ export function DniEntry(props: {dni: string, remove: (_: string) => void}) {
|
||||
if (response.ok) {
|
||||
setPerson(body);
|
||||
setStatus(Status.Ok);
|
||||
loadCertificates();
|
||||
|
||||
} else if (response.status === 404) {
|
||||
console.error(body);
|
||||
setStatus(Status.Error);
|
||||
@ -61,23 +58,6 @@ export function DniEntry(props: {dni: string, remove: (_: string) => void}) {
|
||||
|
||||
onMount(loadPerson);
|
||||
|
||||
const loadCertificates = async() => {
|
||||
setCertStatus(Status.Loading);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/certificate/${props.dni}`);
|
||||
if (response.ok) {
|
||||
const body = await response.json();
|
||||
setCertificates(body);
|
||||
setCertStatus(Status.Ok);
|
||||
} else {
|
||||
setCertStatus(Status.Error);
|
||||
}
|
||||
} catch (e) {
|
||||
setCertStatus(Status.Error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show when={status() !== Status.Error}>
|
||||
@ -116,24 +96,11 @@ export function DniEntry(props: {dni: string, remove: (_: string) => void}) {
|
||||
<div class="text-center">
|
||||
<button
|
||||
class={"rounded-full px-2 hover:bg-c-error first:text-c-error first:hover:text-c-on-error"}
|
||||
onclick={() => props.remove(props.dni)}
|
||||
>
|
||||
<i class="ph ph-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="border-l">
|
||||
<Switch>
|
||||
<Match when={certStatus() === Status.Loading}>
|
||||
<p>Loading...</p>
|
||||
</Match>
|
||||
<Match when={certStatus() === Status.Ok}>
|
||||
<CertArray array={certificates()} />
|
||||
</Match>
|
||||
<Match when={certStatus() === Status.Error}>
|
||||
<p class="text-c-error">Error loading certificates.</p>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<div class="border-l"></div>
|
||||
</Match>
|
||||
|
||||
</Switch>
|
||||
@ -146,34 +113,3 @@ export function DniEntry(props: {dni: string, remove: (_: string) => void}) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function CertArray(props: {array: Array<RegisterReturn>}) {
|
||||
const filtered = createMemo(() => {
|
||||
const nowMs = Date.now();
|
||||
return props.array.filter((cert) => {
|
||||
const date = new Date(cert.fecha_inscripcion);
|
||||
const ms = date.getTime();
|
||||
|
||||
const difference = nowMs - ms;
|
||||
|
||||
return difference < (1000 * 60 * 60 * 24 * 366);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
[
|
||||
<For each={filtered()}>
|
||||
{(cert) => (
|
||||
<div class="mr-4 inline-block underline">
|
||||
{cert.curso_nombre}
|
||||
<span class="font-mono">
|
||||
{cert.fecha_inscripcion}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
]
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,10 @@
|
||||
import { For, createSignal, onMount } from "solid-js";
|
||||
import { For } from "solid-js";
|
||||
import { DniEntry } from "./DniEntry";
|
||||
import { NewRegister } from "../components/NewRegister";
|
||||
import { subjects } from "../subjects";
|
||||
|
||||
export function DniGroup(props: {group: string, index: number}) {
|
||||
const [dnis, setDnis] = createSignal<Array<string>>([]);
|
||||
const dnis = () => [...props.group.matchAll(/\d+/g)];
|
||||
|
||||
onMount(() => {
|
||||
setDnis([...props.group.matchAll(/\d+/g)].map((x) => x.toString()));
|
||||
});
|
||||
|
||||
const removeDni = (dni: string) => {
|
||||
setDnis((prev) => prev.filter((x) => x !== dni));
|
||||
};
|
||||
console.log("Loading group...");
|
||||
|
||||
return (
|
||||
<div class=" grid-cols-[53rem_auto] gap-2 my-8">
|
||||
@ -27,7 +19,7 @@ export function DniGroup(props: {group: string, index: number}) {
|
||||
</div>
|
||||
|
||||
<For each={dnis()}>
|
||||
{(dni) => <DniEntry dni={dni} remove={removeDni} />}
|
||||
{(dni) => <DniEntry dni={dni.toString()} />}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
@ -35,63 +27,7 @@ export function DniGroup(props: {group: string, index: number}) {
|
||||
<h2 class="font-medium text-xl text-c-success pb-2">
|
||||
Grupo #{props.index + 1} - cursos y fechas
|
||||
</h2>
|
||||
<hr />
|
||||
<CourseManager personAmount={dnis().length} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CourseManager(props: {personAmount: number}) {
|
||||
const [courses, setCourses] = createSignal<Array<[number, string]>>([]);
|
||||
|
||||
async function registerCourse(_: number, subjectId: number, date: string): Promise<null | string> {
|
||||
setCourses((x) => [...x, [subjectId, date]]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<NewRegister
|
||||
personId={0}
|
||||
onSuccess={() => {}}
|
||||
registerFn={registerCourse}
|
||||
labelText={"Seleccionar cursos"}
|
||||
/>
|
||||
<div class="p-4">
|
||||
<h2 class="mb-4 font-bold text-xl">Verificar y confirmar cursos</h2>
|
||||
<div class="">
|
||||
<For each={courses()}>
|
||||
{(course) => <Course
|
||||
id={course[0]}
|
||||
date={course[1]}
|
||||
onDelete={(id) => setCourses((x) => x.filter((y) => y[0] !== id))}
|
||||
/>}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="my-2 bg-c-primary text-c-on-primary px-4 py-2 rounded-md cursor-pointer
|
||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
type="button"
|
||||
>
|
||||
Registrar las <b>{props.personAmount} personas</b> en los <b>{courses().length} cursos</b>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Course(props: {id: number, date: string, onDelete: (id: number) => void}) {
|
||||
const courseName = () => subjects().find((x) => x.id === props.id)?.nombre ?? "NOT FOUND";
|
||||
|
||||
return (
|
||||
<div class="grid grid-cols-[auto_10rem_5rem] gap-2 hover:bg-c-surface-variant hover:text-c-on-surface-variant font-mono">
|
||||
<p>{courseName()}</p>
|
||||
<p>{props.date}</p>
|
||||
<div>
|
||||
<button class="underline" onclick={() => props.onDelete(props.id)}>Eliminar</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,13 +1,10 @@
|
||||
import { createSignal, onMount } from "solid-js";
|
||||
import { createSignal } from "solid-js";
|
||||
import { DniTable } from "./AulaVirtual/DniTable";
|
||||
import { Dnis } from "./AulaVirtual/Dnis";
|
||||
import { ensureColors } from "./components/colors";
|
||||
|
||||
export function CertsBatch() {
|
||||
const [dniGroups, setDniGroups] = createSignal<Array<string>>([]);
|
||||
|
||||
onMount(ensureColors);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 class="px-4 py-2 text-2xl font-bold">
|
||||
|
@ -1,24 +1,27 @@
|
||||
import { createSignal, onMount } from "solid-js";
|
||||
import { createSignal } from "solid-js";
|
||||
import { Search } from "./components/Search";
|
||||
import { Person } from "../types/Person";
|
||||
import { Registers } from "./components/Registers";
|
||||
import { NewRegister } from "./components/NewRegister";
|
||||
import { ensureColors } from "./components/colors";
|
||||
|
||||
export function Certs() {
|
||||
const [person, setPerson] = createSignal<Person | null>(null);
|
||||
const [lastUpdate, setLastUpdate] = createSignal(0);
|
||||
|
||||
onMount(ensureColors);
|
||||
|
||||
return (
|
||||
<div class="grid grid-cols-[18rem_25rem_1fr]">
|
||||
<div>
|
||||
<h1 class="px-4 py-2 text-2xl font-bold">
|
||||
Registrar certificado
|
||||
</h1>
|
||||
<Search setPerson={setPerson}/>
|
||||
<NewRegister
|
||||
personId={person()?.id ?? null}
|
||||
onSuccess={() => setLastUpdate((x) => x + 1)}
|
||||
/>
|
||||
<Registers person={person()} lastUpdate={lastUpdate()} />
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<Registers person={person()} lastUpdate={lastUpdate()} />
|
||||
<NewRegister
|
||||
person={person()}
|
||||
onSuccess={() => setLastUpdate((x) => x + 1)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
import type { JSX } from "solid-js";
|
||||
|
||||
export function FilledCard(props: {children?: Array<JSX.Element> | JSX.Element, class?: string}) {
|
||||
return (
|
||||
<div class={`bg-c-surface-variant text-c-on-surface-variant rounded-xl m-2 shadow ${props.class ?? ""}`}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,83 +1,16 @@
|
||||
import { createSignal, Show } from "solid-js";
|
||||
import { createSignal, onMount, Show } from "solid-js";
|
||||
import type { CursoGIE } from "../../model/CursoGIE/cursoGIE.entity";
|
||||
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";
|
||||
import { Person } from "src/types/Person";
|
||||
|
||||
|
||||
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
|
||||
submitter: HTMLElement;
|
||||
}>;
|
||||
|
||||
|
||||
async function defaultNewRegisterFn(personId: number, subjectId: number, date: string): Promise<null | string> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
type RegisterFn = (personId: number, subjectId: number, date: string) => Promise<null | string>;
|
||||
|
||||
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<TabType>("Manual");
|
||||
const [selections, setSelections] = createSignal<Array<[number, string]>>([]);
|
||||
|
||||
const onRegister = () => {
|
||||
setSelections([]);
|
||||
props.onSuccess();
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="h-screen overflow-y-scroll">
|
||||
<FilledCard class="border border-c-outline overflow-hidden">
|
||||
<h2 class="p-4 font-bold text-xl">{props.labelText ?? defaultLabelText}</h2>
|
||||
|
||||
<RegisterTabs active={active()} setActive={setActive} />
|
||||
|
||||
<div class="bg-c-surface p-4 h-[22rem]">
|
||||
<Show when={active() === "Presets"}>
|
||||
<p>Proximamente...</p>
|
||||
</Show>
|
||||
<Show when={active() === "Manual"}>
|
||||
<ManualCerts personId={props.personId} onAdd={(v) => setSelections((x) => [...x, v])} />
|
||||
</Show>
|
||||
</div>
|
||||
</FilledCard>
|
||||
|
||||
<RegisterPreview
|
||||
selections={selections()}
|
||||
personId={props.personId}
|
||||
onDelete={(deleteId) => setSelections((s) => [...s.filter(([id]) => id !== deleteId)])}
|
||||
onRegister={onRegister}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export function NewRegister(props: {person: Person | null, onSuccess: () => void}) {
|
||||
const [subjects, setSubjects] = createSignal<Array<CursoGIE>>([]);
|
||||
const [error, setError] = createSignal("");
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
// Used to update SearchableSelect.tsx manually
|
||||
@ -94,6 +27,23 @@ export function NewRegister(props: {
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
// Loads subjects from DB
|
||||
onMount(async() => {
|
||||
console.log("Retrieve all subjects");
|
||||
|
||||
try {
|
||||
const response = await fetch("/subject/");
|
||||
if (response.ok) {
|
||||
setSubjects(await response.json());
|
||||
} else {
|
||||
setError("No se pudo cargar cursos");
|
||||
}
|
||||
} catch (e) {
|
||||
setError(`No se pudo cargar cursos. ${JSON.stringify(e)}`);
|
||||
}
|
||||
});
|
||||
|
||||
const register: HTMLEventFn = async(ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
@ -115,17 +65,24 @@ export function NewRegister(props: {
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const result = await (props.registerFn ?? defaultNewRegisterFn)(
|
||||
props.personId ?? -1,
|
||||
subject,
|
||||
date,
|
||||
);
|
||||
const response = await fetch("/certificate", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
personId: props.person?.id ?? -1,
|
||||
subjectId: subject,
|
||||
date,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result === null) {
|
||||
if (response.ok) {
|
||||
props.onSuccess();
|
||||
setCount((x) => x + 1);
|
||||
} else {
|
||||
setError(result);
|
||||
const data = await response.json();
|
||||
setError(JSON.stringify(data));
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
@ -133,9 +90,9 @@ export function NewRegister(props: {
|
||||
|
||||
return (
|
||||
<div class="p-4">
|
||||
<h2 class="mb-4 font-bold text-xl">{props.labelText ?? defaultLabelText}</h2>
|
||||
<h2 class="mb-4 font-bold text-xl">3. Crear nuevos registros</h2>
|
||||
|
||||
<Show when={props.personId !== -1}>
|
||||
<Show when={props.person?.id !== -1}>
|
||||
<form
|
||||
class="px-4 grid"
|
||||
style={{"grid-template-columns": "30rem 12rem 10rem auto", "grid-column-gap": "1rem"}}
|
||||
@ -176,104 +133,3 @@ export function NewRegister(props: {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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 (
|
||||
<div class="grid grid-cols-2">
|
||||
<button
|
||||
class={`py-2 border-b-4 ${presetsClasses()}`}
|
||||
onclick={() => props.setActive("Presets")}
|
||||
>
|
||||
Presets
|
||||
</button>
|
||||
<button
|
||||
class={`py-2 border-b-4 ${manualClasses()}`}
|
||||
onclick={() => props.setActive("Manual")}
|
||||
>
|
||||
Manual
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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<number | null>(null);
|
||||
|
||||
const datePicker = (
|
||||
<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) => {
|
||||
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 <SearchableSelect> component
|
||||
setCount((x) => x + 1);
|
||||
};
|
||||
|
||||
console.log(`Person ID: ${props.personId}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onsubmit={register}>
|
||||
<div>
|
||||
<SearchableSelect
|
||||
subjects={subjects()}
|
||||
onChange={setSelectedSubject}
|
||||
count={count()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="relative my-4">
|
||||
{datePicker}
|
||||
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">Fecha</label>
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="bg-c-primary text-c-on-primary px-4 py-2 rounded-full cursor-pointer
|
||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
type="submit"
|
||||
value="Agregar"
|
||||
disabled={props.personId === null}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<p
|
||||
class="my-2 p-1 rounded w-fit mx-4 bg-c-error text-c-on-error"
|
||||
style={{opacity: error() === "" ? "0" : "1", "user-select": "none"}}
|
||||
>
|
||||
{error()}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
|
||||
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 (
|
||||
<FilledCard class="border border-c-outline overflow-hidden">
|
||||
<h2 class="p-4 font-bold text-xl">Confirmar registro</h2>
|
||||
<div class="bg-c-surface p-4">
|
||||
<For each={props.selections}>
|
||||
{([courseId, date]) => <Register courseId={courseId} date={date} onDelete={props.onDelete} />}
|
||||
</For>
|
||||
|
||||
<button
|
||||
class="bg-c-primary text-c-on-primary px-4 py-2 rounded-full cursor-pointer mt-4
|
||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
type="button"
|
||||
disabled={props.selections.length === 0}
|
||||
onclick={submit}
|
||||
>
|
||||
Registrar los {props.selections.length} cursos
|
||||
</button>
|
||||
</div>
|
||||
</FilledCard>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div class="grid grid-cols-[auto_4rem_1.5rem] py-1 px-2 rounded-md border border-c-outline my-1">
|
||||
{courseName()}
|
||||
<span class="font-mono">{isoDateToLocalDate(props.date)}</span>
|
||||
<button
|
||||
class="hover:bg-c-surface-variant rounded-md"
|
||||
onclick={() => props.onDelete(props.courseId)}
|
||||
>
|
||||
<XIcon fill="var(--c-on-surface)" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
async function defaultNewRegisterFn(personId: number, subjectId: number, date: string): Promise<null | string> {
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { createEffect, createSignal, For } from "solid-js";
|
||||
import { createEffect, createMemo, createSignal, For, JSX, Show } from "solid-js";
|
||||
import type {CursoGIE} from "../../../model/CursoGIE/cursoGIE.entity";
|
||||
import { isServer } from "solid-js/web";
|
||||
|
||||
@ -15,14 +15,7 @@ export function SearchableSelect(props: {
|
||||
const inputEl = ev.target as HTMLInputElement;
|
||||
// Clear current selection
|
||||
setSelected(null);
|
||||
|
||||
let filter: string = inputEl.value.toLowerCase();
|
||||
filter = filter.replace("á", "a");
|
||||
filter = filter.replace("é", "e");
|
||||
filter = filter.replace("í", "i");
|
||||
filter = filter.replace("ó", "o");
|
||||
filter = filter.replace("ú", "u");
|
||||
setFilter(filter);
|
||||
setFilter(inputEl.value.toLowerCase());
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
@ -65,36 +58,23 @@ export function SearchableSelect(props: {
|
||||
});
|
||||
}
|
||||
|
||||
const filteredOptions = () => {
|
||||
const filterText = filter();
|
||||
|
||||
return props.subjects.filter((subject) => {
|
||||
let subjectText = subject.nombre.toLowerCase();
|
||||
subjectText = subjectText.replace("á", "a");
|
||||
subjectText = subjectText.replace("é", "e");
|
||||
subjectText = subjectText.replace("í", "i");
|
||||
subjectText = subjectText.replace("ó", "o");
|
||||
subjectText = subjectText.replace("ú", "u");
|
||||
|
||||
return selected() === null && subjectText.indexOf(filterText) !== -1;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{inputElement}
|
||||
<br/>
|
||||
<div
|
||||
class="border-c-outline border-2 rounded overflow-y-scroll h-[10rem]"
|
||||
class="border-c-outline border-2 rounded overflow-y-scroll"
|
||||
style={{"max-height": "18rem", "min-height": "12rem"}}
|
||||
>
|
||||
<For each={filteredOptions()}>
|
||||
<For each={props.subjects}>
|
||||
{(s) => (
|
||||
<button
|
||||
class="w-full text-left py-1 px-2
|
||||
hover:bg-c-primary-container hover:text-c-on-primary-container"
|
||||
class={`w-full text-left py-1 px-2
|
||||
hover:bg-c-primary-container hover:text-c-on-primary-container
|
||||
${s.nombre.toLowerCase().indexOf(filter()) !== -1 && selected() === null ? "block" : "hidden"}`}
|
||||
onclick={(ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
console.log("Click! :D");
|
||||
setSelected(s.id);
|
||||
setInputValue(s.nombre);
|
||||
}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Show, createEffect, createSignal, For, JSX, createMemo } from "solid-js";
|
||||
import { Show, createEffect, createSignal, For, JSX } from "solid-js";
|
||||
import { Person } from "../../types/Person";
|
||||
import { RegisterReturn } from "../../types/RegisterReturn";
|
||||
|
||||
@ -33,12 +33,57 @@ export function Registers(props: { person: Person | null, lastUpdate: number })
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const sortedRegisters = createMemo(() => registers().sort((r1, r2) => ((r1.fecha_inscripcion < r2.fecha_inscripcion) ? 1 : -1)));
|
||||
|
||||
return (
|
||||
<div class="p-4">
|
||||
<h2 class="mb-4 font-bold text-xl">2. Revisar registros actuales</h2>
|
||||
|
||||
<div class="px-4">
|
||||
<Show when={props.person !== null}>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
disabled
|
||||
value={props.person?.nombreCompleto}
|
||||
class="bg-c-background text-c-on-background border-c-outline border rounded px-2 py-1
|
||||
disabled:cursor-text w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[3fr_3fr_2fr_2fr_1fr_1fr] gap-1">
|
||||
<CopyButton
|
||||
copyText={`${props.person!.nombres} ${props.person!.apellidoPaterno} ${props.person!.apellidoMaterno}`}
|
||||
>
|
||||
NOM <b>AP</b>
|
||||
<i class="ph ph-clipboard-text text-2xl align-middle ml-2"></i>
|
||||
</CopyButton>
|
||||
<CopyButton
|
||||
copyText={`${props.person!.apellidoPaterno} ${props.person!.apellidoMaterno} ${props.person!.nombres}`}
|
||||
>
|
||||
<b>AP</b> NOM
|
||||
<i class="ph ph-clipboard-text text-2xl align-middle ml-2"></i>
|
||||
</CopyButton>
|
||||
<CopyButton
|
||||
copyText={`${props.person!.nombres}`}
|
||||
>
|
||||
NOM
|
||||
</CopyButton>
|
||||
<CopyButton
|
||||
copyText={`${props.person!.apellidoPaterno} ${props.person!.apellidoMaterno}`}
|
||||
>
|
||||
<b>AP</b>
|
||||
</CopyButton>
|
||||
<CopyButton
|
||||
copyText={`${props.person!.apellidoPaterno}`}
|
||||
>
|
||||
<b><i>Ap</i></b>
|
||||
</CopyButton>
|
||||
<CopyButton
|
||||
copyText={`${props.person!.apellidoMaterno}`}
|
||||
>
|
||||
<b><i>Am</i></b>
|
||||
</CopyButton>
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="my-2"
|
||||
style={{ display: loading() ? "block" : "none" }}
|
||||
@ -60,7 +105,7 @@ export function Registers(props: { person: Person | null, lastUpdate: number })
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<For each={sortedRegisters()}>
|
||||
<For each={registers().sort((r1, r2) => ((r1.fecha_actual < r2.fecha_actual) ? 1 : -1))}>
|
||||
{(register) => <Register cert={register} onUpdate={loadCertificates} />}
|
||||
</For>
|
||||
</tbody>
|
||||
|
@ -3,9 +3,6 @@ import { JSX } from "solid-js/jsx-runtime";
|
||||
import { Person } from "../../types/Person";
|
||||
import { RegisterPerson } from "./Search/RegisterPerson";
|
||||
import QR from "qrcode";
|
||||
import { CopyIcon } from "../icons/CopyIcon";
|
||||
import { MagnifyingGlassIcon } from "../icons/MagnifyingGlassIcon";
|
||||
import { XIcon } from "../icons/XIcon";
|
||||
|
||||
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
|
||||
submitter: HTMLElement;
|
||||
@ -22,14 +19,11 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
||||
const [error, setError] = createSignal("");
|
||||
const [warning, setWarning] = createSignal("");
|
||||
const [qrBase64, setQrBase64] = createSignal<string | null>(null);
|
||||
const [persona, setPersona] = createSignal<Person | null>(null);
|
||||
|
||||
// Update QR
|
||||
createEffect(() => {
|
||||
if (dni() !== "") {
|
||||
// Old URL: https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()}
|
||||
// New URL: https://eegsac.com/certificado/${dni()}
|
||||
QR.toDataURL(`https://eegsac.com/certificado/${dni()}`, {margin: 1}, (err, res) => {
|
||||
QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()}`, {margin: 1}, (err, res) => {
|
||||
if (err) {
|
||||
console.error("Error creating QR code");
|
||||
return;
|
||||
@ -57,7 +51,6 @@ 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);
|
||||
@ -74,55 +67,30 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
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 (
|
||||
<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 class="p-4 grid" style={{"grid-template-columns": "20rem auto"}}>
|
||||
<div>
|
||||
<h2 class="my-2 font-bold text-xl">1. Buscar persona</h2>
|
||||
<form onSubmit={searchDNI} class="px-4">
|
||||
<label for="search-dni">DNI</label>
|
||||
<br />
|
||||
<InputBox dni={dni()} setDni={setDni} loading={loading()} />
|
||||
|
||||
<br />
|
||||
<input
|
||||
class="bg-c-primary text-c-on-primary px-4 py-2 rounded-md cursor-pointer
|
||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
type="submit"
|
||||
value="Buscar"
|
||||
disabled={loading()}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<p
|
||||
class="my-2 p-1 rounded w-fit mx-4 bg-c-error text-c-on-error"
|
||||
style={{display: error() === "" ? "none" : "block"}}
|
||||
>
|
||||
Error:
|
||||
Error:
|
||||
<br />
|
||||
{error()}
|
||||
</p>
|
||||
@ -136,45 +104,13 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
|
||||
{warning()}
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<div class={`${persona() === null ? "opacity-50 cursor-not-allowed" : ""}`}>
|
||||
<MaterialLabel text={persona()?.apellidoPaterno ?? null} resource="Apellido Paterno" />
|
||||
<MaterialLabel text={persona()?.apellidoMaterno ?? null} resource="Apellido Materno" />
|
||||
<MaterialLabel text={persona()?.nombres ?? null} resource="Nombres" />
|
||||
|
||||
<div class={"relative max-w-[14rem] mx-auto my-6"}>
|
||||
<CopyButton copyText={nombresYApellidos()}>
|
||||
<CopyIcon fill="var(--c-on-primary)" />
|
||||
|
||||
Nombres y <b>Apellidos</b>
|
||||
</CopyButton>
|
||||
|
||||
<CopyButton copyText={apellidosYNombres()}>
|
||||
<CopyIcon fill="var(--c-on-primary)" />
|
||||
|
||||
<b>Apellidos</b> y Nombres
|
||||
</CopyButton>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<CopyButton copyText={apellidos()}>
|
||||
<CopyIcon fill="var(--c-on-primary)" />
|
||||
|
||||
<b>Apellidos</b>
|
||||
</CopyButton>
|
||||
<CopyButton copyText={nombres()}>
|
||||
<CopyIcon fill="var(--c-on-primary)" />
|
||||
|
||||
Nombres
|
||||
</CopyButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={warning() !== ""}>
|
||||
<RegisterPerson dni={dni()} onSuccess={search} />
|
||||
</Show>
|
||||
</div>
|
||||
<div>
|
||||
<img src={qrBase64() ?? ""} height="150" width="150" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -184,10 +120,12 @@ function InputBox(props: {
|
||||
dni: string,
|
||||
setDni: (v: string) => void,
|
||||
}) {
|
||||
const [successAnimation, setSuccessAnimation] = createSignal(false);
|
||||
|
||||
const inputElement = (
|
||||
<input
|
||||
id="search-dni"
|
||||
class="bg-c-background text-c-on-background border-c-outline border-2 rounded px-2 py-1 w-full
|
||||
class="bg-c-background text-c-on-background border-c-outline border-2 rounded px-2 py-1
|
||||
invalid:border-c-error invalid:text-c-error
|
||||
focus:border-c-primary outline-none font-mono
|
||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
@ -208,6 +146,8 @@ function InputBox(props: {
|
||||
|
||||
if (props.dni.length === 8) {
|
||||
navigator.clipboard.writeText(props.dni);
|
||||
setSuccessAnimation(true);
|
||||
setTimeout(() => setSuccessAnimation(false), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
@ -219,86 +159,18 @@ function InputBox(props: {
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="relative max-w-[14rem] mx-auto">
|
||||
<div class="grid gap-2 grid-cols-[10rem_3rem_3rem]">
|
||||
{inputElement}
|
||||
<label for="search-dni" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">DNI</label>
|
||||
<button
|
||||
class="absolute top-1 right-[3.75rem] rounded hover:bg-c-surface-variant"
|
||||
>
|
||||
<MagnifyingGlassIcon fill="var(--c-on-surface)" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-1 right-8 rounded hover:bg-c-surface-variant"
|
||||
class={`${successAnimation() ? "bg-c-success" : "bg-c-primary"} rounded transition-colors`}
|
||||
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)" />
|
||||
<i class={`${successAnimation() ? "text-c-on-success" : "text-c-on-primary"} ph ph-clipboard-text text-2xl align-middle`}></i>
|
||||
</button>
|
||||
<button class="bg-c-error rounded" onclick={clearDni} type="button">
|
||||
<i class="ph ph-trash text-2xl text-c-on-primary align-middle"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function MaterialLabel(props: {text: string | null, resource: string}) {
|
||||
const copyToClipboard: HTMLButtonEvent = (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
if (props.text !== null) {
|
||||
navigator.clipboard.writeText(props.text);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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>
|
||||
<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"
|
||||
>
|
||||
{props.text ?? ""}
|
||||
<span class="select-none"> </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={
|
||||
`${successAnimation() ? "bg-c-success text-c-on-success" : "bg-c-primary text-c-on-primary"
|
||||
} rounded-lg transition-colors py-1 my-1 relative overflow-hidden inline-block w-full`
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,166 +0,0 @@
|
||||
import { isServer } from "solid-js/web";
|
||||
|
||||
const accentOrange = "";
|
||||
|
||||
const accentGreen = `
|
||||
:root {
|
||||
--c-primary: #7cdc6d;
|
||||
--c-on-primary: #003a02;
|
||||
--c-primary-container: #005304;
|
||||
--c-on-primary-container: #97f986;
|
||||
--c-background: #1a1c18;
|
||||
--c-on-background: #e2e3dd;
|
||||
--c-surface: #1a1c18;
|
||||
--c-on-surface: #e2e3dd;
|
||||
--c-outline: #8d9387;
|
||||
--c-surface-variant: #43483f;
|
||||
--c-on-surface-variant: #c3c8bc;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--c-primary: #006e08;
|
||||
--c-on-primary: #ffffff;
|
||||
--c-primary-container: #97f986;
|
||||
--c-on-primary-container: #002201;
|
||||
--c-background: #fcfdf6;
|
||||
--c-on-background: #1a1c18;
|
||||
--c-surface: #fcfdf6;
|
||||
--c-on-surface: #1a1c18;
|
||||
--c-outline: #73796e;
|
||||
--c-surface-variant: #dfe4d8;
|
||||
--c-on-surface-variant: #43483f;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const accentBlue = `
|
||||
:root {
|
||||
--c-primary: #adc6ff;
|
||||
--c-on-primary: #002e69;
|
||||
--c-primary-container: #0e448e;
|
||||
--c-on-primary-container: #d8e2ff;
|
||||
--c-background: #1b1b1f;
|
||||
--c-on-background: #e3e2e6;
|
||||
--c-surface: #1b1b1f;
|
||||
--c-on-surface: #e3e2e6;
|
||||
--c-outline: #8e9099;
|
||||
--c-surface-variant: #44474f;
|
||||
--c-on-surface-variant: #c4c6d0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--c-primary: #315da8;
|
||||
--c-on-primary: #ffffff;
|
||||
--c-primary-container: #d8e2ff;
|
||||
--c-on-primary-container: #001a41;
|
||||
--c-background: #fefbff;
|
||||
--c-on-background: #1b1b1f;
|
||||
--c-surface: #fefbff;
|
||||
--c-on-surface: #1b1b1f;
|
||||
--c-outline: #74777f;
|
||||
--c-surface-variant: #e1e2ec;
|
||||
--c-on-surface-variant: #44474f;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const accentYellow = `
|
||||
:root {
|
||||
--c-primary: #f5bf31;
|
||||
--c-on-primary: #3f2e00;
|
||||
--c-primary-container: #5b4300;
|
||||
--c-on-primary-container: #ffdf9b;
|
||||
--c-background: #1e1b16;
|
||||
--c-on-background: #e9e1d9;
|
||||
--c-surface: #1e1b16;
|
||||
--c-on-surface: #e9e1d9;
|
||||
--c-outline: #999080;
|
||||
--c-surface-variant: #4d4639;
|
||||
--c-on-surface-variant: #d0c5b4;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--c-primary: #785a00;
|
||||
--c-on-primary: #ffffff;
|
||||
--c-primary-container: #ffdf9b;
|
||||
--c-on-primary-container: #251a00;
|
||||
--c-background: #fffbff;
|
||||
--c-on-background: #1e1b16;
|
||||
--c-surface: #fffbff;
|
||||
--c-on-surface: #1e1b16;
|
||||
--c-outline: #7f7667;
|
||||
--c-surface-variant: #ede1cf;
|
||||
--c-on-surface-variant: #4d4639;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const accentPink = `
|
||||
:root {
|
||||
--c-primary: #ffade5;
|
||||
--c-on-primary: #5e0051;
|
||||
--c-primary-container: #80156e;
|
||||
--c-on-primary-container: #ffd7ef;
|
||||
--c-background: #1f1a1d;
|
||||
--c-on-background: #eae0e3;
|
||||
--c-surface: #1f1a1d;
|
||||
--c-on-surface: #eae0e3;
|
||||
--c-outline: #9b8d94;
|
||||
--c-surface-variant: #4f444a;
|
||||
--c-on-surface-variant: #d2c2ca;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--c-primary: #9d3288;
|
||||
--c-on-primary: #ffffff;
|
||||
--c-primary-container: #ffd7ef;
|
||||
--c-on-primary-container: #3a0031;
|
||||
--c-background: #fffbff;
|
||||
--c-on-background: #1f1a1d;
|
||||
--c-surface: #fffbff;
|
||||
--c-on-surface: #1f1a1d;
|
||||
--c-outline: #81737a;
|
||||
--c-surface-variant: #efdee6;
|
||||
--c-on-surface-variant: #4f444a;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const ensureColors = () => {
|
||||
if (!isServer) {
|
||||
console.log("Running in the client!");
|
||||
|
||||
const colorScheme = localStorage.getItem("color-scheme") ?? "orange";
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
|
||||
switch (colorScheme) {
|
||||
case "orange": {
|
||||
styleEl.innerHTML = accentOrange;
|
||||
break;
|
||||
}
|
||||
case "green": {
|
||||
styleEl.innerHTML = accentGreen;
|
||||
break;
|
||||
}
|
||||
case "blue": {
|
||||
styleEl.innerHTML = accentBlue;
|
||||
break;
|
||||
}
|
||||
case "yellow": {
|
||||
styleEl.innerHTML = accentYellow;
|
||||
break;
|
||||
}
|
||||
case "pink": {
|
||||
styleEl.innerHTML = accentPink;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
};
|
@ -1,5 +0,0 @@
|
||||
export function CopyIcon(props: {fill: string}) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill={props.fill} viewBox="0 0 256 256"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function DocxIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill="var(--c-on-surface-variant)" viewBox="0 0 256 256"><path d="M52,144H36a8,8,0,0,0-8,8v56a8,8,0,0,0,8,8H52a36,36,0,0,0,0-72Zm0,56H44V160h8a20,20,0,0,1,0,40Zm169.53-4.91a8,8,0,0,1,.25,11.31A30.06,30.06,0,0,1,200,216c-17.65,0-32-16.15-32-36s14.35-36,32-36a30.06,30.06,0,0,1,21.78,9.6,8,8,0,0,1-11.56,11.06A14.24,14.24,0,0,0,200,160c-8.82,0-16,9-16,20s7.18,20,16,20a14.24,14.24,0,0,0,10.22-4.66A8,8,0,0,1,221.53,195.09ZM128,144c-17.65,0-32,16.15-32,36s14.35,36,32,36,32-16.15,32-36S145.65,144,128,144Zm0,56c-8.82,0-16-9-16-20s7.18-20,16-20,16,9,16,20S136.82,200,128,200ZM48,120a8,8,0,0,0,8-8V40h88V88a8,8,0,0,0,8,8h48v16a8,8,0,0,0,16,0V88a8,8,0,0,0-2.34-5.66l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40v72A8,8,0,0,0,48,120ZM160,51.31,188.69,80H160Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function DownloadIcon(props: {fill: string}) {
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function HomeIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill="var(--c-on-surface-variant)" viewBox="0 0 256 256"><path d="M218.83,103.77l-80-75.48a1.14,1.14,0,0,1-.11-.11,16,16,0,0,0-21.53,0l-.11.11L37.17,103.77A16,16,0,0,0,32,115.55V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V115.55A16,16,0,0,0,218.83,103.77ZM208,208H48V115.55l.11-.1L128,40l79.9,75.43.11.1Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function KeyIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill="var(--c-on-surface-variant)" viewBox="0 0 256 256"><path d="M48,56V200a8,8,0,0,1-16,0V56a8,8,0,0,1,16,0Zm84,54.5L112,117V96a8,8,0,0,0-16,0v21L76,110.5a8,8,0,0,0-5,15.22l20,6.49-12.34,17a8,8,0,1,0,12.94,9.4l12.34-17,12.34,17a8,8,0,1,0,12.94-9.4l-12.34-17,20-6.49A8,8,0,0,0,132,110.5ZM238,115.64A8,8,0,0,0,228,110.5L208,117V96a8,8,0,0,0-16,0v21l-20-6.49a8,8,0,0,0-4.95,15.22l20,6.49-12.34,17a8,8,0,1,0,12.94,9.4l12.34-17,12.34,17a8,8,0,1,0,12.94-9.4l-12.34-17,20-6.49A8,8,0,0,0,238,115.64Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function MagnifyingGlassIcon(props: {fill: string}) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill={props.fill} viewBox="0 0 256 256"><path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function PaletteIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill="var(--c-on-surface-variant)" viewBox="0 0 256 256" style="--darkreader-inline-fill: #000000;" data-darkreader-inline-fill=""><path d="M200.77,53.89A103.27,103.27,0,0,0,128,24h-1.07A104,104,0,0,0,24,128c0,43,26.58,79.06,69.36,94.17A32,32,0,0,0,136,192a16,16,0,0,1,16-16h46.21a31.81,31.81,0,0,0,31.2-24.88,104.43,104.43,0,0,0,2.59-24A103.28,103.28,0,0,0,200.77,53.89Zm13,93.71A15.89,15.89,0,0,1,198.21,160H152a32,32,0,0,0-32,32,16,16,0,0,1-21.31,15.07C62.49,194.3,40,164,40,128a88,88,0,0,1,87.09-88h.9a88.35,88.35,0,0,1,88,87.25A88.86,88.86,0,0,1,213.81,147.6ZM140,76a12,12,0,1,1-12-12A12,12,0,0,1,140,76ZM96,100A12,12,0,1,1,84,88,12,12,0,0,1,96,100Zm0,56a12,12,0,1,1-12-12A12,12,0,0,1,96,156Zm88-56a12,12,0,1,1-12-12A12,12,0,0,1,184,100Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function ScanIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill="var(--c-on-surface-variant)" viewBox="0 0 256 256"><path d="M224,40V80a8,8,0,0,1-16,0V48H176a8,8,0,0,1,0-16h40A8,8,0,0,1,224,40ZM80,208H48V176a8,8,0,0,0-16,0v40a8,8,0,0,0,8,8H80a8,8,0,0,0,0-16Zm136-40a8,8,0,0,0-8,8v32H176a8,8,0,0,0,0,16h40a8,8,0,0,0,8-8V176A8,8,0,0,0,216,168ZM40,88a8,8,0,0,0,8-8V48H80a8,8,0,0,0,0-16H40a8,8,0,0,0-8,8V80A8,8,0,0,0,40,88Zm128,96H88a16,16,0,0,1-16-16V88A16,16,0,0,1,88,72h80a16,16,0,0,1,16,16v80A16,16,0,0,1,168,184ZM88,168h80V88H88Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function StackIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill="var(--c-on-surface-variant)" viewBox="0 0 256 256"><path d="M230.91,172A8,8,0,0,1,228,182.91l-96,56a8,8,0,0,1-8.06,0l-96-56A8,8,0,0,1,36,169.09l92,53.65,92-53.65A8,8,0,0,1,230.91,172ZM220,121.09l-92,53.65L36,121.09A8,8,0,0,0,28,134.91l96,56a8,8,0,0,0,8.06,0l96-56A8,8,0,1,0,220,121.09ZM24,80a8,8,0,0,1,4-6.91l96-56a8,8,0,0,1,8.06,0l96,56a8,8,0,0,1,0,13.82l-96,56a8,8,0,0,1-8.06,0l-96-56A8,8,0,0,1,24,80Zm23.88,0L128,126.74,208.12,80,128,33.26Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function TrashIcon(props: {fill: string}) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill={props.fill} viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
export function XIcon(props: {fill: string}) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block w-6" fill={props.fill} viewBox="0 0 256 256"><path d="M165.66,101.66,139.31,128l26.35,26.34a8,8,0,0,1-11.32,11.32L128,139.31l-26.34,26.35a8,8,0,0,1-11.32-11.32L116.69,128,90.34,101.66a8,8,0,0,1,11.32-11.32L128,116.69l26.34-26.35a8,8,0,0,1,11.32,11.32ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"></path></svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
import { createSignal } from "solid-js";
|
||||
import { CursoGIE } from "src/model/CursoGIE/cursoGIE.entity";
|
||||
|
||||
export const [subjects, setSubjects] = createSignal<Array<CursoGIE>>([]);
|
||||
|
||||
(async() => {
|
||||
try {
|
||||
const response = await fetch("/subject/");
|
||||
if (response.ok) {
|
||||
setSubjects(await response.json());
|
||||
}
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
}
|
||||
})();
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 353 KiB |
Loading…
Reference in New Issue
Block a user