Checkin Seite erstellt
This commit is contained in:
@@ -1,15 +1,44 @@
|
||||
// Check-in Server (separater Express-App auf Port 3334)
|
||||
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const { db } = require('./database');
|
||||
const { getCurrentDate, getCurrentTime, updateTotalHours } = require('./helpers/utils');
|
||||
|
||||
const checkinApp = express();
|
||||
const CHECKIN_PORT = 3336;
|
||||
const CHECKIN_PORT = 3334;
|
||||
|
||||
// View-Engine für Browser-Aufrufe (Bestätigungsseiten)
|
||||
checkinApp.set('view engine', 'ejs');
|
||||
checkinApp.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
// Middleware für Check-in-Server
|
||||
checkinApp.use(express.json());
|
||||
|
||||
/** Erkennt Browser-Aufruf: Accept-Header enthält text/html (z. B. beim Aufruf per QR/Link im Browser). */
|
||||
function wantsHtml(req) {
|
||||
const accept = req.get('Accept') || '';
|
||||
return /text\/html/i.test(accept);
|
||||
}
|
||||
|
||||
/** Sendet je nach Client entweder HTML-Seite oder JSON. */
|
||||
function sendResponse(req, res, success, data) {
|
||||
if (wantsHtml(req)) {
|
||||
const title = data.title || (success ? 'Erfolg' : 'Fehler');
|
||||
const message = data.message || data.error || (success ? 'Aktion erfolgreich.' : 'Ein Fehler ist aufgetreten.');
|
||||
if (!success && data.status) {
|
||||
res.status(data.status);
|
||||
}
|
||||
res.render('checkin-result', { success, title, message });
|
||||
} else {
|
||||
if (success) {
|
||||
res.json({ success: true, ...data });
|
||||
} else {
|
||||
res.status(data.status || 500).json({ success: false, error: data.error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API: Check-in (Kommen)
|
||||
checkinApp.get('/api/checkin/:userId', (req, res) => {
|
||||
const userId = parseInt(req.params.userId);
|
||||
@@ -19,25 +48,27 @@ checkinApp.get('/api/checkin/:userId', (req, res) => {
|
||||
// Prüfe ob User existiert
|
||||
db.get('SELECT id FROM users WHERE id = ?', [userId], (err, user) => {
|
||||
if (err || !user) {
|
||||
return res.status(404).json({ success: false, error: 'Benutzer nicht gefunden' });
|
||||
return sendResponse(req, res, false, { error: 'Benutzer nicht gefunden', status: 404 });
|
||||
}
|
||||
|
||||
// Prüfe ob bereits ein Eintrag für heute existiert
|
||||
db.get('SELECT * FROM timesheet_entries WHERE user_id = ? AND date = ? ORDER BY updated_at DESC, id DESC LIMIT 1',
|
||||
[userId, currentDate], (err, entry) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ success: false, error: 'Fehler beim Abrufen des Eintrags' });
|
||||
return sendResponse(req, res, false, { error: 'Fehler beim Abrufen des Eintrags', status: 500 });
|
||||
}
|
||||
|
||||
const successTitle = 'Hallo, du wurdest erfolgreich eingecheckt';
|
||||
|
||||
if (!entry) {
|
||||
// Kein Eintrag existiert → Erstelle neuen mit start_time
|
||||
db.run(`INSERT INTO timesheet_entries (user_id, date, start_time, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)`,
|
||||
[userId, currentDate, currentTime], (err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ success: false, error: 'Fehler beim Erstellen des Eintrags' });
|
||||
return sendResponse(req, res, false, { error: 'Fehler beim Erstellen des Eintrags', status: 500 });
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
sendResponse(req, res, true, {
|
||||
title: successTitle,
|
||||
message: `Start-Zeit erfasst: ${currentTime}`,
|
||||
start_time: currentTime,
|
||||
date: currentDate
|
||||
@@ -48,10 +79,10 @@ checkinApp.get('/api/checkin/:userId', (req, res) => {
|
||||
db.run('UPDATE timesheet_entries SET start_time = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[currentTime, entry.id], (err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ success: false, error: 'Fehler beim Aktualisieren' });
|
||||
return sendResponse(req, res, false, { error: 'Fehler beim Aktualisieren', status: 500 });
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
sendResponse(req, res, true, {
|
||||
title: successTitle,
|
||||
message: `Start-Zeit erfasst: ${currentTime}`,
|
||||
start_time: currentTime,
|
||||
date: currentDate
|
||||
@@ -59,8 +90,8 @@ checkinApp.get('/api/checkin/:userId', (req, res) => {
|
||||
});
|
||||
} else {
|
||||
// Start-Zeit bereits vorhanden → Ignoriere weiteren Check-in
|
||||
res.json({
|
||||
success: true,
|
||||
sendResponse(req, res, true, {
|
||||
title: successTitle,
|
||||
message: `Bereits eingecheckt um ${entry.start_time}. Check-in ignoriert.`,
|
||||
start_time: entry.start_time,
|
||||
date: currentDate
|
||||
@@ -79,21 +110,20 @@ checkinApp.get('/api/checkout/:userId', (req, res) => {
|
||||
// Prüfe ob User existiert
|
||||
db.get('SELECT id FROM users WHERE id = ?', [userId], (err, user) => {
|
||||
if (err || !user) {
|
||||
return res.status(404).json({ success: false, error: 'Benutzer nicht gefunden' });
|
||||
return sendResponse(req, res, false, { error: 'Benutzer nicht gefunden', status: 404 });
|
||||
}
|
||||
|
||||
// Prüfe ob bereits ein Eintrag für heute existiert
|
||||
db.get('SELECT * FROM timesheet_entries WHERE user_id = ? AND date = ? ORDER BY updated_at DESC, id DESC LIMIT 1',
|
||||
[userId, currentDate], (err, entry) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ success: false, error: 'Fehler beim Abrufen des Eintrags' });
|
||||
return sendResponse(req, res, false, { error: 'Fehler beim Abrufen des Eintrags', status: 500 });
|
||||
}
|
||||
|
||||
if (!entry || !entry.start_time) {
|
||||
// Kein Eintrag oder keine Start-Zeit → Fehler
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Bitte zuerst einchecken (Kommen).'
|
||||
return sendResponse(req, res, false, {
|
||||
error: 'Bitte zuerst einchecken (Kommen).',
|
||||
status: 400
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,11 +135,13 @@ checkinApp.get('/api/checkout/:userId', (req, res) => {
|
||||
db.run('UPDATE timesheet_entries SET end_time = ?, total_hours = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[currentTime, totalHours, entry.id], (err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ success: false, error: 'Fehler beim Aktualisieren' });
|
||||
return sendResponse(req, res, false, { error: 'Fehler beim Aktualisieren', status: 500 });
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
message: `End-Zeit erfasst: ${currentTime}. Gesamtstunden: ${totalHours.toFixed(2)} h`,
|
||||
const successTitle = 'Schönen Feierabend, du wurdest erfolgreich ausgecheckt';
|
||||
const successMessage = `End-Zeit erfasst: ${currentTime}. Gesamtstunden: ${totalHours.toFixed(2)} h`;
|
||||
sendResponse(req, res, true, {
|
||||
title: successTitle,
|
||||
message: successMessage,
|
||||
end_time: currentTime,
|
||||
total_hours: totalHours,
|
||||
date: currentDate
|
||||
|
||||
@@ -5,13 +5,16 @@ Hallo zusammen,
|
||||
Mara ist auf mich mit einer Bitte herangetreten, ob ich die Stundenerfassung digitalisieren kann.
|
||||
Das habe ich die letzten 2 Wochen am abend und am WE gemacht.
|
||||
|
||||
Ich gleube, dass das System jetzt fest fertig ist und ihr es testen könnt
|
||||
Der test soll kleinere Fehler finden und mir noch die möglichkeit geben diese dann zu beheben.
|
||||
Ich glaube, dass das System jetzt fest fertig ist und ihr es testen könnt
|
||||
Der test soll Fehler finden und mir noch die möglichkeit geben diese dann zu beheben.
|
||||
|
||||
Am Montag würde ich gerne eine kurze Einführung für die Leute im Büro geben.
|
||||
Um ca. 11:00 Uhr für so 10-15 Minuten.
|
||||
|
||||
Achtet bitte auf die Überstundenerechnung, da könnte noch der ein oder andere Fehler drin sein.
|
||||
Achtet bitte am Anfangauf die Überstundenerechnung, da könnte noch der ein oder andere Fehler drin sein.
|
||||
|
||||
Die Seite ist im Browser zu finden unter http://stunden.sds-systemtechnik.de:3333 oder http://192.168.120.64:3333
|
||||
|
||||
|
||||
Viele Grüße
|
||||
Carsten Graf
|
||||
|
||||
393
package-lock.json
generated
393
package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"node-cron": "^3.0.3",
|
||||
"pdfkit": "^0.13.0",
|
||||
"ping": "^0.4.4",
|
||||
"qrcode": "^1.5.4",
|
||||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -841,6 +842,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@@ -882,6 +892,46 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
@@ -1104,6 +1154,15 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
@@ -1225,6 +1284,12 @@
|
||||
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
|
||||
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -1528,6 +1593,19 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fontkit": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz",
|
||||
@@ -1672,6 +1750,15 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -2474,6 +2561,18 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
@@ -2975,6 +3074,33 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-map": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||
@@ -2990,6 +3116,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
@@ -3008,6 +3143,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
@@ -3099,6 +3243,15 @@
|
||||
"resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz",
|
||||
"integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g=="
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@@ -3204,6 +3357,23 @@
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||
@@ -3314,6 +3484,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/restructure": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz",
|
||||
@@ -3440,8 +3625,7 @@
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"optional": true
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
@@ -4132,6 +4316,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.20",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
|
||||
@@ -4259,11 +4449,52 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/zip-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
|
||||
@@ -4913,6 +5144,11 @@
|
||||
"get-intrinsic": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@@ -4940,6 +5176,36 @@
|
||||
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
||||
"optional": true
|
||||
},
|
||||
"cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"requires": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
@@ -5099,6 +5365,11 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="
|
||||
},
|
||||
"decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
@@ -5183,6 +5454,11 @@
|
||||
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
|
||||
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="
|
||||
},
|
||||
"dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
|
||||
},
|
||||
"dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -5427,6 +5703,15 @@
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"requires": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"fontkit": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz",
|
||||
@@ -5529,6 +5814,11 @@
|
||||
"wide-align": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -6095,6 +6385,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"requires": {
|
||||
"p-locate": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
@@ -6451,6 +6749,22 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"requires": {
|
||||
"p-try": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"requires": {
|
||||
"p-limit": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"p-map": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||
@@ -6460,6 +6774,11 @@
|
||||
"aggregate-error": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
|
||||
},
|
||||
"package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
@@ -6475,6 +6794,11 @@
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
@@ -6544,6 +6868,11 @@
|
||||
"resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz",
|
||||
"integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g=="
|
||||
},
|
||||
"pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
|
||||
},
|
||||
"possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@@ -6628,6 +6957,16 @@
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"requires": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||
@@ -6708,6 +7047,16 @@
|
||||
"set-function-name": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
|
||||
},
|
||||
"require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
|
||||
},
|
||||
"restructure": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz",
|
||||
@@ -6794,8 +7143,7 @@
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"optional": true
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"set-function-length": {
|
||||
"version": "1.2.2",
|
||||
@@ -7301,6 +7649,11 @@
|
||||
"is-weakset": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
|
||||
},
|
||||
"which-typed-array": {
|
||||
"version": "1.1.20",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
|
||||
@@ -7389,11 +7742,43 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"requires": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"zip-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
|
||||
|
||||
11
package.json
11
package.json
@@ -9,17 +9,18 @@
|
||||
"reset-db": "node reset-db.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"archiver": "^7.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"sqlite3": "^5.1.6",
|
||||
"body-parser": "^1.20.2",
|
||||
"ejs": "^3.1.9",
|
||||
"pdfkit": "^0.13.0",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"ldapjs": "^3.0.7",
|
||||
"node-cron": "^3.0.3",
|
||||
"pdfkit": "^0.13.0",
|
||||
"ping": "^0.4.4",
|
||||
"archiver": "^7.0.1"
|
||||
"qrcode": "^1.5.4",
|
||||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
|
||||
@@ -372,8 +372,13 @@ function renderWeek() {
|
||||
|
||||
// Stunden zur Summe hinzufügen
|
||||
// Bei ganztägigem Urlaub oder Krank sollten es bereits 8 Stunden sein (vom Backend gesetzt)
|
||||
// Feiertag: 8h Basis + gearbeitete Stunden (jede gearbeitete Stunde = Überstunde)
|
||||
// Bei halbem Tag Urlaub werden die Urlaubsstunden später in der Überstunden-Berechnung hinzugezählt
|
||||
totalHours += hours;
|
||||
if (isHoliday) {
|
||||
totalHours += 8 + (hours || 0); // 8h Feiertag + gearbeitete Stunden (= Überstunden)
|
||||
} else {
|
||||
totalHours += hours;
|
||||
}
|
||||
|
||||
// Bearbeitung ist immer möglich, auch nach Abschicken
|
||||
// Bei ganztägigem Urlaub oder Krank werden Zeitfelder deaktiviert; Feiertag: Anzeige, Zeitfelder optional (Überstunden)
|
||||
@@ -404,7 +409,7 @@ function renderWeek() {
|
||||
data-date="${dateStr}" data-field="break_minutes"
|
||||
${timeFieldsDisabled} ${disabled} oninput="saveEntry(this)" onchange="saveEntry(this)">
|
||||
</td>
|
||||
<td><strong id="hours_${dateStr}">${isFullDayVacation ? '8.00 h (Urlaub)' : isSick ? '8.00 h (Krank)' : isHoliday && !hours ? '8.00 h (Feiertag)' : hours.toFixed(2) + ' h'}</strong></td>
|
||||
<td><strong id="hours_${dateStr}">${isFullDayVacation ? '8.00 h (Urlaub)' : isSick ? '8.00 h (Krank)' : isHoliday && !hours ? '8.00 h (Feiertag)' : isHoliday && hours ? '8.00 + ' + hours.toFixed(2) + ' h (Überst.)' : hours.toFixed(2) + ' h'}</strong></td>
|
||||
</tr>
|
||||
<tr class="activities-row">
|
||||
<td colspan="6" class="activities-cell">
|
||||
@@ -576,7 +581,7 @@ function renderWeek() {
|
||||
vacationHours += 4; // Halber Tag = 4 Stunden
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Überstunden berechnen
|
||||
let overtimeTaken = 0;
|
||||
Object.values(currentEntries).forEach(e => {
|
||||
@@ -664,6 +669,23 @@ function updateOvertimeDisplay() {
|
||||
}
|
||||
} else if (sickStatus) {
|
||||
totalHours += 8; // Krank = 8 Stunden
|
||||
} else if (currentHolidayDates.has(dateStr)) {
|
||||
// Feiertag: 8h Basis + gearbeitete Stunden (jede Stunde = Überstunde)
|
||||
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
|
||||
const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`);
|
||||
const startTime = startInput ? startInput.value : '';
|
||||
const endTime = endInput ? endInput.value : '';
|
||||
let worked = 0;
|
||||
if (startTime && endTime) {
|
||||
const breakInput = document.querySelector(`input[data-date="${dateStr}"][data-field="break_minutes"]`);
|
||||
const breakMinutes = parseInt(breakInput ? breakInput.value : 0) || 0;
|
||||
const start = new Date(`2000-01-01T${startTime}`);
|
||||
const end = new Date(`2000-01-01T${endTime}`);
|
||||
worked = (end - start) / (1000 * 60 * 60) - (breakMinutes / 60);
|
||||
} else if (currentEntries[dateStr]?.total_hours) {
|
||||
worked = parseFloat(currentEntries[dateStr].total_hours) || 0;
|
||||
}
|
||||
totalHours += 8 + worked; // 8h Feiertag + gearbeitete Stunden (= Überstunden)
|
||||
} else {
|
||||
// Wenn 8 Überstunden (ganzer Tag) eingetragen sind, zählt der Tag als 0 Stunden
|
||||
if (isFullDayOvertime) {
|
||||
@@ -710,10 +732,7 @@ function updateOvertimeDisplay() {
|
||||
}
|
||||
|
||||
// Überstunden berechnen (wie im Backend: mit adjustedSollStunden)
|
||||
// Wenn 8 Überstunden genommen wurden, zählen diese Tage als 0 Stunden
|
||||
// Die negativen Stunden (wegen 0 statt Sollstunden) werden durch die verbrauchten Überstunden ausgeglichen
|
||||
// Daher: adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours)
|
||||
// So werden die Tage mit 8 Überstunden nicht zu negativen Überstunden führen
|
||||
// totalHours enthält bereits Feiertagsstunden (8h oder gearbeitete Stunden) aus dem Feiertag-Zweig oben
|
||||
const totalHoursWithVacation = totalHours + vacationHours;
|
||||
const adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours);
|
||||
// overtimeHours = Überstunden diese Woche (wie im Backend berechnet)
|
||||
|
||||
@@ -2,9 +2,18 @@
|
||||
|
||||
const { hasRole } = require('../helpers/utils');
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
const { generateCheckinCheckoutQRPDF } = require('../services/pdf-service');
|
||||
|
||||
// Routes registrieren
|
||||
function registerDashboardRoutes(app) {
|
||||
// QR-Code-PDF (Check-in/Check-out) – nur für eingeloggte Nutzer mit Mitarbeiter-Rolle
|
||||
app.get('/api/dashboard/qr-pdf', requireAuth, (req, res) => {
|
||||
if (!hasRole(req, 'mitarbeiter')) {
|
||||
return res.status(403).send('Zugriff verweigert');
|
||||
}
|
||||
generateCheckinCheckoutQRPDF(req, res);
|
||||
});
|
||||
|
||||
// Dashboard für Mitarbeiter
|
||||
app.get('/dashboard', requireAuth, (req, res) => {
|
||||
// Prüfe ob User Mitarbeiter-Rolle hat
|
||||
|
||||
@@ -6,7 +6,7 @@ const { initDatabase } = require('./database');
|
||||
const { getDefaultRole } = require('./helpers/utils');
|
||||
|
||||
const app = express();
|
||||
const PORT = 3335;
|
||||
const PORT = 3333;
|
||||
|
||||
// Middleware
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// PDF-Generierung Service
|
||||
|
||||
const PDFDocument = require('pdfkit');
|
||||
const QRCode = require('qrcode');
|
||||
const { db } = require('../database');
|
||||
const { formatDate, formatDateTime } = require('../helpers/utils');
|
||||
const { getHolidaysForDateRange } = require('./feiertage-service');
|
||||
@@ -528,4 +529,72 @@ function generatePDFToBuffer(timesheetId, req) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { generatePDF, generatePDFToBuffer };
|
||||
// Check-in/Check-out URL-Basis (wie im Dashboard-Frontend)
|
||||
function getCheckinBaseUrl(req) {
|
||||
const baseUrl = `${req.protocol}://${req.get('host')}`;
|
||||
return baseUrl.replace(/:\d+$/, ':3334');
|
||||
}
|
||||
|
||||
// PDF mit Check-in- und Check-out-QR-Codes (A4)
|
||||
async function generateCheckinCheckoutQRPDF(req, res) {
|
||||
const userId = req.session.userId;
|
||||
if (!userId) {
|
||||
return res.status(401).send('Nicht angemeldet');
|
||||
}
|
||||
const checkinBaseUrl = getCheckinBaseUrl(req);
|
||||
const checkinUrl = `${checkinBaseUrl}/api/checkin/${userId}`;
|
||||
const checkoutUrl = `${checkinBaseUrl}/api/checkout/${userId}`;
|
||||
|
||||
try {
|
||||
const [checkinQRBuffer, checkoutQRBuffer] = await Promise.all([
|
||||
QRCode.toBuffer(checkinUrl, { type: 'png', width: 400, margin: 1 }),
|
||||
QRCode.toBuffer(checkoutUrl, { type: 'png', width: 400, margin: 1 })
|
||||
]);
|
||||
|
||||
const firstname = (req.session.firstname || '').replace(/\s+/g, '');
|
||||
const lastname = (req.session.lastname || '').replace(/\s+/g, '');
|
||||
const namePart = [firstname, lastname].filter(Boolean).join('_') || 'User';
|
||||
const today = new Date();
|
||||
const dateStr = today.getFullYear() + '-' +
|
||||
String(today.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(today.getDate()).padStart(2, '0');
|
||||
const filename = `Check-in_Check-out_QR_${namePart}_${dateStr}.pdf`;
|
||||
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
|
||||
const doc = new PDFDocument({ size: 'A4', margin: 50 });
|
||||
doc.pipe(res);
|
||||
|
||||
const pageWidth = 595.28 - 100;
|
||||
const qrSize = 160;
|
||||
const gap = 40;
|
||||
const left1 = 50 + (pageWidth / 2 - qrSize - gap / 2);
|
||||
const left2 = 50 + (pageWidth / 2 + gap / 2);
|
||||
|
||||
doc.fontSize(18).text('Check-in / Check-out – Zeiterfassung', { align: 'center' });
|
||||
doc.moveDown(1.5);
|
||||
|
||||
const topY = doc.y;
|
||||
doc.image(checkinQRBuffer, left1, topY, { width: qrSize, height: qrSize });
|
||||
doc.image(checkoutQRBuffer, left2, topY, { width: qrSize, height: qrSize });
|
||||
|
||||
doc.fontSize(12).font('Helvetica-Bold');
|
||||
doc.text('Check-in', left1, topY + qrSize + 8, { width: qrSize, align: 'center' });
|
||||
doc.text('Check-out', left2, topY + qrSize + 8, { width: qrSize, align: 'center' });
|
||||
|
||||
doc.moveDown(2);
|
||||
doc.font('Helvetica').fontSize(10);
|
||||
doc.text('Scannen Sie den jeweiligen QR-Code zum Erfassen von Arbeitsbeginn (Check-in) bzw. Arbeitsende (Check-out).', 50, doc.y, { width: pageWidth, align: 'center' });
|
||||
|
||||
doc.end();
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Generieren des QR-PDFs:', err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).send('Fehler beim Erstellen des PDFs');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { generatePDF, generatePDFToBuffer, generateCheckinCheckoutQRPDF };
|
||||
|
||||
56
views/checkin-result.ejs
Normal file
56
views/checkin-result.ejs
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><%= title %> - Stundenerfassung</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
padding: 2.5rem;
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.card h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
.card .message {
|
||||
color: #555;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.card.success .icon { color: #27ae60; }
|
||||
.card.success h1 { color: #27ae60; }
|
||||
.card.error .icon { color: #e74c3c; }
|
||||
.card.error h1 { color: #e74c3c; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card <%= success ? 'success' : 'error' %>">
|
||||
<div class="icon" aria-hidden="true"><%= success ? '✓' : '!' %></div>
|
||||
<h1><%= title %></h1>
|
||||
<p class="message"><%= message %></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -116,6 +116,9 @@
|
||||
<button onclick="copyToClipboard('checkoutUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 12px;">
|
||||
<a href="/api/dashboard/qr-pdf" class="btn btn-sm btn-secondary" style="padding: 8px 12px; text-decoration: none; display: inline-block;" download>QR-Code-PDF herunterladen</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IP-Erfassung -->
|
||||
|
||||
Reference in New Issue
Block a user