PDF Generator for Exercise progress

This commit is contained in:
2024-11-08 20:02:37 +01:00
parent be1bc85f40
commit b84d542352
8 changed files with 344 additions and 9 deletions

242
package-lock.json generated Normal file
View File

@@ -0,0 +1,242 @@
{
"name": "eventmaster",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"jspdf": "^2.5.2",
"jspdf-autotable": "^3.8.4"
}
},
"node_modules/@babel/runtime": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"license": "MIT",
"optional": true
},
"node_modules/atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"license": "(MIT OR Apache-2.0)",
"bin": {
"atob": "bin/atob.js"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"license": "(MIT OR Apache-2.0)",
"bin": {
"btoa": "bin/btoa.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/canvg": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
"license": "MIT",
"optional": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/canvg/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT",
"optional": true
},
"node_modules/core-js": {
"version": "3.39.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
"integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"license": "MIT",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/dompurify": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz",
"integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optional": true
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"license": "MIT",
"optional": true,
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/jspdf": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
"integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2",
"atob": "^2.1.2",
"btoa": "^1.2.1",
"fflate": "^0.8.1"
},
"optionalDependencies": {
"canvg": "^3.0.6",
"core-js": "^3.6.0",
"dompurify": "^2.5.4",
"html2canvas": "^1.0.0-rc.5"
}
},
"node_modules/jspdf-autotable": {
"version": "3.8.4",
"resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.4.tgz",
"integrity": "sha512-rSffGoBsJYX83iTRv8Ft7FhqfgEL2nLpGAIiqruEQQ3e4r0qdLFbPUB7N9HAle0I3XgpisvyW751VHCqKUVOgQ==",
"license": "MIT",
"peerDependencies": {
"jspdf": "^2.5.1"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"license": "MIT",
"optional": true
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"license": "MIT",
"optional": true,
"dependencies": {
"performance-now": "^2.1.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
"optional": true,
"engines": {
"node": ">= 0.8.15"
}
},
"node_modules/stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.1.14"
}
},
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"license": "MIT",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"license": "MIT",
"optional": true,
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
}
}
}

6
package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"dependencies": {
"jspdf": "^2.5.2",
"jspdf-autotable": "^3.8.4"
}
}

View File

@@ -4,7 +4,7 @@ import { SeatGroupModel } from '@/data/models/locations/seatGroupModel';
import { SeatModel } from '@/data/models/locations/seatModel';
import { SeatRowModel } from '@/data/models/locations/seatRowModel';
import { SelectedSeatModel } from '@/data/models/ordering/selectedSeatModel';
import { getSeatColor } from '@/helpers/colorScripts';
import { getSeatColor } from '@/scripts/colorScripts';
import { useBasketStore } from '@/stores/basket.store';
const basketStore = useBasketStore()

View File

@@ -1,14 +1,31 @@
<script setup lang="ts">
import scoreCard from './scoreCard.vue';
import { useExerciseStore } from '@/stores/exercise.store';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import { generateResultsPdf } from '@/scripts/pdfScripts';
import { usePreferencesStore } from '@/stores/preferences.store';
const exerciseStore = useExerciseStore()
const preferencesStore = usePreferencesStore()
exerciseStore.getAllExercises()
</script>
<template>
<v-container max-width="1000">
<v-row>
<v-spacer />
<v-col cols="auto">
<outlined-button
prepend-icon="mdi-file-pdf-box"
@click="generateResultsPdf()"
:disabled="preferencesStore.studentName.length < 3 || preferencesStore.registrationNumber.length < 7"
>
PDF generieren
</outlined-button>
</v-col>
</v-row>
<v-row v-if="exerciseStore.fetchInProgress" v-for="i in 3">
<v-col>
<score-card :loading="true"

View File

@@ -1,11 +1,15 @@
<script setup lang="ts">
import cardView from '@/components/basics/cardView.vue';
import { LanguageEnum } from '@/data/enums/languageEnum';
import { ExerciseGroupApiModel } from '@/data/models/exercises/exerciseGroupApiModel';
import { usePreferencesStore } from '@/stores/preferences.store';
defineProps({
exerciseGroup: ExerciseGroupApiModel,
loading: Boolean
})
const preferencesStore = usePreferencesStore()
</script>
<template>
@@ -37,10 +41,10 @@ defineProps({
</v-timeline>
</card-view>
<!-- todo: English -->
<card-view
v-else
:title="$t('help.scoreBoard.exerciseGroupNr', [exerciseGroup.groupNr]) + exerciseGroup.nameDe"
:title="$t('help.scoreBoard.exerciseGroupNr', [exerciseGroup.groupNr]) +
(preferencesStore.language == LanguageEnum.GERMAN ? exerciseGroup.nameDe : exerciseGroup.nameEn)"
:loading="loading"
>
<template #borderless>
@@ -69,14 +73,14 @@ defineProps({
type="text"
:loading="loading"
>
<div class="text-center">
<div class="text-center pb-3">
<div class="text-h6">
{{ exercise.nameDe }}
{{ (preferencesStore.language == LanguageEnum.GERMAN ? exercise.nameDe : exercise.nameEn) }}
</div>
<div>
<!-- todo: English -->
{{ exercise.descriptionDe }}
{{ (preferencesStore.language == LanguageEnum.GERMAN ?
exercise.descriptionDe : exercise.descriptionEn) }}
</div>
</div>
</v-skeleton-loader>

View File

@@ -0,0 +1,66 @@
import { useExerciseStore } from "@/stores/exercise.store";
import { usePreferencesStore } from "@/stores/preferences.store";
import { jsPDF } from "jspdf"
import autoTable from "jspdf-autotable"
import moment from "moment";
export function generateResultsPdf() {
const preferencesStore = usePreferencesStore()
const exerciseStore = useExerciseStore()
const doc = new jsPDF()
const pageHeight = doc.internal.pageSize.height || doc.internal.pageSize.getHeight();
const pageWidth = doc.internal.pageSize.width || doc.internal.pageSize.getWidth()
const midPage = doc.internal.pageSize.getWidth() / 2
const exerciseData = []
exerciseStore.exerciseGroups.forEach(group => {
group.exercises.forEach(exercise => {
exerciseData.push([
group.groupNr + "." + exercise.exerciseNr,
group.nameDe,
exercise.nameDe,
exercise.solved ? 'Ja' : 'Nein'
])
})
})
// Title and image
doc.setFontSize(48)
doc.text("EventMaster", midPage, 25, { align: "center" })
doc.addImage("../../public/logo.png", "PNG", 65, 36, 80, 80)
// Data about student
doc.setFontSize(24)
doc.text([preferencesStore.studentName, preferencesStore.registrationNumber], midPage, 130, { align: "center" })
// Progress total
doc.setFontSize(28)
doc.text("Hat " + exerciseStore.exerciseGroups.reduce((counter, group) => {
for (let exercise of group.exercises) {
if (exercise.solved) {
counter++
}
}
return counter
}, 0) + " von 10 Aufgaben gelöst.", midPage, 160, { align: "center" })
// Progress table
doc.setFontSize(22)
autoTable(doc, {
startY: 170,
head: [[ "Aufgaben-Nr.", "Aufgabengruppe", "Aufgabe", "Abgeschlossen?"]],
body: exerciseData
})
doc.setFontSize(12)
doc.text(["Grundlagen der IT-Sicherheit", "Fachgebiet Usable Security and Privacy", "Institut für IT-Sicherheit", "Leibniz Universität Hannover"], midPage, pageHeight - 30, { align: "center" })
doc.text(moment().format("DD.MM.YYYY, HH:mm:ss"), midPage, pageHeight - 8, { align: "center" })
doc.save("eventmaster-exercise-result.pdf")
}

View File

@@ -39,10 +39,10 @@ export const usePreferencesStore = defineStore('preferencesStore', {
firstStartup: useLocalStorage<Boolean>("hackmycart/preferencesStore/firstStartup", true),
/** Full name of student */
studentName: useLocalStorage<String>("hackmycart/preferencesStore/studentName", ""),
studentName: useLocalStorage<string>("hackmycart/preferencesStore/studentName", ""),
/** Matrikel number */
registrationNumber: useLocalStorage<String>("hackmycart/preferencesStore/registrationNumber", "")
registrationNumber: useLocalStorage<string>("hackmycart/preferencesStore/registrationNumber", "")
}),
actions: {