diff --git a/frontend/package.json b/frontend/package.json
index 55f6bf4..fc04c0f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -28,6 +28,7 @@
"@types/file-saver": "^2.0.5",
"@types/qrcode": "^1.5.1",
"axios": "^1.5.1",
+ "canvg": "^4.0.1",
"file-saver": "^2.0.5",
"qrcode": "^1.5.3",
"solid-js": "^1.7.6"
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index b893ed0..c33583d 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -17,6 +17,9 @@ dependencies:
axios:
specifier: ^1.5.1
version: 1.5.1
+ canvg:
+ specifier: ^4.0.1
+ version: 4.0.1
file-saver:
specifier: ^2.0.5
version: 2.0.5
@@ -956,12 +959,20 @@ packages:
resolution: {integrity: sha512-c0Snqx/IpY+QHnCqEQzo9NjhBNth6gm6/4wDgWt1ML24ldNiStXmxuKJUKM7ob/iAhQWH/dUH1STRt5eTUixlw==}
dev: false
+ /@types/offscreencanvas@2019.7.2:
+ resolution: {integrity: sha512-ujCjOxeA07IbEBQYAkoOI+XFw5sT3nhWJ/xZfPR6reJppDG7iPQPZacQiLTtWH1b3a2NYXWlxvYqa40y/LAixQ==}
+ dev: false
+
/@types/qrcode@1.5.1:
resolution: {integrity: sha512-HpSN675K0PmxIDRpjMI3Mc2GiKo3dNu+X/F5SoItiaDS1lVfgC6Wac1c5lQDfKWbTJUSHWiHKzpJpBZG7k9gaA==}
dependencies:
'@types/node': 20.5.5
dev: false
+ /@types/raf@3.4.2:
+ resolution: {integrity: sha512-sM4HyDVlDFl4goOXPF+g9nNHJFZQGot+HgySjM4cRjqXzjdatcEvYrtG4Ia8XumR9T6k8G2tW9B7hnUj51Uf0A==}
+ dev: false
+
/@types/semver@7.5.0:
resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==}
dev: true
@@ -1345,6 +1356,18 @@ packages:
resolution: {integrity: sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==}
dev: true
+ /canvg@4.0.1:
+ resolution: {integrity: sha512-5gD/d6SiCCT7baLnVr0hokYe93DfcHW2rSqdKOuOQD84YMlyfttnZ8iQsThTdX6koYam+PROz/FuQTo500zqGw==}
+ engines: {node: '>=12.0.0'}
+ dependencies:
+ '@types/offscreencanvas': 2019.7.2
+ '@types/raf': 3.4.2
+ raf: 3.4.1
+ rgbcolor: 1.0.1
+ stackblur-canvas: 2.6.0
+ svg-pathdata: 6.0.3
+ dev: false
+
/chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -2603,6 +2626,10 @@ packages:
engines: {node: '>=8'}
dev: true
+ /performance-now@2.1.0:
+ resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
+ dev: false
+
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
@@ -2734,6 +2761,12 @@ packages:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true
+ /raf@3.4.1:
+ resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
+ dependencies:
+ performance-now: 2.1.0
+ dev: false
+
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: true
@@ -2809,6 +2842,11 @@ packages:
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
dev: true
+ /rgbcolor@1.0.1:
+ resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
+ engines: {node: '>= 0.8.15'}
+ dev: false
+
/rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
@@ -2944,6 +2982,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /stackblur-canvas@2.6.0:
+ resolution: {integrity: sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==}
+ engines: {node: '>=0.1.14'}
+ dev: false
+
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -3035,6 +3078,11 @@ packages:
engines: {node: '>= 0.4'}
dev: true
+ /svg-pathdata@6.0.3:
+ resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==}
+ engines: {node: '>=12.0.0'}
+ dev: false
+
/tailwindcss@3.3.3:
resolution: {integrity: sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==}
engines: {node: '>=14.0.0'}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index c6ca00d..0014b03 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,5 +1,5 @@
import { Route, Routes } from "@solidjs/router";
-import type { Component } from "solid-js";
+import { type Component } from "solid-js";
import { Certs } from "./certs";
import { NavRail } from "./components/NavRail";
import { OnlineClassroom } from "./OnlineClassroom";
@@ -8,7 +8,7 @@ const App: Component = () => (
- En construccion
} />
+ } />
En construccion
} />
@@ -17,4 +17,13 @@ const App: Component = () => (
);
+function Builder() {
+ return (
+
+ );
+}
+
export default App;
diff --git a/frontend/src/certs/Registers/RegisterElement.tsx b/frontend/src/certs/Registers/RegisterElement.tsx
new file mode 100644
index 0000000..c65dee7
--- /dev/null
+++ b/frontend/src/certs/Registers/RegisterElement.tsx
@@ -0,0 +1,148 @@
+import { Show } from "solid-js";
+import { certGenerator } from "../../certGenerator";
+import { Person } from "../../types/Person";
+import { Register } from "../../types/Register";
+import { courseMap } from "../../utils/allCourses";
+import { customLabelsMap } from "../../utils/allCustomLabels";
+import { DownloadSimpleIcon } from "../../icons/DownloadSimpleIcon";
+import { CaretRight } from "../../icons/CaretRight";
+
+export function RegisterElement(props: {register: Register, person: Person, onClick: () => void}) {
+
+ const dateComponents = () => {
+ const [, year, month, day] = /(\d{4})-(\d{2})-(\d{2})/.exec(props.register.register_display_date) ?? [];
+ return {year, month, day};
+ };
+
+ const displayDate = () => {
+ const {year, month, day} = dateComponents();
+ return `${day}/${month}/${year}`;
+ };
+
+ const courseName = () => {
+ const course = courseMap()[props.register.register_course_id];
+ return course?.course_name ?? "Curso no encontrado";
+ };
+
+ const customLabel = () => {
+ const labelId = props.register.register_custom_label;
+ if (labelId === 1) return "";
+ return customLabelsMap()[labelId]?.custom_label_value ?? "";
+ };
+
+ const genCert = () => {
+ const person = props.person;
+ const register = props.register;
+
+ generateCert(person, register);
+ };
+
+ const generateable = () => {
+ const courseN = courseName();
+ return certGenerator[courseN] !== undefined;
+ };
+
+ const createdTodayClasses = () => {
+ // current dat in YYYY-MM-DD format
+ const today = new Date().toISOString()
+ .split("T")[0];
+ const createdToday = props.register.register_creation_date === today;
+
+ return createdToday ? "bg-c-surface-variant" : "";
+ };
+
+ return (
+
+
+
+ {courseName()}
+
+
+
+ {customLabel()}
+
+
+
+
+
+ {displayDate()}
+
+
+
+
+ sin firmas
+
+
+
+
+ -
+
+ {
+ const code = props.register.register_code.toString();
+ const paddedCode = code.padStart(4, "0");
+ navigator.clipboard.writeText(paddedCode);
+ }}
+ >
+ {props.register.register_code}
+
+
+
+
+
+
+
+ );
+}
+
+export function generateCert(person: Person, register: Register) {
+ const courseN = courseMap()[register.register_course_id]?.course_name ?? "Curso no encontrado";
+
+ const generator = certGenerator[courseN];
+ if (generator === undefined) {
+ alert(`El cert \`${courseN}\` no está implementado`);
+ return;
+ }
+
+ const [, year, month, day] = /(\d{4})-(\d{2})-(\d{2})/.exec(register.register_display_date) ?? [];
+ const personFullName = `${person.person_names} ${person.person_paternal_surname} ${person.person_maternal_surname}`;
+
+ // Manage custom label
+ const certCustomLabel = register.register_custom_label === 1
+ ? ""
+ : customLabelsMap()[register.register_custom_label]?.custom_label_value ?? "";
+
+ const certCode = register.register_is_preview ? "0000" : register.register_code.toString().padStart(4, "0");
+
+ generator(`${courseN} - ${personFullName} [${register.register_id.toString(16).toUpperCase()}].docx`, {
+ matpel: null,
+ personFullName,
+ personDni: person.person_dni.toString(),
+ certCode,
+ certYear: year,
+ certMonth: month,
+ certDay: day,
+ certIId: register.register_code,
+ certCustomLabel,
+ });
+}
+
diff --git a/frontend/src/certs/Registers/RegisterSidebar.tsx b/frontend/src/certs/Registers/RegisterSidebar.tsx
index 2152ec9..323653c 100644
--- a/frontend/src/certs/Registers/RegisterSidebar.tsx
+++ b/frontend/src/certs/Registers/RegisterSidebar.tsx
@@ -1,8 +1,18 @@
+import saveAs from "file-saver";
import { Register } from "../../types/Register";
import { courseMap } from "../../utils/allCourses";
-import { formatDate } from "../../utils/functions";
+import { genCarnet } from "../../utils/carnetGenerator";
+import { formatDate, numberToMonth } from "../../utils/functions";
+import { Canvg } from "canvg";
+import { Show } from "solid-js";
+import { Person } from "../../types/Person";
-export function RegisterSidebar(props: {register: Register, close: () => void, onDelete: () => void}) {
+export function RegisterSidebar(props: {
+ register: Register,
+ person: Person,
+ close: () => void,
+ onDelete: () => void,
+}) {
const deleteRegister = async() => {
const res = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/register/${props.register.register_id}`,{
@@ -17,6 +27,41 @@ export function RegisterSidebar(props: {register: Register, close: () => void, o
const courseName = () => courseMap()[props.register.register_course_id]?.course_name ?? "";
+ const generateCarnet = () => {
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
+
+ const p = props.person;
+ const r = props.register;
+
+ const [,month] = r.register_display_date.split("-");
+
+ const monthStr = numberToMonth(parseInt(month, 10));
+
+ if (monthStr === null) {
+ alert(`El cert. tiene un mes invalido (${month}), no se puede generar carnet`);
+ return;
+ }
+
+ const svgHtml = genCarnet(
+ `${p.person_names} ${p.person_paternal_surname} ${p.person_maternal_surname}`,
+ p.person_dni.toString(),
+ r.register_code.toString().padStart(4, "0"),
+ monthStr,
+ );
+ const v = Canvg.fromString(ctx, svgHtml);
+ v.start();
+ v.ready().then(() => {
+ canvas.toBlob((blob) => {
+ if (blob !== null) {
+ saveAs(blob, "carnet.png");
+ } else {
+ console.log("blob is null... :c");
+ }
+ });
+ });
+ };
+
return (