Add QR code to user

master
Fernando 2023-05-11 10:45:12 -05:00
parent 45772bc98f
commit 6ff5fddee6
6 changed files with 178 additions and 15 deletions

View File

@ -1,9 +1,12 @@
import * as fs from "fs";
import {
Document, Packer, Paragraph, TextRun, PageOrientation, ImageRun,
Document, Packer, Paragraph, PageOrientation, ImageRun,
HorizontalPositionRelativeFrom, VerticalPositionRelativeFrom,
convertMillimetersToTwip,
FrameAnchorType,
HorizontalPositionAlign,
VerticalPositionAlign,
TextRun,
} from "docx";
import { join } from "path";
@ -16,6 +19,34 @@ function cmToEmu(cm: number) {
return Math.round(cm * 360000);
}
type ImgConfig = {
name: string,
height: number,
width: number,
horizontalOffset: number,
verticalOffset: number,
}
function getImage(data: ImgConfig): ImageRun {
return new ImageRun({
data: fs.readFileSync(join(__dirname, "img", data.name)),
transformation: {
height: cm(data.height),
width: cm(data.width),
},
floating: {
zIndex: 0,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(data.horizontalOffset),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(data.verticalOffset),
},
},
});
}
const imgFondoDoc = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "fondo_certificado.png")),
@ -227,6 +258,31 @@ const imgEATE = new ImageRun({
},
});
const certificateName = new Paragraph({
frame: {
position: {
x: cmToEmu(3.13),
y: cmToEmu(1.39),
},
width: cm(14.28) * 5,
height: cm(3.19) * 5,
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
},
children: [
new TextRun({
text: "sample text",
bold: true,
font: "Arial",
}),
],
});
const doc = new Document({
@ -240,6 +296,7 @@ const doc = new Document({
},
},
children: [
certificateName,
new Paragraph({
children: [
imgFondoDoc,

View File

@ -35,6 +35,7 @@
"docx": "^8.0.4",
"dotenv": "^16.0.3",
"mysql2": "^3.2.4",
"qrcode": "^1.5.3",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"solid-js": "^1.7.4",

View File

@ -34,6 +34,9 @@ dependencies:
mysql2:
specifier: ^3.2.4
version: 3.3.0
qrcode:
specifier: ^1.5.3
version: 1.5.3
reflect-metadata:
specifier: ^0.1.13
version: 0.1.13
@ -2491,7 +2494,6 @@ packages:
/camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
dev: true
/camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
@ -2605,6 +2607,14 @@ packages:
engines: {node: '>= 10'}
dev: true
/cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
dev: false
/cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
dependencies:
@ -2808,6 +2818,11 @@ packages:
dependencies:
ms: 2.1.2
/decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
dev: false
/dedent@0.7.0:
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
dev: true
@ -2869,6 +2884,10 @@ packages:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
/dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dev: false
/dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@ -2926,6 +2945,10 @@ packages:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
dev: true
/encode-utf8@1.0.3:
resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
dev: false
/encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
@ -3403,7 +3426,6 @@ packages:
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: true
/find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
@ -4551,7 +4573,6 @@ packages:
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: true
/locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
@ -4962,7 +4983,6 @@ packages:
engines: {node: '>=6'}
dependencies:
p-try: 2.2.0
dev: true
/p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
@ -4976,7 +4996,6 @@ packages:
engines: {node: '>=8'}
dependencies:
p-limit: 2.3.0
dev: true
/p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
@ -4988,7 +5007,6 @@ packages:
/p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
dev: true
/pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
@ -5032,7 +5050,6 @@ packages:
/path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
dev: true
/path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
@ -5134,6 +5151,11 @@ packages:
engines: {node: '>=4'}
dev: true
/pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
dev: false
/postcss-import@15.1.0(postcss@8.4.23):
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
engines: {node: '>=14.0.0'}
@ -5266,6 +5288,17 @@ packages:
resolution: {integrity: sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==}
dev: true
/qrcode@1.5.3:
resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
engines: {node: '>=10.13.0'}
hasBin: true
dependencies:
dijkstrajs: 1.0.3
encode-utf8: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
dev: false
/qs@6.11.0:
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
engines: {node: '>=0.6'}
@ -5390,6 +5423,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
dev: false
/resolve-cwd@3.0.0:
resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
engines: {node: '>=8'}
@ -5628,6 +5665,10 @@ packages:
transitivePeerDependencies:
- supports-color
/set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: false
/set-cookie-parser@2.6.0:
resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
dev: true
@ -6451,6 +6492,10 @@ packages:
tr46: 0.0.3
webidl-conversions: 3.0.1
/which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
dev: false
/which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@ -6471,6 +6516,15 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: false
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@ -6514,6 +6568,10 @@ packages:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
/y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
dev: false
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -6536,6 +6594,14 @@ packages:
engines: {node: '>= 14'}
dev: true
/yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
dev: false
/yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
@ -6545,6 +6611,23 @@ packages:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
/yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
dev: false
/yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}

View File

@ -10,7 +10,7 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Serve static files
app.use("/static", express.static(path.join(__dirname, "..", "static")));
app.use("/static", express.static(path.join(__dirname, "..", "..", "static")));
await app.listen(3000);
}

View File

@ -18,7 +18,7 @@ export function Certs() {
<Search setPerson={setPerson}/>
<Registers person={person()} lastUpdate={lastUpdate()} />
<NewRegister
personId={person()?.id ?? -1}
person={person()}
onSuccess={() => setLastUpdate((x) => x + 1)}
/>
</div>

View File

@ -1,13 +1,16 @@
import { createSignal, onMount, Show } from "solid-js";
import { createEffect, 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 { Person } from "src/types/Person";
import QR from "qrcode";
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
submitter: HTMLElement;
}>;
export function NewRegister(props: {personId: number, onSuccess: () => void}) {
export function NewRegister(props: {person: Person | null, onSuccess: () => void}) {
const [subjects, setSubjects] = createSignal<Array<CursoGIE>>([]);
const [error, setError] = createSignal("");
const [loading, setLoading] = createSignal(false);
@ -16,6 +19,22 @@ export function NewRegister(props: {personId: number, onSuccess: () => void}) {
const [selectedSubject, setSelectedSubject] = createSignal<number | null>(null);
const [qrBase64, setQrBase64] = createSignal<string | null>(null);
// Update QR
createEffect(() => {
if (props.person !== null) {
QR.toDataURL(`https://www.eegsac.com/alumnosceertificados.php?DNI=${props.person.dni}`, (err, res) => {
if (err) {
console.error("Error creating QR code");
return;
}
setQrBase64(res);
});
}
});
const datePicker = (
<input
id="create-date"
@ -69,7 +88,7 @@ export function NewRegister(props: {personId: number, onSuccess: () => void}) {
"Content-Type": "application/json",
},
body: JSON.stringify({
personId: props.personId,
personId: props.person?.id ?? -1,
subjectId: subject,
date,
}),
@ -90,7 +109,7 @@ export function NewRegister(props: {personId: number, onSuccess: () => void}) {
<div class="p-4">
<h2 class="my-4 font-bold text-xl">3. Crear nuevos registros</h2>
<Show when={props.personId !== -1}>
<Show when={props.person?.id !== -1}>
<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"}}
@ -100,7 +119,7 @@ export function NewRegister(props: {personId: number, onSuccess: () => void}) {
<form
class="px-4 grid"
style={{"grid-template-columns": "30rem 12rem auto", "grid-column-gap": "1rem"}}
style={{"grid-template-columns": "30rem 12rem 10rem auto", "grid-column-gap": "1rem"}}
onsubmit={register}
>
<div>
@ -128,6 +147,9 @@ export function NewRegister(props: {personId: number, onSuccess: () => void}) {
disabled={loading()}
/>
</div>
<div>
<img src={qrBase64() ?? ""} height="225" width="225" />
</div>
</form>
</Show>
</div>