#2-protocol #6

Merged
jkeffects merged 15 commits from #2-protocol into main 2024-10-29 14:43:29 +00:00
14 changed files with 708 additions and 24 deletions
Showing only changes of commit c1e9784b4a - Show all commits

156
package-lock.json generated
View file

@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@headlessui/vue": "^1.7.13", "@headlessui/vue": "^1.7.13",
"@heroicons/vue": "^2.1.5", "@heroicons/vue": "^2.1.5",
"@types/lodash.isequal": "^4.5.8", "@vueup/vue-quill": "^1.2.0",
"axios": "^0.26.1", "axios": "^0.26.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
@ -31,6 +31,7 @@
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/eslint": "~9.6.0", "@types/eslint": "~9.6.0",
"@types/lodash.clonedeep": "^4.5.9", "@types/lodash.clonedeep": "^4.5.9",
"@types/lodash.isequal": "^4.5.8",
"@types/node": "^20.14.5", "@types/node": "^20.14.5",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
@ -3070,6 +3071,7 @@
"version": "4.17.7", "version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
"integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/lodash.clonedeep": { "node_modules/@types/lodash.clonedeep": {
@ -3086,6 +3088,7 @@
"version": "4.5.8", "version": "4.5.8",
"resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz", "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz",
"integrity": "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==", "integrity": "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/lodash": "*" "@types/lodash": "*"
@ -3680,6 +3683,19 @@
"integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
"dev": true "dev": true
}, },
"node_modules/@vueup/vue-quill": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@vueup/vue-quill/-/vue-quill-1.2.0.tgz",
"integrity": "sha512-kd5QPSHMDpycklojPXno2Kw2JSiKMYduKYQckTm1RJoVDA557MnyUXgcuuDpry4HY/Rny9nGNcK+m3AHk94wag==",
"license": "MIT",
"dependencies": {
"quill": "^1.3.7",
"quill-delta": "^4.2.2"
},
"peerDependencies": {
"vue": "^3.2.41"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.12.1", "version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@ -4311,6 +4327,15 @@
"wrap-ansi": "^6.2.0" "wrap-ansi": "^6.2.0"
} }
}, },
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color": { "node_modules/color": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@ -4589,6 +4614,26 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"license": "MIT",
"dependencies": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-extend": { "node_modules/deep-extend": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@ -4673,7 +4718,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"dependencies": { "dependencies": {
"define-data-property": "^1.0.1", "define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0", "has-property-descriptors": "^1.0.0",
@ -5237,6 +5281,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
"license": "MIT"
},
"node_modules/execa": { "node_modules/execa": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
@ -5269,6 +5319,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -5550,7 +5606,6 @@
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"dev": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@ -5815,7 +5870,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": { "dependencies": {
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3"
}, },
@ -5976,6 +6030,22 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@ -6099,7 +6169,6 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"dependencies": { "dependencies": {
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
}, },
@ -6236,7 +6305,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.2", "call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
@ -6996,11 +7064,26 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": { "node_modules/object-keys": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -7126,6 +7209,12 @@
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
"dev": true "dev": true
}, },
"node_modules/parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
"license": "BSD-3-Clause"
},
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -7669,6 +7758,57 @@
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
"dev": true "dev": true
}, },
"node_modules/quill": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"license": "BSD-3-Clause",
"dependencies": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
}
},
"node_modules/quill-delta": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-4.2.2.tgz",
"integrity": "sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==",
"license": "MIT",
"dependencies": {
"fast-diff": "1.2.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
}
},
"node_modules/quill-delta/node_modules/fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"license": "Apache-2.0"
},
"node_modules/quill/node_modules/fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
"license": "Apache-2.0"
},
"node_modules/quill/node_modules/quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"license": "MIT",
"dependencies": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -7787,7 +7927,6 @@
"version": "1.5.2", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
"integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.6", "call-bind": "^1.0.6",
"define-properties": "^1.2.1", "define-properties": "^1.2.1",
@ -8090,7 +8229,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
"dev": true,
"dependencies": { "dependencies": {
"define-data-property": "^1.1.4", "define-data-property": "^1.1.4",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",

View file

@ -26,6 +26,7 @@
"dependencies": { "dependencies": {
"@headlessui/vue": "^1.7.13", "@headlessui/vue": "^1.7.13",
"@heroicons/vue": "^2.1.5", "@heroicons/vue": "^2.1.5",
"@vueup/vue-quill": "^1.2.0",
"axios": "^0.26.1", "axios": "^0.26.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",

View file

@ -0,0 +1,27 @@
<template>
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
<RouterLink
:to="{ name: 'admin-club-protocol-overview', params: { protocolId: protocol.id } }"
class="bg-primary p-2 text-white flex flex-row justify-between items-center"
>
<p>{{ protocol.title }}</p>
</RouterLink>
<div class="p-2">
<p>Infos</p>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import type { ProtocolViewModel } from "../../../../viewmodels/admin/protocol.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocol: { type: Object as PropType<ProtocolViewModel>, default: {} },
},
});
</script>

View file

@ -1,12 +1,8 @@
import type { AxiosInstance } from "axios";
import type { NProgress } from "nprogress";
import type { RouteLocationNormalizedLoaded, Router } from "vue-router"; import type { RouteLocationNormalizedLoaded, Router } from "vue-router";
declare module "@vue/runtime-core" { declare module "@vue/runtime-core" {
interface ComponentCustomProperties { interface ComponentCustomProperties {
$dev: boolean; $dev: boolean;
$http: AxiosInstance;
$progress: NProgress;
$router: Router; $router: Router;
$route: RouteLocationNormalizedLoaded; $route: RouteLocationNormalizedLoaded;
} }

View file

@ -7,6 +7,7 @@ import { isSetup } from "./setupGuard";
import { abilityAndNavUpdate } from "./adminGuard"; import { abilityAndNavUpdate } from "./adminGuard";
import type { PermissionType, PermissionSection, PermissionModule } from "@/types/permissionTypes"; import type { PermissionType, PermissionSection, PermissionModule } from "@/types/permissionTypes";
import { resetMemberStores, setMemberId } from "./memberGuard"; import { resetMemberStores, setMemberId } from "./memberGuard";
import { resetProtocolStores, setProtocolId } from "./protocolGuard";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -148,10 +149,41 @@ const router = createRouter({
}, },
{ {
path: "protocol", path: "protocol",
name: "admin-club-protocol", name: "admin-club-protocol-route",
component: () => import("@/views/admin/members/Overview.vue"), component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "club", module: "protocoll" }, meta: { type: "read", section: "club", module: "protocol" },
beforeEnter: [abilityAndNavUpdate], beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-club-protocol",
component: () => import("@/views/admin/protocol/Protocol.vue"),
beforeEnter: [resetProtocolStores],
},
{
path: ":protocolId",
name: "admin-club-protocol-routing",
component: () => import("@/views/admin/protocol/ProtocolRouting.vue"),
beforeEnter: [setProtocolId],
props: true,
children: [
{
path: "overview",
name: "admin-club-protocol-overview",
component: () => import("@/views/admin/protocol/ProtocolOverview.vue"),
props: true,
},
{
path: "edit",
name: "admin-club-protocol-edit",
component: () => import("@/views/admin/protocol/ProtocolEdit.vue"),
meta: { type: "update", section: "club", module: "member" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
}, },
], ],
}, },

View file

@ -0,0 +1,16 @@
import { useProtocolStore } from "@/stores/admin/protocol";
export async function setProtocolId(to: any, from: any, next: any) {
const protocol = useProtocolStore();
protocol.activeProtocol = to.params?.protocolId ?? null;
next();
}
export async function resetProtocolStores(to: any, from: any, next: any) {
const protocol = useProtocolStore();
protocol.activeProtocol = null;
protocol.activeProtocolObj = null;
next();
}

View file

@ -0,0 +1,80 @@
import { defineStore } from "pinia";
import type { CreateProtocolViewModel, UpdateProtocolViewModel } from "@/viewmodels/admin/protocol.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import type { ProtocolViewModel } from "@/viewmodels/admin/protocol.models";
export const useProtocolStore = defineStore("protocol", {
state: () => {
return {
protocols: [] as Array<ProtocolViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeProtocol: null as number | null,
activeProtocolObj: null as ProtocolViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchProtocols(offset = 0, count = 25, clear = false) {
if (clear) this.protocols = [];
this.loading = "loading";
http
.get(`/admin/protocol?offset=${offset}&count=${count}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.protocols
.filter((elem: ProtocolViewModel) => this.protocols.findIndex((m) => m.id == elem.id) == -1)
.map((elem: ProtocolViewModel, index: number): ProtocolViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: ProtocolViewModel & { tab_pos: number }) => {
this.protocols.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
fetchProtocolByActiveId() {
this.loadingActive = "loading";
http
.get(`/admin/protocol/${this.activeProtocol}`)
.then((res) => {
this.activeProtocolObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchProtocolById(id: number) {
return http.get(`/admin/protocol/${id}`);
},
async createProtocol(protocol: CreateProtocolViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/protocol`, {
title: protocol.title,
date: protocol.date,
});
this.fetchProtocols();
return result;
},
async updateActiveProtocol(protocol: UpdateProtocolViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/protocol/${protocol.id}`, {
title: protocol.title,
date: protocol.date,
});
this.fetchProtocols();
return result;
},
async deleteProtocol(protocol: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/protocol/${protocol}`);
this.fetchProtocols();
return result;
},
},
});

View file

@ -4,7 +4,7 @@ export type PermissionModule =
| "member" | "member"
| "calendar" | "calendar"
| "newsletter" | "newsletter"
| "protocoll" | "protocol"
| "qualification" | "qualification"
| "award" | "award"
| "executive_position" | "executive_position"
@ -39,7 +39,7 @@ export const permissionModules: Array<PermissionModule> = [
"member", "member",
"calendar", "calendar",
"newsletter", "newsletter",
"protocoll", "protocol",
"qualification", "qualification",
"award", "award",
"executive_position", "executive_position",
@ -50,7 +50,7 @@ export const permissionModules: Array<PermissionModule> = [
]; ];
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"]; export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
export const sectionsAndModules: SectionsAndModulesObject = { export const sectionsAndModules: SectionsAndModulesObject = {
club: ["member", "calendar", "newsletter", "protocoll"], club: ["member", "calendar", "newsletter", "protocol"],
settings: ["qualification", "award", "executive_position", "communication", "membership_status"], settings: ["qualification", "award", "executive_position", "communication", "membership_status"],
user: ["user", "role"], user: ["user", "role"],
}; };

View file

@ -0,0 +1,16 @@
export interface ProtocolViewModel {
id: number;
title: string;
date: Date;
}
export interface CreateProtocolViewModel {
title: string;
date: Date;
}
export interface UpdateProtocolViewModel {
id: number;
title: string;
date: Date;
}

View file

@ -69,11 +69,11 @@ export default defineComponent({
}) })
.then((result) => { .then((result) => {
this.loginStatus = "success"; this.loginStatus = "success";
localStorage.setItem("accessToken", result.data.accessToken), localStorage.setItem("accessToken", result.data.accessToken);
localStorage.setItem("refreshToken", result.data.refreshToken), localStorage.setItem("refreshToken", result.data.refreshToken);
setTimeout(() => { setTimeout(() => {
this.$router.push(`/admin`); this.$router.push(`/admin`);
}, 1000); }, 1000);
}) })
.catch((err) => { .catch((err) => {
this.loginStatus = "failed"; this.loginStatus = "failed";

View file

@ -0,0 +1,151 @@
<template>
<MainTemplate>
<template #topBar>
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8">Protokolle</h1>
</div>
</template>
<template #diffMain>
<div class="flex flex-col w-full h-full gap-2 justify-center px-7">
<div class="flex flex-col w-full grow gap-2 pr-2 overflow-y-scroll">
<ProtocolListItem v-for="protocol in protocols" :key="protocol.id" :protocol="protocol" />
</div>
<div class="flex flex-row w-full justify-between select-none">
<p class="text-sm font-normal text-gray-500">
Elemente <span class="font-semibold text-gray-900">{{ showingText }}</span> von
<span class="font-semibold text-gray-900">{{ entryCount }}</span>
</p>
<ul class="flex flex-row text-sm h-8">
<li
class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 first:rounded-s-lg last:rounded-e-lg"
:class="[currentPage > 0 ? 'cursor-pointer hover:bg-gray-100 hover:text-gray-700' : 'opacity-50']"
@click="loadPage(currentPage - 1)"
>
<ChevronLeftIcon class="h-4" />
</li>
<li
v-for="page in displayedPagesNumbers"
:key="page"
class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 first:rounded-s-lg last:rounded-e-lg"
:class="[currentPage == page ? 'font-bold border-primary' : '', page != '.' ? ' cursor-pointer' : '']"
@click="loadPage(page)"
>
{{ typeof page == "number" ? page + 1 : "..." }}
</li>
<li
class="flex h-8 w-8 items-center justify-center text-gray-500 bg-white border border-gray-300 first:rounded-s-lg last:rounded-e-lg"
:class="[
currentPage + 1 < countOfPages ? 'cursor-pointer hover:bg-gray-100 hover:text-gray-700' : 'opacity-50',
]"
@click="loadPage(currentPage + 1)"
>
<ChevronRightIcon class="h-4" />
</li>
</ul>
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Protokoll erstellen</button>
</div>
</div>
</template>
</MainTemplate>
</template>
<script setup lang="ts">
import { defineAsyncComponent, defineComponent, markRaw } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/vue/20/solid";
import { useProtocolStore } from "@/stores/admin/protocol";
import ProtocolListItem from "@/components/admin/club/protocol/ProtocolListItem.vue";
import { useModalStore } from "@/stores/modal";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
currentPage: 0,
maxEntriesPerPage: 25,
};
},
computed: {
...mapState(useProtocolStore, ["protocols", "totalCount"]),
entryCount() {
return this.totalCount ?? this.protocols.length;
},
showingStart() {
return this.currentPage * this.maxEntriesPerPage;
},
showingEnd() {
let max = this.currentPage * this.maxEntriesPerPage + this.maxEntriesPerPage;
if (max > this.entryCount) max = this.entryCount;
return max;
},
showingText() {
return `${this.entryCount != 0 ? this.showingStart + 1 : 0} - ${this.showingEnd}`;
},
countOfPages() {
return Math.ceil(this.entryCount / this.maxEntriesPerPage);
},
displayedPagesNumbers(): Array<number | "."> {
//indicate if "." or page number gets pushed
let stateOfPush = false;
return [...new Array(this.countOfPages)].reduce((acc, curr, index) => {
if (
// always display first 2 pages
index <= 1 ||
// always display last 2 pages
index >= this.countOfPages - 2 ||
// always display 1 pages around current page
(this.currentPage - 1 <= index && index <= this.currentPage + 1)
) {
acc.push(index);
stateOfPush = false;
return acc;
}
// abort if placeholder already added to array
if (stateOfPush == true) return acc;
// show placeholder if pagenumber is not actively rendered
acc.push(".");
stateOfPush = true;
return acc;
}, []);
},
visibleRows() {
return this.filterData(this.protocols, this.showingStart, this.showingEnd);
},
},
mounted() {
this.fetchProtocols(0, this.maxEntriesPerPage, true);
},
methods: {
...mapActions(useProtocolStore, ["fetchProtocols"]),
...mapActions(useModalStore, ["openModal"]),
loadPage(newPage: number | ".") {
if (newPage == ".") return;
if (newPage < 0 || newPage >= this.countOfPages) return;
let pageStart = newPage * this.maxEntriesPerPage;
let pageEnd = newPage * this.maxEntriesPerPage + this.maxEntriesPerPage;
if (pageEnd > this.entryCount) pageEnd = this.entryCount;
let loadedElementCount = this.filterData(this.protocols, pageStart, pageEnd).length;
if (loadedElementCount < this.maxEntriesPerPage) this.fetchProtocols(pageStart, this.maxEntriesPerPage);
this.currentPage = newPage;
},
filterData(array: Array<any>, start: number, end: number): Array<any> {
return array.filter((elem, index) => (elem?.tab_pos ?? index) >= start && (elem?.tab_pos ?? index) < end);
},
openCreateModal() {
// this.openModal(
// markRaw(defineAsyncComponent(() => import("@/components/admin/club/protocol/CreateProtocolModal.vue")))
// );
},
},
});
</script>

View file

@ -0,0 +1,111 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<Spinner v-if="loading == 'loading'" class="mx-auto" />
<p v-else-if="loading == 'failed'">laden fehlgeschlagen</p>
<form
v-else-if="protocol != null"
class="flex flex-col gap-4 py-2 w-full max-w-xl mx-auto"
@submit.prevent="triggerUpdate"
>
<p class="mx-auto">Protokoll bearbeiten</p>
<div class="flex flex-row justify-end gap-2">
<button primary-outline type="reset" class="!w-fit" :disabled="canSaveOrReset" @click="resetForm">
verwerfen
</button>
<button primary type="submit" class="!w-fit" :disabled="status == 'loading' || canSaveOrReset">
speichern
</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { useProtocolStore } from "@/stores/admin/protocol";
import type { ProtocolViewModel, UpdateProtocolViewModel } from "@/viewmodels/admin/protocol.models";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import cloneDeep from "lodash.clonedeep";
import isEqual from "lodash.isEqual";
import { Salutation } from "@/enums/salutation";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocolId: String,
},
watch: {
loadingActive() {
if (this.loading == "loading") {
this.loading = this.loadingActive;
}
if (this.loadingActive == "fetched") this.protocol = cloneDeep(this.activeProtocolObj);
},
},
data() {
return {
loading: "loading" as "loading" | "fetched" | "failed",
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
protocol: null as null | ProtocolViewModel,
timeout: null as any,
salutations: [] as Array<string>,
};
},
computed: {
canSaveOrReset(): boolean {
return isEqual(this.activeProtocolObj, this.protocol);
},
...mapState(useProtocolStore, ["activeProtocolObj", "loadingActive"]),
},
mounted() {
this.fetchItem();
this.salutations = Object.values(Salutation);
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useProtocolStore, ["fetchProtocolByActiveId", "updateActiveProtocol"]),
resetForm() {
this.protocol = cloneDeep(this.activeProtocolObj);
},
fetchItem() {
this.fetchProtocolByActiveId();
},
triggerUpdate(e: any) {
if (this.protocol == null) return;
let formData = e.target.elements;
let updateProtocol: UpdateProtocolViewModel = {
id: this.protocol.id,
title: formData.title.value,
date: formData.date.value,
};
this.status = "loading";
this.updateActiveProtocol(updateProtocol)
.then(() => {
this.fetchItem();
this.status = { status: "success" };
})
.catch((err) => {
this.status = { status: "failed" };
})
.finally(() => {
this.timeout = setTimeout(() => {
this.status = null;
}, 2000);
});
},
},
});
</script>

View file

@ -0,0 +1,34 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<div v-if="activeProtocol != null" class="flex flex-col gap-2 w-full">Protokoll Details</div>
<Spinner v-if="loadingActive == 'loading'" class="mx-auto" />
<p v-else-if="loadingActive == 'failed'" @click="fetchProtocolByActiveId" class="cursor-pointer">
&#8634; laden fehlgeschlagen
</p>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import Spinner from "@/components/Spinner.vue";
import { useProtocolStore } from "@/stores/admin/protocol";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocolId: String,
},
computed: {
...mapState(useProtocolStore, ["activeProtocol", "loadingActive"]),
},
mounted() {
this.fetchProtocolByActiveId();
},
methods: {
...mapActions(useProtocolStore, ["fetchProtocolByActiveId"]),
},
});
</script>

View file

@ -0,0 +1,82 @@
<template>
<MainTemplate>
<template #headerInsert>
<RouterLink to="../" class="text-primary">zurück zur Liste</RouterLink>
</template>
<template #topBar>
<div class="flex flex-row gap-2 items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8 min-h-fit grow">
{{ activeProtocolObj?.title }}, {{ activeProtocolObj?.date }}
</h1>
<RouterLink :to="{ name: 'admin-club-protocol-edit' }">
<PencilIcon class="w-5 h-5" />
</RouterLink>
<TrashIcon class="w-5 h-5 cursor-pointer" @click="openDeleteModal" />
</div>
</template>
<template #diffMain>
<div class="flex flex-col gap-2 grow px-7 overflow-hidden">
<div class="flex flex-col grow gap-2 overflow-hidden">
<div class="w-full flex flex-row max-lg:flex-wrap justify-center">
<RouterLink
v-for="tab in tabs"
:key="tab.route"
v-slot="{ isActive }"
:to="{ name: tab.route }"
class="w-1/2 md:w-1/3 lg:w-full p-0.5 first:pl-0 last:pr-0"
>
<p
:class="[
'w-full rounded-lg py-2.5 text-sm text-center font-medium leading-5 focus:ring-0 outline-none',
isActive ? 'bg-red-200 shadow border-b-2 border-primary rounded-b-none' : ' hover:bg-red-200',
]"
>
{{ tab.title }}
</p>
</RouterLink>
</div>
<RouterView />
</div>
</div>
</template>
</MainTemplate>
</template>
<script setup lang="ts">
import { defineAsyncComponent, defineComponent, markRaw } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { RouterLink, RouterView } from "vue-router";
import { useProtocolStore } from "@/stores/admin/protocol";
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "@/stores/modal";
</script>
<script lang="ts">
export default defineComponent({
props: {
protocolId: String,
},
data() {
return {
tabs: [{ route: "admin-club-protocol-overview", title: "Übersicht" }],
};
},
computed: {
...mapState(useProtocolStore, ["activeProtocolObj"]),
},
mounted() {
this.fetchProtocolByActiveId();
},
methods: {
...mapActions(useProtocolStore, ["fetchProtocolByActiveId"]),
...mapActions(useModalStore, ["openModal"]),
openDeleteModal() {
// this.openModal(
// markRaw(defineAsyncComponent(() => import("@/components/admin/club/protocol/DeleteProtocolModal.vue"))),
// parseInt(this.protocolId ?? "")
// );
},
},
});
</script>