diff --git a/package-lock.json b/package-lock.json index 27f88078..318d6b80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@vue-flow/core": "^1.43.1", "@vueuse/core": "^12.7.0", "@vueuse/head": "^2.0.0", + "@vueuse/integrations": "^13.7.0", "animate.css": "^4.1.1", "axios": "^1.7.8", "bulma": "^1.0.2", @@ -36,6 +37,7 @@ "chart.js": "^4.4.7", "dayspan-vuetify": "^0.4.0", "flow": "^0.2.3", + "qrcode": "^1.5.4", "sass": "^1.81.1", "sweetalert2": "^11.14.5", "tesseract.js": "^6.0.1", @@ -45,6 +47,7 @@ "vue-cal": "^5.0.1-rc.20", "vue-chartjs": "^5.3.2", "vue-popperjs": "^2.3.0", + "vue-qrcode-reader": "^5.7.3", "vue-router": "^4.5.0", "vue-toast-notification": "^3.1.3", "vue3-cookies": "^1.0.6", @@ -3466,6 +3469,18 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.5.tgz", "integrity": "sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==" }, + "node_modules/@types/dom-webcodecs": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.16.tgz", + "integrity": "sha512-gRNWaC3YW5EzhPRjVYy7BnxCbtLGqsgu+uTkmV/IxOF1bllFD+FAJ1KBdsDFsuJB+F+CE+nWmMlWt8vaZ3yYXA==", + "license": "MIT" + }, + "node_modules/@types/emscripten": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.40.1.tgz", + "integrity": "sha512-sr53lnYkQNhjHNN0oJDdUm5564biioI5DuOpycufDVK7D3y+GR3oUswe2rlwY1nPNyusHbrJ9WoTyIHl4/Bpwg==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -4069,6 +4084,116 @@ "vue": ">=2.7 || >=3" } }, + "node_modules/@vueuse/integrations": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-13.7.0.tgz", + "integrity": "sha512-Na5p0ONLepNV/xCBi8vBMuzCOZh9CFT/OHnrUlABWXgWTWSHM3wrVaLS1xvAijPLU5B1ysyJDDW/hKak80oLGA==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "13.7.0", + "@vueuse/shared": "13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7 || ^8", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/core": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.7.0.tgz", + "integrity": "sha512-myagn09+c6BmS6yHc1gTwwsdZilAovHslMjyykmZH3JNyzI5HoWhv114IIdytXiPipdHJ2gDUx0PB93jRduJYg==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.7.0", + "@vueuse/shared": "13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/metadata": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.7.0.tgz", + "integrity": "sha512-8okFhS/1ite8EwUdZZfvTYowNTfXmVCOrBFlA31O0HD8HKXhY+WtTRyF0LwbpJfoFPc+s9anNJIXMVrvP7UTZg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/shared": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.7.0.tgz", + "integrity": "sha512-Wi2LpJi4UA9kM0OZ0FCZslACp92HlVNw1KPaDY6RAzvQ+J1s7seOtcOpmkfbD5aBSmMn9NvOakc8ZxMxmDXTIg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/@vueuse/metadata": { "version": "12.7.0", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.7.0.tgz", @@ -4362,6 +4487,16 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/barcode-detector": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-2.2.2.tgz", + "integrity": "sha512-JcSekql+EV93evfzF9zBr+Y6aRfkR+QFvgyzbwQ0dbymZXoAI9+WgT7H1E429f+3RKNncHz2CW98VQtaaKpmfQ==", + "license": "MIT", + "dependencies": { + "@types/dom-webcodecs": "^0.1.11", + "zxing-wasm": "1.1.3" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4658,6 +4793,15 @@ "resolved": "https://registry.npmjs.org/callguard/-/callguard-2.0.0.tgz", "integrity": "sha512-I3nd+fuj20FK1qu00ImrbH+II+8ULS6ioYr9igqR1xyqySoqc3DiHEyUM0mkoAdKeLGg2CtGnO8R3VRQX5krpQ==" }, + "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/caniuse-lite": { "version": "1.0.30001707", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", @@ -4805,6 +4949,17 @@ "node": ">= 10" } }, + "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/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -5264,6 +5419,15 @@ } } }, + "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/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -5378,6 +5542,12 @@ "node": ">=0.10" } }, + "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/dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", @@ -5915,6 +6085,19 @@ "node": ">=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/flow": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/flow/-/flow-0.2.3.tgz", @@ -6110,6 +6293,15 @@ "node": ">=6.9.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", @@ -7312,6 +7504,18 @@ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", "dev": true }, + "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.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -7816,6 +8020,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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-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/packrup": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/packrup/-/packrup-0.1.2.tgz", @@ -7865,6 +8105,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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", @@ -8131,6 +8380,32 @@ "node": ">=6" } }, + "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/qrcode/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/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -8367,6 +8642,15 @@ "regjsparser": "bin/parser" } }, + "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-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -8376,6 +8660,12 @@ "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/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -8940,6 +9230,12 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, + "node_modules/sdp": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz", + "integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==", + "license": "MIT" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -8957,6 +9253,12 @@ "randombytes": "^2.1.0" } }, + "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==", + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -10266,6 +10568,19 @@ "popper.js": "^1.15.0" } }, + "node_modules/vue-qrcode-reader": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.7.3.tgz", + "integrity": "sha512-iSGko42FsEvdHyizBMBs/X+HMO9Z5ONDxjW+mQdoraOR5emRNedmjC5SEJdYzGz8ZP5ME3lwB4iHy3S7MOt5Qw==", + "license": "MIT", + "dependencies": { + "barcode-detector": "2.2.2", + "webrtc-adapter": "8.2.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/vue-router": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", @@ -10329,6 +10644,19 @@ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true }, + "node_modules/webrtc-adapter": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz", + "integrity": "sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==", + "license": "BSD-3-Clause", + "dependencies": { + "sdp": "^3.2.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", @@ -10424,6 +10752,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.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", @@ -10839,11 +11173,52 @@ "node": ">=0.4" } }, + "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": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, + "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/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -10897,6 +11272,15 @@ "engines": { "node": "*" } + }, + "node_modules/zxing-wasm": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-1.1.3.tgz", + "integrity": "sha512-MYm9k/5YVs4ZOTIFwlRjfFKD0crhefgbnt1+6TEpmKUDFp3E2uwqGSKwQOd2hOIsta/7Usq4hnpNRYTLoljnfA==", + "license": "MIT", + "dependencies": { + "@types/emscripten": "^1.39.10" + } } } } diff --git a/package.json b/package.json index 75c82b80..07cd107b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@vue-flow/core": "^1.43.1", "@vueuse/core": "^12.7.0", "@vueuse/head": "^2.0.0", + "@vueuse/integrations": "^13.7.0", "animate.css": "^4.1.1", "axios": "^1.7.8", "bulma": "^1.0.2", @@ -37,6 +38,7 @@ "chart.js": "^4.4.7", "dayspan-vuetify": "^0.4.0", "flow": "^0.2.3", + "qrcode": "^1.5.4", "sass": "^1.81.1", "sweetalert2": "^11.14.5", "tesseract.js": "^6.0.1", @@ -46,6 +48,7 @@ "vue-cal": "^5.0.1-rc.20", "vue-chartjs": "^5.3.2", "vue-popperjs": "^2.3.0", + "vue-qrcode-reader": "^5.7.3", "vue-router": "^4.5.0", "vue-toast-notification": "^3.1.3", "vue3-cookies": "^1.0.6", diff --git a/src/components/displays/department/pos/steps/mobile/sounds/onAfterSuccessfulScan.mp3 b/public/sounds/success.mp3 similarity index 100% rename from src/components/displays/department/pos/steps/mobile/sounds/onAfterSuccessfulScan.mp3 rename to public/sounds/success.mp3 diff --git a/src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile1.vue b/src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile1.vue index 910945f4..0106080c 100644 --- a/src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile1.vue +++ b/src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile1.vue @@ -15,24 +15,16 @@ import PosDepartmentStepMobileButtonNextStep from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileButtonNextStep.vue"; import { manualInput, vehicles, camera } from "./objects/PosDepartmentStepMobileFlow.vue"; import { PosSearchResult } from "./objects/PosSearchResult.vue"; -import { searchVehicle, register_new_search, customer_id } from "@/components/shop/POSDepartmentProcess.vue"; import RegistrationNumberSearchResult from "@/components/models/pos/step1/RegistrationNumberSearchResult.vue"; import UnknownCustomer from "@/components/viewport/elements/icons/UnknownCustomer.vue"; import VerifiedCustomer from "@/components/viewport/elements/icons/VerifiedCustomer.vue"; import BookedCustomer from "@/components/viewport/elements/icons/BookedCustomer.vue"; import KnownCustomer from "@/components/viewport/elements/icons/KnownCustomer.vue"; import CardPaymentCustomer from "@/components/viewport/elements/icons/CardPaymentCustomer.vue"; -import {VehicleStatusKey} from "@/components/displays/department/pos/steps/mobile/objects/PosVehicleStatus.vue"; import PosDepartmentStepMobileFixedBottomControl from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileFixedBottomControl.vue"; import PosDepartmentStepMobile1Location from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1Location.vue"; -onMounted(() => { - // Set the header to be transparent - setTransparency(!manualInput.value); // Set transparency based on manualInput state - setBackgroundColor(backgroundColors.default); // Set the default background color - setOverflow(false); // Prevent scrolling -}); const lastParsedImage = ref(null); const setLastCapturedImage = (image: string) => { @@ -177,6 +169,13 @@ const shouldCustomerBeModified = computed(() => { return vehicles.activeVehicleIndex.value === 1; }); +onMounted(() => { + // Set the header to be transparent + setTransparency(!manualInput.value); // Set transparency based on manualInput state + setBackgroundColor(backgroundColors.default); // Set the default background color + setOverflow(false); // Prevent scrolling +}); + onUnmounted(() => { // Reset the header settings when the component is unmounted setTransparency(false); // Reset transparency diff --git a/src/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue b/src/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue index 9f14f516..201c2bfa 100644 --- a/src/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue +++ b/src/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue @@ -7,6 +7,7 @@ import type {PosPopup} from './PosPopup.vue'; import type {PosOrder} from './PosOrder.vue'; import type {Addon} from "@/components/displays/department/pos/steps/mobile/objects/PosAddon.vue"; import type { PosLocation } from './PosLocation.vue'; +import type { PosSoundEffect } from './PosSoundEffect.vue'; /** Views */ // Step 1 @@ -20,6 +21,54 @@ const views = { // Step 2: Vehicle Selection from a list. vehicleSelection, }; +/** Sound effects */ +const soundEffects = ref([]); +const soundEffectsEnabled = ref(true); +const playSoundEffect = (soundEffect: PosSoundEffect) => { + if (!soundEffectsEnabled.value) return; + const audio = new Audio(soundEffect.src); + audio.volume = soundEffect.volume ?? 1.0; + audio.play().catch(error => { + console.error('Error playing sound effect:', error); + }); +} +const toggleSoundEffects = () => { + soundEffectsEnabled.value = !soundEffectsEnabled.value; +} +const setSoundEffects = (enabled: boolean) => { + soundEffectsEnabled.value = enabled; +} +const getSoundEffects = () => { + return soundEffects.value; +} +const addSoundEffect = (soundEffect: PosSoundEffect) => { + soundEffects.value.push(soundEffect); +} +const clearSoundEffects = () => { + soundEffects.value = []; +} +const defaultSoundEffects = ref<{ [key: string]: PosSoundEffect }>({ + onAfterSuccessfulScan: { + id: 'success', + src: '/sounds/success.mp3', + volume: 0.5, + pitch: 1.0, + delay: 0, + loop: false, + } +}); +const listDefaultSoundEffects = computed(() => defaultSoundEffects.value); +const sounds = { + soundEffects, + enabled: soundEffectsEnabled, + play: playSoundEffect, // example: sounds.play(sounds.list.value.onAfterSuccessfulScan) + toggle: toggleSoundEffects, + set: setSoundEffects, + get: getSoundEffects, + add: addSoundEffect, + clear: clearSoundEffects, + list: listDefaultSoundEffects +} /** Device location */ const location = ref(null); const locationTimeout = ref(5000); // Timeout for location retrieval in milliseconds @@ -705,6 +754,7 @@ const pos = { popups, reset, locations, + sounds, } /** Persistent data */ @@ -1063,6 +1113,17 @@ export { resetPos, resetLastSavedTime, + // Sounds + sounds, + soundEffects, + soundEffectsEnabled, + playSoundEffect, + toggleSoundEffects, + setSoundEffects, + getSoundEffects, + addSoundEffect, + clearSoundEffects, + // Vehicles vehicles, activeVehicleIndex, diff --git a/src/components/displays/department/pos/steps/mobile/objects/PosSoundEffect.vue b/src/components/displays/department/pos/steps/mobile/objects/PosSoundEffect.vue new file mode 100644 index 00000000..0d36ed14 --- /dev/null +++ b/src/components/displays/department/pos/steps/mobile/objects/PosSoundEffect.vue @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/src/components/displays/superuser/tables/usersTable.vue b/src/components/displays/superuser/tables/usersTable.vue index d02fa487..2968d5b0 100644 --- a/src/components/displays/superuser/tables/usersTable.vue +++ b/src/components/displays/superuser/tables/usersTable.vue @@ -2,8 +2,8 @@ import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue"; defineProps(['objects']); -import { ref } from 'vue'; import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue"; +import Swal from "sweetalert2"; // Get the departments (If the departments are not already loaded) if (departments.value.length === 0) { @@ -20,6 +20,34 @@ const redirect = (path) => { window.location = path; } +const onShowImpersonationQRCode = (src, directLink) => { + Swal.fire({ + title: 'Scan QR-koden for at logge ind som bruger', + html: `QR-kode`, + showCloseButton: true, + showConfirmButton: !!directLink, + showCancelButton: true, + focusConfirm: false, + confirmButtonText: 'Kopiér link', + cancelButtonText: 'Luk', + preConfirm: () => { + navigator.clipboard.writeText(directLink); + Swal.showValidationMessage('Link kopieret til udklipsholderen'); + } + }); +} + +const onClickShowImpersonationQRCode = async (userId) => { + // Get the session token + const newToken = await SessionUser.superUser.intimidate.getImpersonationToken(userId); + // Create the impersonation link + const link = `${window.location.protocol}//${window.location.host}/login/qr?token=${newToken}`; + // Generate a QR code for the impersonation link + const response = await SessionUser.superUser.intimidate.showImpersonationQRCode(link); + // Show the QR code in a modal + onShowImpersonationQRCode(response.value, link); +} + diff --git a/src/components/forms/auth/LoginForm.vue b/src/components/forms/auth/LoginForm.vue index 408b9456..940f8a69 100644 --- a/src/components/forms/auth/LoginForm.vue +++ b/src/components/forms/auth/LoginForm.vue @@ -92,7 +92,16 @@ SessionUser.auth.reCAPTCHA.preCheck.get().then((response) => {
- Login +
+ Login + + + + + + Login with QR code + +
diff --git a/src/components/models/navigation/items/NavigationMenuItemsAdmin.vue b/src/components/models/navigation/items/NavigationMenuItemsAdmin.vue index 0d2fde36..546ce618 100644 --- a/src/components/models/navigation/items/NavigationMenuItemsAdmin.vue +++ b/src/components/models/navigation/items/NavigationMenuItemsAdmin.vue @@ -78,7 +78,7 @@ const parsedItems = () => { export default defineComponent({ - name: 'NavigationMenuItemsGuest', + name: 'NavigationMenuItemsAdmin', setup() { return { items, diff --git a/src/components/models/navigation/items/NavigationMenuItemsGlobal.vue b/src/components/models/navigation/items/NavigationMenuItemsGlobal.vue index 2284d686..7e15f043 100644 --- a/src/components/models/navigation/items/NavigationMenuItemsGlobal.vue +++ b/src/components/models/navigation/items/NavigationMenuItemsGlobal.vue @@ -25,7 +25,7 @@ const items = ref([ label: 'Sign out →', to: `/logout`, type: 'link', - permissions: ['guest'], + permissions: ['fetch_session'], } ]); @@ -35,7 +35,7 @@ const parsedItems = () => { export default defineComponent({ - name: 'NavigationMenuItemsGuest', + name: 'NavigationMenuItemsGlobal', setup() { return { items, diff --git a/src/components/session/token/SessionUser.vue b/src/components/session/token/SessionUser.vue index 6b3014ba..42802a57 100644 --- a/src/components/session/token/SessionUser.vue +++ b/src/components/session/token/SessionUser.vue @@ -38,7 +38,6 @@ import {XLVaskVehicleTypes} from "@/components/session/token/SessionUser/Objects import {OpenAI} from "@/components/session/token/superUser/modules/openAI/OpenAI.vue"; import {ObjectsGlobal} from "@/components/session/token/SessionUser/Objects/ObjectsGlobal.vue"; import Swal from "sweetalert2"; -import {useRouter} from "vue-router"; import router from "@/router.js"; /** @@ -122,6 +121,15 @@ export const getSessionData = async () => { .catch((error) => { parseError(error, 'auth'); console.error(error); + Swal.fire({ + title: 'Fejl ved hentning af brugerdata', + text: 'Der opstod en fejl ved hentning af dine brugerdata. Prøv at logge ind igen.', + icon: 'error', + confirmButtonText: 'Ok' + }).then(() => { + SessionUser.auth.forceClearSession(); + SessionUser.functions.redirectTo.pages.login(); + }); }); }; diff --git a/src/components/session/token/superUserObject.vue b/src/components/session/token/superUserObject.vue index 16780094..fe82f402 100644 --- a/src/components/session/token/superUserObject.vue +++ b/src/components/session/token/superUserObject.vue @@ -16,6 +16,8 @@ import { Entra } from "@/components/session/token/superUser/modules/entra/Entra. import { Limble } from "@/components/session/token/superUser/modules/limble/Limble.vue"; import { OcrSpace } from "@/components/session/token/superUser/modules/ocrSpace/OcrSpace.vue"; import { OpenAI } from "@/components/session/token/superUser/modules/openAI/OpenAI.vue"; +import { useQRCode } from '@vueuse/integrations/useQRCode'; + /** * The superuser object @@ -41,6 +43,25 @@ export const SuperUserObject = { }, /** Intimidate a user */ intimidate: { + /** + * Get an impersonation (session) token for a user + * @param userId + * @returns {Promise} The impersonation token + */ + getImpersonationToken: async (userId) => { + return await authenticatedRequest("/su/intimidate", "POST", {user_id: userId}) + .then( + (response) => { + // Return the token + return response.data.data.token; + } + ) + .catch( + (error) => { + parseError(error, 'auth'); + } + ); + }, /** * Intimidate a user * @param userId @@ -65,6 +86,14 @@ export const SuperUserObject = { } ); }, + /** + * Show a QR code for logging in as a user + * @param link + * @returns {Promise} The QR code URL + */ + showImpersonationQRCode: async (link) => { + return useQRCode(link); + }, /** * Go back from intimidating a user */ diff --git a/src/components/viewport/page/headers/menu/NavigationMenuDepartment.vue b/src/components/viewport/page/headers/menu/NavigationMenuDepartment.vue index ae3850c7..bf10ed93 100644 --- a/src/components/viewport/page/headers/menu/NavigationMenuDepartment.vue +++ b/src/components/viewport/page/headers/menu/NavigationMenuDepartment.vue @@ -14,7 +14,10 @@ type department = { } const options = computed(() => { - return departments.value.map((department: department) => { + // Filter out departments the user does not have access to + return departments.value.filter((department: department) => { + return SessionUser.canAccessDepartment(department.id); + }).map((department: department) => { return { value: department.id, label: department.name, diff --git a/src/views/DefaultPage.vue b/src/views/DefaultPage.vue index c820b85b..7dcde3d4 100644 --- a/src/views/DefaultPage.vue +++ b/src/views/DefaultPage.vue @@ -70,6 +70,13 @@ const attemptRedirect = (skipMobileCheck = false) => { if ((isMobile.value && highestAccessLevel.route === 'superuser' || highestAccessLevel.route === 'admin') && !skipMobileCheck) { // Attempt to get the user's location data here attemptAutoSelectDepartment.value = true; + // If the user only has access to one department, redirect to that department immediately + if (departments.value.filter(dept => SessionUser.canAccessDepartment(dept.id)).length === 1) { + const singleDept = departments.value.find(dept => SessionUser.canAccessDepartment(dept.id)); + console.log('User has access to only one department, redirecting to that department with ID:', singleDept.id); + router.push({ name: 'pos', params: { departmentId: singleDept.id } }); + return; // Exit the function to prevent further execution + } return; } // Redirect to the route associated with the highest access level @@ -114,8 +121,16 @@ watch(attemptAutoSelectDepartment, (newValue) => { console.log('Timeout reached for location fetching, stopping attempts'); // Check if the location data is still not available if (!locations?.get()) { - console.log('Location data not available, stopping auto-select attempts and redirecting to highest access level route'); - attemptRedirect(true); // Skip mobile check to avoid loops + // If the user only has access to one department, redirect to that department immediately + if (departments.value.filter(dept => SessionUser.canAccessDepartment(dept.id)).length === 1) { + const singleDept = departments.value.find(dept => SessionUser.canAccessDepartment(dept.id)); + console.log('User has access to only one department, redirecting to that department with ID:', singleDept.id); + router.push({ name: 'pos', params: { departmentId: singleDept.id } }); + return; // Exit the function to prevent further execution + } else { + console.log('Location data not available, stopping auto-select attempts and redirecting to highest access level route'); + attemptRedirect(true); // Skip mobile check to avoid loops + } } }, locations.defaultTimeout.value); } diff --git a/src/views/auth/LoginQR.vue b/src/views/auth/LoginQR.vue index 3fa9bbf2..193d83ff 100644 --- a/src/views/auth/LoginQR.vue +++ b/src/views/auth/LoginQR.vue @@ -1,42 +1,327 @@ \ No newline at end of file