[FE] Improve /certificate UI

master
Fernando 2023-05-23 15:55:52 -05:00
parent 3d348cf12e
commit a527f4e33e
13 changed files with 50 additions and 582 deletions

View File

@ -1,540 +0,0 @@
import * as fs from "fs";
import {
Document, Packer, Paragraph, PageOrientation, ImageRun,
HorizontalPositionRelativeFrom, VerticalPositionRelativeFrom,
convertMillimetersToTwip,
FrameAnchorType,
HorizontalPositionAlign,
VerticalPositionAlign,
TextRun,
AlignmentType,
ShadingType,
} from "docx";
import { join } from "path";
import * as QR from "qrcode";
function cm(centimeters: number) {
return convertMillimetersToTwip((100 * centimeters) / 150);
}
function cmToEmu(cm: number) {
return Math.round(cm * 360000);
}
function cmText(cm: number) {
return Math.round(cm * 567);
}
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")),
transformation: {
height: cm(20.97),
width: cm(29.8),
},
floating: {
zIndex: 0,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: 0,
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: 0,
},
behindDocument: true,
},
});
const imgMatpel = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "matpel-logo.png")),
transformation: {
height: cm(2.81),
width: cm(2.81),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(0.7),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(0.7),
},
},
});
const imgCIP = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "colegio_ingenieros_logo.png")),
transformation: {
height: cm(2.15),
width: cm(2.15),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(0.91),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(3.64),
},
},
});
const imgCEE = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "cee_logo.png")),
transformation: {
height: cm(2.22),
width: cm(3.11),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(0.29),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(18),
},
},
});
const imgMTC = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "mtc_logo.png")),
transformation: {
height: cm(1.3),
width: cm(4.08),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(5.13),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(19.33),
},
},
});
const imgEEG = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "eeg_f_logo.png")),
transformation: {
height: cm(4.39),
width: cm(6.4),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(23.25),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(0),
},
},
});
const imgOSHA = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "osha_logo.png")),
transformation: {
height: cm(1.55),
width: cm(4.46),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(24.34),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(4.95),
},
},
});
const imgAguila = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "aguila_logo.png")),
transformation: {
height: cm(2.62),
width: cm(2.68),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(25.45),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(6.57),
},
},
});
const imgMichigan = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "michigan_logo.png")),
transformation: {
height: cm(2.47),
width: cm(2.6),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(25.59),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(9.54),
},
},
});
const imgCEEM = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "ceem_logo.jpg")),
transformation: {
height: cm(1),
width: cm(3.99),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(24.97),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(12.5),
},
},
});
const imgEATE = new ImageRun({
data: fs.readFileSync(join(__dirname, "img", "eate_logo.jpg")),
transformation: {
height: cm(2.21),
width: cm(3.28),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(25.54),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(14.09),
},
},
});
const certificateName = new Paragraph({
frame: {
position: {
x: cmText(3.13),
y: cmText(1.9),
},
width: cmText(14.28),
height: cmText(3.19),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
},
children: [
new TextRun({
text: "CAPACITACIÓN EN MANEJO; MANIPULACIÓN Y TRANSPORTE DE MATERIALES Y RESIDUOS PELIGROSOS - MATPEL I - Advertencia".toUpperCase(),
bold: true,
font: "Arial",
size: 32,
}),
],
alignment: AlignmentType.CENTER,
});
const certificatePersonName = new Paragraph({
frame: {
position: {
x: cmText(0),
y: cmText(6.5),
},
width: cmText(23),
height: cmText(1.5),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
},
children: [
new TextRun({
text: "fernando enrique araoz morales".toUpperCase(),
bold: true,
font: "Arial",
allCaps: true,
size: 48,
underline: {},
}),
],
alignment: AlignmentType.CENTER,
});
// "Se expide el presente certificado a:"
const certificateExpedite = new Paragraph({
frame: {
position: {
x: cmText(2.85),
y: cmText(5.15),
},
width: cmText(14.28),
height: cmText(1),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
},
children: [
new TextRun({
text: "Se expide el presente certificado a:",
font: "Arial",
size: 22,
}),
],
alignment: AlignmentType.CENTER,
});
// N° XXXX-202X-EEG
const certificateCode = new Paragraph({
frame: {
position: {
x: cmText(17.1),
y: cmText(5.35),
},
width: cmText(3.68),
height: cmText(0.7),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
},
children: [
new TextRun({
text: "N° 0524-2023-EEG",
font: "Arial",
size: 20,
}),
],
alignment: AlignmentType.CENTER,
});
// Identificado con dni ...
const certificateBody = new Paragraph({
frame: {
position: {
x: cmText(1.51),
y: cmText(8.35),
},
width: cmText(20.08),
height: cmText(3.7),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
},
children: [
new TextRun({
text: "Identificado con DNI N° ",
font: "Arial",
size: 24,
}),
new TextRun({
text: "74059695",
font: "Arial",
size: 24,
bold: true,
}),
new TextRun({
text: ", al haber aprobado el curso de capacitación sobre ",
font: "Arial",
size: 24,
}),
new TextRun({
text: "MANEJO DE MATERIALES Y RESIDUOS PELIGROSOS - MATPEL I - Advertencia",
font: "Arial",
size: 24,
bold: true,
}),
new TextRun({
text: ", según lo estipulado en la Ley N°28256 (ley que regula el Transporte de Materiales y Residuos Peligrosos) y Decreto Supremo N° 021-2008-MTC (Reglamento Nacional de Transporte Terrestre de Materiales y Residuos Peligrosos) con una duración de 12 horas lectivas.\n",
font: "Arial",
size: 24,
}),
],
alignment: AlignmentType.JUSTIFIED,
});
// Se expide certificado...
const certificateFinishLabel = new Paragraph({
frame: {
position: {
x: cmText(1.52),
y: cmText(11.25),
},
width: cmText(20.1),
height: cmText(0.8),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
},
children: [
new TextRun({
text: "Se expide certificado para los fines que se estime conveniente.",
font: "Arial",
size: 24,
}),
],
alignment: AlignmentType.CENTER,
style: "highlighting: none;",
});
// Fecha de Emision: ...
const certificateDate = new Paragraph({
frame: {
position: {
x: cmText(16),
y: cmText(16.5),
},
width: cmText(9),
height: cmText(1.4),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.TEXT,
},
},
children: [
new TextRun({
text: "Fecha de Emisión:\t\t{fecha_emision}",
font: "Arial",
size: 20,
break: 1,
}),
new TextRun({
text: "Fecha de Expiración:\t\t{fecha_expiracion}",
font: "Arial",
size: 20,
break: 1,
}),
],
alignment: AlignmentType.LEFT,
style: "highlighting: none;",
});
async function create() {
const qr = await QR.toDataURL(/* join(__dirname, "img", "qr.png"), */ `https://www.eegsac.com/alumnoscertificados.php?DNI=${"74059695"}`, {margin: 1});
const imgQR = new ImageRun({
data: qr, // fs.readFileSync(join(__dirname, "img", "qr.png")),
transformation: {
height: cm(2.47),
width: cm(2.47),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(26.3),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(16.48),
},
},
});
const doc = new Document({
sections: [
{
properties: {
page: {
size: {
orientation: PageOrientation.LANDSCAPE,
},
},
},
children: [
certificateName,
certificatePersonName,
certificateExpedite,
certificateCode,
certificateBody,
certificateFinishLabel,
certificateDate,
new Paragraph({
children: [
imgFondoDoc,
imgMatpel,
imgCIP,
imgCEE,
imgMTC,
imgEEG,
imgOSHA,
imgAguila,
imgMichigan,
imgCEEM,
imgEATE,
imgQR,
],
}),
],
},
],
});
// Used to export the file into a .docx file
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
// fs.rmSync(join(__dirname, "temp", "qr.png"));
});
}
create();

View File

@ -12,6 +12,7 @@ const { glob } = require("glob");
entryPoints: files, entryPoints: files,
bundle: false, bundle: false,
minify: true, minify: true,
sourcemap: false,
logLevel: "info", logLevel: "info",
plugins: [solidPlugin({ plugins: [solidPlugin({
solid: { solid: {
@ -32,6 +33,7 @@ build({
], ],
bundle: true, bundle: true,
minify: true, minify: true,
sourcemap: false,
logLevel: "info", logLevel: "info",
plugins: [ plugins: [
solidPlugin({ solidPlugin({

View File

@ -31,7 +31,7 @@ type ImgConfig = {
export function getImage(data: ImgConfig): ImageRun { export function getImage(data: ImgConfig): ImageRun {
return new ImageRun({ return new ImageRun({
// Magic path, based on dist folder // Magic path, based on dist folder
data: fs.readFileSync(join(__dirname, "..", "..", "..", "img", data.name)), data: fs.readFileSync(join(__dirname, "..", "..", "img", data.name)),
transformation: { transformation: {
height: cm(data.height), height: cm(data.height),
width: cm(data.width), width: cm(data.width),
@ -62,7 +62,6 @@ export async function getQR(data : {
const qr = await QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${data.dni}`, {margin: 1}); const qr = await QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${data.dni}`, {margin: 1});
return new ImageRun({ return new ImageRun({
// Magic path, based on dist folder
data: qr, data: qr,
transformation: { transformation: {
height: cm(data.height), height: cm(data.height),

View File

@ -10,6 +10,7 @@ import { getMatpel } from "./generator/matpel";
import { getMecanicaBasica } from "./generator/mecanicaBasica"; import { getMecanicaBasica } from "./generator/mecanicaBasica";
import { getManejoDefensivo } from "./generator/manejoDefensivo"; import { getManejoDefensivo } from "./generator/manejoDefensivo";
const generatable = [1, 2, 10, 11, 12];
@Injectable() @Injectable()
export class CertificateService { export class CertificateService {
@ -39,6 +40,7 @@ export class CertificateService {
curso_nombre: raw.curso_nombre, curso_nombre: raw.curso_nombre,
personaId: raw.persona?.id, personaId: raw.persona?.id,
cursoGIEId: raw.cursoGIE?.id, cursoGIEId: raw.cursoGIE?.id,
generatable: generatable.indexOf(raw.curso) !== -1,
})); }));
} }
@ -57,7 +59,6 @@ export class CertificateService {
}, },
}); });
if (registerR.length === 0) { if (registerR.length === 0) {
throw new BadRequestException("ID invalido"); throw new BadRequestException("ID invalido");
} }

View File

@ -10,7 +10,7 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
// Serve static files // 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); await app.listen(3000);
} }

View File

@ -13,4 +13,7 @@ export interface RegisterReturn {
// New fields // New fields
personaId?: number, personaId?: number,
cursoGIEId?: number, cursoGIEId?: number,
// Can be automatically generated
generatable: boolean,
} }

View File

@ -16,11 +16,17 @@ export function Certs() {
Registrar certificado Registrar certificado
</h1> </h1>
<Search setPerson={setPerson}/> <Search setPerson={setPerson}/>
<div
class="grid"
style={{"grid-template-columns": "50% 50%"}}
>
<Registers person={person()} lastUpdate={lastUpdate()} /> <Registers person={person()} lastUpdate={lastUpdate()} />
<NewRegister <NewRegister
person={person()} person={person()}
onSuccess={() => setLastUpdate((x) => x + 1)} onSuccess={() => setLastUpdate((x) => x + 1)}
/> />
</div> </div>
</div>
); );
} }

View File

@ -18,10 +18,6 @@ export function NewRegister(props: {person: Person | null, onSuccess: () => void
const [selectedSubject, setSelectedSubject] = createSignal<number | null>(null); const [selectedSubject, setSelectedSubject] = createSignal<number | null>(null);
const datePicker = ( const datePicker = (
<input <input
id="create-date" id="create-date"
@ -94,16 +90,9 @@ export function NewRegister(props: {person: Person | null, onSuccess: () => void
return ( return (
<div class="p-4"> <div class="p-4">
<h2 class="my-4 font-bold text-xl">3. Crear nuevos registros</h2> <h2 class="mb-4 font-bold text-xl">3. Crear nuevos registros</h2>
<Show when={props.person?.id !== -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"}}
>
{error()}&nbsp;
</p>
<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"}}
@ -123,9 +112,7 @@ export function NewRegister(props: {person: Person | null, onSuccess: () => void
<br/> <br/>
{datePicker} {datePicker}
<br/> <br/>
</div> <br />
<div>
<br/>
<input <input
class="bg-c-primary text-c-on-primary px-4 py-2 rounded-md cursor-pointer class="bg-c-primary text-c-on-primary px-4 py-2 rounded-md cursor-pointer
disabled:opacity-50 disabled:cursor-not-allowed" disabled:opacity-50 disabled:cursor-not-allowed"
@ -135,6 +122,13 @@ export function NewRegister(props: {person: Person | null, onSuccess: () => void
/> />
</div> </div>
</form> </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>
</Show> </Show>
</div> </div>
); );

View File

@ -64,7 +64,7 @@ export function SearchableSelect(props: {
<br/> <br/>
<div <div
class="border-c-outline border-2 rounded overflow-y-scroll" class="border-c-outline border-2 rounded overflow-y-scroll"
style={{"max-height": "12rem", "min-height": "12rem"}} style={{"max-height": "18rem", "min-height": "12rem"}}
> >
<For each={props.subjects}> <For each={props.subjects}>
{(s) => ( {(s) => (

View File

@ -36,7 +36,7 @@ export function Registers(props: { person: Person | null, lastUpdate: number })
return ( return (
<div class="p-4"> <div class="p-4">
<h2 class="my-4 font-bold text-xl">2. Revisar registros actuales</h2> <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}>
@ -60,7 +60,6 @@ export function Registers(props: { person: Person | null, lastUpdate: number })
<table class="table-auto border border-c-outline my-4"> <table class="table-auto border border-c-outline my-4">
<thead> <thead>
<tr> <tr>
<th class="p-2">APELLIDOS Y NOMBRES</th>
<th class="p-2">CURSO</th> <th class="p-2">CURSO</th>
<th class="p-2">FECHA</th> <th class="p-2">FECHA</th>
<th class="p-2">CÓDIGO</th> <th class="p-2">CÓDIGO</th>
@ -152,11 +151,11 @@ function Register(props: {cert: RegisterReturn, onUpdate: () => void}) {
return ( return (
<tr class="odd:bg-c-surface-variant"> <tr class="odd:bg-c-surface-variant">
<td class="py-2 px-4">{props.cert.nombre}</td>
<td class="py-2 px-4">{props.cert.curso_nombre}</td> <td class="py-2 px-4">{props.cert.curso_nombre}</td>
<td class="py-2 px-4 font-mono">{props.cert.fecha_inscripcion.toString()}</td> <td class="py-2 px-4 font-mono">{props.cert.fecha_inscripcion.toString()}</td>
<td class="py-2 px-4">{props.cert.codigo}</td> <td class="py-2 px-4">{props.cert.codigo}</td>
<td class="py-2 pl-8 pr-4"> <td class="py-2 px-4">
<Show when={props.cert.generatable}>
<button <button
class="rounded-full py-1 px-2 shadow" class="rounded-full py-1 px-2 shadow"
style={{"background-color": "#0055d5", "color": "#ffffff"}} style={{"background-color": "#0055d5", "color": "#ffffff"}}
@ -164,8 +163,9 @@ function Register(props: {cert: RegisterReturn, onUpdate: () => void}) {
> >
DOCX DOCX
</button> </button>
</Show>
</td> </td>
<td class="py-2 pl-8 pr-4"> <td class="py-2 px-4">
<button <button
class="rounded-full py-1 px-2 shadow class="rounded-full py-1 px-2 shadow
bg-c-primary bg-c-primary
@ -176,7 +176,7 @@ function Register(props: {cert: RegisterReturn, onUpdate: () => void}) {
Editar Editar
</button> </button>
</td> </td>
<td class="py-2 pl-4 pr-8"> <td class="py-2 px-4">
<button <button
class={`rounded-full py-1 px-2 shadow class={`rounded-full py-1 px-2 shadow
${deleteConfirmation() ? "bg-c-error-container text-c-on-error-container" : "bg-c-error text-c-on-error"} ${deleteConfirmation() ? "bg-c-error-container text-c-on-error-container" : "bg-c-error text-c-on-error"}

View File

@ -67,7 +67,7 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
}; };
return ( return (
<div class="p-4 grid" style={{"grid-template-columns": "40rem auto"}}> <div class="p-4 grid" style={{"grid-template-columns": "20rem auto"}}>
<div> <div>
<h2 class="my-2 font-bold text-xl">1. Buscar persona</h2> <h2 class="my-2 font-bold text-xl">1. Buscar persona</h2>
<form onSubmit={searchDNI} class="px-4"> <form onSubmit={searchDNI} class="px-4">
@ -77,7 +77,7 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
id="search-dni" id="search-dni"
class="bg-c-background text-c-on-background border-c-outline border-2 rounded px-2 py-1 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 focus:border-c-primary outline-none font-mono
disabled:opacity-50 disabled:cursor-not-allowed" disabled:opacity-50 disabled:cursor-not-allowed"
type="text" type="text"
minLength={8} minLength={8}
@ -123,7 +123,7 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
</Show> </Show>
</div> </div>
<div> <div>
<img src={qrBase64() ?? ""} height="225" width="225" /> <img src={qrBase64() ?? ""} height="150" width="150" />
</div> </div>
</div> </div>
); );

View File

@ -1,4 +1,8 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] "exclude": ["node_modules", "test", "dist", "**/*spec.ts"],
"compilerOptions": {
"declaration": false,
"sourceMap": false
}
} }

View File

@ -23,6 +23,5 @@
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",
"src/**/*.tsx", "src/**/*.tsx",
"docx/**/*.ts",
] ]
} }