diff --git a/.gitignore b/.gitignore
index 553e1345c..889ff5b2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
.DS_Store
.thumbs.db
node_modules
+junit.xml
# Quasar core related directories
.quasar
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index a870d0f04..5c8908497 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -3,7 +3,7 @@
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
- "johnsoncodehk.volar",
+ "Vue.volar",
"wayou.vscode-todo-highlight"
],
"unwantedRecommendations": [
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 59aa4abd3..ecc1d50d7 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,7 +2,7 @@
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
- "editor.defaultFormatter": "johnsoncodehk.volar",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"json.schemas": [
@@ -11,9 +11,6 @@
"url": "https://on.cypress.io/cypress.schema.json"
}
],
- "[javascript]": {
- "editor.defaultFormatter": "vscode.typescript-language-features"
- },
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
diff --git a/package-lock.json b/package-lock.json
index 1af3be8e6..4b651824f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,10 +8,11 @@
"name": "salix-front",
"version": "0.0.1",
"dependencies": {
- "@quasar/extras": "^1.15.5",
+ "@quasar/extras": "^1.15.6",
"axios": "^0.21.1",
"core-js": "^3.6.5",
- "quasar": "^2.10.0",
+ "pinia": "^2.0.24",
+ "quasar": "^2.10.2",
"validator": "^13.7.0",
"vue": "^3.0.0",
"vue-i18n": "^9.0.0",
@@ -20,6 +21,7 @@
"devDependencies": {
"@babel/eslint-parser": "^7.13.14",
"@intlify/vue-i18n-loader": "^4.1.0",
+ "@pinia/testing": "^0.0.14",
"@quasar/app-webpack": "^3.6.2",
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.2.2",
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.10",
@@ -3271,6 +3273,47 @@
"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": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
@@ -3519,9 +3562,9 @@
}
},
"node_modules/@quasar/extras": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.15.5.tgz",
- "integrity": "sha512-JzKKx5/eKAip3X3bZUEJOOWT9NudqjF01gcce6rtyviko49OU4r+ekyJU3QQIKF8ZqnjZ+DpsVpMWBBZnO6hSQ==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.15.6.tgz",
+ "integrity": "sha512-lG3wrcz47c86N/j1ULZugmyVfwpmnsJpjtSWh+LhaFfe0g1kTMdAnxkWGKsa3ouZ4QBcnkrNan0kYSnKc3MiBg==",
"funding": {
"type": "github",
"url": "https://donate.quasar.dev"
@@ -4307,9 +4350,9 @@
}
},
"node_modules/@vue/devtools-api": {
- "version": "6.1.3",
- "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.3.tgz",
- "integrity": "sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg=="
+ "version": "6.4.5",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
+ "integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
},
"node_modules/@vue/reactivity": {
"version": "3.2.31",
@@ -15743,6 +15786,56 @@
"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": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
@@ -16622,9 +16715,9 @@
}
},
"node_modules/quasar": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.10.0.tgz",
- "integrity": "sha512-PHGcrzPQfFa4tv9a5Z/3D2uat48D4WC9Ad/imzHk/k3G41t0eFMH6glCjAvpCWF2q8dBYIg4nEchiPhlujbKsw==",
+ "version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.10.2.tgz",
+ "integrity": "sha512-y6suu0f2hJKrnFPHzx+p2EBVGzDF6xHaqYGkDIsMNkhxsrO9Qi2+dZCjq1J6+48EJiqPEOn8t9X/gT7yLSSnLw==",
"engines": {
"node": ">= 10.18.1",
"npm": ">= 6.13.4",
@@ -19517,7 +19610,7 @@
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
- "dev": true,
+ "devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -23355,6 +23448,24 @@
"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": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
@@ -23528,9 +23639,9 @@
}
},
"@quasar/extras": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.15.5.tgz",
- "integrity": "sha512-JzKKx5/eKAip3X3bZUEJOOWT9NudqjF01gcce6rtyviko49OU4r+ekyJU3QQIKF8ZqnjZ+DpsVpMWBBZnO6hSQ=="
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.15.6.tgz",
+ "integrity": "sha512-lG3wrcz47c86N/j1ULZugmyVfwpmnsJpjtSWh+LhaFfe0g1kTMdAnxkWGKsa3ouZ4QBcnkrNan0kYSnKc3MiBg=="
},
"@quasar/fastclick": {
"version": "1.1.5",
@@ -24211,9 +24322,9 @@
}
},
"@vue/devtools-api": {
- "version": "6.1.3",
- "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.3.tgz",
- "integrity": "sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg=="
+ "version": "6.4.5",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
+ "integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
},
"@vue/reactivity": {
"version": "3.2.31",
@@ -32797,6 +32908,23 @@
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"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": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
@@ -33385,9 +33513,9 @@
}
},
"quasar": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.10.0.tgz",
- "integrity": "sha512-PHGcrzPQfFa4tv9a5Z/3D2uat48D4WC9Ad/imzHk/k3G41t0eFMH6glCjAvpCWF2q8dBYIg4nEchiPhlujbKsw=="
+ "version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.10.2.tgz",
+ "integrity": "sha512-y6suu0f2hJKrnFPHzx+p2EBVGzDF6xHaqYGkDIsMNkhxsrO9Qi2+dZCjq1J6+48EJiqPEOn8t9X/gT7yLSSnLw=="
},
"queue-microtask": {
"version": "1.2.3",
@@ -35619,7 +35747,7 @@
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
- "dev": true
+ "devOptional": true
},
"uglify-js": {
"version": "3.15.3",
diff --git a/package.json b/package.json
index 3060e75ac..36a49575e 100644
--- a/package.json
+++ b/package.json
@@ -18,10 +18,11 @@
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
},
"dependencies": {
- "@quasar/extras": "^1.15.5",
+ "@quasar/extras": "^1.15.6",
"axios": "^0.21.1",
"core-js": "^3.6.5",
- "quasar": "^2.10.0",
+ "pinia": "^2.0.24",
+ "quasar": "^2.10.2",
"validator": "^13.7.0",
"vue": "^3.0.0",
"vue-i18n": "^9.0.0",
@@ -30,6 +31,7 @@
"devDependencies": {
"@babel/eslint-parser": "^7.13.14",
"@intlify/vue-i18n-loader": "^4.1.0",
+ "@pinia/testing": "^0.0.14",
"@quasar/app-webpack": "^3.6.2",
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.2.2",
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.10",
diff --git a/quasar.config.js b/quasar.config.js
index 83265295f..83a3abf3e 100644
--- a/quasar.config.js
+++ b/quasar.config.js
@@ -23,7 +23,7 @@ module.exports = configure(function (ctx) {
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// 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
css: ['app.scss'],
@@ -38,8 +38,9 @@ module.exports = configure(function (ctx) {
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
- 'roboto-font', // optional, you are not bound to it
- 'material-icons', // optional, you are not bound to it
+ 'roboto-font',
+ 'material-icons-outlined',
+ 'material-symbols-outlined',
],
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build
diff --git a/src/App.vue b/src/App.vue
index f381d2576..2ef0843bd 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,4 +1,5 @@
+
+
+
+
+
+
+
+
+ {{ t(item.title) }}
+
+
diff --git a/src/components/LeftMenuItemGroup.vue b/src/components/LeftMenuItemGroup.vue
new file mode 100644
index 000000000..8305c2ec4
--- /dev/null
+++ b/src/components/LeftMenuItemGroup.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+ {{ t(item.title) }}
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue
index ec407ace5..cb1283eb5 100644
--- a/src/components/Navbar.vue
+++ b/src/components/Navbar.vue
@@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
import UserPanel from 'components/UserPanel.vue';
-import FavoriteModules from './FavoriteModules.vue';
+import PinnedModules from './PinnedModules.vue';
const { t } = useI18n();
const session = useSession();
@@ -43,11 +43,11 @@ function onToggleDrawer() {
-
+
- {{ t('globals.favoriteModules') }}
+ {{ t('globals.pinnedModules') }}
-
+
diff --git a/src/components/FavoriteModules.vue b/src/components/PinnedModules.vue
similarity index 63%
rename from src/components/FavoriteModules.vue
rename to src/components/PinnedModules.vue
index 1a01bd1c7..27767a248 100644
--- a/src/components/FavoriteModules.vue
+++ b/src/components/PinnedModules.vue
@@ -1,14 +1,16 @@
@@ -17,22 +19,22 @@ onMounted(() => {
class="row q-pa-md q-col-gutter-lg"
max-width="350px"
max-height="400px"
- v-if="navigation.favorites.value.length"
+ v-if="pinnedModules.length"
>
-
+
- {{ t(`${module.name}.pageTitles.${module.title}`) }}
+ {{ t(item.title) }}
diff --git a/src/components/UserPanel.vue b/src/components/UserPanel.vue
index 6c061284d..c6264f07f 100644
--- a/src/components/UserPanel.vue
+++ b/src/components/UserPanel.vue
@@ -45,7 +45,7 @@ onMounted(async () => {
});
function updatePreferences() {
- if (user.value.darkMode) {
+ if (user.value.darkMode !== null) {
darkMode.value = user.value.darkMode;
}
if (user.value.lang) {
diff --git a/src/components/__tests__/Leftmenu.spec.js b/src/components/__tests__/Leftmenu.spec.js
index 9d0212c77..472e3394b 100644
--- a/src/components/__tests__/Leftmenu.spec.js
+++ b/src/components/__tests__/Leftmenu.spec.js
@@ -2,97 +2,103 @@ import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper } from 'app/tests/jest/jestHelpers';
import Leftmenu from '../LeftMenu.vue';
+import { createTestingPinia } from '@pinia/testing';
+import { useNavigationStore } from 'src/stores/useNavigationStore';
+
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
- currentRoute: { value: 'myCurrentRoute' }
+ currentRoute: { value: 'myCurrentRoute' },
+ }),
+ useRoute: () => ({
+ matched: [],
}),
}));
-jest.mock('src/router/routes', () => ([
+jest.mock('src/router/modules', () => [
{
- path: '/',
- name: 'Main',
+ path: '/customer',
+ name: 'Customer',
+ meta: {
+ title: 'customers',
+ icon: 'vn:client',
+ },
+ menus: {
+ main: ['CustomerList', 'CustomerCreate'],
+ card: ['CustomerBasicData'],
+ },
children: [
{
- path: '/dashboard',
- name: 'Dashboard',
- meta: { title: 'dashboard', icon: 'dashboard' }
- },
- {
- path: '/customer',
- name: 'Customer',
- meta: {
- title: 'customers',
- icon: 'vn:client'
- },
+ path: '',
+ name: 'CustomerMain',
children: [
{
- path: '',
- name: 'CustomerMain',
- children: [
- {
- path: 'list',
- name: 'CustomerList',
- meta: {
- title: 'list',
- icon: 'view_list',
- }
- },
- {
- path: 'create',
- name: 'CustomerCreate',
- meta: {
- title: 'createCustomer',
- icon: 'vn:addperson',
- }
- },
- ]
- }
- ]
- }
+ path: 'list',
+ name: 'CustomerList',
+ meta: {
+ title: 'list',
+ icon: 'view_list',
+ },
+ },
+ {
+ path: 'create',
+ name: 'CustomerCreate',
+ meta: {
+ title: 'createCustomer',
+ icon: 'vn:addperson',
+ },
+ },
+ ],
+ },
],
},
-]));
+]);
describe('Leftmenu', () => {
let vm;
- beforeAll(() => {
- vm = createWrapper(Leftmenu).vm;
- });
+ let navigation;
+ beforeAll(async () => {
+ vm = createWrapper(Leftmenu, {
+ propsData: {
+ source: 'main',
+ },
+ global: {
+ plugins: [createTestingPinia({ stubActions: false })],
+ },
+ }).vm;
- 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));
+ 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 a proper formated object with two child items', async () => {
- const expectedMenuItem = [{
- name: 'CustomerList',
- title: 'list',
- icon: 'view_list',
- stateName: 'CustomerList'
- },
- {
- name: 'CustomerCreate',
- title: 'createCustomer',
- icon: 'vn:addperson',
- stateName: 'CustomerCreate'
- }];
+ const expectedMenuItem = [
+ {
+ name: 'CustomerList',
+ title: 'customer.pageTitles.list',
+ icon: 'view_list',
+ },
+ {
+ name: 'CustomerCreate',
+ title: 'customer.pageTitles.createCustomer',
+ icon: 'vn:addperson',
+ },
+ ];
- const secondMenuItem = vm.navigation.modules.value[1];
- expect(secondMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
- expect(secondMenuItem.children.length).toEqual(2)
+ const firstMenuItem = vm.items[0];
+ expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem));
});
});
diff --git a/src/composables/__tests__/useNavigation.spec.js b/src/composables/__tests__/useNavigation.spec.js.kk
similarity index 100%
rename from src/composables/__tests__/useNavigation.spec.js
rename to src/composables/__tests__/useNavigation.spec.js.kk
diff --git a/src/composables/useNavigation.js b/src/composables/useNavigation.js
deleted file mode 100644
index a5c58ec09..000000000
--- a/src/composables/useNavigation.js
+++ /dev/null
@@ -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 };
-}
\ No newline at end of file
diff --git a/src/composables/useRole.js b/src/composables/useRole.js
index eb2eebd28..b8da153d3 100644
--- a/src/composables/useRole.js
+++ b/src/composables/useRole.js
@@ -6,7 +6,7 @@ export function useRole() {
async function fetch() {
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 = {
id: data.user.id,
@@ -14,7 +14,7 @@ export function useRole() {
nickname: data.user.nickname,
lang: data.user.lang || 'es',
darkMode: data.user.userConfig.darkMode,
- }
+ };
state.setUser(userData);
state.setRoles(roles);
}
@@ -32,6 +32,6 @@ export function useRole() {
return {
fetch,
hasAny,
- state
+ state,
};
-}
\ No newline at end of file
+}
diff --git a/src/filters/index.js b/src/filters/index.js
index c08f897c4..5fa3bd9c3 100644
--- a/src/filters/index.js
+++ b/src/filters/index.js
@@ -2,10 +2,12 @@ import toLowerCase from './toLowerCase';
import toDate from './toDate';
import toCurrency from './toCurrency';
import toPercentage from './toPercentage';
+import toLowerCamel from './toLowerCamel';
import dashIfEmpty from './dashIfEmpty';
export {
toLowerCase,
+ toLowerCamel,
toDate,
toCurrency,
toPercentage,
diff --git a/src/filters/toLowerCamel.js b/src/filters/toLowerCamel.js
new file mode 100644
index 000000000..ebfe28bc2
--- /dev/null
+++ b/src/filters/toLowerCamel.js
@@ -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);
+}
\ No newline at end of file
diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js
index fe73aa9ae..119d7362d 100644
--- a/src/i18n/en/index.js
+++ b/src/i18n/en/index.js
@@ -9,7 +9,7 @@ export default {
backToDashboard: 'Return to dashboard',
notifications: 'Notifications',
userPanel: 'User panel',
- favoriteModules: 'Favorite modules',
+ pinnedModules: 'Pinned modules',
darkMode: 'Dark mode',
logOut: 'Log out',
dataSaved: 'Data saved',
@@ -355,5 +355,9 @@ export default {
summary: 'Summary',
moreOptions: 'More options',
},
+ leftMenu: {
+ addToPinned: 'Add to pinned',
+ removeFromPinned: 'Remove from pinned',
+ },
},
};
diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js
index f212e2d45..89b52e237 100644
--- a/src/i18n/es/index.js
+++ b/src/i18n/es/index.js
@@ -9,7 +9,7 @@ export default {
backToDashboard: 'Volver al tablón',
notifications: 'Notificaciones',
userPanel: 'Panel de usuario',
- favoriteModules: 'Módulos favoritos',
+ pinnedModules: 'Módulos fijados',
darkMode: 'Modo oscuro',
logOut: 'Cerrar sesión',
dataSaved: 'Datos guardados',
@@ -354,5 +354,9 @@ export default {
summary: 'Resumen',
moreOptions: 'Más opciones',
},
+ leftMenu: {
+ addToPinned: 'Añadir a fijados',
+ removeFromPinned: 'Eliminar de fijados',
+ },
},
};
diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue
index 32dd3f24e..cf8f7687c 100644
--- a/src/pages/Claim/Card/ClaimCard.vue
+++ b/src/pages/Claim/Card/ClaimCard.vue
@@ -1,9 +1,8 @@
@@ -11,20 +10,7 @@ const state = useState();
-
-
-
-
-
- {{ t('claim.pageTitles.basicData') }}
-
-
-
-
-
- {{ t('claim.pageTitles.rma') }}
-
-
+
diff --git a/src/pages/Customer/Card/CustomerCard.vue b/src/pages/Customer/Card/CustomerCard.vue
index e78602aae..73a436a69 100644
--- a/src/pages/Customer/Card/CustomerCard.vue
+++ b/src/pages/Customer/Card/CustomerCard.vue
@@ -1,40 +1,16 @@
-
-
-
-
-
- {{ t('customer.pageTitles.basicData') }}
-
-
-
+
diff --git a/src/pages/Dashboard/Dashboard.vue b/src/pages/Dashboard/Dashboard.vue
index d84a53ab9..107e3fd3b 100644
--- a/src/pages/Dashboard/Dashboard.vue
+++ b/src/pages/Dashboard/Dashboard.vue
@@ -1,12 +1,19 @@
@@ -17,35 +24,24 @@ const modules = useNavigation();
-
-
-
-
{{ t('globals.favoriteModules') }}
+
+
{{ t('globals.pinnedModules') }}
-
+
- {{ t(`${module.name}.pageTitles.${module.title}`) }}
+ {{ t(item.title) }}
diff --git a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
index 27bb5439c..092931bb2 100644
--- a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
+++ b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue
@@ -29,7 +29,6 @@ function fetch() {
axios.get(`InvoiceOuts/${id}/summary`).then(({ data }) => {
invoiceOut.value = data.invoiceOut;
tax.value = data.invoiceOut.taxesBreakdown;
- console.log('tax', tax);
});
axios.get(`InvoiceOuts/${id}/getTickets`).then(({ data }) => {
diff --git a/src/pages/InvoiceOut/Card/InvoiceOutSummaryDialog.vue b/src/pages/InvoiceOut/Card/InvoiceOutSummaryDialog.vue
index 0e5b3127a..d04841307 100644
--- a/src/pages/InvoiceOut/Card/InvoiceOutSummaryDialog.vue
+++ b/src/pages/InvoiceOut/Card/InvoiceOutSummaryDialog.vue
@@ -19,3 +19,11 @@ const { dialogRef, onDialogHide } = useDialogPluginComponent();
+
+
diff --git a/src/pages/Ticket/Card/TicketCard.vue b/src/pages/Ticket/Card/TicketCard.vue
index b5645c8cf..71df0dfce 100644
--- a/src/pages/Ticket/Card/TicketCard.vue
+++ b/src/pages/Ticket/Card/TicketCard.vue
@@ -1,24 +1,16 @@
-
-
-
-
-
- {{ t('ticket.pageTitles.boxing') }}
-
-
+
diff --git a/src/router/index.js b/src/router/index.js
index d3ea402fa..8fe77f2c9 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,11 +1,11 @@
import { route } from 'quasar/wrappers';
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router';
-// import { Notify } from 'quasar';
import routes from './routes';
import { i18n } from 'src/boot/i18n';
import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
import { useRole } from 'src/composables/useRole';
+import { toLowerCamel } from 'src/filters';
const state = useState();
const session = useSession();
@@ -25,8 +25,8 @@ export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER
? createMemoryHistory
: process.env.VUE_ROUTER_MODE === 'history'
- ? createWebHistory
- : createWebHashHistory;
+ ? createWebHistory
+ : createWebHashHistory;
const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
@@ -46,26 +46,14 @@ export default route(function (/* { store, ssrContext } */) {
}
if (isLoggedIn()) {
- // try {
const stateRoles = state.getRoles().value;
if (stateRoles.length === 0) {
await role.fetch();
}
- // } catch (error) {
- // Notify.create({
- // message: t('errors.statusUnauthorized'),
- // type: 'negative',
- // });
-
- // session.destroy();
- // return next({ path: '/login' });
- // }
-
const matches = to.matched;
- const hasRequiredRoles = matches.every(route => {
+ const hasRequiredRoles = matches.every((route) => {
const meta = route.meta;
- if (meta && meta.roles)
- return role.hasAny(meta.roles)
+ if (meta && meta.roles) return role.hasAny(meta.roles);
return true;
});
@@ -85,7 +73,7 @@ export default route(function (/* { store, ssrContext } */) {
if (matches && matches.length > 1) {
const module = matches[1];
const moduleTitle = module.meta && module.meta.title;
- moduleName = module.name.toLowerCase();
+ moduleName = toLowerCamel(module.name);
if (moduleTitle) {
title = t(`${moduleName}.pageTitles.${moduleTitle}`);
}
diff --git a/src/router/modules/claim.js b/src/router/modules/claim.js
index 641629c9c..1c1cfb50e 100644
--- a/src/router/modules/claim.js
+++ b/src/router/modules/claim.js
@@ -5,10 +5,14 @@ export default {
path: '/claim',
meta: {
title: 'claims',
- icon: 'vn:claims'
+ icon: 'vn:claims',
},
component: RouterView,
redirect: { name: 'ClaimMain' },
+ menus: {
+ main: ['ClaimList', 'ClaimRmaList'],
+ card: ['ClaimBasicData', 'ClaimRma'],
+ },
children: [
{
name: 'ClaimMain',
@@ -31,11 +35,11 @@ export default {
meta: {
title: 'rmaList',
icon: 'vn:barcode',
- roles: ['claimManager']
+ roles: ['claimManager'],
},
component: () => import('src/pages/Claim/ClaimRmaList.vue'),
- }
- ]
+ },
+ ],
},
{
name: 'ClaimCard',
@@ -47,7 +51,8 @@ export default {
name: 'ClaimSummary',
path: 'summary',
meta: {
- title: 'summary'
+ title: 'summary',
+ icon: 'launch',
},
component: () => import('src/pages/Claim/Card/ClaimSummary.vue'),
},
@@ -56,7 +61,8 @@ export default {
path: 'basic-data',
meta: {
title: 'basicData',
- roles: ['salesPerson']
+ icon: 'vn:settings',
+ roles: ['salesPerson'],
},
component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'),
},
@@ -65,11 +71,12 @@ export default {
path: 'rma',
meta: {
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'),
+ },
+ ],
},
- ]
-};
\ No newline at end of file
+ ],
+};
diff --git a/src/router/modules/customer.js b/src/router/modules/customer.js
index 32cd3446c..1c2d421b6 100644
--- a/src/router/modules/customer.js
+++ b/src/router/modules/customer.js
@@ -5,10 +5,14 @@ export default {
name: 'Customer',
meta: {
title: 'customers',
- icon: 'vn:client'
+ icon: 'vn:client',
},
component: RouterView,
redirect: { name: 'CustomerMain' },
+ menus: {
+ main: ['CustomerList', 'CustomerCreate'],
+ card: ['CustomerBasicData'],
+ },
children: [
{
path: '',
@@ -35,7 +39,7 @@ export default {
},
component: () => import('src/pages/Customer/CustomerCreate.vue'),
},
- ]
+ ],
},
{
name: 'CustomerCard',
@@ -47,7 +51,8 @@ export default {
name: 'CustomerSummary',
path: 'summary',
meta: {
- title: 'summary'
+ title: 'summary',
+ icon: 'launch',
},
component: () => import('src/pages/Customer/Card/CustomerSummary.vue'),
},
@@ -55,11 +60,12 @@ export default {
path: 'basic-data',
name: 'CustomerBasicData',
meta: {
- title: 'basicData'
+ title: 'basicData',
+ icon: 'vn:settings',
},
component: () => import('src/pages/Customer/Card/CustomerBasicData.vue'),
- }
- ]
+ },
+ ],
},
- ]
-};
\ No newline at end of file
+ ],
+};
diff --git a/src/router/modules/index.js b/src/router/modules/index.js
new file mode 100644
index 000000000..dcb3fa5c2
--- /dev/null
+++ b/src/router/modules/index.js
@@ -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
+]
diff --git a/src/router/modules/invoiceOut.js b/src/router/modules/invoiceOut.js
index 9df12584f..a9236b713 100644
--- a/src/router/modules/invoiceOut.js
+++ b/src/router/modules/invoiceOut.js
@@ -9,6 +9,9 @@ export default {
},
component: RouterView,
redirect: { name: 'InvoiceOutMain' },
+ menus: {
+ main: ['InvoiceOutList']
+ },
children: [
{
path: '',
diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js
index b9ce8dcf8..4f06a1808 100644
--- a/src/router/modules/ticket.js
+++ b/src/router/modules/ticket.js
@@ -5,10 +5,14 @@ export default {
path: '/ticket',
meta: {
title: 'tickets',
- icon: 'vn:ticket'
+ icon: 'vn:ticket',
},
component: RouterView,
redirect: { name: 'TicketMain' },
+ menus: {
+ main: ['TicketList'],
+ card: ['TicketBoxing'],
+ },
children: [
{
name: 'TicketMain',
@@ -35,8 +39,7 @@ export default {
},
component: () => import('src/pages/Ticket/TicketList.vue'),
},
-
- ]
+ ],
},
{
name: 'TicketCard',
@@ -48,7 +51,8 @@ export default {
name: 'TicketSummary',
path: 'summary',
meta: {
- title: 'summary'
+ title: 'summary',
+ icon: 'launch',
},
component: () => import('src/pages/Ticket/Card/TicketSummary.vue'),
},
@@ -56,7 +60,8 @@ export default {
name: 'TicketBasicData',
path: 'basic-data',
meta: {
- title: 'basicData'
+ title: 'basicData',
+ icon: 'vn:settings',
},
component: () => import('src/pages/Ticket/Card/TicketBasicData.vue'),
},
@@ -64,11 +69,12 @@ export default {
path: 'boxing',
name: 'TicketBoxing',
meta: {
- title: 'boxing'
+ title: 'boxing',
+ icon: 'vn:package',
},
component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'),
- }
- ]
+ },
+ ],
},
- ]
+ ],
};
diff --git a/src/stores/useNavigationStore.js b/src/stores/useNavigationStore.js
new file mode 100644
index 000000000..6d1a74326
--- /dev/null
+++ b/src/stores/useNavigationStore.js
@@ -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,
+ };
+});
diff --git a/tests/jest/jestHelpers.js b/tests/jest/jestHelpers.js
index 85134dcb0..c095d44b4 100644
--- a/tests/jest/jestHelpers.js
+++ b/tests/jest/jestHelpers.js
@@ -1,25 +1,27 @@
import { mount, flushPromises } from '@vue/test-utils';
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest';
import { i18n } from 'src/boot/i18n';
-import { Notify } from 'quasar';
+import { Notify, Dialog } from 'quasar';
import axios from 'axios';
-// Specify here Quasar config you'll need to test your component
installQuasarPlugin({
plugins: {
- Notify
- }
+ Notify,
+ Dialog,
+ },
});
export function createWrapper(component, options) {
- const mountOptions = {
- global: {
- plugins: [i18n]
- }
- };
+ const mountOptions = {};
- if (options instanceof Object)
- Object.assign(mountOptions, options)
+ if (options instanceof Object) 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 vm = wrapper.vm;
@@ -27,7 +29,4 @@ export function createWrapper(component, options) {
return { vm, wrapper };
}
-export {
- axios,
- flushPromises
-}
+export { axios, flushPromises };