Compare commits

..

No commits in common. "246bec540cc002593f34a2f690ab8b01e2442545" and "40914aab1ae6cf60d3421d0b825c4625351c5e3d" have entirely different histories.

31 changed files with 10631 additions and 888 deletions

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import {
AlignmentType, AlignmentType,
BorderStyle, BorderStyle,
} from "docx"; } from "docx";
import { cmText, createSimpleTextP, getImage, getQR } from "./utils"; import { cmText, createSimpleText, createSimpleTextP, getImage, getQR } from "./utils";
import { CertData } from "./CertData"; import { CertData } from "./CertData";
const imgFondoDoc = getImage({ const imgFondoDoc = getImage({
@ -30,7 +30,7 @@ const imgEATE = getImage({
height: 2.28, height: 2.28,
width: 3.39, width: 3.39,
horizontalOffset: 0.94, horizontalOffset: 0.94,
verticalOffset: 15.67, verticalOffset: 13.77,
}); });
const imgMTC = getImage({ const imgMTC = getImage({
@ -38,7 +38,7 @@ const imgMTC = getImage({
height: 1.6, height: 1.6,
width: 5, width: 5,
horizontalOffset: 0.9, horizontalOffset: 0.9,
verticalOffset: 18.3, verticalOffset: 17.22,
}); });
const imgOSHA = getImage({ const imgOSHA = getImage({
@ -73,7 +73,7 @@ const tCertificate = createSimpleTextP({
// Se expide el presente // Se expide el presente
const tLabel3 = createSimpleTextP({ const tLabel3 = createSimpleTextP({
xPosition: 11.08, xPosition: 11.08,
yPosition: 5.43, yPosition: 4.62,
width: 7.74, width: 7.74,
height: 0.5, height: 0.5,
text: "Se expide el presente a:", text: "Se expide el presente a:",
@ -84,7 +84,7 @@ const tLabel3 = createSimpleTextP({
// SUPERVISOR ESCOLTA MATPEL // SUPERVISOR ESCOLTA MATPEL
const tCourse = createSimpleTextP({ const tCourse = createSimpleTextP({
xPosition: 7.28, xPosition: 7.28,
yPosition: 8.75, yPosition: 7.94,
width: 15.42, width: 15.42,
height: 1.5, height: 1.5,
text: "SUPERVISOR ESCOLTA MATPEL", text: "SUPERVISOR ESCOLTA MATPEL",
@ -95,7 +95,7 @@ const tCourse = createSimpleTextP({
// Por haber aprobado la dormacion... // Por haber aprobado la dormacion...
const tLabel2 = createSimpleTextP({ const tLabel2 = createSimpleTextP({
xPosition: 10.93, xPosition: 10.93,
yPosition: 7.95, yPosition: 7.14,
width: 7.74, width: 7.74,
height: 0.6, height: 0.6,
text: "Por haber aprobado la formación en el curso", text: "Por haber aprobado la formación en el curso",
@ -106,7 +106,7 @@ const tLabel2 = createSimpleTextP({
// Temas tratados... // Temas tratados...
const tTopics = createSimpleTextP({ const tTopics = createSimpleTextP({
xPosition: 5.04, xPosition: 5.04,
yPosition: 10.3, yPosition: 9.49,
width: 19.34, width: 19.34,
height: 1.5, 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.", 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: // Respaldado por:
const tHours = createSimpleTextP({ const tHours = createSimpleTextP({
xPosition: 1.15, xPosition: 1.15,
yPosition: 15, yPosition: 13.15,
width: 3.07, width: 3.07,
height: 0.5, height: 0.5,
text: "Respaldado por:", text: "Respaldado por:",
@ -131,7 +131,7 @@ const tHours = createSimpleTextP({
// Chile // Chile
const tChile = createSimpleTextP({ const tChile = createSimpleTextP({
xPosition: 3.07, xPosition: 3.07,
yPosition: 17.83, yPosition: 15.98,
width: 1.43, width: 1.43,
height: 0.5, height: 0.5,
text: "CHILE", text: "CHILE",
@ -144,7 +144,7 @@ const tChile = createSimpleTextP({
// Se expide certificado... // Se expide certificado...
const tFinishLabel = createSimpleTextP({ const tFinishLabel = createSimpleTextP({
xPosition: 8.22, xPosition: 8.22,
yPosition: 12.68, yPosition: 11.87,
width: 13.15, width: 13.15,
height: 0.5, height: 0.5,
text: "Se expide el presente certificado para los fines que se estime conveniente", text: "Se expide el presente certificado para los fines que se estime conveniente",
@ -160,7 +160,7 @@ const photoSection = new Paragraph({
frame: { frame: {
position: { position: {
x: cmText(25), x: cmText(25),
y: cmText(5.2), y: cmText(4.37),
}, },
height: cmText(3.57), height: cmText(3.57),
width: cmText(2.81), width: cmText(2.81),
@ -199,13 +199,13 @@ export async function supervisorEscolta(props: CertData<null>): Promise<Buffer>
height: 2.5, height: 2.5,
width: 2.5, width: 2.5,
horizontalOffset: 25.85, horizontalOffset: 25.85,
verticalOffset: 17.49, verticalOffset: 15.88,
}); });
// FERNANDO ARAOZ // FERNANDO ARAOZ
const tName = createSimpleTextP({ const tName = createSimpleTextP({
xPosition: 3.78, xPosition: 3.78,
yPosition: 6.02, yPosition: 5.21,
width: 22.07, width: 22.07,
height: 1.5, height: 1.5,
text: props.personFullName, text: props.personFullName,
@ -219,7 +219,7 @@ export async function supervisorEscolta(props: CertData<null>): Promise<Buffer>
frame: { frame: {
position: { position: {
x: cmText(11.84), x: cmText(11.84),
y: cmText(7.35), y: cmText(6.54),
}, },
width: cmText(6.02), width: cmText(6.02),
height: cmText(0.6), height: cmText(0.6),
@ -249,7 +249,7 @@ export async function supervisorEscolta(props: CertData<null>): Promise<Buffer>
frame: { frame: {
position: { position: {
x: cmText(19.62), x: cmText(19.62),
y: cmText(19.2), y: cmText(17.58),
}, },
width: cmText(5.87), width: cmText(5.87),
height: cmText(0.75), height: cmText(0.75),
@ -277,7 +277,7 @@ export async function supervisorEscolta(props: CertData<null>): Promise<Buffer>
// N° XXXX-20XX-EEG // N° XXXX-20XX-EEG
const tCertCode = createSimpleTextP({ const tCertCode = createSimpleTextP({
xPosition: 24.59, xPosition: 24.59,
yPosition: 9, yPosition: 8.07,
width: 3.67, width: 3.67,
height: 0.5, height: 0.5,
text: `${props.certCode}-${props.certYear}-EEG`, text: `${props.certCode}-${props.certYear}-EEG`,

View File

@ -61,9 +61,7 @@ export async function getQR(data : {
verticalOffset: number, verticalOffset: number,
behindDocument?: boolean, behindDocument?: boolean,
}): Promise<ImageRun> { }): Promise<ImageRun> {
// Old URL: https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()} const qr = await QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${data.dni}&iid=${data.iid}`, {margin: 1});
// New URL: https://eegsac.com/certificado/${dni()}
const qr = await QR.toDataURL(`https://eegsac.com/certificado/${data.dni}?iid=${data.iid}`, {margin: 1});
return new ImageRun({ return new ImageRun({
data: qr, data: qr,

View File

@ -3,7 +3,7 @@ import { renderToString } from "solid-js/web";
import { CertsBatch } from "src/views/BatchCerts"; import { CertsBatch } from "src/views/BatchCerts";
import { template } from "./BatchCerts.template"; import { template } from "./BatchCerts.template";
@Controller("batch-mode") @Controller("batch-certs")
export class BatchCertController { export class BatchCertController {
@Get() @Get()
entry(): string { entry(): string {

View File

@ -9,7 +9,6 @@ export function template(ssr: string): string {
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/static/styles.css?t=${Date.now()}" /> <link rel="stylesheet" href="/static/styles.css?t=${Date.now()}" />
<link rel="icon" type="image/png" href="/static/favicon.png">
<!-- Phosphor icons --> <!-- Phosphor icons -->
<link <link
rel="stylesheet" rel="stylesheet"

View File

@ -184,7 +184,7 @@ export class CertificateService {
certificate.curso = data.subjectId; certificate.curso = data.subjectId;
certificate.codigo = await this.getNextRegisterCode(data.subjectId); certificate.codigo = await this.getNextRegisterCode(data.subjectId);
certificate.fecha_actual = new Date(); 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.curso_nombre = subject.nombre;
certificate.persona = person; certificate.persona = person;

View File

@ -8,7 +8,6 @@ export function template(ssr: string): string {
<title>Registrar certificados - EEGSAC</title> <title>Registrar certificados - EEGSAC</title>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <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()}" /> <link rel="stylesheet" href="/static/styles.css?t=${Date.now()}" />
<!-- Phosphor icons --> <!-- Phosphor icons -->
<link <link

View File

@ -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 { Person } from "src/types/Person";
import { DniRegister } from "./DniEntry/DniRegister"; import { DniRegister } from "./DniEntry/DniRegister";
import { RegisterReturn } from "src/types/RegisterReturn";
/** /**
* Sample data * Sample data
@ -28,11 +27,9 @@ enum Status {
Error, Error,
} }
export function DniEntry(props: {dni: string, remove: (_: string) => void}) { export function DniEntry(props: {dni: string}) {
const [person, setPerson] = createSignal<Person | null>(null); const [person, setPerson] = createSignal<Person | null>(null);
const [status, setStatus] = createSignal<Status>(Status.Empty); const [status, setStatus] = createSignal<Status>(Status.Empty);
const [certificates, setCertificates] = createSignal<Array<RegisterReturn>>([]);
const [certStatus, setCertStatus] = createSignal<Status>(Status.Empty);
const loadPerson = async() => { const loadPerson = async() => {
setStatus(Status.Loading); setStatus(Status.Loading);
@ -43,7 +40,7 @@ export function DniEntry(props: {dni: string, remove: (_: string) => void}) {
if (response.ok) { if (response.ok) {
setPerson(body); setPerson(body);
setStatus(Status.Ok); setStatus(Status.Ok);
loadCertificates();
} else if (response.status === 404) { } else if (response.status === 404) {
console.error(body); console.error(body);
setStatus(Status.Error); setStatus(Status.Error);
@ -61,23 +58,6 @@ export function DniEntry(props: {dni: string, remove: (_: string) => void}) {
onMount(loadPerson); 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 ( return (
<> <>
<Show when={status() !== Status.Error}> <Show when={status() !== Status.Error}>
@ -116,24 +96,11 @@ export function DniEntry(props: {dni: string, remove: (_: string) => void}) {
<div class="text-center"> <div class="text-center">
<button <button
class={"rounded-full px-2 hover:bg-c-error first:text-c-error first:hover:text-c-on-error"} 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> <i class="ph ph-x"></i>
</button> </button>
</div> </div>
<div class="border-l"> <div class="border-l"></div>
<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>
</Match> </Match>
</Switch> </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}&nbsp;
<span class="font-mono">
{cert.fecha_inscripcion}
</span>
</div>
)}
</For>
]
</div>
);
}

View File

@ -1,18 +1,10 @@
import { For, createSignal, onMount } from "solid-js"; import { For } from "solid-js";
import { DniEntry } from "./DniEntry"; import { DniEntry } from "./DniEntry";
import { NewRegister } from "../components/NewRegister";
import { subjects } from "../subjects";
export function DniGroup(props: {group: string, index: number}) { export function DniGroup(props: {group: string, index: number}) {
const [dnis, setDnis] = createSignal<Array<string>>([]); const dnis = () => [...props.group.matchAll(/\d+/g)];
onMount(() => { console.log("Loading group...");
setDnis([...props.group.matchAll(/\d+/g)].map((x) => x.toString()));
});
const removeDni = (dni: string) => {
setDnis((prev) => prev.filter((x) => x !== dni));
};
return ( return (
<div class=" grid-cols-[53rem_auto] gap-2 my-8"> <div class=" grid-cols-[53rem_auto] gap-2 my-8">
@ -27,7 +19,7 @@ export function DniGroup(props: {group: string, index: number}) {
</div> </div>
<For each={dnis()}> <For each={dnis()}>
{(dni) => <DniEntry dni={dni} remove={removeDni} />} {(dni) => <DniEntry dni={dni.toString()} />}
</For> </For>
</div> </div>
@ -35,63 +27,7 @@ export function DniGroup(props: {group: string, index: number}) {
<h2 class="font-medium text-xl text-c-success pb-2"> <h2 class="font-medium text-xl text-c-success pb-2">
Grupo #{props.index + 1} - cursos y fechas Grupo #{props.index + 1} - cursos y fechas
</h2> </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>
</div> </div>
); );

View File

@ -1,13 +1,10 @@
import { createSignal, onMount } from "solid-js"; import { createSignal } from "solid-js";
import { DniTable } from "./AulaVirtual/DniTable"; import { DniTable } from "./AulaVirtual/DniTable";
import { Dnis } from "./AulaVirtual/Dnis"; import { Dnis } from "./AulaVirtual/Dnis";
import { ensureColors } from "./components/colors";
export function CertsBatch() { export function CertsBatch() {
const [dniGroups, setDniGroups] = createSignal<Array<string>>([]); const [dniGroups, setDniGroups] = createSignal<Array<string>>([]);
onMount(ensureColors);
return ( return (
<div> <div>
<h1 class="px-4 py-2 text-2xl font-bold"> <h1 class="px-4 py-2 text-2xl font-bold">

View File

@ -1,24 +1,27 @@
import { createSignal, onMount } from "solid-js"; import { createSignal } from "solid-js";
import { Search } from "./components/Search"; import { Search } from "./components/Search";
import { Person } from "../types/Person"; import { Person } from "../types/Person";
import { Registers } from "./components/Registers"; import { Registers } from "./components/Registers";
import { NewRegister } from "./components/NewRegister"; import { NewRegister } from "./components/NewRegister";
import { ensureColors } from "./components/colors";
export function Certs() { export function Certs() {
const [person, setPerson] = createSignal<Person | null>(null); const [person, setPerson] = createSignal<Person | null>(null);
const [lastUpdate, setLastUpdate] = createSignal(0); const [lastUpdate, setLastUpdate] = createSignal(0);
onMount(ensureColors);
return ( 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}/> <Search setPerson={setPerson}/>
<NewRegister <div class="grid grid-cols-2 gap-2">
personId={person()?.id ?? null} <Registers person={person()} lastUpdate={lastUpdate()} />
onSuccess={() => setLastUpdate((x) => x + 1)} <NewRegister
/> person={person()}
<Registers person={person()} lastUpdate={lastUpdate()} /> onSuccess={() => setLastUpdate((x) => x + 1)}
/>
</div>
</div> </div>
); );
} }

View File

@ -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>
);
}

View File

@ -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 { SearchableSelect } from "./NewRegister/SearchableSelect";
import { JSX } from "solid-js/jsx-runtime"; import { JSX } from "solid-js/jsx-runtime";
import { subjects } from "../subjects"; import { Person } from "src/types/Person";
import { FilledCard } from "./FilledCard";
import { RegisterPreview } from "./NewRegister/RegisterPreview";
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & { type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
submitter: HTMLElement; submitter: HTMLElement;
}>; }>;
export function NewRegister(props: {person: Person | null, onSuccess: () => void}) {
async function defaultNewRegisterFn(personId: number, subjectId: number, date: string): Promise<null | string> { const [subjects, setSubjects] = createSignal<Array<CursoGIE>>([]);
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>
);
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
const [loading, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
// Used to update SearchableSelect.tsx manually // 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) => { const register: HTMLEventFn = async(ev) => {
ev.preventDefault(); ev.preventDefault();
@ -115,17 +65,24 @@ export function NewRegister(props: {
setLoading(true); setLoading(true);
const result = await (props.registerFn ?? defaultNewRegisterFn)( const response = await fetch("/certificate", {
props.personId ?? -1, method: "POST",
subject, headers: {
date, "Content-Type": "application/json",
); },
body: JSON.stringify({
personId: props.person?.id ?? -1,
subjectId: subject,
date,
}),
});
if (result === null) { if (response.ok) {
props.onSuccess(); props.onSuccess();
setCount((x) => x + 1); setCount((x) => x + 1);
} else { } else {
setError(result); const data = await response.json();
setError(JSON.stringify(data));
} }
setLoading(false); setLoading(false);
@ -133,9 +90,9 @@ export function NewRegister(props: {
return ( return (
<div class="p-4"> <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 <form
class="px-4 grid" class="px-4 grid"
style={{"grid-template-columns": "30rem 12rem 10rem auto", "grid-column-gap": "1rem"}} style={{"grid-template-columns": "30rem 12rem 10rem auto", "grid-column-gap": "1rem"}}
@ -176,104 +133,3 @@ export function NewRegister(props: {
</div> </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()}&nbsp;
</p>
</>
);
}

View File

@ -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);
}
}

View File

@ -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 type {CursoGIE} from "../../../model/CursoGIE/cursoGIE.entity";
import { isServer } from "solid-js/web"; import { isServer } from "solid-js/web";
@ -15,14 +15,7 @@ export function SearchableSelect(props: {
const inputEl = ev.target as HTMLInputElement; const inputEl = ev.target as HTMLInputElement;
// Clear current selection // Clear current selection
setSelected(null); setSelected(null);
setFilter(inputEl.value.toLowerCase());
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);
}; };
createEffect(() => { 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 ( return (
<> <>
{inputElement} {inputElement}
<br/> <br/>
<div <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) => ( {(s) => (
<button <button
class="w-full text-left py-1 px-2 class={`w-full text-left py-1 px-2
hover:bg-c-primary-container hover:text-c-on-primary-container" hover:bg-c-primary-container hover:text-c-on-primary-container
${s.nombre.toLowerCase().indexOf(filter()) !== -1 && selected() === null ? "block" : "hidden"}`}
onclick={(ev) => { onclick={(ev) => {
ev.preventDefault(); ev.preventDefault();
console.log("Click! :D");
setSelected(s.id); setSelected(s.id);
setInputValue(s.nombre); setInputValue(s.nombre);
}} }}

View File

@ -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 { Person } from "../../types/Person";
import { RegisterReturn } from "../../types/RegisterReturn"; import { RegisterReturn } from "../../types/RegisterReturn";
@ -33,12 +33,57 @@ export function Registers(props: { person: Person | null, lastUpdate: number })
setLoading(false); setLoading(false);
}; };
const sortedRegisters = createMemo(() => registers().sort((r1, r2) => ((r1.fecha_inscripcion < r2.fecha_inscripcion) ? 1 : -1)));
return ( return (
<div class="p-4"> <div class="p-4">
<h2 class="mb-4 font-bold text-xl">2. Revisar registros actuales</h2>
<div class="px-4"> <div class="px-4">
<Show when={props.person !== null}> <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 <p
class="my-2" class="my-2"
style={{ display: loading() ? "block" : "none" }} style={{ display: loading() ? "block" : "none" }}
@ -60,7 +105,7 @@ export function Registers(props: { person: Person | null, lastUpdate: number })
</thead> </thead>
<tbody> <tbody>
<For each={sortedRegisters()}> <For each={registers().sort((r1, r2) => ((r1.fecha_actual < r2.fecha_actual) ? 1 : -1))}>
{(register) => <Register cert={register} onUpdate={loadCertificates} />} {(register) => <Register cert={register} onUpdate={loadCertificates} />}
</For> </For>
</tbody> </tbody>

View File

@ -3,9 +3,6 @@ import { JSX } from "solid-js/jsx-runtime";
import { Person } from "../../types/Person"; import { Person } from "../../types/Person";
import { RegisterPerson } from "./Search/RegisterPerson"; import { RegisterPerson } from "./Search/RegisterPerson";
import QR from "qrcode"; 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 & { type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
submitter: HTMLElement; submitter: HTMLElement;
@ -22,14 +19,11 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
const [warning, setWarning] = 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);
// Update QR // Update QR
createEffect(() => { createEffect(() => {
if (dni() !== "") { if (dni() !== "") {
// Old URL: https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()} QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()}`, {margin: 1}, (err, res) => {
// New URL: https://eegsac.com/certificado/${dni()}
QR.toDataURL(`https://eegsac.com/certificado/${dni()}`, {margin: 1}, (err, res) => {
if (err) { if (err) {
console.error("Error creating QR code"); console.error("Error creating QR code");
return; return;
@ -57,7 +51,6 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
const response = await fetch(`/person/${dni()}`); const response = await fetch(`/person/${dni()}`);
const body = await response.json(); const body = await response.json();
if (response.ok) { if (response.ok) {
setPersona(body);
props.setPerson(body); props.setPerson(body);
} else if (response.status === 404) { } else if (response.status === 404) {
console.error(body); console.error(body);
@ -74,55 +67,30 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
setLoading(false); 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 ( return (
<div> <div class="p-4 grid" style={{"grid-template-columns": "20rem auto"}}>
<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> <div>
<h2 class="my-2 font-bold text-xl">1. Buscar persona</h2>
<form onSubmit={searchDNI} class="px-4"> <form onSubmit={searchDNI} class="px-4">
<label for="search-dni">DNI</label>
<br />
<InputBox dni={dni()} setDni={setDni} loading={loading()} /> <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> </form>
<p <p
class="my-2 p-1 rounded w-fit mx-4 bg-c-error text-c-on-error" class="my-2 p-1 rounded w-fit mx-4 bg-c-error text-c-on-error"
style={{display: error() === "" ? "none" : "block"}} style={{display: error() === "" ? "none" : "block"}}
> >
Error: Error:
<br /> <br />
{error()} {error()}
</p> </p>
@ -136,45 +104,13 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
{warning()} {warning()}
</p> </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)" />
&nbsp;
Nombres y <b>Apellidos</b>
</CopyButton>
<CopyButton copyText={apellidosYNombres()}>
<CopyIcon fill="var(--c-on-primary)" />
&nbsp;
<b>Apellidos</b> y Nombres
</CopyButton>
<div class="grid grid-cols-2 gap-2">
<CopyButton copyText={apellidos()}>
<CopyIcon fill="var(--c-on-primary)" />
&nbsp;
<b>Apellidos</b>
</CopyButton>
<CopyButton copyText={nombres()}>
<CopyIcon fill="var(--c-on-primary)" />
&nbsp;
Nombres
</CopyButton>
</div>
</div>
</div>
<Show when={warning() !== ""}> <Show when={warning() !== ""}>
<RegisterPerson dni={dni()} onSuccess={search} /> <RegisterPerson dni={dni()} onSuccess={search} />
</Show> </Show>
</div> </div>
<div>
<img src={qrBase64() ?? ""} height="150" width="150" />
</div>
</div> </div>
); );
} }
@ -184,10 +120,12 @@ function InputBox(props: {
dni: string, dni: string,
setDni: (v: string) => void, setDni: (v: string) => void,
}) { }) {
const [successAnimation, setSuccessAnimation] = createSignal(false);
const inputElement = ( const inputElement = (
<input <input
id="search-dni" 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 invalid:border-c-error invalid:text-c-error
focus:border-c-primary outline-none font-mono focus:border-c-primary outline-none font-mono
disabled:opacity-50 disabled:cursor-not-allowed" disabled:opacity-50 disabled:cursor-not-allowed"
@ -208,6 +146,8 @@ function InputBox(props: {
if (props.dni.length === 8) { if (props.dni.length === 8) {
navigator.clipboard.writeText(props.dni); navigator.clipboard.writeText(props.dni);
setSuccessAnimation(true);
setTimeout(() => setSuccessAnimation(false), 1000);
} }
}; };
@ -219,86 +159,18 @@ function InputBox(props: {
}; };
return ( return (
<div class="relative max-w-[14rem] mx-auto"> <div class="grid gap-2 grid-cols-[10rem_3rem_3rem]">
{inputElement} {inputElement}
<label for="search-dni" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">DNI</label>
<button <button
class="absolute top-1 right-[3.75rem] rounded hover:bg-c-surface-variant" class={`${successAnimation() ? "bg-c-success" : "bg-c-primary"} rounded transition-colors`}
>
<MagnifyingGlassIcon fill="var(--c-on-surface)" />
</button>
<button
type="button"
class="absolute top-1 right-8 rounded hover:bg-c-surface-variant"
onclick={copyToClipboard} onclick={copyToClipboard}
>
<CopyIcon fill="var(--c-on-surface)" />
</button>
<button
type="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> </button>
</div> </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">&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={
`${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>
);
}

View File

@ -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);
}
};

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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