diff --git a/README.md b/README.md index 71693b77..b43ca54b 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,21 @@ Preferences -> Tools -> Actions on Save * Run eslint --fix * Run prettier +## Translations + +We use (vue-i18n)[https://kazupon.github.io/vue-i18n/] for translations +and (vue-i18n-extract)[https://github.com/Spittal/vue-i18n-extract] for helper +scripts. + +``` +# will create a report on command line with missing translations +npm run vue-i18n-extract + +# add missing translations to files, see docs for more options +cd client +npx vue-i18n-extract --add +``` + ## Deployment to CapRover ### CapRover Dev (vbv-lernwelt.control.iterativ.ch) @@ -131,3 +146,24 @@ Preferences -> Tools -> Actions on Save ![](docs/envfile_plugin_settings.png) #### Install the tailwind css Plugin from Jetbrains + +## currents.dev + +This project uses [currents.dev](https://currents.dev) to run the Cypress tests concurrently. +The following steps were taken to set it up: + +- Create a new project on currents.dev +- Add the generated `projectId` to the cypress configuration file +- Install `@currents/cli` as a dev dependency +- Add the following script to the `package.json` file: + ```json + "cypress:ci": "currents run --parallel --record --key $CURRENTS_KEY", + ``` +- Create a new Bitbucket pipelines job that exports the `CURRENTS_KEY` and runs the `cypress:ci` script +- Refactor the pipeline steps so that the dependencies are installed first and then the other steps are run in + parallel. + You can then run multiple instances of the previously created job in parallel. + See `.bitbucket-pipelines.yml` for an example. + +> 💡 The number of cypress worker jobs depends on the number of cypress tests. +> Too many workers for too few tests will result in a lot of idle workers. diff --git a/client/.prettierignore b/client/.prettierignore index de4d1f00..80cb10cb 100644 --- a/client/.prettierignore +++ b/client/.prettierignore @@ -1,2 +1,4 @@ dist node_modules +**/__tests__/*.json +src/colors.json diff --git a/client/.prettierrc b/client/.prettierrc index 8258aa33..7542914d 100644 --- a/client/.prettierrc +++ b/client/.prettierrc @@ -1,8 +1,9 @@ { + "htmlWhitespaceSensitivity": "ignore", + "jsonRecursiveSort": true, + "organizeImportsSkipDestructiveCodeActions": true, + "printWidth": 88, "semi": true, "singleQuote": false, - "tabWidth": 2, - "printWidth": 88, - "organizeImportsSkipDestructiveCodeActions": true, - "htmlWhitespaceSensitivity": "ignore" + "tabWidth": 2 } diff --git a/client/package-lock.json b/client/package-lock.json index e2df688f..08d5fb5c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,6 +21,7 @@ "pinia": "^2.0.21", "vue": "^3.2.38", "vue-i18n": "^9.2.2", + "vue-i18n-extract": "^2.0.7", "vue-router": "^4.1.5" }, "devDependencies": { @@ -51,6 +52,7 @@ "postcss-import": "^14.1.0", "prettier": "^2.7.1", "prettier-plugin-organize-imports": "^3.1.1", + "prettier-plugin-sort-json": "^1.0.0", "prettier-plugin-tailwindcss": "^0.2.1", "replace-in-file": "^6.3.5", "sass": "^1.54.6", @@ -3345,6 +3347,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -4225,8 +4233,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-query": { "version": "5.1.3", @@ -4409,8 +4416,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -4467,7 +4473,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -4569,6 +4574,14 @@ "node": ">=10.16.0" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4942,8 +4955,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/constant-case": { "version": "3.0.4", @@ -5725,6 +5737,26 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, + "node_modules/dot-object": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz", + "integrity": "sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==", + "dependencies": { + "commander": "^4.0.0", + "glob": "^7.1.5" + }, + "bin": { + "dot-object": "bin/dot-object" + } + }, + "node_modules/dot-object/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, "node_modules/dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -6953,8 +6985,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", @@ -7046,7 +7077,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7084,7 +7114,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7094,7 +7123,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7552,7 +7580,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -7561,8 +7588,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/inquirer": { "version": "8.2.5", @@ -7946,6 +7972,14 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -8062,7 +8096,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -8675,7 +8708,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -8965,7 +8997,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -9205,7 +9236,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9522,6 +9552,21 @@ } } }, + "node_modules/prettier-plugin-sort-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-1.0.0.tgz", + "integrity": "sha512-XgcaF/Sojax1vD6j53wNIByx0rp7ecang+A8W0eM+Ks3yBFu/qXjJNvUtC1lEWeYbNfmRs/d8FyYJCYozAVENw==", + "dev": true, + "dependencies": { + "@types/prettier": "^2.7.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "prettier": "^2.3.2" + } + }, "node_modules/prettier-plugin-tailwindcss": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.1.tgz", @@ -11125,6 +11170,39 @@ "vue": "^3.0.0" } }, + "node_modules/vue-i18n-extract": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/vue-i18n-extract/-/vue-i18n-extract-2.0.7.tgz", + "integrity": "sha512-i1NW5R58S720iQ1BEk+6ILo3hT6UA8mtYNNolSH4rt9345qvXdvA6GHy2+jHozdDAKHwlu9VvS/+vIMKs1UYQw==", + "dependencies": { + "cac": "^6.7.12", + "dot-object": "^2.1.4", + "glob": "^8.0.1", + "is-valid-glob": "^1.0.0", + "js-yaml": "^4.1.0" + }, + "bin": { + "vue-i18n-extract": "bin/vue-i18n-extract.js" + } + }, + "node_modules/vue-i18n-extract/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/vue-router": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", @@ -11460,8 +11538,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.11.0", @@ -14317,6 +14394,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, "@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -15009,8 +15092,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "aria-query": { "version": "5.1.3", @@ -15149,8 +15231,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -15190,7 +15271,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "requires": { "balanced-match": "^1.0.0" } @@ -15256,6 +15336,11 @@ "streamsearch": "^1.1.0" } }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==" + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -15544,8 +15629,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "constant-case": { "version": "3.0.4", @@ -16143,6 +16227,22 @@ } } }, + "dot-object": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz", + "integrity": "sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==", + "requires": { + "commander": "^4.0.0", + "glob": "^7.1.5" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + } + } + }, "dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -16977,8 +17077,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.2", @@ -17042,7 +17141,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -17056,7 +17154,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -17066,7 +17163,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -17416,7 +17512,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -17425,8 +17520,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inquirer": { "version": "8.2.5", @@ -17703,6 +17797,11 @@ } } }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==" + }, "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -17801,7 +17900,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } @@ -18274,7 +18372,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, "requires": { "brace-expansion": "^2.0.1" } @@ -18490,7 +18587,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -18678,8 +18774,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -18847,6 +18942,15 @@ "dev": true, "requires": {} }, + "prettier-plugin-sort-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-1.0.0.tgz", + "integrity": "sha512-XgcaF/Sojax1vD6j53wNIByx0rp7ecang+A8W0eM+Ks3yBFu/qXjJNvUtC1lEWeYbNfmRs/d8FyYJCYozAVENw==", + "dev": true, + "requires": { + "@types/prettier": "^2.7.2" + } + }, "prettier-plugin-tailwindcss": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.1.tgz", @@ -19980,6 +20084,32 @@ "@vue/devtools-api": "^6.2.1" } }, + "vue-i18n-extract": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/vue-i18n-extract/-/vue-i18n-extract-2.0.7.tgz", + "integrity": "sha512-i1NW5R58S720iQ1BEk+6ILo3hT6UA8mtYNNolSH4rt9345qvXdvA6GHy2+jHozdDAKHwlu9VvS/+vIMKs1UYQw==", + "requires": { + "cac": "^6.7.12", + "dot-object": "^2.1.4", + "glob": "^8.0.1", + "is-valid-glob": "^1.0.0", + "js-yaml": "^4.1.0" + }, + "dependencies": { + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + } + } + }, "vue-router": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", @@ -20233,8 +20363,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ws": { "version": "8.11.0", diff --git a/client/package.json b/client/package.json index 4d8a7a34..92c91e05 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ "coverage": "vitest run --coverage", "typecheck": "vue-tsc --noEmit -p tsconfig.app.json --composite false", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "vue-i18n-extract": "vue-i18n-extract report", "prettier": "prettier . --write", "prettier:check": "prettier . --check", "tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --watch" @@ -28,6 +29,7 @@ "pinia": "^2.0.21", "vue": "^3.2.38", "vue-i18n": "^9.2.2", + "vue-i18n-extract": "^2.0.7", "vue-router": "^4.1.5" }, "devDependencies": { @@ -58,6 +60,7 @@ "postcss-import": "^14.1.0", "prettier": "^2.7.1", "prettier-plugin-organize-imports": "^3.1.1", + "prettier-plugin-sort-json": "^1.0.0", "prettier-plugin-tailwindcss": "^0.2.1", "replace-in-file": "^6.3.5", "sass": "^1.54.6", diff --git a/client/src/components/AppFooter.vue b/client/src/components/AppFooter.vue index 19f67a71..4174bd72 100644 --- a/client/src/components/AppFooter.vue +++ b/client/src/components/AppFooter.vue @@ -1,15 +1,16 @@ @@ -21,19 +22,27 @@ async function changeLocale(event: Event) {
{{ $t("footer.imprint") }}
VBV_VERSION_BUILD_NUMBER_VBV
-
Deutsch
- + + + + + +
{{ $t("footer.contact") }}
diff --git a/client/src/components/learningPath/page/LearningPathAppointmentsMock.vue b/client/src/components/learningPath/page/LearningPathAppointmentsMock.vue new file mode 100644 index 00000000..f151385c --- /dev/null +++ b/client/src/components/learningPath/page/LearningPathAppointmentsMock.vue @@ -0,0 +1,12 @@ + diff --git a/client/src/components/learningPath/LearningPathCircle.vue b/client/src/components/learningPath/page/LearningPathCircle.vue similarity index 88% rename from client/src/components/learningPath/LearningPathCircle.vue rename to client/src/components/learningPath/page/LearningPathCircle.vue index 55239b18..410976ae 100644 --- a/client/src/components/learningPath/LearningPathCircle.vue +++ b/client/src/components/learningPath/page/LearningPathCircle.vue @@ -51,9 +51,17 @@ const pieData = computed(() => { .reverse() as CircleSector[]; }); -const width = 450; -const height = 450; -const radius = Math.min(width, height) / 2.4; +const width = 100; +const height = 100; +const radius = 50; + +const svgId = computed(() => { + // Generate a random id for the svg element like 'circle-visualization-ziurxxmp' + return `circle-visualization-${Math.random() + .toString(36) + .replace(/[^a-z]+/g, "") + .substring(2, 10)}`; +}); function getColor(sector: CircleSector) { let color = colors.gray[300]; @@ -67,7 +75,7 @@ function getColor(sector: CircleSector) { } function render() { - const svg = d3.select(".circle-visualization"); + const svg = d3.select("#" + svgId.value); // Clean svg before adding new stuff. svg.selectAll("*").remove(); @@ -124,10 +132,10 @@ function render() { diff --git a/client/src/components/learningPath/page/LearningPathContinueButton.vue b/client/src/components/learningPath/page/LearningPathContinueButton.vue new file mode 100644 index 00000000..fc9005a7 --- /dev/null +++ b/client/src/components/learningPath/page/LearningPathContinueButton.vue @@ -0,0 +1,24 @@ + + + diff --git a/client/src/components/learningPath/page/LearningPathListView.vue b/client/src/components/learningPath/page/LearningPathListView.vue new file mode 100644 index 00000000..f37707da --- /dev/null +++ b/client/src/components/learningPath/page/LearningPathListView.vue @@ -0,0 +1,61 @@ + + + diff --git a/client/src/components/learningPath/page/LearningPathPathView.vue b/client/src/components/learningPath/page/LearningPathPathView.vue new file mode 100644 index 00000000..79257442 --- /dev/null +++ b/client/src/components/learningPath/page/LearningPathPathView.vue @@ -0,0 +1,130 @@ + + + diff --git a/client/src/components/learningPath/page/LearningPathProgress.vue b/client/src/components/learningPath/page/LearningPathProgress.vue new file mode 100644 index 00000000..744f1ca0 --- /dev/null +++ b/client/src/components/learningPath/page/LearningPathProgress.vue @@ -0,0 +1,33 @@ + + + diff --git a/client/src/components/learningPath/page/LearningPathScrollButton.vue b/client/src/components/learningPath/page/LearningPathScrollButton.vue new file mode 100644 index 00000000..4fd384c2 --- /dev/null +++ b/client/src/components/learningPath/page/LearningPathScrollButton.vue @@ -0,0 +1,40 @@ + + + diff --git a/client/src/components/learningPath/page/LearningPathTopics.vue b/client/src/components/learningPath/page/LearningPathTopics.vue new file mode 100644 index 00000000..bd8c192c --- /dev/null +++ b/client/src/components/learningPath/page/LearningPathTopics.vue @@ -0,0 +1,27 @@ + + + diff --git a/client/src/components/learningPath/page/LearningPathViewSwitch.vue b/client/src/components/learningPath/page/LearningPathViewSwitch.vue new file mode 100644 index 00000000..41b80a1c --- /dev/null +++ b/client/src/components/learningPath/page/LearningPathViewSwitch.vue @@ -0,0 +1,52 @@ + + + diff --git a/client/src/components/learningPath/page/utils.ts b/client/src/components/learningPath/page/utils.ts new file mode 100644 index 00000000..dd6a4d67 --- /dev/null +++ b/client/src/components/learningPath/page/utils.ts @@ -0,0 +1,20 @@ +import type { + CircleSectorData, + CircleSectorProgress, +} from "@/components/learningPath/page/LearningPathCircle.vue"; +import type { Circle } from "@/services/circle"; + +export function calculateCircleSectorData(circle: Circle): CircleSectorData[] { + const sectors = circle.learningSequences.map((ls) => { + let progress: CircleSectorProgress = "none"; + if (circle.allFinishedInLearningSequence(ls.translation_key)) { + progress = "finished"; + } else if (circle.someFinishedInLearningSequence(ls.translation_key)) { + progress = "in_progress"; + } + return { + progress: progress, + }; + }); + return sectors; +} diff --git a/client/src/components/ui/ItToggleSwitch.vue b/client/src/components/ui/ItToggleSwitch.vue new file mode 100644 index 00000000..b48df7eb --- /dev/null +++ b/client/src/components/ui/ItToggleSwitch.vue @@ -0,0 +1,26 @@ + + + diff --git a/client/src/fetchHelpers.ts b/client/src/fetchHelpers.ts index abfe7d6a..f9a4d699 100644 --- a/client/src/fetchHelpers.ts +++ b/client/src/fetchHelpers.ts @@ -29,6 +29,7 @@ export const itPost = (url: RequestInfo, data: unknown, options: RequestInit = { }, options?.headers ) as HeadersInit; + options.headers = headers; options = Object.assign( { diff --git a/client/src/i18n.ts b/client/src/i18n.ts index 7a222ab9..ccd76d3a 100644 --- a/client/src/i18n.ts +++ b/client/src/i18n.ts @@ -1,9 +1,10 @@ +import type { AvailableLanguages } from "@/stores/user"; import dayjs from "dayjs"; import { nextTick } from "vue"; import { createI18n } from "vue-i18n"; // https://vue-i18n.intlify.dev/guide/advanced/lazy.html -export const SUPPORT_LOCALES = ["de", "fr", "it"]; +export const SUPPORT_LOCALES: AvailableLanguages[] = ["de", "fr"]; let i18n: any = null; export function setupI18n( diff --git a/client/src/locales/de.json b/client/src/locales/de.json index 61cc2089..d826026b 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -1,170 +1,179 @@ { + "circlePage": { + "circleContentBoxTitle": "Das lernst du in diesem Circle.", + "contactExpertButton": "Fachexpertin kontaktieren", + "contactExpertDescription": "Tausche dich mit der Fachexpertin für den Circle {circleName} aus.", + "documents": { + "action": "Unterlagen hochladen", + "chooseLearningSequence": "Bitte wähle eine Lernsequenz aus", + "chooseName": "Bitte wähle einen Namen", + "chooseSequence": "Wähle eine Lernsequenz aus", + "expertDescription": "Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.", + "fileLabel": "Datei", + "maxFileSize": "Maximale Dateigrösse: 20 MB", + "modalAction": "Datei auswählen", + "modalFileName": "Name", + "modalNameInformation": "Max. 70 Zeichen", + "selectFile": "Bitte wähle eine Datei aus", + "title": "Unterlagen", + "uploadErrorMessage": "Beim Hochladen ist ein Fehler aufgetreten. Bitte versuche es erneut.", + "userDescription": "Hier findest du die Unterlagen, die dir die Fachexpertin zur Verfügung gestellt hat." + }, + "duration": "Dauer", + "gotQuestions": "Hast du Fragen?", + "learnMore": "Erfahre mehr dazu" + }, + "cockpit": { + "examsDone": "Abgelegte Prüfungen von Teilnehmer.", + "feedbacksDone": "Abgeschickte Feedbacks von Teilnehmer.", + "notifyTask": "Benachrichtigen", + "notifyTaskDescription": "Teilnehmer benachrichtigen", + "profileLink": "Profil anzeigen", + "progress": "Fortschritt", + "tasksDone": "Erledigte Transferaufträge von Teilnehmer.", + "title": "Cockpit" + }, + "competences": { + "assessAgain": "Sich nochmals einschätzen", + "assessment": "Einschätzungen", + "competences": "Kompetenzen", + "lastImprovements": "Letzte verbesserte Kompetenzen", + "notAssessed": "Nicht eingeschätzt", + "title": "KompetenzNavi" + }, + "constants": { + "no": "Nein", + "satisfied": "zufrieden", + "unsatisfied": "unzufrieden", + "verySatisfied": "sehr zufrieden", + "veryUnsatisfied": "sehr unzufrieden", + "yes": "Ja" + }, + "dashboard": { + "welcome": "Willkommen, {name}" + }, + "feedback": { + "answers": "Antworten", + "areYouSatisfied": "Wie zufrieden bist du?", + "average": "Durchschnitt", + "circleFeedback": "Feedback zum Circle", + "completionDescription": "Dein Feedback ist anonym. Dein Vor- und Nachname werden bei deiner Trainer/-in nicht angezeigt.", + "completionTitle": "Schicke dein Feedback an {name}", + "courseNegativeFeedbackLabel": "Wo sehen Sie Verbesserungspotenzial?", + "coursePositiveFeedbackLabel": "Was hat Ihnen besonders gut gefallen?", + "feedbackPageInfo": "Teilnehmer haben das Feedback ausgefüllt", + "feedbackPageTitle": "Feedback zum Lehrgang", + "feedbackSent": "Dein Feedback wurde abgeschickt", + "goalAttainmentLabel": "Zielerreichung insgesamt", + "happy": "Zufrieden", + "instructorCompetenceLabel": "Der Kursleiter war themenstark, fachkompetent.", + "instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:", + "instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen und aufgegriffen.", + "intro": "{name}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.", + "materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?", + "noFeedbacks": "Es wurden noch keine Feedbacks abgegeben", + "proficiencyLabel": "Wie beurteilen Sie Ihre Sicherheit bezüglichen den Themen nach dem Kurs?", + "questionTitle": "Frage", + "receivedMaterialsLabel": "Haben Sie Vorbereitungsunterlagen (z.B. eLearning) erhalten?", + "recommendLabel": "Würden Sie den Kurs weiterempfehlen?", + "satisfactionLabel": "Zufriedenheit insgesamt", + "sendFeedback": "Feedback abschicken", + "sentByUsers": "Von {count} Teilnehmern ausgefüllt", + "showDetails": "Details anzeigen", + "unhappy": "Unzufrieden", + "veryHappy": "Sehr zufrieden", + "veryUnhappy": "Sehr unzufrieden" + }, + "footer": { + "contact": "Kontakt", + "dataProtection": "Datenschutzbestimmungen", + "faq": "FAQ", + "imprint": "Impressum" + }, "general": { - "nextStep": "Weiter geht's", - "start": "Los geht's", - "backToLearningPath": "zurück zum Lernpfad", - "backToCircle": "zurück zum Circle", - "next": "Weiter", "back": "zurück", "backCapitalized": "@.capitalize:general.back", - "save": "Speichern", - "send": "Senden", - "learningUnit": "Lerneinheit", - "learningPath": "Lernpfad", - "learningSequence": "Lernsequenz", - "show": "Anschauen", + "backToCircle": "zurück zum Circle", + "backToLearningPath": "zurück zum Lernpfad", + "certificate": "Zertifikat | Zertifikate", "circles": "Circles", - "transferTask": "Transferauftrag | Transferaufträge", - "feedback": "Feedback | Feedbacks", "exam": "Prüfung | Prüfungen", "examResult": "Prüfungsresultat | Prüfungsresultate", - "certificate": "Zertifikat | Zertifikate", + "feedback": "Feedback | Feedbacks", + "learningPath": "Lernpfad", + "learningSequence": "Lernsequenz", + "learningUnit": "Lerneinheit", + "next": "Weiter", + "nextStep": "Weiter geht's", + "no": "Nein", "notification": "Benachrichtigung | Benachrichtigungen", "profileLink": "Profil anzeigen", + "save": "Speichern", + "send": "Senden", + "settings": "Kontoeinstellungen", "shop": "Shop", - "yes": "Ja", - "no": "Nein", + "show": "Anschauen", "showAll": "Alle anschauen", - "settings": "Kontoeinstellungen" + "start": "Los geht's", + "transferTask": "Transferauftrag | Transferaufträge", + "yes": "Ja" + }, + "language": { + "de": "Deutsch", + "fr": "Französisch" + }, + "learningContent": { + "completeAndContinue": "Als erledigt markieren" + }, + "learningPathPage": { + "currentCircle": "Aktueller Circle", + "listView": "Listenansicht", + "nextStep": "Nächster Schritt", + "pathView": "Pfadansicht", + "progressText": "Du hast { inProgressCount } von { allCount } Circles bearbeitet", + "showListView": "Listenansicht anzeigen", + "topics": "Themen:", + "welcomeBack": "Hallo { name }! Willkommen zurück in deinem Lehrgang:" }, "mainNavigation": { "logout": "Abmelden", "profile": "Profil" }, - "dashboard": { - "welcome": "Willkommen, {name}" - }, - "learningPathPage": { - "welcomeBack": "Willkommen zurück, {name}", - "showListView": "Listenansicht anzeigen", - "nextStep": "Nächster Schritt" - }, - "circlePage": { - "duration": "Dauer", - "circleContentBoxTitle": "Das lernst du in diesem Circle.", - "gotQuestions": "Hast du Fragen?", - "contactExpertButton": "Fachexpertin kontaktieren", - "contactExpertDescription": "Tausche dich mit der Fachexpertin für den Circle {circleName} aus.", - "learnMore": "Erfahre mehr dazu", - "documents": { - "title": "Unterlagen", - "expertDescription": "Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.", - "userDescription": "Hier findest du die Unterlagen, die dir die Fachexpertin zur Verfügung gestellt hat.", - "action": "Unterlagen hochladen", - "modalAction": "Datei auswählen", - "fileLabel": "Datei", - "modalFileName": "Name", - "modalNameInformation": "Max. 70 Zeichen", - "chooseSequence": "Wähle eine Lernsequenz aus", - "selectFile": "Bitte wähle eine Datei aus", - "chooseName": "Bitte wähle einen Namen", - "chooseLearningSequence": "Bitte wähle eine Lernsequenz aus", - "uploadErrorMessage": "Beim Hochladen ist ein Fehler aufgetreten. Bitte versuche es erneut.", - "maxFileSize": "Maximale Dateigrösse: 20 MB" - } - }, - "learningContent": { - "completeAndContinue": "Als erledigt markieren" - }, - "selfEvaluation": { - "selfEvaluation": "Selbsteinschätzung", - "title": "@:selfEvaluation.selfEvaluation {title}", - "steps": "Schritt {current} von {max}", - "instruction": [ - "Überprüfe, ob du in der Lernheinheit", - "alles verstanden hast.", - "Lies die folgende Aussage und bewerte sie:" - ], - "yes": "Ja, ich kann das", - "no": "Das muss ich nochmals anschauen", - "progressText": "Schau dein Fortschritt in deinem KompetenzNavi:", - "progressLink": "KompetenzNavi öffnen", - "selfEvaluationYes": "@:selfEvaluation: Ich kann das.", - "selfEvaluationNo": "@:selfEvaluation: Muss ich nochmals anschauen." - }, - "competences": { - "competences": "Kompetenzen", - "title": "KompetenzNavi", - "lastImprovements": "Letzte verbesserte Kompetenzen", - "assessment": "Einschätzungen", - "notAssessed": "Nicht eingeschätzt", - "assessAgain": "Sich nochmals einschätzen" - }, "mediaLibrary": { - "title": "Mediathek", - "learningMedia": { - "titel": "Lernmedien", - "description": "Finde eine vollständige Liste der Bücher und anderen Medien, auf die im Kurs verwiesen wird." - }, "handlungsfelder": { - "title": "Handlungsfeld | Handlungsfelder", - "description": "Finde alle Ressourcen der Handlungsfelder wie Lernmedien, Links und andere nützliche Informationen." - } - }, - "footer": { - "dataProtection": "Datenschutzbestimmungen", - "imprint": "Impressum", - "contact": "Kontakt", - "faq": "FAQ" - }, - "cockpit": { - "title": "Cockpit", - "tasksDone": "Erledigte Transferaufträge von Teilnehmer.", - "feedbacksDone": "Abgeschickte Feedbacks von Teilnehmer.", - "examsDone": "Abgelegte Prüfungen von Teilnehmer.", - "progress": "Fortschritt", - "profileLink": "Profil anzeigen", - "notifyTaskDescription": "Teilnehmer benachrichtigen", - "notifyTask": "Benachrichtigen" + "description": "Finde alle Ressourcen der Handlungsfelder wie Lernmedien, Links und andere nützliche Informationen.", + "title": "Handlungsfeld | Handlungsfelder" + }, + "learningMedia": { + "description": "Finde eine vollständige Liste der Bücher und anderen Medien, auf die im Kurs verwiesen wird.", + "titel": "Lernmedien" + }, + "title": "Mediathek" }, "messages": { "sendMessage": "Nachricht schreiben" }, - "feedback": { - "intro": "{name}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.", - "areYouSatisfied": "Wie zufrieden bist du?", - "recommendLabel": "Würden Sie den Kurs weiterempfehlen?", - "satisfactionLabel": "Zufriedenheit insgesamt", - "goalAttainmentLabel": "Zielerreichung insgesamt", - "proficiencyLabel": "Wie beurteilen Sie Ihre Sicherheit bezüglichen den Themen nach dem Kurs?", - "receivedMaterialsLabel": "Haben Sie Vorbereitungsunterlagen (z.B. eLearning) erhalten?", - "materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?", - "instructorCompetenceLabel": "Der Kursleiter war themenstark, fachkompetent.", - "instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen und aufgegriffen.", - "instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:", - "courseNegativeFeedbackLabel": "Wo sehen Sie Verbesserungspotenzial?", - "coursePositiveFeedbackLabel": "Was hat Ihnen besonders gut gefallen?", - "completionTitle": "Schicke dein Feedback an {name}", - "completionDescription": "Dein Feedback ist anonym. Dein Vor- und Nachname werden bei deiner Trainer/-in nicht angezeigt.", - "sendFeedback": "Feedback abschicken", - "feedbackSent": "Dein Feedback wurde abgeschickt", - "circleFeedback": "Feedback zum Circle", - "showDetails": "Details anzeigen", - "sentByUsers": "Von {count} Teilnehmern ausgefüllt", - "feedbackPageTitle": "Feedback zum Lehrgang", - "feedbackPageInfo": "Teilnehmer haben das Feedback ausgefüllt", - "questionTitle": "Frage", - "veryUnhappy": "Sehr unzufrieden", - "unhappy": "Unzufrieden", - "happy": "Zufrieden", - "veryHappy": "Sehr zufrieden", - "average": "Durchschnitt", - "answers": "Antworten", - "noFeedbacks": "Es wurden noch keine Feedbacks abgegeben" - }, "notifications": { "load_more": "Mehr laden", "no_notifications": "Du hast derzeit keine Benachrichtigungen" }, + "selfEvaluation": { + "instruction": [ + "Überprüfe, ob du in der Lernheinheit", + "alles verstanden hast.", + "Lies die folgende Aussage und bewerte sie:" + ], + "no": "Das muss ich nochmals anschauen", + "progressLink": "KompetenzNavi öffnen", + "progressText": "Schau dein Fortschritt in deinem KompetenzNavi:", + "selfEvaluation": "Selbsteinschätzung", + "selfEvaluationNo": "@:selfEvaluation: Muss ich nochmals anschauen.", + "selfEvaluationYes": "@:selfEvaluation: Ich kann das.", + "steps": "Schritt {current} von {max}", + "title": "@:selfEvaluation.selfEvaluation {title}", + "yes": "Ja, ich kann das" + }, "settings": { "emailNotifications": "Email Benachrichtigungen" - }, - "constants": { - "yes": "Ja", - "no": "Nein", - "verySatisfied": "sehr zufrieden", - "satisfied": "zufrieden", - "unsatisfied": "unzufrieden", - "veryUnsatisfied": "sehr unzufrieden" } } diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index 0b590e24..bac20b85 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -1,159 +1,174 @@ { - "general": { - "nextStep": "Prochaine étape", - "start": "Commencer", - "backToLearningPath": "zurück zum Lernpfad", - "backToCircle": "zurück zum Circle", - "next": "Weiter", - "back": "zurück", - "backCapitalized": "@.capitalize:general.back", - "save": "Speichern", - "learningUnit": "Lerneinheit", - "learningPath": "Lernpfad", - "learningSequence": "Lernsequenz", - "show": "Anschauen", - "circles": "Circles", - "transferTask": "Transferauftrag | Transferaufträge", - "feedback": "Feedback | Feedbacks", - "exam": "Prüfung | Prüfungen", - "examResult": "Prüfungsresultat | Prüfungsresultate", - "certificate": "Zertifikat | Zertifikate", - "notification": "Benachrichtigung | Benachrichtigungen", - "profileLink": "Profil anzeigen", - "shop": "Shop", - "yes": "Ja", - "no": "Nein" - }, - "mainNavigation": { - "logout": "Abmelden", - "settings": "Kontoeinstellungen" - }, - "dashboard": { - "welcome": "Willkommen, {name}" - }, - "learningPathPage": { - "welcomeBack": "Willkommen zurück, {name}", - "showListView": "Listenansicht anzeigen", - "nextStep": "Nächster Schritt" - }, "circlePage": { - "duration": "Dauer", - "circleContentBoxTitle": "Das lernst du in diesem Circle.", - "gotQuestions": "Hast du Fragen?", - "contactExpertButton": "Fachexpertin kontaktieren", - "contactExpertDescription": "Tausche dich mit der Fachexpertin für den Circle {circleName} aus.", - "learnMore": "Erfahre mehr dazu", + "circleContentBoxTitle": "C'est ce que tu apprends dans ce Cercle.", + "contactExpertButton": "Contacter l'expert", + "contactExpertDescription": "Echange avec l'experte du cercle {circleName} .", "documents": { - "title": "Unterlagen", - "expertDescription": "Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.", - "userDescription": "Hier findest du die Unterlagen, die dir die Fachexpertin zur Verfügung gestellt hat.", - "action": "Unterlagen hochladen", - "modalAction": "Datei auswählen", - "fileLabel": "Datei", - "modalFileName": "Name", - "modalNameInformation": "Max. 70 Zeichen", - "chooseSequence": "Wähle eine Lernsequenz aus", - "selectFile": "Bitte wähle eine Datei aus", - "chooseName": "Bitte wähle einen Namen", - "chooseLearningSequence": "Bitte wähle eine Lernsequenz aus", - "uploadErrorMessage": "Beim Hochladen ist ein Fehler aufgetreten. Bitte versuche es erneut.", - "maxFileSize": "Maximale Dateigrösse: 20 MB" - } - }, - "learningContent": { - "completeAndContinue": "Als erledigt markieren" - }, - "selfEvaluation": { - "selfEvaluation": "Selbsteinschätzung", - "title": "@:selfEvaluation.selfEvaluation {title}", - "steps": "Schritt {current} von {max}", - "instruction": [ - "Überprüfe, ob du in der Lernheinheit", - "alles verstanden hast.", - "Lies die folgende Aussage und bewerte sie:" - ], - "yes": "Ja, ich kann das", - "no": "Das muss ich nochmals anschauen", - "progressText": "Schau dein Fortschritt in deinem KompetenzNavi:", - "progressLink": "KompetenzNavi öffnen", - "selfEvaluationYes": "@:selfEvaluation: Ich kann das.", - "selfEvaluationNo": "@:selfEvaluation: Muss ich nochmals anschauen." - }, - "competences": { - "competences": "Kompetenzen", - "title": "KompetenzNavi", - "lastImprovements": "Letzte verbesserte Kompetenzen", - "showAll": "Alle anschauen", - "assessment": "Einschätzungen", - "notAssessed": "Nicht eingeschätzt", - "assessAgain": "Sich nochmals einschätzen" - }, - "mediaLibrary": { - "title": "Mediathek", - "learningMedia": { - "titel": "Lernmedien", - "description": "Finde eine vollständige Liste der Bücher und anderen Medien, auf die im Kurs verwiesen wird." + "action": "Télécharger des documents", + "chooseLearningSequence": "Choisis une séquence d'apprentissage", + "chooseName": "Choisis un nom", + "chooseSequence": "Choisis une séquence d'apprentissage", + "expertDescription": "Mettre à disposition de tes apprenants des contenus supplémentaires", + "fileLabel": "Fichier", + "maxFileSize": "Taille maximale du fichier : 20 Mo", + "modalAction": "Choisir un fichier", + "modalFileName": "Nom", + "modalNameInformation": "Max. 70 caractères", + "selectFile": "Choisis un fichier", + "title": "Documents", + "uploadErrorMessage": "Une erreur est survenue lors du téléchargement. Veuillez réessayer.", + "userDescription": "Tu trouveras ici les documents que l'experte en la matière a mis à ta disposition." }, - "handlungsfelder": { - "title": "Handlungsfeld | Handlungsfelder", - "description": "Finde alle Ressourcen der Handlungsfelder wie Lernmedien, Links und andere nützliche Informationen." - } - }, - "footer": { - "dataProtection": "Datenschutzbestimmungen", - "imprint": "Impressum", - "contact": "Kontakt", - "faq": "FAQ" + "duration": "Durée", + "gotQuestions": "Tu as des questions ?", + "learnMore": "En savoir plus sur ce sujet" }, "cockpit": { - "title": "Cockpit", - "tasksDone": "Erledigte Transferaufträge von Teilnehmer.", - "feedbacksDone": "Abgeschickte Feedbacks von Teilnehmer.", - "examsDone": "Abgelegte Prüfungen von Teilnehmer.", - "progress": "Fortschritt", - "profileLink": "Profil anzeigen" + "examsDone": "Examens passés par les participants.", + "feedbacksDone": "Feedbacks envoyés par les participants.", + "notifyTask": "notifier", + "notifyTaskDescription": "Notifier les participants", + "profileLink": "Voir le profil", + "progress": "Progrès", + "tasksDone": "Ordres de transfert terminés par le participant.", + "title": "Cockpit" }, - "messages": { - "sendMessage": "Nachricht schreiben" - }, - "feedback": { - "intro": "{name}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.", - "areYouSatisfied": "Wie zufrieden bist du?", - "recommendLabel": "Würden Sie den Kurs weiterempfehlen?", - "satisfactionLabel": "Zufriedenheit insgesamt", - "goalAttainmentLabel": "Zielerreichung insgesamt", - "proficiencyLabel": "Wie beurteilen Sie Ihre Sicherheit bezüglichen den Themen nach dem Kurs?", - "receivedMaterialsLabel": "Haben Sie Vorbereitungsunterlagen (z.B. eLearning) erhalten?", - "materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?", - "instructorCompetenceLabel": "Der Kursleiter war themenstark, fachkompetent.", - "instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen und aufgegriffen.", - "instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:", - "courseNegativeFeedbackLabel": "Wo sehen Sie Verbesserungspotenzial?", - "coursePositiveFeedbackLabel": "Was hat Ihnen besonders gut gefallen?", - "completionTitle": "Schicke dein Feedback an {name}", - "completionDescription": "Dein Feedback ist anonym. Dein Vor- und Nachname werden bei deiner Trainer/-in nicht angezeigt.", - "sendFeedback": "Feedback abschicken", - "feedbackSent": "Dein Feedback wurde abgeschickt", - "circleFeedback": "Feedback zum Circle", - "showDetails": "Details anzeigen", - "sentByUsers": "Von {count} Teilnehmern ausgefüllt", - "feedbackPageTitle": "Feedback zum Lehrgang", - "feedbackPageInfo": "Teilnehmer haben das Feedback ausgefüllt", - "questionTitle": "Frage", - "veryUnhappy": "Sehr unzufrieden", - "unhappy": "Unzufrieden", - "happy": "Zufrieden", - "veryHappy": "Sehr zufrieden", - "average": "Durchschnitt", - "answers": "Antworten", - "noFeedbacks": "Es wurden noch keine Feedbacks abgegeben" + "competences": { + "assessAgain": "S'évaluer à nouveau", + "assessment": "évaluation", + "competences": "Compétences", + "lastImprovements": "Dernières compétences améliorées", + "notAssessed": "Non évalué", + "title": "CompetenceNavi" }, "constants": { - "yes": "Ja", - "no": "Nein", - "verySatisfied": "sehr zufrieden", - "satisfied": "zufrieden", - "unsatisfied": "unzufrieden", - "veryUnsatisfied": "sehr unzufrieden" + "no": "Non", + "satisfied": "satisfait", + "unsatisfied": "insatisfait", + "verySatisfied": "très satisfait", + "veryUnsatisfied": "très insatisfait", + "yes": "Our" + }, + "dashboard": { + "welcome": "Bienvenue, {name}" + }, + "feedback": { + "answers": "Réponses", + "areYouSatisfied": "A quel point es-tu satisfait ?", + "average": "Moyenne", + "circleFeedback": "Feedback sur le Cercle", + "completionDescription": "Ton feedback est anonyme. Ton prénom et ton nom n'apparaissent pas sur le site de ton formateur.", + "completionTitle": "Envoie ton feedback à {name}", + "courseNegativeFeedbackLabel": "Où voyez-vous un potentiel d'amélioration ?", + "coursePositiveFeedbackLabel": "Qu'avez-vous particulièrement apprécié ?", + "feedbackPageInfo": "Les participants ont complété le feedback", + "feedbackPageTitle": "Feedback sur la formation", + "feedbackSent": "Ton feedback a été envoyé", + "goalAttainmentLabel": "Total des objectifs réalisés", + "happy": "Satisfait", + "instructorCompetenceLabel": "Le formateur était très compétent.", + "instructorOpenFeedbackLabel": "Ce que je voulais dire d'autre au formateur :", + "instructorRespectLabel": "Les questions et les suggestions des participants au cours ont été prises au sérieux et traitées.", + "intro": "{name}, ton formateur te demande de lui donner un feedback. C'est facultatif, mais cela l'aidera à améliorer ton expérience d'apprentissage.", + "materialsRatingLabel": "Si oui, comment jugez-vous les documents de préparation (par exemple, eLearning) ?", + "noFeedbacks": "Aucun feedback n'a encore été donné", + "proficiencyLabel": "Comment évaluez-vous votre sécurité par rapport aux thèmes après le cours ?", + "questionTitle": "Question", + "receivedMaterialsLabel": "Avez-vous reçu des documents de préparation (par ex. eLearning) ?", + "recommendLabel": "Recommanderiez-vous ce cours?", + "satisfactionLabel": "Satisfaction totale", + "sendFeedback": "Envoyer le feedback", + "sentByUsers": "Rempli par {count} participants", + "showDetails": "Voir les détails", + "unhappy": "insatisfait", + "veryHappy": "très satisfait", + "veryUnhappy": "très insatisfait" + }, + "footer": { + "contact": "Contact", + "dataProtection": "Politique de confidentialité", + "faq": "FAQ", + "imprint": "Impressum" + }, + "general": { + "back": "retour", + "backCapitalized": "@.capitalize:general.back", + "backToCircle": "retour au Cercle", + "backToLearningPath": "retour au parcours d'apprentissage", + "certificate": "Certificat | Certificats", + "circles": "Cercles", + "exam": "Examen | Examens", + "examResult": "Résultat de l'examen | Résultats de l'examen", + "feedback": "Feedback | Feedbacks", + "learningPath": "Parcours d'apprentissage", + "learningSequence": "Séquence d'apprentissage", + "learningUnit": "Unité d'apprentissage", + "next": "Suivant", + "nextStep": "A suivre", + "no": "Non", + "notification": "Notification | Notifications", + "profileLink": "Voir le profil", + "save": "Sauvegarde", + "send": "Envoyer", + "settings": "Configuration du compte", + "shop": "Shop", + "show": "Montrer", + "showAll": "Montrer tous", + "start": "On y va", + "transferTask": "Ordre de transfert | Ordres de transfert", + "yes": "Our" + }, + "language": { + "de": "Allemand", + "fr": "Français" + }, + "learningContent": { + "completeAndContinue": "Marquer comme fait" + }, + "learningPathPage": { + "nextStep": "Prochaine étape", + "showListView": "Voir la liste", + "welcomeBack": "Bienvenue, {name}" + }, + "mainNavigation": { + "logout": "Fermer la session", + "profile": "Profil" + }, + "mediaLibrary": { + "handlungsfelder": { + "description": "Trouve toutes les ressources des domaines d'action comme les médias d'apprentissage, les liens et autres informations utiles.", + "title": "Domaine d'action | Domaines d'action" + }, + "learningMedia": { + "description": "Trouve une liste complète des livres et autres supports auxquels il est fait référence dans le cours.", + "titel": "Médias d'apprentissage" + }, + "title": "Médiathèque" + }, + "messages": { + "sendMessage": "Envoyer un message" + }, + "notifications": { + "load_more": "Plus de détails", + "no_notifications": "Tu n'as pas de notifications pour le moment" + }, + "selfEvaluation": { + "instruction": [ + "Vérifie que dans l'unité d'apprentissage", + "tu as tout compris.", + "Lis et évalue l'affirmation suivante :" + ], + "no": "Je dois regarder à nouveau", + "progressLink": "Ouvrir CompetenceNavi", + "progressText": "Regarde ta progression dans ton CompetenceNavi :", + "selfEvaluation": "Évaluation personnelle", + "selfEvaluationNo": "@:selfEvaluation: Je dois le regarder à nouveau.", + "selfEvaluationYes": "@:selfEvaluation: Je peux faire ça.", + "steps": "Etape {current} de {max}", + "title": "@:selfEvaluation.selfEvaluation {title}", + "yes": "Oui, je peux faire ça" + }, + "settings": { + "emailNotifications": "Notifications par e-mail" } } diff --git a/client/src/pages/DashboardPage.vue b/client/src/pages/DashboardPage.vue index 66b80653..5293c9c9 100644 --- a/client/src/pages/DashboardPage.vue +++ b/client/src/pages/DashboardPage.vue @@ -45,7 +45,11 @@ onMounted(async () => { >
- + {{ $t("general.nextStep") }}
diff --git a/client/src/pages/StyleGuidePage.vue b/client/src/pages/StyleGuidePage.vue index 858a55fc..d176e69a 100644 --- a/client/src/pages/StyleGuidePage.vue +++ b/client/src/pages/StyleGuidePage.vue @@ -1,7 +1,7 @@ - - diff --git a/client/src/router/__tests__/guards.spec.ts b/client/src/router/__tests__/guards.spec.ts new file mode 100644 index 00000000..c4384db1 --- /dev/null +++ b/client/src/router/__tests__/guards.spec.ts @@ -0,0 +1,39 @@ +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, vi } from "vitest"; +import * as courseSessions from "../../stores/courseSessions"; +import { expertRequired } from "../guards"; + +describe("Guards", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + beforeEach(() => { + // creates a fresh pinia and make it active so it's automatically picked + // up by any useStore() call without having to pass it to it: + // `useStore(pinia)` + setActivePinia(createPinia()); + }); + + it("cannot route to cockpit", () => { + vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({ + hasCockpit: false, + }); + const slug = "test"; + expect(expertRequired({ params: { courseSlug: "test" } })).toEqual( + `/course/${slug}/learn` + ); + }); + + it("can route to cockpit", () => { + vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({ + hasCockpit: true, + }); + const to = { + params: { + courseSlug: "test", + }, + }; + expect(expertRequired(to)).toEqual(to); + }); +}); diff --git a/client/src/router/guards.ts b/client/src/router/guards.ts index e4902f33..47dddb6c 100644 --- a/client/src/router/guards.ts +++ b/client/src/router/guards.ts @@ -1,7 +1,8 @@ +import { useCourseSessionsStore } from "@/stores/courseSessions"; import { useUserStore } from "@/stores/user"; -import type { NavigationGuardWithThis, RouteLocationNormalized } from "vue-router"; +import type { NavigationGuard, RouteLocationNormalized } from "vue-router"; -export const updateLoggedIn: NavigationGuardWithThis = async () => { +export const updateLoggedIn: NavigationGuard = async () => { const loggedIn = getCookieValue("loginStatus") === "true"; const userStore = useUserStore(); @@ -11,7 +12,7 @@ export const updateLoggedIn: NavigationGuardWithThis = async () => { } }; -export const redirectToLoginIfRequired: NavigationGuardWithThis = (to) => { +export const redirectToLoginIfRequired: NavigationGuard = (to) => { const userStore = useUserStore(); if (loginRequired(to) && !userStore.loggedIn) { return `/login?next=${to.fullPath}`; @@ -32,3 +33,13 @@ export const getCookieValue = (cookieName: string): string => { const loginRequired = (to: RouteLocationNormalized) => { return !to.meta?.public; }; + +export const expertRequired: NavigationGuard = (to: RouteLocationNormalized) => { + const courseSessionsStore = useCourseSessionsStore(); + if (courseSessionsStore.hasCockpit) { + return to; + } else { + const courseSlug = to.params.courseSlug as string; + return `/course/${courseSlug}/learn`; + } +}; diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 0fa65d18..340f6b6e 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -1,6 +1,10 @@ import DashboardPage from "@/pages/DashboardPage.vue"; import LoginPage from "@/pages/LoginPage.vue"; -import { redirectToLoginIfRequired, updateLoggedIn } from "@/router/guards"; +import { + expertRequired, + redirectToLoginIfRequired, + updateLoggedIn, +} from "@/router/guards"; import { useAppStore } from "@/stores/app"; import { createRouter, createWebHistory } from "vue-router"; @@ -103,6 +107,7 @@ const router = createRouter({ path: "/course/:courseSlug/cockpit", props: true, component: () => import("@/pages/cockpit/CockpitParentPage.vue"), + beforeEnter: [expertRequired], children: [ { path: "", diff --git a/client/src/services/circle.ts b/client/src/services/circle.ts index 7aec902f..14d6da42 100644 --- a/client/src/services/circle.ts +++ b/client/src/services/circle.ts @@ -222,6 +222,12 @@ export class Circle implements WagtailCircle { return false; } + public isComplete(): boolean { + return this.learningSequences.every((ls) => + this.allFinishedInLearningSequence(ls.translation_key) + ); + } + public parseCompletionData(completionData: CourseCompletion[]) { this.flatChildren.forEach((page) => { const pageIndex = completionData.findIndex((e) => { diff --git a/client/src/services/learningPath.ts b/client/src/services/learningPath.ts index 564a4aff..82b9f432 100644 --- a/client/src/services/learningPath.ts +++ b/client/src/services/learningPath.ts @@ -10,6 +10,11 @@ import type { WagtailLearningPath, } from "@/types"; +export interface ContinueData { + url: string; + has_no_progress: boolean; +} + function getLastCompleted(courseId: number, completionData: CourseCompletion[]) { return orderBy(completionData, ["updated_at"], "desc").find((c: CourseCompletion) => { return ( @@ -125,4 +130,25 @@ export class LearningPath implements WagtailLearningPath { } } } + + public get continueData(): ContinueData { + if (this.nextLearningContent) { + const circle = this.nextLearningContent.parentCircle; + const url = + this.nextLearningContent.parentLearningSequence?.frontend_url || + circle.frontend_url; + const isFirst = + this.nextLearningContent.translation_key === + this.circles[0].flatLearningContents[0].translation_key; + return { + url, + has_no_progress: isFirst, + }; + } + + return { + url: "", + has_no_progress: true, + }; + } } diff --git a/client/src/stores/__tests__/courseSession.spec.ts b/client/src/stores/__tests__/courseSession.spec.ts new file mode 100644 index 00000000..fd0a6121 --- /dev/null +++ b/client/src/stores/__tests__/courseSession.spec.ts @@ -0,0 +1,91 @@ +import { itGetCached, itPost } from "@/fetchHelpers"; +import type { CourseSession } from "@/types"; +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, vi } from "vitest"; +import { useRoute } from "vue-router"; +import { useCourseSessionsStore } from "../courseSessions"; +import { useUserStore } from "../user"; + +let user = {}; +let courseSessions: CourseSession[] = []; + +describe("CourseSession Store", () => { + vi.mock("vue-router", () => ({ + useRoute: () => ({ + path: "/course/test-course/learn/", + }), + })); + + vi.mock("@/fetchHelpers", () => { + const itGetCached = () => Promise.resolve([]); + const itPost = () => Promise.resolve([]); + + return { + itGetCached, + itPost, + }; + }); + + beforeEach(() => { + // creates a fresh pinia and make it active so it's automatically picked + // up by any useStore() call without having to pass it to it: + // `useStore(pinia)` + setActivePinia(createPinia()); + user = { + is_superuser: false, + course_session_expert: [], + }; + courseSessions = [ + { + id: 1, + created_at: "2021-05-11T10:00:00.000000Z", + updated_at: "2023-05-11T10:00:00.000000Z", + course: { + id: 1, + title: "Test Course", + category_name: "Test Category", + slug: "test-course", + }, + title: "Test Course Session", + start_date: "2022-05-11T10:00:00.000000Z", + end_date: "2023-05-11T10:00:00.000000Z", + learning_path_url: "/course/test-course/learn/", + competence_url: "/course/test-course/competence/", + course_url: "/course/test-course/", + media_library_url: "/course/test-course/media/", + additional_json_data: {}, + documents: [], + users: [], + }, + ]; + }); + + it("normal user has no cockpit", () => { + const userStore = useUserStore(); + const courseSessionsStore = useCourseSessionsStore(); + userStore.$patch(user); + courseSessionsStore.$patch({ courseSessions }); + + expect(courseSessionsStore.hasCockpit).toBeFalsy(); + }); + + it("superuser has cockpit", () => { + const userStore = useUserStore(); + const courseSessionsStore = useCourseSessionsStore(); + userStore.$patch(Object.assign(user, { is_superuser: true })); + courseSessionsStore.$patch({ courseSessions }); + + expect(courseSessionsStore.hasCockpit).toBeTruthy(); + }); + + it("expert has cockpit", () => { + const userStore = useUserStore(); + const courseSessionsStore = useCourseSessionsStore(); + userStore.$patch( + Object.assign(user, { course_session_experts: [courseSessions[0].id] }) + ); + courseSessionsStore.$patch({ courseSessions }); + + expect(courseSessionsStore.hasCockpit).toBeTruthy(); + }); +}); diff --git a/client/src/stores/notifications.ts b/client/src/stores/notifications.ts index 104b3e9f..ec6dcdf3 100644 --- a/client/src/stores/notifications.ts +++ b/client/src/stores/notifications.ts @@ -1,7 +1,9 @@ import { itGet } from "@/fetchHelpers"; +import { useUserStore } from "@/stores/user"; import type { Notification } from "@/types"; +import log from "loglevel"; import { defineStore } from "pinia"; -import { ref } from "vue"; +import { ref, watch } from "vue"; type NotificationListData = { all_count: number; @@ -29,8 +31,18 @@ export const useNotificationsStore = defineStore("notifications", () => { hasUnread.value = data.unread_count !== 0; } - updateUnreadCount(); - setInterval(async () => await updateUnreadCount(), 30000); + const userStore = useUserStore(); + let timerHandle: number | null = null; + watch(userStore, () => { + if (userStore.loggedIn && timerHandle === null) { + log.debug("Notification polling started"); + updateUnreadCount(); + timerHandle = setInterval(async () => await updateUnreadCount(), 30000); + } else if (!userStore.loggedIn) { + log.debug("Notification polling stopped"); + timerHandle = null; + } + }); return { loadNotifications, hasUnread, allCount }; }); diff --git a/client/src/stores/user.ts b/client/src/stores/user.ts index 3c06473a..df9564f4 100644 --- a/client/src/stores/user.ts +++ b/client/src/stores/user.ts @@ -8,7 +8,7 @@ import { defineStore } from "pinia"; const logoutRedirectUrl = import.meta.env.VITE_LOGOUT_REDIRECT; // typed state https://stackoverflow.com/questions/71012513/when-using-pinia-and-typescript-how-do-you-use-an-action-to-set-the-state -export type availableLanguages = "de" | "fr" | "it"; +export type AvailableLanguages = "de" | "fr" | "it"; export type UserState = { id: number; @@ -20,7 +20,7 @@ export type UserState = { is_superuser: boolean; course_session_experts: number[]; loggedIn: boolean; - language: availableLanguages; + language: AvailableLanguages; }; const initialUserState: UserState = { @@ -36,7 +36,7 @@ const initialUserState: UserState = { language: "de", }; -async function setLocale(language: availableLanguages) { +async function setLocale(language: AvailableLanguages) { await loadLocaleMessages(language); setI18nLanguage(language); } @@ -92,7 +92,7 @@ export const useUserStore = defineStore({ appStore.userLoaded = true; await setLocale(data.language); }, - async setUserLanguages(language: availableLanguages) { + async setUserLanguages(language: AvailableLanguages) { await setLocale(language); this.$state.language = language; await itPost("/api/core/me/", { language }, { method: "PUT" }); diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json index 0d2e0c18..1a19788b 100644 --- a/client/tsconfig.app.json +++ b/client/tsconfig.app.json @@ -1,15 +1,15 @@ { - "extends": "@vue/tsconfig/tsconfig.web.json", - "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], - "exclude": ["src/**/__tests__/*"], "compilerOptions": { - "lib": ["ES2021", "DOM", "DOM.Iterable"], - "composite": true, - "strict": true, "allowJs": true, "baseUrl": ".", + "composite": true, + "lib": ["ES2021", "DOM", "DOM.Iterable"], "paths": { "@/*": ["./src/*"] - } - } + }, + "strict": true + }, + "exclude": ["src/**/__tests__/*"], + "extends": "@vue/tsconfig/tsconfig.web.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"] } diff --git a/client/tsconfig.vite-config.json b/client/tsconfig.vite-config.json index d20d8726..e3f2fdc1 100644 --- a/client/tsconfig.vite-config.json +++ b/client/tsconfig.vite-config.json @@ -1,8 +1,8 @@ { - "extends": "@vue/tsconfig/tsconfig.node.json", - "include": ["vite.config.*"], "compilerOptions": { "composite": true, "types": ["node", "vitest"] - } + }, + "extends": "@vue/tsconfig/tsconfig.node.json", + "include": ["vite.config.*"] } diff --git a/client/tsconfig.vitest.json b/client/tsconfig.vitest.json index 67705fd2..5e470cc6 100644 --- a/client/tsconfig.vitest.json +++ b/client/tsconfig.vitest.json @@ -1,9 +1,9 @@ { - "extends": "./tsconfig.app.json", - "exclude": [], "compilerOptions": { "composite": true, "lib": [], "types": ["node", "jsdom", "vitest/globals"] - } + }, + "exclude": [], + "extends": "./tsconfig.app.json" } diff --git a/client/vue-i18n-extract.config.js b/client/vue-i18n-extract.config.js new file mode 100644 index 00000000..211d01d8 --- /dev/null +++ b/client/vue-i18n-extract.config.js @@ -0,0 +1,12 @@ +// eslint-disable-next-line +module.exports = { + vueFiles: "./src/**/*.?(js|vue)", + languageFiles: "./src/locales/*.json", + exclude: ["translation_key_1", "translation_key_2"], + output: false, + add: false, + remove: false, + ci: false, + separator: ".", + noEmptyTranslation: "", +}; diff --git a/cypress/e2e/learningPath.cy.js b/cypress/e2e/learningPath.cy.js index 83c3738e..bd235d3f 100644 --- a/cypress/e2e/learningPath.cy.js +++ b/cypress/e2e/learningPath.cy.js @@ -1,4 +1,4 @@ -import {login} from "./helpers"; +import { login } from "./helpers"; describe("learningPath page", () => { beforeEach(() => { @@ -19,7 +19,7 @@ describe("learningPath page", () => { login("admin", "test"); cy.visit("/course/versicherungsvermittler-in-alt/learn"); - cy.get('[data-cy="circle-analyse"]').click({force: true}); + cy.get('[data-cy="circle-Analyse"]').click({ force: true }); cy.url().should( "include", @@ -28,20 +28,15 @@ describe("learningPath page", () => { cy.get('[data-cy="circle-title"]').should("contain", "Analyse"); }); - it("open listView and click on circle will open circle", () => { + it("switch between list and path view", () => { login("admin", "test"); cy.visit("/course/versicherungsvermittler-in-alt/learn"); - cy.get('[data-cy="show-list-view"]').click(); - cy.get('[data-cy="full-screen-modal"]').should("be.visible"); - - cy.get('[data-cy="circle-analyse-vertical"]').click({force: true}); - - cy.url().should( - "include", - "/course/versicherungsvermittler-in-alt/learn/analyse" - ); - cy.get('[data-cy="circle-title"]').should("contain", "Analyse"); + cy.get('[data-cy="lp-path-view"]').should("be.visible"); + cy.get('[data-cy="view-switch"]').click(); + cy.get('[data-cy="lp-list-view"]').should("be.visible"); + cy.get('[data-cy="view-switch"]').click(); + cy.get('[data-cy="lp-path-view"]').should("be.visible"); }); it("weiter gehts button will open next circle", () => { @@ -49,21 +44,25 @@ describe("learningPath page", () => { cy.visit("/course/versicherungsvermittler-in-alt/learn"); // first click will open first circle - cy.get('[data-cy="lp-continue-button"]').should("contain", "Los geht's"); - cy.get('[data-cy="lp-continue-button"]').click(); + cy.get('[data-cy="lp-continue-button"]') + .filter(":visible") + .should("contain", "Los geht's") + .click(); cy.get('[data-cy="circle-title"]').should("contain", "Basis"); cy.get('[data-cy="back-to-learning-path-button"]').click(); // mark a learning content in second circle - cy.get('[data-cy="circle-analyse"]').click({force: true}); + cy.get('[data-cy="circle-Analyse"]').click({ force: true }); cy.get( '[data-cy="versicherungsvermittler-in-alt-lp-circle-analyse-lc-fachcheck-fahrzeug-checkbox"] > .cy-checkbox' ).click(); cy.get('[data-cy="back-to-learning-path-button"]').click(); // click on continue should go to unit-test-circle - cy.get('[data-cy="lp-continue-button"]').should("contain", "Weiter geht's"); - cy.get('[data-cy="lp-continue-button"]').click(); + cy.get('[data-cy="lp-continue-button"]') + .filter(":visible") + .should("contain", "Weiter geht's") + .click(); cy.get('[data-cy="circle-title"]').should("contain", "Analyse"); }); }); diff --git a/git-crypt-encrypted-files.txt b/git-crypt-encrypted-files.txt index b6fc905c..9136f91d 100644 --- a/git-crypt-encrypted-files.txt +++ b/git-crypt-encrypted-files.txt @@ -1,8 +1,8 @@ -encrypted: env_secrets/caprover_dev.env -encrypted: env_secrets/caprover_prod.env -encrypted: env_secrets/caprover_stage.env -encrypted: env_secrets/local_chrigu.env -encrypted: env_secrets/local_daniel.env -encrypted: env_secrets/local_elia.env -encrypted: env_secrets/local_lorenz.env -encrypted: env_secrets/production.env + encrypted: env_secrets/caprover_dev.env + encrypted: env_secrets/caprover_prod.env + encrypted: env_secrets/caprover_stage.env + encrypted: env_secrets/local_chrigu.env + encrypted: env_secrets/local_daniel.env + encrypted: env_secrets/local_elia.env + encrypted: env_secrets/local_lorenz.env + encrypted: env_secrets/production.env diff --git a/package.json b/package.json index 6229e1a5..3f1eb448 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "test": "echo \"Error: no test specified\" && exit 1", "cypress:open": "cypress open", "cypress:ci": "currents run --parallel --record --key $CURRENTS_KEY", - "prettier": "npm run prettier --prefix client" + "prettier": "npm run prettier --prefix client", + "vue-i18n-extract": "npm run vue-i18n-extract --prefix client" }, "devDependencies": { "@currents/cli": "^3.1.3", diff --git a/server/vbv_lernwelt/competence/create_uk_competence_profile.py b/server/vbv_lernwelt/competence/create_uk_competence_profile.py index 03808e4a..03f89947 100644 --- a/server/vbv_lernwelt/competence/create_uk_competence_profile.py +++ b/server/vbv_lernwelt/competence/create_uk_competence_profile.py @@ -4,12 +4,12 @@ from vbv_lernwelt.competence.factories import ( PerformanceCriteriaFactory, ) from vbv_lernwelt.competence.models import CompetencePage -from vbv_lernwelt.course.consts import COURSE_UK1 +from vbv_lernwelt.course.consts import COURSE_UK, COURSE_UK_FR from vbv_lernwelt.course.models import CoursePage from vbv_lernwelt.learnpath.models import LearningPath, LearningUnit -def create_uk_competence_profile(course_id=COURSE_UK1): +def create_uk_competence_profile(course_id=COURSE_UK): course_page = CoursePage.objects.get(course_id=course_id) slug_prefix = course_page.get_children().exact_type(LearningPath).first().slug @@ -20,104 +20,33 @@ def create_uk_competence_profile(course_id=COURSE_UK1): competences = [ { - "competence_id": "A1", - "title": "Weiterempfehlung für Neukunden generieren", - "items": [ - "Verhandlungsgeschick", - "Überzeugtes Auftreten", - ], - }, - { - "competence_id": "A2", - "title": "Kundengespräche vereinbaren", - "items": [ - "Gesprächsführung / Fragetechniken", - "Selbstorganisation", - "Arbeitstechniken", - "Psychologische Kenntnisse / Kommunikations-psychologie", - ], - }, - { - "competence_id": "A3", - "title": "Auftritt in den sozialen Medien zeitgemäss halten", - "items": [ - "Gesetzliche und Compliance-Anforderungen der Versicherer", - "Datenschutzgesetz", - "Kommunikation in den sozialen Medien", - ], - }, - {"competence_id": "A4", "title": "Kundendaten erfassen", "items": []}, - { - "competence_id": "B1", - "title": "Wünsche, Ziele und Bedürfnisse der Kunden im Gespräch ermitteln", - "items": [ - "Gesprächsführung", - "Fragetechniken", - "Kundenpsychologie", - ], - }, - { - "competence_id": "B2", - "title": "Analyse des Kundenbedarfs und des Kundenbedürfnisses durchführen", - "items": [ - "Fragetechniken", - "Visuelle Hilfsmittel / Visualisierungstechniken", - ], - }, - { - "competence_id": "B3", - "title": "Individuelle Lösungsvorschläge erarbeiten", - "items": [ - "Fundierte Produktekenntnisse", - "Regulatorische Vorschriften", - ], - }, - { - "competence_id": "B4", - "title": "Lösungsvorschläge präsentieren und umsetzen", - "items": [ - "Verhandlungsstrategien", - "Fundierte Produktkenntnisse", - "Visuelle Hilfsmittel / Visualisierungstechniken", - ], - }, - { - "competence_id": "C1", - "title": "Cross- und Upselling; bestehende fremdverwaltete Versicherungspolicen prüfen und in das Portfolio aufnehmen", - "items": [ - "Produktkenntnisse", - "Gesprächsführung", - "Kommunikation", - "Fragetechnik", - "Verhandlungsgeschick", - "Vertragsrecht", - "Regulatorische Vorgaben", - "UVG, BVG, KVG, VVG", - ], - }, - { - "competence_id": "C2", - "title": "Änderungswünsche entgegennehmen und bestehende Verträge anpassen", - "items": [ - "Produktkenntnisse", - "Gesprächsführung", - "Kommunikation", - "Fragetechnik", - "Verhandlungsgeschick", - "Vertragsrecht", - "Regulatorische Vorgaben", - "UVG, BVG, KVG, VVG", - ], - }, - { - "competence_id": "C3", - "title": "Kunden im Schadenfall unterstützen", + "competence_id": "e4", + "title": "Betriebsbezogene Inhalte multimedial aufbereiten", "items": [], }, - {"competence_id": "C4", "title": "Bestehende Kunden pflegen", "items": []}, { - "competence_id": "C5", - "title": "Versicherungsanträge nachbearbeiten", + "competence_id": "c3", + "title": "Betriebliche Prozesse dokumentieren, koordinieren und umsetzen", + "items": [], + }, + { + "competence_id": "e2", + "title": "Informationen im wirtschaftlichen und kaufmännischen Bereich recherchieren", + "items": [], + }, + { + "competence_id": "d2", + "title": "Informations- und Beratungsgespräche mit Kunden oder Lieferanten führen", + "items": [], + }, + { + "competence_id": "d3", + "title": "Betriebliche Prozesse dokumentieren, koordinieren und umsetzen", + "items": [], + }, + { + "competence_id": "d1", + "title": "Anliegen von Kunden oder Lieferanten entgegennehmen", "items": [], }, ] @@ -130,165 +59,235 @@ def create_uk_competence_profile(course_id=COURSE_UK1): items=[("item", i) for i in c["items"]], ) - # Daten anhand von WEVM_Version Oktober 2022 - # Einstieg/Beobachten – Selbsteinschätzung «Einkommenssicherung» PerformanceCriteriaFactory( parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4" ), - competence_id="A2.1", - title="Ich bin fähig je nach (Neu-) Kunde Form und Ort für das Gespräch festzulegen.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-kickoff-lu-einführung" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" - ), - competence_id="A2.2", - title="Ich bin fähig mir intern und extern die nötigen Informationen über den (Neu-) Kunden zu beschaffen.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-kickoff-lu-arbeits-und-lerntechnik" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" - ), - competence_id="A2.3", - title="Ich bin fähig die Terminierung auf das Thema Einkommenssicherung auszurichten.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-kickoff-lu-versicherung" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" - ), - competence_id="A2.4", - title="Ich bin fähig für das zu führende Gespräch eine Agenda zu erstellen.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-kickoff-lu-beratung-und-verkauf" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" - ), - competence_id="A2.5", - title="Ich bin fähig für das Handlungsfeld «Einkommenssicherung» geeignete Hilfsmittel und Unterlagen zusammenzustellen.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-kickoff-lu-sozialer-auftritt" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="B1" - ), - competence_id="B1.1", - title="Ich bin fähig dem Kunden den Gesprächsablauf und den Zeitrahmen (mittels Agenda) aufzuzeigen.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-basis-lu-einführung" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="B1" - ), - competence_id="B1.2", - title="Ich bin fähig mich beim Kunden korrekt zu identifizieren (VAG 45).", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-basis-lu-arbeits-und-lerntechnik" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="B2" - ), - competence_id="B2.3", - title="Ich bin fähig alle erforderlichen Unterlagen einzufordern.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-basis-lu-versicherung" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A1" - ), - competence_id="A1.6", - title="Ich bin fähig im täglichen Kontakt potenzielle Kundinnen und Kunden zu erkennen.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-basis-lu-versicherung" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" - ), - competence_id="A2.1", - title="Ich bin fähig je nach (Neu-) Kunde Form und Ort für das Gespräch festzulegen.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-basis-lu-beratung-und-verkauf" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" - ), - competence_id="A2.2", - title="Ich bin fähig mir intern und extern die nötigen Informationen über den (Neu-) Kunden zu beschaffen.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-basis-lu-sozialer-auftritt" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" - ), - competence_id="A2.3", - title="Ich bin fähig die Terminierung auf das Thema Fahrzeug auszurichten.", - learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-fahrzeug-lu-versicherung" - ), - ) - PerformanceCriteriaFactory( - parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" - ), - competence_id="A2.4", - title="Ich bin fähig für das zu führende Gespräch eine Agenda zu erstellen.", + competence_id="e4.pv.ük1", + title="Sie erläutern die Dienstleistungen des Betriebs. (K2)", learning_unit=LearningUnit.objects.get( slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" ), ) PerformanceCriteriaFactory( parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4" ), - competence_id="A2.5", - title="Ich bin fähig für das zu führende Gespräch geeignete Hilfsmittel und Unterlagen zusammenzustellen.", + competence_id="e4.pv.ük3", + title="Sie stellen die Wertschöpfungskette und die verschiedenen organisatorischen Bereiche ihres Betriebs dar. (K2).", learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-fahrzeug-lu-arbeits-und-lerntechnik" + slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" ), ) PerformanceCriteriaFactory( parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4" ), - competence_id="A2.3", - title="Ich bin fähig die Terminierung auf das Thema Reisen auszurichten.", + competence_id="e4.pv.ük4", + title="Sie erläutern die relevanten rechtlichen Grundlagen ihrer Branche situationsgerecht. (K2)", learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-fahrzeug-lu-versicherung" + slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" ), ) PerformanceCriteriaFactory( parent=CompetencePage.objects.get( - slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2" + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2" ), - competence_id="A2.4", - title="Ich bin fähig für das zu führende Gespräch eine Agenda zu erstellen.", + competence_id="d2.pv.ük1", + title="Sie beschreiben die wichtigsten gesetzlichen Grundlagen im Versicherungsbereich umfassend. (K2)", learning_unit=LearningUnit.objects.get( - slug=f"{slug_prefix}-circle-fahrzeug-lu-sozialer-auftritt" + slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2" + ), + competence_id="d2.pv.ük2", + title="Sie nennen die relevanten Dienstleistungen und Produkte im Versicherungsbereich. (K1)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2" + ), + competence_id="d2.pv.ük3", + title="Sie erklären die Leistungen und Produkte im Versicherungsbereich. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2" + ), + competence_id="d2.pv.ük4", + title="Sie erläutern die Prozesse und Abläufe im privaten Versicherungsbereich verständlich. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="c3" + ), + competence_id="c3.pv.ük7", + title="Sie erläutern die gesetzlichen Bestimmungen bei Schaden- und Leistungsfällen. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="c3" + ), + competence_id="c3.pv.ük11", + title="Sie beurteilen gängige Versicherungslösungen fachkundig. (K3)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung" + ), + ) + + +def create_uk_fr_competence_profile(course_id=COURSE_UK_FR): + course_page = CoursePage.objects.get(course_id=course_id) + slug_prefix = course_page.get_children().exact_type(LearningPath).first().slug + + competence_profile_page = CompetenceProfilePageFactory( + title="KompetenzNavi", + parent=course_page, + ) + + competences = [ + { + "competence_id": "e4", + "title": "Betriebsbezogene Inhalte multimedial aufbereiten", + "items": [], + }, + { + "competence_id": "c3", + "title": "Betriebliche Prozesse dokumentieren, koordinieren und umsetzen", + "items": [], + }, + { + "competence_id": "e2", + "title": "Informationen im wirtschaftlichen und kaufmännischen Bereich recherchieren", + "items": [], + }, + { + "competence_id": "d2", + "title": "Informations- und Beratungsgespräche mit Kunden oder Lieferanten führen", + "items": [], + }, + { + "competence_id": "d3", + "title": "Betriebliche Prozesse dokumentieren, koordinieren und umsetzen", + "items": [], + }, + { + "competence_id": "d1", + "title": "Anliegen von Kunden oder Lieferanten entgegennehmen", + "items": [], + }, + ] + + for c in competences: + CompetencePageFactory( + parent=competence_profile_page, + competence_id=c["competence_id"], + title=c["title"], + items=[("item", i) for i in c["items"]], + ) + + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4" + ), + competence_id="e4.pv.ük1", + title="Sie erläutern die Dienstleistungen des Betriebs. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4" + ), + competence_id="e4.pv.ük3", + title="Sie stellen die Wertschöpfungskette und die verschiedenen organisatorischen Bereiche ihres Betriebs dar. (K2).", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4" + ), + competence_id="e4.pv.ük4", + title="Sie erläutern die relevanten rechtlichen Grundlagen ihrer Branche situationsgerecht. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2" + ), + competence_id="d2.pv.ük1", + title="Sie beschreiben die wichtigsten gesetzlichen Grundlagen im Versicherungsbereich umfassend. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2" + ), + competence_id="d2.pv.ük2", + title="Sie nennen die relevanten Dienstleistungen und Produkte im Versicherungsbereich. (K1)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2" + ), + competence_id="d2.pv.ük3", + title="Sie erklären die Leistungen und Produkte im Versicherungsbereich. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2" + ), + competence_id="d2.pv.ük4", + title="Sie erläutern die Prozesse und Abläufe im privaten Versicherungsbereich verständlich. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="c3" + ), + competence_id="c3.pv.ük7", + title="Sie erläutern die gesetzlichen Bestimmungen bei Schaden- und Leistungsfällen. (K2)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" + ), + ) + PerformanceCriteriaFactory( + parent=CompetencePage.objects.get( + slug__startswith=slug_prefix.replace("-lp", ""), competence_id="c3" + ), + competence_id="c3.pv.ük11", + title="Sie beurteilen gängige Versicherungslösungen fachkundig. (K3)", + learning_unit=LearningUnit.objects.get( + slug=f"{slug_prefix}-circle-véhicule-lu-einführung" ), ) diff --git a/server/vbv_lernwelt/core/management/commands/cypress_reset.py b/server/vbv_lernwelt/core/management/commands/cypress_reset.py index 74d3961d..de26ad62 100644 --- a/server/vbv_lernwelt/core/management/commands/cypress_reset.py +++ b/server/vbv_lernwelt/core/management/commands/cypress_reset.py @@ -1,5 +1,6 @@ import djclick as click +from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import CourseCompletion from vbv_lernwelt.notify.models import Notification @@ -9,3 +10,4 @@ def command(): print("cypress reset data") CourseCompletion.objects.all().delete() Notification.objects.all().delete() + User.objects.all().update(language="de") diff --git a/server/vbv_lernwelt/course/consts.py b/server/vbv_lernwelt/course/consts.py index ee148f60..94ba8517 100644 --- a/server/vbv_lernwelt/course/consts.py +++ b/server/vbv_lernwelt/course/consts.py @@ -1,4 +1,5 @@ COURSE_TEST_ID = -1 COURSE_VERSICHERUNGSVERMITTLERIN_OLD_ID = -2 -COURSE_UK1 = -3 +COURSE_UK = -3 COURSE_VERSICHERUNGSVERMITTLERIN_ID = -4 +COURSE_UK_FR = -5 diff --git a/server/vbv_lernwelt/course/management/commands/create_default_courses.py b/server/vbv_lernwelt/course/management/commands/create_default_courses.py index d51d1076..7e717c9e 100644 --- a/server/vbv_lernwelt/course/management/commands/create_default_courses.py +++ b/server/vbv_lernwelt/course/management/commands/create_default_courses.py @@ -3,6 +3,7 @@ from wagtail.models import Page from vbv_lernwelt.competence.create_uk_competence_profile import ( create_uk_competence_profile, + create_uk_fr_competence_profile, ) from vbv_lernwelt.competence.create_vv_competence_profile import ( create_vv_competence_profile, @@ -14,7 +15,8 @@ from vbv_lernwelt.core.create_default_users import default_users from vbv_lernwelt.core.models import User from vbv_lernwelt.course.consts import ( COURSE_TEST_ID, - COURSE_UK1, + COURSE_UK, + COURSE_UK_FR, COURSE_VERSICHERUNGSVERMITTLERIN_ID, COURSE_VERSICHERUNGSVERMITTLERIN_OLD_ID, ) @@ -23,6 +25,7 @@ from vbv_lernwelt.course.creators.versicherungsvermittlerin import ( create_versicherungsvermittlerin_with_categories, ) from vbv_lernwelt.course.management.commands.create_uk_course import ( + create_uk_fr_learning_path, create_uk_learning_path, ) from vbv_lernwelt.course.models import CourseSession, CourseSessionUser @@ -66,13 +69,21 @@ def command(): create_vv_new_learning_path() create_default_media_library(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID) - # Überbetriebliche Kurse + # Überbetriebliche Kurse DE create_versicherungsvermittlerin_with_categories( - course_id=COURSE_UK1, title="Überbetriebliche Kurse" + course_id=COURSE_UK, title="Überbetriebliche Kurse" ) - create_uk_learning_path(course_id=COURSE_UK1) - create_uk_competence_profile(course_id=COURSE_UK1) - create_default_media_library(course_id=COURSE_UK1) + create_uk_learning_path(course_id=COURSE_UK) + create_uk_competence_profile(course_id=COURSE_UK) + create_default_media_library(course_id=COURSE_UK) + + # Überbetriebliche Kurse FR + create_versicherungsvermittlerin_with_categories( + course_id=COURSE_UK_FR, title="Course hors établissement" + ) + create_uk_fr_learning_path(course_id=COURSE_UK_FR) + create_uk_fr_competence_profile(course_id=COURSE_UK_FR) + create_default_media_library(course_id=COURSE_UK_FR) # test course create_test_course() @@ -137,88 +148,120 @@ def command(): ) # course session Überbetriebliche Kurse Lehrjahr 1 - Region Bern - cs = CourseSession.objects.create( - course_id=COURSE_UK1, - title="Überbetriebliche Kurse Lehrjahr 1 - Region Bern", - ) - # for user_data in default_users: - # CourseSessionUser.objects.create( - # course_session=cs, - # user=User.objects.get(username=user_data["email"]), - # ) - # csu = CourseSessionUser.objects.create( - # course_session=cs, - # user=User.objects.get(username="trainer-uk1.einstieg@eiger-versicherungen.ch"), - # ) - # csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-einstieg")) - # csu = CourseSessionUser.objects.create( - # course_session=cs, - # user=User.objects.get(username="trainer-uk1.analyse@eiger-versicherungen.ch"), - # ) - # csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-analyse")) - # csu = CourseSessionUser.objects.create( - # course_session=cs, - # user=User.objects.get(username="student-uk1-bern@eiger-versicherungen.ch"), - # ) + courses = [ + { + "course_id": COURSE_UK, + "title": "Überbetriebliche Kurse Lehrjahr 1 - Region Bern", + "basis_slug": "überbetriebliche-kurse-lp-circle-basis", + "kickoff_slug": "überbetriebliche-kurse-lp-circle-kickoff", + "haushalt1_slug": "überbetriebliche-kurse-lp-circle-haushalt-teil-1", + "fahrzeug_slug": "überbetriebliche-kurse-lp-circle-fahrzeug", + "haushalt2_slug": "überbetriebliche-kurse-lp-circle-haushalt-teil-2", + }, + { + "course_id": COURSE_UK_FR, + "title": "Cours hors établissement année 1 - Région Fribourg", + "basis_slug": "course-hors-établissement-lp-circle-basis", + "kickoff_slug": "course-hors-établissement-lp-circle-kickoff", + "haushalt1_slug": "course-hors-établissement-lp-circle-haushalt-teil-1", + "fahrzeug_slug": "course-hors-établissement-lp-circle-fahrzeug", + "haushalt2_slug": "course-hors-établissement-lp-circle-haushalt-teil-2", + }, + ] + for course in courses: + cs = CourseSession.objects.create( + course_id=course["course_id"], + title=course["title"], + ) + # for user_data in default_users: + # CourseSessionUser.objects.create( + # course_session=cs, + # user=User.objects.get(username=user_data["email"]), + # ) + # csu = CourseSessionUser.objects.create( + # course_session=cs, + # user=User.objects.get(username="trainer-uk1.einstieg@eiger-versicherungen.ch"), + # ) + # csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-einstieg")) + # csu = CourseSessionUser.objects.create( + # course_session=cs, + # user=User.objects.get(username="trainer-uk1.analyse@eiger-versicherungen.ch"), + # ) + # csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-analyse")) + # csu = CourseSessionUser.objects.create( + # course_session=cs, + # user=User.objects.get(username="student-uk1-bern@eiger-versicherungen.ch"), + # ) - # figma demo users and data - csu = CourseSessionUser.objects.create( - course_session=cs, - user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"), - role=CourseSessionUser.Role.EXPERT, - ) - csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff")) - csu.expert.add( - Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-1") - ) - csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-fahrzeug")) - csu = CourseSessionUser.objects.create( - course_session=cs, - user=User.objects.get(username="andreas.feuz@eiger-versicherungen.ch"), - role=CourseSessionUser.Role.EXPERT, - ) - csu.expert.add( - Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2") - ) - csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis")) - _csu = CourseSessionUser.objects.create( - course_session=cs, - user=User.objects.get(username="michael.meier@example.com"), - ) - _csu = CourseSessionUser.objects.create( - course_session=cs, - user=User.objects.get(username="lina.egger@example.com"), - ) - _csu = CourseSessionUser.objects.create( - course_session=cs, - user=User.objects.get(username="evelyn.schmid@example.com"), - ) + # figma demo users and data + csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"), + role=CourseSessionUser.Role.EXPERT, + ) + csu.expert.add( + Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff") + ) + csu.expert.add( + Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-1") + ) + csu.expert.add( + Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-fahrzeug") + ) + csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(username="andreas.feuz@eiger-versicherungen.ch"), + role=CourseSessionUser.Role.EXPERT, + ) + csu.expert.add( + Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2") + ) + csu.expert.add( + Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis") + ) + _csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(username="michael.meier@example.com"), + ) + _csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(username="lina.egger@example.com"), + ) + _csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(username="evelyn.schmid@example.com"), + ) - _csu = CourseSessionUser.objects.create( - course_session=cs, - user=User.objects.get(username="christoph.bosshard@vbv-afa.ch"), - ) + _csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(username="christoph.bosshard@vbv-afa.ch"), + ) - _csu = CourseSessionUser.objects.create( - course_session=cs, - user=User.objects.get(username="axel.manderbach@lernetz.ch"), - ) + _csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(username="bianca.muster@eiger-versicherungen.ch"), + ) - create_feedback( - Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff"), cs, 3 - ) - create_feedback( - Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2"), - cs, - 14, - ) - create_feedback( - Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis"), cs, 4 - ) + _csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(username="axel.manderbach@lernetz.ch"), + ) + + create_feedback( + Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff"), cs, 3 + ) + create_feedback( + Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2"), + cs, + 14, + ) + create_feedback( + Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis"), cs, 4 + ) # course session Überbetriebliche Kurse Lehrjahr 1 - Region Zürich cs = CourseSession.objects.create( - course_id=COURSE_UK1, + course_id=COURSE_UK, title="Überbetriebliche Kurse Lehrjahr 1 - Region Zürich", ) # for user_data in default_users: @@ -248,7 +291,7 @@ def command(): # initial completion data for slug, status, email in [ ( - "überbetriebliche-kurse-competence-crit-a21-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük4-allgemein", "success", "michael.meier@example.com", ), @@ -273,7 +316,7 @@ def command(): "michael.meier@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a22-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük3-allgemein", "success", "michael.meier@example.com", ), @@ -293,7 +336,7 @@ def command(): "michael.meier@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük1-allgemein", "success", "michael.meier@example.com", ), @@ -313,7 +356,7 @@ def command(): "michael.meier@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a24-allgemein", + "überbetriebliche-kurse-competence-crit-d2pvük4-allgemein", "fail", "michael.meier@example.com", ), @@ -338,7 +381,7 @@ def command(): "michael.meier@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a25-allgemein", + "überbetriebliche-kurse-competence-crit-d2pvük3-allgemein", "success", "michael.meier@example.com", ), @@ -368,7 +411,7 @@ def command(): "michael.meier@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a24-allgemein-1", + "überbetriebliche-kurse-competence-crit-d2pvük2-allgemein", "success", "michael.meier@example.com", ), @@ -393,7 +436,7 @@ def command(): "michael.meier@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a25-allgemein-1", + "überbetriebliche-kurse-competence-crit-d2pvük1-allgemein", "success", "michael.meier@example.com", ), @@ -413,7 +456,7 @@ def command(): "michael.meier@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein-1", + "überbetriebliche-kurse-competence-crit-c3pvük7-allgemein", "fail", "michael.meier@example.com", ), @@ -433,7 +476,7 @@ def command(): "michael.meier@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein-2", + "überbetriebliche-kurse-competence-crit-c3pvük11-allgemein", "fail", "michael.meier@example.com", ), @@ -498,7 +541,7 @@ def command(): "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a21-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük4-allgemein", "success", "lina.egger@example.com", ), @@ -523,7 +566,7 @@ def command(): "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a22-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük3-allgemein", "success", "lina.egger@example.com", ), @@ -543,7 +586,7 @@ def command(): "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük1-allgemein", "success", "lina.egger@example.com", ), @@ -563,7 +606,7 @@ def command(): "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a24-allgemein", + "überbetriebliche-kurse-competence-crit-d2pvük4-allgemein", "success", "lina.egger@example.com", ), @@ -583,7 +626,7 @@ def command(): "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a25-allgemein", + "überbetriebliche-kurse-competence-crit-d2pvük3-allgemein", "success", "lina.egger@example.com", ), @@ -613,7 +656,7 @@ def command(): "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a24-allgemein-1", + "überbetriebliche-kurse-competence-crit-d2pvük2-allgemein", "success", "lina.egger@example.com", ), @@ -638,7 +681,7 @@ def command(): "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a25-allgemein-1", + "überbetriebliche-kurse-competence-crit-d2pvük1-allgemein", "success", "lina.egger@example.com", ), @@ -658,12 +701,12 @@ def command(): "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein-1", + "überbetriebliche-kurse-competence-crit-c3pvük7-allgemein", "success", "lina.egger@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein-2", + "überbetriebliche-kurse-competence-crit-c3pvük11-allgemein", "success", "lina.egger@example.com", ), @@ -697,11 +740,6 @@ def command(): "success", "lina.egger@example.com", ), - ( - "überbetriebliche-kurse-competence-crit-a24-allgemein-2", - "success", - "lina.egger@example.com", - ), ( "überbetriebliche-kurse-lp-circle-fahrzeug-lc-mediathek-4", "success", @@ -773,7 +811,7 @@ def command(): "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a21-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük4-allgemein", "success", "evelyn.schmid@example.com", ), @@ -798,7 +836,7 @@ def command(): "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a22-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük3-allgemein", "fail", "evelyn.schmid@example.com", ), @@ -818,7 +856,7 @@ def command(): "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein", + "überbetriebliche-kurse-competence-crit-e4pvük1-allgemein", "fail", "evelyn.schmid@example.com", ), @@ -838,7 +876,7 @@ def command(): "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a24-allgemein", + "überbetriebliche-kurse-competence-crit-d2pvük4-allgemein", "success", "evelyn.schmid@example.com", ), @@ -858,7 +896,7 @@ def command(): "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a25-allgemein", + "überbetriebliche-kurse-competence-crit-d2pvük3-allgemein", "success", "evelyn.schmid@example.com", ), @@ -873,7 +911,7 @@ def command(): "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a24-allgemein-1", + "überbetriebliche-kurse-competence-crit-d2pvük2-allgemein", "success", "evelyn.schmid@example.com", ), @@ -898,7 +936,7 @@ def command(): "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a25-allgemein-1", + "überbetriebliche-kurse-competence-crit-d2pvük1-allgemein", "fail", "evelyn.schmid@example.com", ), @@ -918,12 +956,12 @@ def command(): "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein-1", + "überbetriebliche-kurse-competence-crit-c3pvük7-allgemein", "success", "evelyn.schmid@example.com", ), ( - "überbetriebliche-kurse-competence-crit-a23-allgemein-2", + "überbetriebliche-kurse-competence-crit-c3pvük11-allgemein", "fail", "evelyn.schmid@example.com", ), @@ -962,11 +1000,6 @@ def command(): "success", "evelyn.schmid@example.com", ), - ( - "überbetriebliche-kurse-competence-crit-a24-allgemein-2", - "fail", - "evelyn.schmid@example.com", - ), ( "überbetriebliche-kurse-lp-circle-fahrzeug-lc-lerninhalt-offen-1", "success", diff --git a/server/vbv_lernwelt/course/management/commands/create_uk_course.py b/server/vbv_lernwelt/course/management/commands/create_uk_course.py index 372f24d7..89f039da 100644 --- a/server/vbv_lernwelt/course/management/commands/create_uk_course.py +++ b/server/vbv_lernwelt/course/management/commands/create_uk_course.py @@ -5,7 +5,7 @@ from wagtail.models import Locale, Page, Site from wagtail_localize.models import LocaleSynchronization from vbv_lernwelt.core.admin import User -from vbv_lernwelt.course.consts import COURSE_UK1 +from vbv_lernwelt.course.consts import COURSE_UK, COURSE_UK_FR from vbv_lernwelt.course.models import CoursePage from vbv_lernwelt.learnpath.create_vv_learning_path import ( create_learning_content_beenden, @@ -19,11 +19,8 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import ( TopicFactory, ) -# todo: remove when all Handlungsfelder are ready -READY_HF = ["Fahrzeug", "Reisen"] - -def create_uk_learning_path(course_id=COURSE_UK1, user=None, skip_locales=True): +def create_uk_learning_path(course_id=COURSE_UK, user=None, skip_locales=True): if user is None: user = User.objects.get(username="info@iterativ.ch") @@ -42,27 +39,26 @@ def create_uk_learning_path(course_id=COURSE_UK1, user=None, skip_locales=True): parent=course_page, ) - TopicFactory(title="Kickoff", is_visible=False, parent=lp) + TopicFactory(title="üK1", is_visible=True, parent=lp) create_uk_circle(lp, title="Kickoff") - - TopicFactory(title="Basis", is_visible=False, parent=lp) create_uk_circle(lp, title="Basis") - - TopicFactory(title="Fahrzeug", is_visible=False, parent=lp) create_uk_circle(lp, title="Fahrzeug") - - TopicFactory(title="Haushalt Teil 1", is_visible=False, parent=lp) create_uk_circle(lp, title="Haushalt Teil 1") - - TopicFactory(title="Haushalt Teil 2", is_visible=False, parent=lp) create_uk_circle(lp, title="Haushalt Teil 2") - # circle_analyse = create_circle("Betreuen", lp) - # create_circle_children(circle_analyse, "Betreuen") - # - # TopicFactory(title="Prüfung", is_visible=True, parent=lp) - # circle_analyse = create_circle("Prüfungsvorbereitung", lp) - # create_circle_children(circle_analyse, "Prüfungsvorbereitung") + TopicFactory(title="üK2", is_visible=True, parent=lp) + create_uk_circle(lp, title="Reisen & Rechtsstreitigkeiten") + create_uk_circle(lp, title="Wohneigentum") + create_uk_circle(lp, title="KMU Teil 1") + create_uk_circle(lp, title="KMU Teil 2") + create_uk_circle(lp, title="3-Säulenkonzept") + create_uk_circle(lp, title="Einkommenssicherung (Invalidität)") + create_uk_circle(lp, title="Einkommenssicherung (Todesfall)") + create_uk_circle(lp, title="Pensionierung (Todesfall)") + TopicFactory(title="üK3", is_visible=True, parent=lp) + create_uk_circle(lp, title="Gesundheit") + create_uk_circle(lp, title="Prüfungsvorbereitung Teil 1") + create_uk_circle(lp, title="Prüfungsvorbereitung Teil 2") # locales if not skip_locales: @@ -81,6 +77,62 @@ def create_uk_learning_path(course_id=COURSE_UK1, user=None, skip_locales=True): Page.objects.update(owner=user) +def create_uk_fr_learning_path(course_id=COURSE_UK_FR, user=None, skip_locales=True): + if user is None: + user = User.objects.get(username="info@iterativ.ch") + + site = Site.objects.filter(is_default_site=True).first() + + if not site: + site = wagtail_factories.SiteFactory(is_default_site=True) + + if settings.APP_ENVIRONMENT == "development": + site.port = 8000 + site.save() + + course_page = CoursePage.objects.get(course_id=course_id) + lp = LearningPathFactory( + title="Lernpfad", + parent=course_page, + ) + + TopicFactory(title="1ère année", is_visible=True, parent=lp) + create_uk_circle(lp, title="Coup d'envoi") + create_uk_circle(lp, title="Base") + create_uk_circle(lp, title="Véhicule") + create_uk_circle(lp, title="Budget Partie 1") + create_uk_circle(lp, title="Budget Partie 2") + + TopicFactory(title="2ème année", is_visible=True, parent=lp) + create_uk_circle(lp, title="Voyages / Protection juridique") + create_uk_circle(lp, title="Proprieté du logement") + create_uk_circle(lp, title="PME Partie 1") + create_uk_circle(lp, title="PME Partie 2") + create_uk_circle(lp, title="Concept des 3") + create_uk_circle(lp, title="Garantie des revenus, Partie 1") + create_uk_circle(lp, title="Garantie des revenus, Partie 2") + create_uk_circle(lp, title="Retraite") + TopicFactory(title="3ème année", is_visible=True, parent=lp) + create_uk_circle(lp, title="Santé") + create_uk_circle(lp, title="Préparation à l'examen, Partie 1") + create_uk_circle(lp, title="Préparation à l'examen, Partie 2") + # locales + if not skip_locales: + locale_de = Locale.objects.get(language_code="de-CH") + locale_fr, _ = Locale.objects.get_or_create(language_code="fr-CH") + LocaleSynchronization.objects.get_or_create( + locale_id=locale_fr.id, sync_from_id=locale_de.id + ) + locale_it, _ = Locale.objects.get_or_create(language_code="it-CH") + LocaleSynchronization.objects.get_or_create( + locale_id=locale_it.id, sync_from_id=locale_de.id + ) + call_command("sync_locale_trees") + + # all pages belong to 'admin' by default + Page.objects.update(owner=user) + + def create_uk_circle(lp, title="Kickoff"): circle = CircleFactory( title=title, diff --git a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py index b8378e75..de82a091 100644 --- a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py +++ b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py @@ -61,15 +61,11 @@ def create_vv_new_learning_path( create_circle_standard( lp, title="Haushalt", lc_title="Rafael und Claudia ziehen zusammen" ) - create_circle_standard_small( - lp, - title="Rechtsstreitigkeiten", - lc_title="Rafael Fasel hat Ärger mit seinem Vermieter", - ) + + create_circle_rechtsstreitigkeiten(lp) create_circle_reisen(lp) - create_circle_standard( - lp, title="Einkommenssicherung", lc_title="Patrizia und Marco sichern sich ab" - ) + create_circle_einkommenssicherung(lp) + create_circle_standard_small( lp, title="Wohneigentum", @@ -100,17 +96,17 @@ def create_vv_new_learning_path( lc_title="Familie Babic spart auf ein Ziel", lu_title="Sparinstrumente, Idee und Funktionsweise von Anlagefonds", ) - create_circle_standard( - lp, - title="KMU", - lc_title="Anne Fleur übernimmt den Blumenladen", - ) create_circle_standard_small( lp, title="Selbstständigkeit", lc_title="Patrizia macht sich selbstständig", lu_title="Selbsständigerwerbende versichern", ) + create_circle_standard( + lp, + title="KMU", + lc_title="Anne Fleur übernimmt den Blumenladen", + ) TopicFactory(title="Prüfung", parent=lp) create_circle_pruefungsvorbereitung(lp) @@ -386,7 +382,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"): parent=circle, category_name="Fahrzeug", competence_id="V1", - wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-einstieg-xapi-yXLHE5Xo/index.html", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-einstieg-xapi-L3QlrrCt/index.html", learning_unit_title="Gesprächsvorbereitung und -einstieg", ) @@ -396,7 +392,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"): parent=circle, category_name="Fahrzeug", competence_id="V2", - wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-analyse-xapi-SmrKAa0J/index.html", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-analyse-xapi-YtviM_SF/index.html", learning_unit_title="Bedarfsanalyse, Ist- und Soll-Situation", ) @@ -406,7 +402,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"): parent=circle, category_name="Fahrzeug", competence_id="V3", - wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-losung-xapi-3rzf8ySd/index.html", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-losung-xapi-jTdlOjOF/index.html", learning_unit_title="Lösungsvorschlag erarbeiten und präsentieren", ) @@ -416,7 +412,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"): parent=circle, category_name="Fahrzeug", competence_id="V4", - wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-abschluss-xapi-WbFBv-4y/index.html", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-abschluss-xapi-OBWheB8q/index.html", learning_unit_title="Gesprächszusammenfassung, Abschluss und Nachbereitung", ) @@ -450,6 +446,46 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"): ) +def create_circle_rechtsstreitigkeiten(lp, title="Rechtsstreitigkeiten"): + circle = CircleFactory( + title=title, + parent=lp, + ) + LearningSequenceFactory(title="Einführung", parent=circle, icon="it-icon-ls-start") + LearningUnitFactory(title="Einführung", parent=circle) + LearningContentFactory( + title="Verschaff dir einen Überblick", + parent=circle, + contents=[ + ( + "video", + VideoBlockFactory( + url="https://player.vimeo.com/video/772512710?h=30f912f15a", + description="Willkommen im Lehrgang Versicherungsvermitler VBV", + ), + ) + ], + ) + LearningContentFactory( + title="Mediathek", + parent=circle, + ) + + LearningSequenceFactory( + title="Rechtsstreitigkeiten", parent=circle, icon="it-icon-ls-watch" + ) + create_standard_learning_unit( + "Rafael Fasel hat Ärger mit seinem Vermieter", + parent=circle, + category_name=title, + competence_id="V1", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-hat-arger-mit-seinem-vermieter-xapi-qI-Aqj8O/index.html", + learning_unit_title="Terminvereinbarung, Vorbereitung und Gesprächseröffnung", + ) + + create_learning_sequence_transfer(circle, title="Rechtsstreitigkeiten") + + def create_circle_reisen(lp, title="Reisen"): circle = CircleFactory( title=title, @@ -481,7 +517,7 @@ def create_circle_reisen(lp, title="Reisen"): parent=circle, category_name=title, competence_id="V1", - wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-einstieg-xapi-_BfVBK8d/index.html", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-einstieg-xapi-UnxjqXFB/index.html", learning_unit_title="Terminvereinbarung, Vorbereitung und Gesprächseröffnung", ) @@ -491,7 +527,7 @@ def create_circle_reisen(lp, title="Reisen"): parent=circle, category_name=title, competence_id="V2", - wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-analyse-xapi-mtXA4uBz/index.html", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-analyse-xapi-FZoZOP9y/index.html", learning_unit_title="Bedarfsanalyse, Ist- und Soll-Situation", ) @@ -501,7 +537,7 @@ def create_circle_reisen(lp, title="Reisen"): parent=circle, category_name=title, competence_id="V3", - wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-losung-xapi-hn7GwUTn/index.html", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-losung-xapi-DK5AsZ5M/index.html", learning_unit_title="Lösungsvorschlag erarbeiten und präsentieren", ) @@ -511,7 +547,7 @@ def create_circle_reisen(lp, title="Reisen"): parent=circle, category_name=title, competence_id="V4", - wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-abschluss-xapi-Qv4X-yua/index.html", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-abschluss-xapi--4t5XKAf/index.html", learning_unit_title="Gesprächszusammenfassung, Abschluss und Nachbereitung", ) @@ -520,6 +556,76 @@ def create_circle_reisen(lp, title="Reisen"): ) +def create_circle_einkommenssicherung(lp, title="Einkommenssicherung"): + circle = CircleFactory( + title=title, + parent=lp, + ) + LearningSequenceFactory(title="Einführung", parent=circle, icon="it-icon-ls-start") + LearningUnitFactory(title="Einführung", parent=circle) + LearningContentFactory( + title="Verschaff dir einen Überblick", + parent=circle, + contents=[ + ( + "video", + VideoBlockFactory( + url="https://player.vimeo.com/video/772512710?h=30f912f15a", + description="Willkommen im Lehrgang Versicherungsvermitler VBV", + ), + ) + ], + ) + LearningContentFactory( + title="Mediathek", + parent=circle, + ) + + LearningSequenceFactory(title="Einstieg", parent=circle, icon="it-icon-ls-watch") + create_standard_learning_unit( + "Patrizia und Marco sichern sich ab - Einstieg", + parent=circle, + category_name=title, + competence_id="V1", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/patrizia-marco-sichern-sich-ab-einstieg-xapi-jkH97GgC/index.html", + learning_unit_title="Terminvereinbarung, Vorbereitung und Gesprächseröffnung", + ) + + LearningSequenceFactory(title="Analyse", parent=circle, icon="it-icon-ls-watch") + create_standard_learning_unit( + "Patrizia und Marco sichern sich ab - Analyse", + parent=circle, + category_name=title, + competence_id="V2", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/patrizia-marco-sichern-sich-ab-analyse-xapi-MGcooCtm/index.html", + learning_unit_title="Bedarfsanalyse, Ist- und Soll-Situation", + ) + + LearningSequenceFactory(title="Lösung", parent=circle, icon="it-icon-ls-watch") + create_standard_learning_unit( + "Patrizia und Marco sichern sich ab - Lösung", + parent=circle, + category_name=title, + competence_id="V3", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/patrizia-marco-sichern-sich-ab-losung-xapi-nzoOdKIE/index.html", + learning_unit_title="Lösungsvorschlag erarbeiten und präsentieren", + ) + + LearningSequenceFactory(title="Abschluss", parent=circle, icon="it-icon-ls-watch") + create_standard_learning_unit( + "Patrizia und Marco sichern sich ab - Abschluss", + parent=circle, + category_name=title, + competence_id="V4", + wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/patrizia-marco-sichern-sich-ab-abschluss-xapi--lXKe6xt/index.html", + learning_unit_title="Gesprächszusammenfassung, Abschluss und Nachbereitung", + ) + + create_learning_sequence_transfer( + circle, title="Einkommenssicherung", lc_praxis_title="Heirat: Was ändert sich?" + ) + + def create_circle_standard_small( lp, title, lc_title, lu_title=None, lc_praxis_title=None ): diff --git a/server/vbv_lernwelt/static/icons/icon-globe.svg b/server/vbv_lernwelt/static/icons/icon-globe.svg new file mode 100644 index 00000000..0cdad129 --- /dev/null +++ b/server/vbv_lernwelt/static/icons/icon-globe.svg @@ -0,0 +1,3 @@ + + +