forked from verdnatura/salix-front
Merge pull request '4722-routes_refactor' (!32) from 4722-routes_refactor into dev
Reviewed-on: verdnatura/salix-front#32
This commit is contained in:
commit
144a99510f
|
@ -1,6 +1,7 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.thumbs.db
|
.thumbs.db
|
||||||
node_modules
|
node_modules
|
||||||
|
junit.xml
|
||||||
|
|
||||||
# Quasar core related directories
|
# Quasar core related directories
|
||||||
.quasar
|
.quasar
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"editorconfig.editorconfig",
|
"editorconfig.editorconfig",
|
||||||
"johnsoncodehk.volar",
|
"Vue.volar",
|
||||||
"wayou.vscode-todo-highlight"
|
"wayou.vscode-todo-highlight"
|
||||||
],
|
],
|
||||||
"unwantedRecommendations": [
|
"unwantedRecommendations": [
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"editor.bracketPairColorization.enabled": true,
|
"editor.bracketPairColorization.enabled": true,
|
||||||
"editor.guides.bracketPairs": true,
|
"editor.guides.bracketPairs": true,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "johnsoncodehk.volar",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
|
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
|
||||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
|
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
|
||||||
"json.schemas": [
|
"json.schemas": [
|
||||||
|
@ -11,9 +11,6 @@
|
||||||
"url": "https://on.cypress.io/cypress.schema.json"
|
"url": "https://on.cypress.io/cypress.schema.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
|
||||||
},
|
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,11 @@
|
||||||
"name": "salix-front",
|
"name": "salix-front",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.15.5",
|
"@quasar/extras": "^1.15.6",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"quasar": "^2.10.0",
|
"pinia": "^2.0.24",
|
||||||
|
"quasar": "^2.10.2",
|
||||||
"validator": "^13.7.0",
|
"validator": "^13.7.0",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-i18n": "^9.0.0",
|
"vue-i18n": "^9.0.0",
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.13.14",
|
"@babel/eslint-parser": "^7.13.14",
|
||||||
"@intlify/vue-i18n-loader": "^4.1.0",
|
"@intlify/vue-i18n-loader": "^4.1.0",
|
||||||
|
"@pinia/testing": "^0.0.14",
|
||||||
"@quasar/app-webpack": "^3.6.2",
|
"@quasar/app-webpack": "^3.6.2",
|
||||||
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.2.2",
|
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.2.2",
|
||||||
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.10",
|
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.10",
|
||||||
|
@ -3271,6 +3273,47 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@pinia/testing": {
|
||||||
|
"version": "0.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.0.14.tgz",
|
||||||
|
"integrity": "sha512-ZmZwVNd/NnKYLIfjfuKl0zlJ3UdiXFpsHzSlL6wCeezSlyrqGMxsIQKv0J6fleu38gyCNTPBEipfxrt8V4+VIg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pinia": ">=2.0.19"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@pinia/testing/node_modules/vue-demi": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@polka/url": {
|
"node_modules/@polka/url": {
|
||||||
"version": "1.0.0-next.21",
|
"version": "1.0.0-next.21",
|
||||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
|
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
|
||||||
|
@ -3519,9 +3562,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@quasar/extras": {
|
"node_modules/@quasar/extras": {
|
||||||
"version": "1.15.5",
|
"version": "1.15.6",
|
||||||
"resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.15.5.tgz",
|
"resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.15.6.tgz",
|
||||||
"integrity": "sha512-JzKKx5/eKAip3X3bZUEJOOWT9NudqjF01gcce6rtyviko49OU4r+ekyJU3QQIKF8ZqnjZ+DpsVpMWBBZnO6hSQ==",
|
"integrity": "sha512-lG3wrcz47c86N/j1ULZugmyVfwpmnsJpjtSWh+LhaFfe0g1kTMdAnxkWGKsa3ouZ4QBcnkrNan0kYSnKc3MiBg==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://donate.quasar.dev"
|
"url": "https://donate.quasar.dev"
|
||||||
|
@ -4307,9 +4350,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/devtools-api": {
|
"node_modules/@vue/devtools-api": {
|
||||||
"version": "6.1.3",
|
"version": "6.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
|
||||||
"integrity": "sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg=="
|
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue/reactivity": {
|
"node_modules/@vue/reactivity": {
|
||||||
"version": "3.2.31",
|
"version": "3.2.31",
|
||||||
|
@ -15743,6 +15786,56 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia": {
|
||||||
|
"version": "2.0.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.24.tgz",
|
||||||
|
"integrity": "sha512-DDLd4Iphyc+6PYYYbx7jkb6WP9gecgu9bz9huyB5rb7CdJI3DhzYiZI+/Ih8MLewRrP9DSpslF/BgSNrJtZU7A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.4.5",
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.4.0",
|
||||||
|
"typescript": ">=4.4.4",
|
||||||
|
"vue": "^2.6.14 || ^3.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pinia/node_modules/vue-demi": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
|
||||||
|
@ -16622,9 +16715,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/quasar": {
|
"node_modules/quasar": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.10.2.tgz",
|
||||||
"integrity": "sha512-PHGcrzPQfFa4tv9a5Z/3D2uat48D4WC9Ad/imzHk/k3G41t0eFMH6glCjAvpCWF2q8dBYIg4nEchiPhlujbKsw==",
|
"integrity": "sha512-y6suu0f2hJKrnFPHzx+p2EBVGzDF6xHaqYGkDIsMNkhxsrO9Qi2+dZCjq1J6+48EJiqPEOn8t9X/gT7yLSSnLw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.18.1",
|
"node": ">= 10.18.1",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
|
@ -19517,7 +19610,7 @@
|
||||||
"version": "4.5.5",
|
"version": "4.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
|
||||||
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
|
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
@ -23355,6 +23448,24 @@
|
||||||
"fastq": "^1.6.0"
|
"fastq": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@pinia/testing": {
|
||||||
|
"version": "0.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.0.14.tgz",
|
||||||
|
"integrity": "sha512-ZmZwVNd/NnKYLIfjfuKl0zlJ3UdiXFpsHzSlL6wCeezSlyrqGMxsIQKv0J6fleu38gyCNTPBEipfxrt8V4+VIg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@polka/url": {
|
"@polka/url": {
|
||||||
"version": "1.0.0-next.21",
|
"version": "1.0.0-next.21",
|
||||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
|
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
|
||||||
|
@ -23528,9 +23639,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@quasar/extras": {
|
"@quasar/extras": {
|
||||||
"version": "1.15.5",
|
"version": "1.15.6",
|
||||||
"resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.15.5.tgz",
|
"resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.15.6.tgz",
|
||||||
"integrity": "sha512-JzKKx5/eKAip3X3bZUEJOOWT9NudqjF01gcce6rtyviko49OU4r+ekyJU3QQIKF8ZqnjZ+DpsVpMWBBZnO6hSQ=="
|
"integrity": "sha512-lG3wrcz47c86N/j1ULZugmyVfwpmnsJpjtSWh+LhaFfe0g1kTMdAnxkWGKsa3ouZ4QBcnkrNan0kYSnKc3MiBg=="
|
||||||
},
|
},
|
||||||
"@quasar/fastclick": {
|
"@quasar/fastclick": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
|
@ -24211,9 +24322,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/devtools-api": {
|
"@vue/devtools-api": {
|
||||||
"version": "6.1.3",
|
"version": "6.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
|
||||||
"integrity": "sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg=="
|
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
|
||||||
},
|
},
|
||||||
"@vue/reactivity": {
|
"@vue/reactivity": {
|
||||||
"version": "3.2.31",
|
"version": "3.2.31",
|
||||||
|
@ -32797,6 +32908,23 @@
|
||||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"pinia": {
|
||||||
|
"version": "2.0.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.24.tgz",
|
||||||
|
"integrity": "sha512-DDLd4Iphyc+6PYYYbx7jkb6WP9gecgu9bz9huyB5rb7CdJI3DhzYiZI+/Ih8MLewRrP9DSpslF/BgSNrJtZU7A==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/devtools-api": "^6.4.5",
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"pirates": {
|
"pirates": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
|
||||||
|
@ -33385,9 +33513,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quasar": {
|
"quasar": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.10.2.tgz",
|
||||||
"integrity": "sha512-PHGcrzPQfFa4tv9a5Z/3D2uat48D4WC9Ad/imzHk/k3G41t0eFMH6glCjAvpCWF2q8dBYIg4nEchiPhlujbKsw=="
|
"integrity": "sha512-y6suu0f2hJKrnFPHzx+p2EBVGzDF6xHaqYGkDIsMNkhxsrO9Qi2+dZCjq1J6+48EJiqPEOn8t9X/gT7yLSSnLw=="
|
||||||
},
|
},
|
||||||
"queue-microtask": {
|
"queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
|
@ -35619,7 +35747,7 @@
|
||||||
"version": "4.5.5",
|
"version": "4.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
|
||||||
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
|
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.15.3",
|
"version": "3.15.3",
|
||||||
|
|
|
@ -18,10 +18,11 @@
|
||||||
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
|
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.15.5",
|
"@quasar/extras": "^1.15.6",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"quasar": "^2.10.0",
|
"pinia": "^2.0.24",
|
||||||
|
"quasar": "^2.10.2",
|
||||||
"validator": "^13.7.0",
|
"validator": "^13.7.0",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-i18n": "^9.0.0",
|
"vue-i18n": "^9.0.0",
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.13.14",
|
"@babel/eslint-parser": "^7.13.14",
|
||||||
"@intlify/vue-i18n-loader": "^4.1.0",
|
"@intlify/vue-i18n-loader": "^4.1.0",
|
||||||
|
"@pinia/testing": "^0.0.14",
|
||||||
"@quasar/app-webpack": "^3.6.2",
|
"@quasar/app-webpack": "^3.6.2",
|
||||||
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.2.2",
|
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.2.2",
|
||||||
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.10",
|
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.10",
|
||||||
|
|
|
@ -23,7 +23,7 @@ module.exports = configure(function (ctx) {
|
||||||
// app boot file (/src/boot)
|
// app boot file (/src/boot)
|
||||||
// --> boot files are part of "main.js"
|
// --> boot files are part of "main.js"
|
||||||
// https://v2.quasar.dev/quasar-cli-webpack/boot-files
|
// https://v2.quasar.dev/quasar-cli-webpack/boot-files
|
||||||
boot: ['i18n', 'axios'],
|
boot: ['i18n', 'axios', 'pinia'],
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
|
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
|
||||||
css: ['app.scss'],
|
css: ['app.scss'],
|
||||||
|
@ -38,8 +38,9 @@ module.exports = configure(function (ctx) {
|
||||||
// 'line-awesome',
|
// 'line-awesome',
|
||||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
|
||||||
'roboto-font', // optional, you are not bound to it
|
'roboto-font',
|
||||||
'material-icons', // optional, you are not bound to it
|
'material-icons-outlined',
|
||||||
|
'material-symbols-outlined',
|
||||||
],
|
],
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build
|
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build
|
||||||
|
|
21
src/App.vue
21
src/App.vue
|
@ -1,4 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
@ -8,9 +9,22 @@ import { useSession } from 'src/composables/useSession';
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const { t } = useI18n();
|
const { t, availableLocales, locale, fallbackLocale } = useI18n();
|
||||||
const { isLoggedIn } = session;
|
const { isLoggedIn } = session;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let userLang = window.navigator.language;
|
||||||
|
if (userLang.includes('-')) {
|
||||||
|
userLang = userLang.split('-')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableLocales.includes(userLang)) {
|
||||||
|
locale.value = userLang;
|
||||||
|
} else {
|
||||||
|
locale.value = fallbackLocale;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
quasar.iconMapFn = (iconName) => {
|
quasar.iconMapFn = (iconName) => {
|
||||||
if (iconName.startsWith('vn:')) {
|
if (iconName.startsWith('vn:')) {
|
||||||
const name = iconName.substring(3);
|
const name = iconName.substring(3);
|
||||||
|
@ -19,6 +33,11 @@ quasar.iconMapFn = (iconName) => {
|
||||||
cls: `icon-${name}`,
|
cls: `icon-${name}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cls: 'material-symbols-outlined',
|
||||||
|
content: iconName,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function responseError(error) {
|
function responseError(error) {
|
||||||
|
|
|
@ -11,21 +11,14 @@ const session = useSession();
|
||||||
jest.mock('vue-router', () => ({
|
jest.mock('vue-router', () => ({
|
||||||
useRouter: () => ({
|
useRouter: () => ({
|
||||||
push: mockPush,
|
push: mockPush,
|
||||||
currentRoute: { value: 'myCurrentRoute' }
|
currentRoute: { value: 'myCurrentRoute' },
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('src/composables/useSession', () => ({
|
jest.mock('src/composables/useSession', () => ({
|
||||||
useSession: () => ({
|
useSession: () => ({
|
||||||
isLoggedIn: mockLoggedIn,
|
isLoggedIn: mockLoggedIn,
|
||||||
destroy: mockDestroy
|
destroy: mockDestroy,
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('vue-i18n', () => ({
|
|
||||||
createI18n: () => { },
|
|
||||||
useI18n: () => ({
|
|
||||||
t: () => { }
|
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -34,11 +27,10 @@ describe('App', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const options = {
|
const options = {
|
||||||
global: {
|
global: {
|
||||||
stubs: ['router-view']
|
stubs: ['router-view'],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
vm = createWrapper(App, options).vm;
|
vm = createWrapper(App, options).vm;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a login error message', async () => {
|
it('should return a login error message', async () => {
|
||||||
|
@ -48,17 +40,17 @@ describe('App', () => {
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
response: {
|
response: {
|
||||||
status: 401
|
status: 401,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
|
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
|
||||||
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
{
|
expect.objectContaining({
|
||||||
|
message: 'Invalid username or password',
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'login.loginError'
|
})
|
||||||
}
|
);
|
||||||
));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an unauthorized error message', async () => {
|
it('should return an unauthorized error message', async () => {
|
||||||
|
@ -68,17 +60,17 @@ describe('App', () => {
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
response: {
|
response: {
|
||||||
status: 401
|
status: 401,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
|
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
|
||||||
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
|
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||||
{
|
expect.objectContaining({
|
||||||
|
message: 'Access denied',
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'errors.statusUnauthorized'
|
})
|
||||||
}
|
);
|
||||||
));
|
|
||||||
|
|
||||||
expect(session.destroy).toHaveBeenCalled();
|
expect(session.destroy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,9 +3,11 @@ import { createI18n } from 'vue-i18n';
|
||||||
import messages from 'src/i18n';
|
import messages from 'src/i18n';
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: 'es',
|
locale: 'en',
|
||||||
|
fallbackLocale: 'en',
|
||||||
messages,
|
messages,
|
||||||
legacy: false
|
legacy: false,
|
||||||
|
missingWarn: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { boot } from 'quasar/wrappers';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
|
app.use(pinia);
|
||||||
|
});
|
|
@ -1,17 +1,106 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import axios from 'axios';
|
||||||
|
import { onMounted, ref, computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRole } from 'src/composables/useRole';
|
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useNavigation } from 'src/composables/useNavigation';
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useNavigationStore } from 'src/stores/useNavigationStore';
|
||||||
|
import { toLowerCamel } from 'src/filters';
|
||||||
|
import routes from 'src/router/modules';
|
||||||
|
import LeftMenuItem from './LeftMenuItem.vue';
|
||||||
|
import LeftMenuItemGroup from './LeftMenuItemGroup.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { hasAny } = useRole();
|
const route = useRoute();
|
||||||
const navigation = useNavigation();
|
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
|
const navigation = useNavigationStore();
|
||||||
|
|
||||||
async function onToggleFavoriteModule(moduleName, event) {
|
const props = defineProps({
|
||||||
await navigation.toggleFavorite(moduleName, event);
|
source: {
|
||||||
|
type: String,
|
||||||
|
default: 'main',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
navigation.fetchPinned().then(getRoutes());
|
||||||
|
});
|
||||||
|
|
||||||
|
function findMatches(search, item) {
|
||||||
|
const matches = [];
|
||||||
|
function findRoute(search, item) {
|
||||||
|
for (const child of item.children) {
|
||||||
|
if (search.indexOf(child.name) > -1) {
|
||||||
|
matches.push(child);
|
||||||
|
} else if (child.children) {
|
||||||
|
findRoute(search, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findRoute(search, item);
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addChildren(module, route, parent) {
|
||||||
|
if (route.menus) {
|
||||||
|
const mainMenus = route.menus[props.source];
|
||||||
|
const matches = findMatches(mainMenus, route);
|
||||||
|
|
||||||
|
for (const child of matches) {
|
||||||
|
navigation.addMenuItem(module, child, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinnedItems = computed(() => {
|
||||||
|
return items.value.filter((item) => item.isPinned);
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = ref([]);
|
||||||
|
function getRoutes() {
|
||||||
|
if (props.source === 'main') {
|
||||||
|
const modules = Object.assign([], navigation.getModules().value);
|
||||||
|
|
||||||
|
for (const item of modules) {
|
||||||
|
const moduleDef = routes.find((route) => toLowerCamel(route.name) === item.module);
|
||||||
|
item.children = [];
|
||||||
|
|
||||||
|
if (!moduleDef) continue;
|
||||||
|
|
||||||
|
addChildren(item.module, moduleDef, item.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
items.value = modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.source === 'card') {
|
||||||
|
const currentRoute = route.matched[1];
|
||||||
|
const currentModule = toLowerCamel(currentRoute.name);
|
||||||
|
const moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule);
|
||||||
|
|
||||||
|
if (!moduleDef) return;
|
||||||
|
|
||||||
|
addChildren(currentModule, moduleDef, items.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function togglePinned(item, event) {
|
||||||
|
if (event.defaultPrevented) return;
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const data = { moduleName: item.module };
|
||||||
|
const response = await axios.post('StarredModules/toggleStarredModule', data);
|
||||||
|
|
||||||
|
item.isPinned = false;
|
||||||
|
|
||||||
|
if (response.data && response.data.id) {
|
||||||
|
item.isPinned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigation.togglePinned(item.module);
|
||||||
|
|
||||||
quasar.notify({
|
quasar.notify({
|
||||||
message: t('globals.dataSaved'),
|
message: t('globals.dataSaved'),
|
||||||
|
@ -22,149 +111,89 @@ async function onToggleFavoriteModule(moduleName, event) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-list padding>
|
<q-list padding>
|
||||||
<q-item-label header>{{ t('globals.favoriteModules') }}</q-item-label>
|
<template v-if="$props.source === 'main'">
|
||||||
<template v-for="module in navigation.favorites.value" :key="module.title">
|
<q-item-label header>
|
||||||
<div class="module" v-if="!module.children">
|
{{ t('globals.pinnedModules') }}
|
||||||
<q-item
|
</q-item-label>
|
||||||
clickable
|
<template v-for="item in pinnedItems" :key="item.name">
|
||||||
v-ripple
|
<template v-if="item.children">
|
||||||
active-class="text-primary"
|
<left-menu-item-group :item="item" group="pinnedModules" class="pinned">
|
||||||
:key="module.title"
|
<template #side>
|
||||||
:to="{ name: module.stateName }"
|
<q-btn
|
||||||
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
|
v-if="item.isPinned === true"
|
||||||
|
@click="togglePinned(item, $event)"
|
||||||
|
icon="vn:pin_off"
|
||||||
|
size="xs"
|
||||||
|
flat
|
||||||
|
round
|
||||||
>
|
>
|
||||||
<q-item-section avatar :if="module.icon">
|
<q-tooltip>{{ t('components.leftMenu.removeFromPinned') }}</q-tooltip>
|
||||||
<q-icon :name="module.icon" />
|
</q-btn>
|
||||||
</q-item-section>
|
<q-btn
|
||||||
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
|
v-if="item.isPinned === false"
|
||||||
<q-item-section side>
|
@click="togglePinned(item, $event)"
|
||||||
<div @click="onToggleFavoriteModule(module.name, $event)" class="row items-center">
|
icon="vn:pin"
|
||||||
<q-icon name="vn:pin_off"></q-icon>
|
size="xs"
|
||||||
</div>
|
flat
|
||||||
</q-item-section>
|
round
|
||||||
</q-item>
|
>
|
||||||
</div>
|
<q-tooltip>{{ t('components.leftMenu.addToPinned') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
</left-menu-item-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="module.children">
|
<left-menu-item v-if="!item.children" :item="item" />
|
||||||
<q-expansion-item
|
|
||||||
class="module"
|
|
||||||
active-class="text-primary"
|
|
||||||
:label="t(`${module.name}.pageTitles.${module.title}`)"
|
|
||||||
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
|
|
||||||
:to="{ name: module.stateName }"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon :name="module.icon"></q-icon>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
|
|
||||||
<q-item-section side>
|
|
||||||
<div @click="onToggleFavoriteModule(module.name, $event)" class="row items-center">
|
|
||||||
<q-icon name="vn:pin_off"></q-icon>
|
|
||||||
</div>
|
|
||||||
</q-item-section>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-for="section in module.children" :key="section.title">
|
|
||||||
<q-item
|
|
||||||
clickable
|
|
||||||
v-ripple
|
|
||||||
active-class="text-primary"
|
|
||||||
:to="{ name: section.stateName }"
|
|
||||||
v-if="!section.roles || !section.roles.length || hasAny(section.roles)"
|
|
||||||
>
|
|
||||||
<q-item-section avatar :if="section.icon">
|
|
||||||
<q-icon :name="section.icon" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>{{ t(`${module.name}.pageTitles.${section.title}`) }}</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-expansion-item>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</q-list>
|
|
||||||
|
|
||||||
<q-separator />
|
<q-separator />
|
||||||
|
|
||||||
<q-expansion-item :label="t('moduleIndex.allModules')">
|
<q-expansion-item :label="t('moduleIndex.allModules')">
|
||||||
<q-list padding>
|
<template v-for="item in items" :key="item.name">
|
||||||
<template v-for="module in navigation.modules.value" :key="module.title">
|
<template v-if="item.children">
|
||||||
<div class="module" v-if="!module.children">
|
<left-menu-item-group :item="item" group="modules">
|
||||||
<q-item
|
<template #side>
|
||||||
class="module"
|
<q-btn
|
||||||
clickable
|
v-if="item.isPinned === true"
|
||||||
v-ripple
|
@click="togglePinned(item, $event)"
|
||||||
active-class="text-primary"
|
icon="vn:pin_off"
|
||||||
:key="module.title"
|
size="xs"
|
||||||
:to="{ name: module.stateName }"
|
flat
|
||||||
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
|
round
|
||||||
>
|
>
|
||||||
<q-item-section avatar :if="module.icon">
|
<q-tooltip>{{ t('components.leftMenu.removeFromPinned') }}</q-tooltip>
|
||||||
<q-icon :name="module.icon" />
|
</q-btn>
|
||||||
</q-item-section>
|
<q-btn
|
||||||
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
|
v-if="item.isPinned === false"
|
||||||
<q-item-section side>
|
@click="togglePinned(item, $event)"
|
||||||
<div
|
icon="vn:pin"
|
||||||
@click="onToggleFavoriteModule(module.name, $event)"
|
size="xs"
|
||||||
class="row items-center"
|
flat
|
||||||
v-if="module.name != 'dashboard'"
|
round
|
||||||
>
|
>
|
||||||
<q-icon name="vn:pin"></q-icon>
|
<q-tooltip>{{ t('components.leftMenu.addToPinned') }}</q-tooltip>
|
||||||
</div>
|
</q-btn>
|
||||||
</q-item-section>
|
</template>
|
||||||
</q-item>
|
</left-menu-item-group>
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-if="module.children">
|
|
||||||
<q-expansion-item
|
|
||||||
class="module"
|
|
||||||
active-class="text-primary"
|
|
||||||
:label="t(`${module.name}.pageTitles.${module.title}`)"
|
|
||||||
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
|
|
||||||
:to="{ name: module.stateName }"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon :name="module.icon"></q-icon>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
|
|
||||||
<q-item-section side>
|
|
||||||
<div
|
|
||||||
@click="onToggleFavoriteModule(module.name, $event)"
|
|
||||||
class="row items-center"
|
|
||||||
v-if="module.name != 'dashboard'"
|
|
||||||
>
|
|
||||||
<q-icon name="vn:pin"></q-icon>
|
|
||||||
</div>
|
|
||||||
</q-item-section>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-for="section in module.children" :key="section.title">
|
|
||||||
<q-item
|
|
||||||
clickable
|
|
||||||
v-ripple
|
|
||||||
active-class="text-primary"
|
|
||||||
:to="{ name: section.stateName }"
|
|
||||||
v-if="!section.roles || !section.roles.length || hasAny(section.roles)"
|
|
||||||
>
|
|
||||||
<q-item-section avatar :if="section.icon">
|
|
||||||
<q-icon :name="section.icon" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>{{ t(`${module.name}.pageTitles.${section.title}`) }}</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
</template>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template v-if="$props.source === 'card'">
|
||||||
|
<template v-for="item in items" :key="item.name">
|
||||||
|
<left-menu-item v-if="!item.children" :item="item" />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-expansion-item>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.module .icon-pin,
|
.pinned .icon-pin,
|
||||||
.module .icon-pin_off {
|
.pinned .icon-pin_off {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
.module:hover .icon-pin,
|
|
||||||
.module:hover .icon-pin_off {
|
.pinned:hover .icon-pin,
|
||||||
|
.pinned:hover .icon-pin_off {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = computed(() => props.item);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-item active-class="text-primary" :to="{ name: item.name }" clickable v-ripple>
|
||||||
|
<q-item-section avatar v-if="item.icon">
|
||||||
|
<q-icon :name="item.icon" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar v-if="!item.icon">
|
||||||
|
<q-icon name="disabled_by_default" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t(item.title) }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import LeftMenuItem from './LeftMenuItem.vue';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = computed(() => props.item);
|
||||||
|
const isOpened = computed(() => {
|
||||||
|
const { matched } = route;
|
||||||
|
const { name } = item.value;
|
||||||
|
|
||||||
|
return matched.some((item) => item.name === name);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-expansion-item
|
||||||
|
:group="props.group"
|
||||||
|
active-class="text-primary"
|
||||||
|
:label="item.title"
|
||||||
|
:to="{ name: item.name }"
|
||||||
|
expand-separator
|
||||||
|
:default-opened="isOpened"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon :name="item.icon"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t(item.title) }}</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<slot name="side" :item="item" />
|
||||||
|
</q-item-section>
|
||||||
|
</template>
|
||||||
|
<template v-for="section in item.children" :key="section.name">
|
||||||
|
<left-menu-item :item="section" />
|
||||||
|
</template>
|
||||||
|
</q-expansion-item>
|
||||||
|
</template>
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
import UserPanel from 'components/UserPanel.vue';
|
import UserPanel from 'components/UserPanel.vue';
|
||||||
import FavoriteModules from './FavoriteModules.vue';
|
import PinnedModules from './PinnedModules.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
|
@ -43,11 +43,11 @@ function onToggleDrawer() {
|
||||||
<q-space></q-space>
|
<q-space></q-space>
|
||||||
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
|
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
|
||||||
<div id="header-actions"></div>
|
<div id="header-actions"></div>
|
||||||
<q-btn id="favoriteModules" icon="apps" flat dense rounded>
|
<q-btn id="pinnedModules" icon="apps" flat dense rounded>
|
||||||
<q-tooltip bottom>
|
<q-tooltip bottom>
|
||||||
{{ t('globals.favoriteModules') }}
|
{{ t('globals.pinnedModules') }}
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
<FavoriteModules />
|
<PinnedModules />
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn rounded dense flat no-wrap id="user">
|
<q-btn rounded dense flat no-wrap id="user">
|
||||||
<q-avatar size="lg">
|
<q-avatar size="lg">
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted } from 'vue';
|
import { onMounted, computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useNavigation } from 'src/composables/useNavigation';
|
import { useNavigationStore } from 'src/stores/useNavigationStore';
|
||||||
|
|
||||||
|
const navigation = useNavigationStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const navigation = useNavigation();
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
navigation.fetchFavorites();
|
navigation.fetchPinned();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pinnedModules = computed(() => navigation.getPinnedModules());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -17,22 +19,22 @@ onMounted(() => {
|
||||||
class="row q-pa-md q-col-gutter-lg"
|
class="row q-pa-md q-col-gutter-lg"
|
||||||
max-width="350px"
|
max-width="350px"
|
||||||
max-height="400px"
|
max-height="400px"
|
||||||
v-if="navigation.favorites.value.length"
|
v-if="pinnedModules.length"
|
||||||
>
|
>
|
||||||
<div v-for="module of navigation.favorites.value" :key="module.title" class="row no-wrap q-pa-xs flex-item">
|
<div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item">
|
||||||
<q-btn
|
<q-btn
|
||||||
align="evenly"
|
align="evenly"
|
||||||
padding="16px"
|
padding="16px"
|
||||||
flat
|
flat
|
||||||
stack
|
stack
|
||||||
size="lg"
|
size="lg"
|
||||||
:icon="module.icon"
|
:icon="item.icon"
|
||||||
color="primary"
|
color="primary"
|
||||||
class="col-4 button"
|
class="col-4 button"
|
||||||
:to="{ name: module.stateName }"
|
:to="{ name: item.name }"
|
||||||
>
|
>
|
||||||
<div class="text-center text-primary button-text">
|
<div class="text-center text-primary button-text">
|
||||||
{{ t(`${module.name}.pageTitles.${module.title}`) }}
|
{{ t(item.title) }}
|
||||||
</div>
|
</div>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
|
@ -45,7 +45,7 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function updatePreferences() {
|
function updatePreferences() {
|
||||||
if (user.value.darkMode) {
|
if (user.value.darkMode !== null) {
|
||||||
darkMode.value = user.value.darkMode;
|
darkMode.value = user.value.darkMode;
|
||||||
}
|
}
|
||||||
if (user.value.lang) {
|
if (user.value.lang) {
|
||||||
|
|
|
@ -2,31 +2,32 @@ import { jest, describe, expect, it, beforeAll } from '@jest/globals';
|
||||||
import { createWrapper } from 'app/tests/jest/jestHelpers';
|
import { createWrapper } from 'app/tests/jest/jestHelpers';
|
||||||
import Leftmenu from '../LeftMenu.vue';
|
import Leftmenu from '../LeftMenu.vue';
|
||||||
|
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { useNavigationStore } from 'src/stores/useNavigationStore';
|
||||||
|
|
||||||
const mockPush = jest.fn();
|
const mockPush = jest.fn();
|
||||||
|
|
||||||
jest.mock('vue-router', () => ({
|
jest.mock('vue-router', () => ({
|
||||||
useRouter: () => ({
|
useRouter: () => ({
|
||||||
push: mockPush,
|
push: mockPush,
|
||||||
currentRoute: { value: 'myCurrentRoute' }
|
currentRoute: { value: 'myCurrentRoute' },
|
||||||
|
}),
|
||||||
|
useRoute: () => ({
|
||||||
|
matched: [],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('src/router/routes', () => ([
|
jest.mock('src/router/modules', () => [
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'Main',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/dashboard',
|
|
||||||
name: 'Dashboard',
|
|
||||||
meta: { title: 'dashboard', icon: 'dashboard' }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/customer',
|
path: '/customer',
|
||||||
name: 'Customer',
|
name: 'Customer',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'customers',
|
title: 'customers',
|
||||||
icon: 'vn:client'
|
icon: 'vn:client',
|
||||||
|
},
|
||||||
|
menus: {
|
||||||
|
main: ['CustomerList', 'CustomerCreate'],
|
||||||
|
card: ['CustomerBasicData'],
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -39,7 +40,7 @@ jest.mock('src/router/routes', () => ([
|
||||||
meta: {
|
meta: {
|
||||||
title: 'list',
|
title: 'list',
|
||||||
icon: 'view_list',
|
icon: 'view_list',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'create',
|
path: 'create',
|
||||||
|
@ -47,52 +48,57 @@ jest.mock('src/router/routes', () => ([
|
||||||
meta: {
|
meta: {
|
||||||
title: 'createCustomer',
|
title: 'createCustomer',
|
||||||
icon: 'vn:addperson',
|
icon: 'vn:addperson',
|
||||||
}
|
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]));
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
describe('Leftmenu', () => {
|
describe('Leftmenu', () => {
|
||||||
let vm;
|
let vm;
|
||||||
beforeAll(() => {
|
let navigation;
|
||||||
vm = createWrapper(Leftmenu).vm;
|
beforeAll(async () => {
|
||||||
|
vm = createWrapper(Leftmenu, {
|
||||||
|
propsData: {
|
||||||
|
source: 'main',
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
plugins: [createTestingPinia({ stubActions: false })],
|
||||||
|
},
|
||||||
|
}).vm;
|
||||||
|
|
||||||
|
navigation = useNavigationStore();
|
||||||
|
navigation.modules = ['customer']; // I should mock to have just one module but isn´t working
|
||||||
|
navigation.fetchPinned = jest.fn().mockReturnValue(Promise.resolve(true));
|
||||||
|
navigation.getModules = jest.fn().mockReturnValue({
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
name: 'customer',
|
||||||
|
title: 'customer.pageTitles.customers',
|
||||||
|
icon: 'vn:customer',
|
||||||
|
module: 'customer',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the proper formated object without the children property', async () => {
|
|
||||||
const expectedMenuItem = {
|
|
||||||
stateName: 'Dashboard',
|
|
||||||
name: 'dashboard',
|
|
||||||
roles: [],
|
|
||||||
icon: 'dashboard',
|
|
||||||
title: 'dashboard'
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstMenuItem = vm.navigation.modules.value[0];
|
|
||||||
expect(firstMenuItem.children).toBeUndefined();
|
|
||||||
expect(firstMenuItem).toEqual(expect.objectContaining(expectedMenuItem));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a proper formated object with two child items', async () => {
|
it('should return a proper formated object with two child items', async () => {
|
||||||
const expectedMenuItem = [{
|
const expectedMenuItem = [
|
||||||
|
{
|
||||||
name: 'CustomerList',
|
name: 'CustomerList',
|
||||||
title: 'list',
|
title: 'customer.pageTitles.list',
|
||||||
icon: 'view_list',
|
icon: 'view_list',
|
||||||
stateName: 'CustomerList'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'CustomerCreate',
|
name: 'CustomerCreate',
|
||||||
title: 'createCustomer',
|
title: 'customer.pageTitles.createCustomer',
|
||||||
icon: 'vn:addperson',
|
icon: 'vn:addperson',
|
||||||
stateName: 'CustomerCreate'
|
},
|
||||||
}];
|
];
|
||||||
|
|
||||||
const secondMenuItem = vm.navigation.modules.value[1];
|
const firstMenuItem = vm.items[0];
|
||||||
expect(secondMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
|
expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
|
||||||
expect(secondMenuItem.children.length).toEqual(2)
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
import routes from 'src/router/routes';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const favorites = ref([]);
|
|
||||||
const modules = ref([]);
|
|
||||||
|
|
||||||
const mainRoute = routes.find((route) => route.path === '/');
|
|
||||||
const moduleRoutes = (mainRoute && mainRoute.children) || [];
|
|
||||||
|
|
||||||
for (const route of moduleRoutes) {
|
|
||||||
const module = {
|
|
||||||
stateName: route.name,
|
|
||||||
name: route.name.toLowerCase(),
|
|
||||||
roles: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (route.meta) {
|
|
||||||
Object.assign(module, route.meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.children && route.children.length) {
|
|
||||||
const [moduleMain] = route.children;
|
|
||||||
const routes = moduleMain.children;
|
|
||||||
|
|
||||||
module.children = routes.map((route) => {
|
|
||||||
const submodule = {
|
|
||||||
stateName: route.name,
|
|
||||||
name: route.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(submodule, route.meta);
|
|
||||||
|
|
||||||
return submodule;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
modules.value.push(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useNavigation() {
|
|
||||||
const salixModules = {
|
|
||||||
customer: 'Clients',
|
|
||||||
claim: 'Claims',
|
|
||||||
entry: 'Entries',
|
|
||||||
invoiceIn: 'Invoices In',
|
|
||||||
invoiceOut: 'Invoices Out',
|
|
||||||
item: 'Items',
|
|
||||||
monitor: 'Monitors',
|
|
||||||
order: 'Orders',
|
|
||||||
route: 'Routes',
|
|
||||||
supplier: 'Suppliers',
|
|
||||||
ticket: 'Tickets',
|
|
||||||
travel: 'Travels',
|
|
||||||
user: 'Users',
|
|
||||||
worker: 'Workers',
|
|
||||||
zone: 'Zones',
|
|
||||||
};
|
|
||||||
|
|
||||||
async function fetchFavorites() {
|
|
||||||
const response = await axios.get('StarredModules/getStarredModules');
|
|
||||||
|
|
||||||
const filteredModules = modules.value.filter((module) => {
|
|
||||||
return response.data.find((element) => element.moduleFk == salixModules[module.name]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (favorites.value = filteredModules);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toggleFavorite(moduleName, event) {
|
|
||||||
if (event.defaultPrevented) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const params = { moduleName: salixModules[moduleName] };
|
|
||||||
const query = 'StarredModules/toggleStarredModule';
|
|
||||||
await axios.post(query, params);
|
|
||||||
|
|
||||||
updateFavorites(moduleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFavorites(name) {
|
|
||||||
if (!favorites.value.find((module) => module.name == name)) {
|
|
||||||
const newStarreModule = modules.value.find((module) => module.name == name);
|
|
||||||
favorites.value.push(newStarreModule);
|
|
||||||
} else {
|
|
||||||
const moduleToRemove = favorites.value.find((module) => module.name == name);
|
|
||||||
favorites.value.splice(favorites.value.indexOf(moduleToRemove), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { modules, favorites, toggleFavorite, fetchFavorites, updateFavorites };
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@ export function useRole() {
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const { data } = await axios.get('Accounts/acl');
|
const { data } = await axios.get('Accounts/acl');
|
||||||
const roles = data.roles.map(userRoles => userRoles.role.name);
|
const roles = data.roles.map((userRoles) => userRoles.role.name);
|
||||||
|
|
||||||
const userData = {
|
const userData = {
|
||||||
id: data.user.id,
|
id: data.user.id,
|
||||||
|
@ -14,7 +14,7 @@ export function useRole() {
|
||||||
nickname: data.user.nickname,
|
nickname: data.user.nickname,
|
||||||
lang: data.user.lang || 'es',
|
lang: data.user.lang || 'es',
|
||||||
darkMode: data.user.userConfig.darkMode,
|
darkMode: data.user.userConfig.darkMode,
|
||||||
}
|
};
|
||||||
state.setUser(userData);
|
state.setUser(userData);
|
||||||
state.setRoles(roles);
|
state.setRoles(roles);
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,6 @@ export function useRole() {
|
||||||
return {
|
return {
|
||||||
fetch,
|
fetch,
|
||||||
hasAny,
|
hasAny,
|
||||||
state
|
state,
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -2,10 +2,12 @@ import toLowerCase from './toLowerCase';
|
||||||
import toDate from './toDate';
|
import toDate from './toDate';
|
||||||
import toCurrency from './toCurrency';
|
import toCurrency from './toCurrency';
|
||||||
import toPercentage from './toPercentage';
|
import toPercentage from './toPercentage';
|
||||||
|
import toLowerCamel from './toLowerCamel';
|
||||||
import dashIfEmpty from './dashIfEmpty';
|
import dashIfEmpty from './dashIfEmpty';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
toLowerCase,
|
toLowerCase,
|
||||||
|
toLowerCamel,
|
||||||
toDate,
|
toDate,
|
||||||
toCurrency,
|
toCurrency,
|
||||||
toPercentage,
|
toPercentage,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default function toLowerCamel(value) {
|
||||||
|
if (!value) return;
|
||||||
|
if (typeof (value) !== 'string') return value;
|
||||||
|
return value.charAt(0).toLowerCase() + value.slice(1);
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ export default {
|
||||||
backToDashboard: 'Return to dashboard',
|
backToDashboard: 'Return to dashboard',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
userPanel: 'User panel',
|
userPanel: 'User panel',
|
||||||
favoriteModules: 'Favorite modules',
|
pinnedModules: 'Pinned modules',
|
||||||
darkMode: 'Dark mode',
|
darkMode: 'Dark mode',
|
||||||
logOut: 'Log out',
|
logOut: 'Log out',
|
||||||
dataSaved: 'Data saved',
|
dataSaved: 'Data saved',
|
||||||
|
@ -355,5 +355,9 @@ export default {
|
||||||
summary: 'Summary',
|
summary: 'Summary',
|
||||||
moreOptions: 'More options',
|
moreOptions: 'More options',
|
||||||
},
|
},
|
||||||
|
leftMenu: {
|
||||||
|
addToPinned: 'Add to pinned',
|
||||||
|
removeFromPinned: 'Remove from pinned',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default {
|
||||||
backToDashboard: 'Volver al tablón',
|
backToDashboard: 'Volver al tablón',
|
||||||
notifications: 'Notificaciones',
|
notifications: 'Notificaciones',
|
||||||
userPanel: 'Panel de usuario',
|
userPanel: 'Panel de usuario',
|
||||||
favoriteModules: 'Módulos favoritos',
|
pinnedModules: 'Módulos fijados',
|
||||||
darkMode: 'Modo oscuro',
|
darkMode: 'Modo oscuro',
|
||||||
logOut: 'Cerrar sesión',
|
logOut: 'Cerrar sesión',
|
||||||
dataSaved: 'Datos guardados',
|
dataSaved: 'Datos guardados',
|
||||||
|
@ -354,5 +354,9 @@ export default {
|
||||||
summary: 'Resumen',
|
summary: 'Resumen',
|
||||||
moreOptions: 'Más opciones',
|
moreOptions: 'Más opciones',
|
||||||
},
|
},
|
||||||
|
leftMenu: {
|
||||||
|
addToPinned: 'Añadir a fijados',
|
||||||
|
removeFromPinned: 'Eliminar de fijados',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useState } from 'composables/useState';
|
import { useState } from 'composables/useState';
|
||||||
import ClaimDescriptor from './ClaimDescriptor.vue';
|
import ClaimDescriptor from './ClaimDescriptor.vue';
|
||||||
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -11,20 +10,7 @@ const state = useState();
|
||||||
<q-scroll-area class="fit">
|
<q-scroll-area class="fit">
|
||||||
<claim-descriptor />
|
<claim-descriptor />
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-list>
|
<left-menu source="card" />
|
||||||
<q-item :to="{ name: 'ClaimBasicData' }" clickable v-ripple>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="vn:settings" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>{{ t('claim.pageTitles.basicData') }}</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item :to="{ name: 'ClaimRma' }" clickable v-ripple>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="vn:barcode" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>{{ t('claim.pageTitles.rma') }}</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
|
|
|
@ -1,40 +1,16 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import CustomerDescriptor from './CustomerDescriptor.vue';
|
import CustomerDescriptor from './CustomerDescriptor.vue';
|
||||||
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const { t } = useI18n();
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
||||||
<q-scroll-area class="fit">
|
<q-scroll-area class="fit">
|
||||||
<customer-descriptor />
|
<customer-descriptor />
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-list>
|
<left-menu source="card" />
|
||||||
<q-item :to="{ name: 'CustomerBasicData' }" clickable v-ripple>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="vn:settings" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>{{ t('customer.pageTitles.basicData') }}</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<!-- <q-item clickable v-ripple>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="notes" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>Notes</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-expansion-item icon="more" label="More options" expand-icon-toggle expand-separator>
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable v-ripple>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="person" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>Option</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-expansion-item> -->
|
|
||||||
</q-list>
|
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted, computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import LeftMenu from 'components/LeftMenu.vue';
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
import { useNavigation } from 'composables/useNavigation';
|
import { useNavigationStore } from 'src/stores/useNavigationStore';
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const modules = useNavigation();
|
const navigation = useNavigationStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
navigation.fetchPinned();
|
||||||
|
});
|
||||||
|
|
||||||
|
const pinnedModules = computed(() => navigation.getPinnedModules());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -17,35 +24,24 @@ const modules = useNavigation();
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
<q-page class="q-pa-md">
|
<q-page class="q-pa-md">
|
||||||
<!-- <q-banner v-if="$q.screen.gt.xs" inline-actions rounded class="bg-orange text-white q-mb-lg">
|
|
||||||
Employee notification message
|
|
||||||
<template #action>
|
|
||||||
<q-btn flat label="Dismiss" />
|
|
||||||
</template>
|
|
||||||
</q-banner> -->
|
|
||||||
|
|
||||||
<div class="row items-start wrap q-col-gutter-md q-mb-lg">
|
<div class="row items-start wrap q-col-gutter-md q-mb-lg">
|
||||||
<div class="col-12 col-md" v-if="modules.favorites.value.length">
|
<div class="col-12 col-md" v-if="pinnedModules.length">
|
||||||
<div class="text-h6 text-grey-8 q-mb-sm">{{ t('globals.favoriteModules') }}</div>
|
<div class="text-h6 text-grey-8 q-mb-sm">{{ t('globals.pinnedModules') }}</div>
|
||||||
<q-card class="row flex-container">
|
<q-card class="row flex-container">
|
||||||
<div
|
<div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item">
|
||||||
v-for="module of modules.favorites.value"
|
|
||||||
:key="module.title"
|
|
||||||
class="row no-wrap q-pa-xs flex-item"
|
|
||||||
>
|
|
||||||
<q-btn
|
<q-btn
|
||||||
align="evenly"
|
align="evenly"
|
||||||
padding="16px"
|
padding="16px"
|
||||||
flat
|
flat
|
||||||
stack
|
stack
|
||||||
size="lg"
|
size="lg"
|
||||||
:icon="module.icon"
|
:icon="item.icon"
|
||||||
color="orange-6"
|
color="orange-6"
|
||||||
class="col-4 button"
|
class="col-4 button"
|
||||||
:to="{ name: module.stateName }"
|
:to="{ name: item.name }"
|
||||||
>
|
>
|
||||||
<div class="text-center text-primary button-text">
|
<div class="text-center text-primary button-text">
|
||||||
{{ t(`${module.name}.pageTitles.${module.title}`) }}
|
{{ t(item.title) }}
|
||||||
</div>
|
</div>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,7 +29,6 @@ function fetch() {
|
||||||
axios.get(`InvoiceOuts/${id}/summary`).then(({ data }) => {
|
axios.get(`InvoiceOuts/${id}/summary`).then(({ data }) => {
|
||||||
invoiceOut.value = data.invoiceOut;
|
invoiceOut.value = data.invoiceOut;
|
||||||
tax.value = data.invoiceOut.taxesBreakdown;
|
tax.value = data.invoiceOut.taxesBreakdown;
|
||||||
console.log('tax', tax);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
axios.get(`InvoiceOuts/${id}/getTickets`).then(({ data }) => {
|
axios.get(`InvoiceOuts/${id}/getTickets`).then(({ data }) => {
|
||||||
|
|
|
@ -19,3 +19,11 @@ const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||||
<invoiceOut-summary v-if="$props.id" :id="$props.id" />
|
<invoiceOut-summary v-if="$props.id" :id="$props.id" />
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.q-dialog .summary .header {
|
||||||
|
position: sticky;
|
||||||
|
z-index: $z-max;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,24 +1,16 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import TicketDescriptor from './TicketDescriptor.vue';
|
import TicketDescriptor from './TicketDescriptor.vue';
|
||||||
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const { t } = useI18n();
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
||||||
<q-scroll-area class="fit">
|
<q-scroll-area class="fit">
|
||||||
<ticket-descriptor />
|
<ticket-descriptor />
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-list>
|
<left-menu source="card" />
|
||||||
<q-item :to="{ name: 'TicketBoxing' }" clickable v-ripple>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="vn:package" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>{{ t('ticket.pageTitles.boxing') }}</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { route } from 'quasar/wrappers';
|
import { route } from 'quasar/wrappers';
|
||||||
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router';
|
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router';
|
||||||
// import { Notify } from 'quasar';
|
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { i18n } from 'src/boot/i18n';
|
import { i18n } from 'src/boot/i18n';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
import { useRole } from 'src/composables/useRole';
|
import { useRole } from 'src/composables/useRole';
|
||||||
|
import { toLowerCamel } from 'src/filters';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
|
@ -46,26 +46,14 @@ export default route(function (/* { store, ssrContext } */) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn()) {
|
if (isLoggedIn()) {
|
||||||
// try {
|
|
||||||
const stateRoles = state.getRoles().value;
|
const stateRoles = state.getRoles().value;
|
||||||
if (stateRoles.length === 0) {
|
if (stateRoles.length === 0) {
|
||||||
await role.fetch();
|
await role.fetch();
|
||||||
}
|
}
|
||||||
// } catch (error) {
|
|
||||||
// Notify.create({
|
|
||||||
// message: t('errors.statusUnauthorized'),
|
|
||||||
// type: 'negative',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// session.destroy();
|
|
||||||
// return next({ path: '/login' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
const matches = to.matched;
|
const matches = to.matched;
|
||||||
const hasRequiredRoles = matches.every(route => {
|
const hasRequiredRoles = matches.every((route) => {
|
||||||
const meta = route.meta;
|
const meta = route.meta;
|
||||||
if (meta && meta.roles)
|
if (meta && meta.roles) return role.hasAny(meta.roles);
|
||||||
return role.hasAny(meta.roles)
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -85,7 +73,7 @@ export default route(function (/* { store, ssrContext } */) {
|
||||||
if (matches && matches.length > 1) {
|
if (matches && matches.length > 1) {
|
||||||
const module = matches[1];
|
const module = matches[1];
|
||||||
const moduleTitle = module.meta && module.meta.title;
|
const moduleTitle = module.meta && module.meta.title;
|
||||||
moduleName = module.name.toLowerCase();
|
moduleName = toLowerCamel(module.name);
|
||||||
if (moduleTitle) {
|
if (moduleTitle) {
|
||||||
title = t(`${moduleName}.pageTitles.${moduleTitle}`);
|
title = t(`${moduleName}.pageTitles.${moduleTitle}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,14 @@ export default {
|
||||||
path: '/claim',
|
path: '/claim',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'claims',
|
title: 'claims',
|
||||||
icon: 'vn:claims'
|
icon: 'vn:claims',
|
||||||
},
|
},
|
||||||
component: RouterView,
|
component: RouterView,
|
||||||
redirect: { name: 'ClaimMain' },
|
redirect: { name: 'ClaimMain' },
|
||||||
|
menus: {
|
||||||
|
main: ['ClaimList', 'ClaimRmaList'],
|
||||||
|
card: ['ClaimBasicData', 'ClaimRma'],
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'ClaimMain',
|
name: 'ClaimMain',
|
||||||
|
@ -31,11 +35,11 @@ export default {
|
||||||
meta: {
|
meta: {
|
||||||
title: 'rmaList',
|
title: 'rmaList',
|
||||||
icon: 'vn:barcode',
|
icon: 'vn:barcode',
|
||||||
roles: ['claimManager']
|
roles: ['claimManager'],
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Claim/ClaimRmaList.vue'),
|
component: () => import('src/pages/Claim/ClaimRmaList.vue'),
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ClaimCard',
|
name: 'ClaimCard',
|
||||||
|
@ -47,7 +51,8 @@ export default {
|
||||||
name: 'ClaimSummary',
|
name: 'ClaimSummary',
|
||||||
path: 'summary',
|
path: 'summary',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'summary'
|
title: 'summary',
|
||||||
|
icon: 'launch',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Claim/Card/ClaimSummary.vue'),
|
component: () => import('src/pages/Claim/Card/ClaimSummary.vue'),
|
||||||
},
|
},
|
||||||
|
@ -56,7 +61,8 @@ export default {
|
||||||
path: 'basic-data',
|
path: 'basic-data',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'basicData',
|
title: 'basicData',
|
||||||
roles: ['salesPerson']
|
icon: 'vn:settings',
|
||||||
|
roles: ['salesPerson'],
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'),
|
component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'),
|
||||||
},
|
},
|
||||||
|
@ -65,11 +71,12 @@ export default {
|
||||||
path: 'rma',
|
path: 'rma',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'rma',
|
title: 'rma',
|
||||||
roles: ['claimManager']
|
icon: 'vn:barcode',
|
||||||
|
roles: ['claimManager'],
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Claim/Card/ClaimRma.vue')
|
component: () => import('src/pages/Claim/Card/ClaimRma.vue'),
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
|
@ -5,10 +5,14 @@ export default {
|
||||||
name: 'Customer',
|
name: 'Customer',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'customers',
|
title: 'customers',
|
||||||
icon: 'vn:client'
|
icon: 'vn:client',
|
||||||
},
|
},
|
||||||
component: RouterView,
|
component: RouterView,
|
||||||
redirect: { name: 'CustomerMain' },
|
redirect: { name: 'CustomerMain' },
|
||||||
|
menus: {
|
||||||
|
main: ['CustomerList', 'CustomerCreate'],
|
||||||
|
card: ['CustomerBasicData'],
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
@ -35,7 +39,7 @@ export default {
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Customer/CustomerCreate.vue'),
|
component: () => import('src/pages/Customer/CustomerCreate.vue'),
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'CustomerCard',
|
name: 'CustomerCard',
|
||||||
|
@ -47,7 +51,8 @@ export default {
|
||||||
name: 'CustomerSummary',
|
name: 'CustomerSummary',
|
||||||
path: 'summary',
|
path: 'summary',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'summary'
|
title: 'summary',
|
||||||
|
icon: 'launch',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Customer/Card/CustomerSummary.vue'),
|
component: () => import('src/pages/Customer/Card/CustomerSummary.vue'),
|
||||||
},
|
},
|
||||||
|
@ -55,11 +60,12 @@ export default {
|
||||||
path: 'basic-data',
|
path: 'basic-data',
|
||||||
name: 'CustomerBasicData',
|
name: 'CustomerBasicData',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'basicData'
|
title: 'basicData',
|
||||||
|
icon: 'vn:settings',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Customer/Card/CustomerBasicData.vue'),
|
component: () => import('src/pages/Customer/Card/CustomerBasicData.vue'),
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Customer from './customer';
|
||||||
|
import Ticket from './ticket';
|
||||||
|
import Claim from './claim';
|
||||||
|
import InvoiceOut from './invoiceOut';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
Customer,
|
||||||
|
Ticket,
|
||||||
|
Claim,
|
||||||
|
InvoiceOut
|
||||||
|
]
|
|
@ -9,6 +9,9 @@ export default {
|
||||||
},
|
},
|
||||||
component: RouterView,
|
component: RouterView,
|
||||||
redirect: { name: 'InvoiceOutMain' },
|
redirect: { name: 'InvoiceOutMain' },
|
||||||
|
menus: {
|
||||||
|
main: ['InvoiceOutList']
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|
|
@ -5,10 +5,14 @@ export default {
|
||||||
path: '/ticket',
|
path: '/ticket',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'tickets',
|
title: 'tickets',
|
||||||
icon: 'vn:ticket'
|
icon: 'vn:ticket',
|
||||||
},
|
},
|
||||||
component: RouterView,
|
component: RouterView,
|
||||||
redirect: { name: 'TicketMain' },
|
redirect: { name: 'TicketMain' },
|
||||||
|
menus: {
|
||||||
|
main: ['TicketList'],
|
||||||
|
card: ['TicketBoxing'],
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'TicketMain',
|
name: 'TicketMain',
|
||||||
|
@ -35,8 +39,7 @@ export default {
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Ticket/TicketList.vue'),
|
component: () => import('src/pages/Ticket/TicketList.vue'),
|
||||||
},
|
},
|
||||||
|
],
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'TicketCard',
|
name: 'TicketCard',
|
||||||
|
@ -48,7 +51,8 @@ export default {
|
||||||
name: 'TicketSummary',
|
name: 'TicketSummary',
|
||||||
path: 'summary',
|
path: 'summary',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'summary'
|
title: 'summary',
|
||||||
|
icon: 'launch',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Ticket/Card/TicketSummary.vue'),
|
component: () => import('src/pages/Ticket/Card/TicketSummary.vue'),
|
||||||
},
|
},
|
||||||
|
@ -56,7 +60,8 @@ export default {
|
||||||
name: 'TicketBasicData',
|
name: 'TicketBasicData',
|
||||||
path: 'basic-data',
|
path: 'basic-data',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'basicData'
|
title: 'basicData',
|
||||||
|
icon: 'vn:settings',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Ticket/Card/TicketBasicData.vue'),
|
component: () => import('src/pages/Ticket/Card/TicketBasicData.vue'),
|
||||||
},
|
},
|
||||||
|
@ -64,11 +69,12 @@ export default {
|
||||||
path: 'boxing',
|
path: 'boxing',
|
||||||
name: 'TicketBoxing',
|
name: 'TicketBoxing',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'boxing'
|
title: 'boxing',
|
||||||
|
icon: 'vn:package',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'),
|
component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'),
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { toLowerCamel } from 'src/filters';
|
||||||
|
import { useRole } from 'src/composables/useRole';
|
||||||
|
import routes from 'src/router/modules';
|
||||||
|
|
||||||
|
export const useNavigationStore = defineStore('navigationStore', () => {
|
||||||
|
const modules = ['customer', 'claim', 'ticket', 'invoiceOut'];
|
||||||
|
const pinnedModules = ref([]);
|
||||||
|
const role = useRole();
|
||||||
|
|
||||||
|
function getModules() {
|
||||||
|
const modulesRoutes = ref([]);
|
||||||
|
for (const module of modules) {
|
||||||
|
const moduleDef = routes.find((route) => toLowerCamel(route.name) === module);
|
||||||
|
if (!moduleDef) continue;
|
||||||
|
|
||||||
|
const item = addMenuItem(module, moduleDef, modulesRoutes.value);
|
||||||
|
if (!item) continue;
|
||||||
|
|
||||||
|
item.module = module;
|
||||||
|
item.isPinned = false;
|
||||||
|
|
||||||
|
if (pinnedModules.value.includes(module)) {
|
||||||
|
item.isPinned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modulesRoutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPinnedModules() {
|
||||||
|
const modules = getModules();
|
||||||
|
|
||||||
|
return modules && modules.value.filter((item) => item.isPinned);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMenuItem(module, route, parent) {
|
||||||
|
const { meta } = route;
|
||||||
|
|
||||||
|
if (meta && meta.roles && role.hasAny(meta.roles) === false) return;
|
||||||
|
|
||||||
|
const item = {
|
||||||
|
name: route.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
item.title = `${module}.pageTitles.${meta.title}`;
|
||||||
|
item.icon = meta.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.push(item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPinned() {
|
||||||
|
if (pinnedModules.value.length) return;
|
||||||
|
|
||||||
|
const response = await axios.get('StarredModules/getStarredModules');
|
||||||
|
pinnedModules.value = response.data.map((row) => row.moduleFk);
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePinned(module) {
|
||||||
|
if (pinnedModules.value.includes(module)) {
|
||||||
|
const index = pinnedModules.value.indexOf(module);
|
||||||
|
pinnedModules.value.splice(index, 1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pinnedModules.value.push(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
modules,
|
||||||
|
pinnedModules,
|
||||||
|
getModules,
|
||||||
|
getPinnedModules,
|
||||||
|
fetchPinned,
|
||||||
|
togglePinned,
|
||||||
|
addMenuItem,
|
||||||
|
};
|
||||||
|
});
|
|
@ -1,25 +1,27 @@
|
||||||
import { mount, flushPromises } from '@vue/test-utils';
|
import { mount, flushPromises } from '@vue/test-utils';
|
||||||
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest';
|
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest';
|
||||||
import { i18n } from 'src/boot/i18n';
|
import { i18n } from 'src/boot/i18n';
|
||||||
import { Notify } from 'quasar';
|
import { Notify, Dialog } from 'quasar';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
// Specify here Quasar config you'll need to test your component
|
|
||||||
installQuasarPlugin({
|
installQuasarPlugin({
|
||||||
plugins: {
|
plugins: {
|
||||||
Notify
|
Notify,
|
||||||
}
|
Dialog,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createWrapper(component, options) {
|
export function createWrapper(component, options) {
|
||||||
const mountOptions = {
|
const mountOptions = {};
|
||||||
global: {
|
|
||||||
plugins: [i18n]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options instanceof Object)
|
if (options instanceof Object) Object.assign(mountOptions, options);
|
||||||
Object.assign(mountOptions, options)
|
|
||||||
|
if (mountOptions.global && mountOptions.global.plugins) {
|
||||||
|
mountOptions.global.plugins.push(i18n);
|
||||||
|
} else {
|
||||||
|
if (!mountOptions.global) mountOptions.global = {};
|
||||||
|
mountOptions.global.plugins = [i18n];
|
||||||
|
}
|
||||||
|
|
||||||
const wrapper = mount(component, mountOptions);
|
const wrapper = mount(component, mountOptions);
|
||||||
const vm = wrapper.vm;
|
const vm = wrapper.vm;
|
||||||
|
@ -27,7 +29,4 @@ export function createWrapper(component, options) {
|
||||||
return { vm, wrapper };
|
return { vm, wrapper };
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { axios, flushPromises };
|
||||||
axios,
|
|
||||||
flushPromises
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue