forked from verdnatura/salix-front
Merge branch '4834-create-worker-module' into 4797-worker-notification-selector
This commit is contained in:
commit
f98c82185f
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ module.exports = {
|
||||||
'^src/(.*)$': '<rootDir>/src/$1',
|
'^src/(.*)$': '<rootDir>/src/$1',
|
||||||
'^app/(.*)$': '<rootDir>/$1',
|
'^app/(.*)$': '<rootDir>/$1',
|
||||||
'^components/(.*)$': '<rootDir>/src/components/$1',
|
'^components/(.*)$': '<rootDir>/src/components/$1',
|
||||||
|
'^composables/(.*)$': '<rootDir>/src/composables/$1',
|
||||||
|
'^filters/(.*)$': '<rootDir>/src/filters/$1',
|
||||||
'^layouts/(.*)$': '<rootDir>/src/layouts/$1',
|
'^layouts/(.*)$': '<rootDir>/src/layouts/$1',
|
||||||
'^pages/(.*)$': '<rootDir>/src/pages/$1',
|
'^pages/(.*)$': '<rootDir>/src/pages/$1',
|
||||||
'^assets/(.*)$': '<rootDir>/src/assets/$1',
|
'^assets/(.*)$': '<rootDir>/src/assets/$1',
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"src/*": ["src/*"],
|
"src/*": ["src/*"],
|
||||||
"app/*": ["*"],
|
"app/*": ["*"],
|
||||||
"components/*": ["src/components/*"],
|
"components/*": ["src/components/*"],
|
||||||
|
"composables/*": ["src/composables/*"],
|
||||||
"layouts/*": ["src/layouts/*"],
|
"layouts/*": ["src/layouts/*"],
|
||||||
"pages/*": ["src/pages/*"],
|
"pages/*": ["src/pages/*"],
|
||||||
"assets/*": ["src/assets/*"],
|
"assets/*": ["src/assets/*"],
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
|
@ -18,27 +18,29 @@
|
||||||
"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.8",
|
||||||
"axios": "^0.21.1",
|
"axios": "^1.2.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"quasar": "^2.10.0",
|
"pinia": "^2.0.28",
|
||||||
|
"quasar": "^2.11.1",
|
||||||
"validator": "^13.7.0",
|
"validator": "^13.7.0",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.2.45",
|
||||||
"vue-i18n": "^9.0.0",
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-router": "^4.0.0"
|
"vue-router": "^4.1.6"
|
||||||
},
|
},
|
||||||
"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-beta.5",
|
||||||
"eslint": "^8.10.0",
|
"eslint": "^8.30.0",
|
||||||
"eslint-config-prettier": "^8.1.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-cypress": "^2.11.3",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"eslint-plugin-jest": "^25.2.2",
|
"eslint-plugin-jest": "^27.1.7",
|
||||||
"eslint-plugin-vue": "^8.5.0",
|
"eslint-plugin-vue": "^8.7.1",
|
||||||
"eslint-webpack-plugin": "^3.1.1",
|
"eslint-webpack-plugin": "^3.2.0",
|
||||||
"jest-junit": "^13.0.0",
|
"jest-junit": "^13.0.0",
|
||||||
"prettier": "^2.5.1"
|
"prettier": "^2.5.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js
|
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js
|
||||||
|
|
||||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||||
|
|
||||||
const { configure } = require('quasar/wrappers');
|
const { configure } = require('quasar/wrappers');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = configure(function (ctx) {
|
module.exports = configure(function (ctx) {
|
||||||
return {
|
return {
|
||||||
|
@ -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
|
||||||
|
@ -68,15 +69,23 @@ module.exports = configure(function (ctx) {
|
||||||
|
|
||||||
chainWebpack(chain) {
|
chainWebpack(chain) {
|
||||||
chain.module
|
chain.module
|
||||||
.rule("i18n")
|
.rule('i18n')
|
||||||
.resourceQuery(/blockType=i18n/)
|
.resourceQuery(/blockType=i18n/)
|
||||||
.type('javascript/auto')
|
.type('javascript/auto')
|
||||||
.use("i18n")
|
.use('i18n')
|
||||||
.loader("@intlify/vue-i18n-loader")
|
.loader('@intlify/vue-i18n-loader')
|
||||||
.end();
|
.end();
|
||||||
|
|
||||||
chain.plugin('eslint-webpack-plugin')
|
chain.plugin('eslint-webpack-plugin').use(ESLintPlugin, [{ extensions: ['js', 'vue'] }]);
|
||||||
.use(ESLintPlugin, [{ extensions: ['js', 'vue'] }]);
|
},
|
||||||
|
extendWebpack(cfg) {
|
||||||
|
cfg.resolve.alias = {
|
||||||
|
...cfg.resolve.alias, // This adds the existing alias
|
||||||
|
|
||||||
|
// Add your own alias like this
|
||||||
|
composables: path.resolve(__dirname, './src/composables'),
|
||||||
|
filters: path.resolve(__dirname, './src/filters'),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -91,7 +100,7 @@ module.exports = configure(function (ctx) {
|
||||||
target: 'http://0.0.0.0:3000',
|
target: 'http://0.0.0.0:3000',
|
||||||
logLevel: 'debug',
|
logLevel: 'debug',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false
|
secure: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -100,9 +109,9 @@ module.exports = configure(function (ctx) {
|
||||||
framework: {
|
framework: {
|
||||||
config: {
|
config: {
|
||||||
brand: {
|
brand: {
|
||||||
primary: 'orange'
|
primary: 'orange',
|
||||||
},
|
},
|
||||||
dark: 'auto'
|
dark: 'auto',
|
||||||
},
|
},
|
||||||
lang: 'es',
|
lang: 'es',
|
||||||
|
|
||||||
|
|
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 }) => {
|
||||||
|
@ -13,4 +15,4 @@ export default boot(({ app }) => {
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { i18n };
|
export { i18n };
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { boot } from 'quasar/wrappers';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
|
app.use(pinia);
|
||||||
|
});
|
|
@ -6,7 +6,7 @@ import axios from 'axios';
|
||||||
|
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import { useValidator } from 'src/composables/useValidator';
|
import { useValidator } from 'src/composables/useValidator';
|
||||||
import SkeletonForm from 'src/components/SkeletonForm.vue';
|
import SkeletonForm from 'components/ui/SkeletonForm.vue';
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
|
@ -1,17 +1,107 @@
|
||||||
<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(async () => {
|
||||||
|
await navigation.fetchPinned();
|
||||||
|
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 +112,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)"
|
||||||
<q-item-section avatar :if="module.icon">
|
icon="vn:pin_off"
|
||||||
<q-icon :name="module.icon" />
|
size="xs"
|
||||||
</q-item-section>
|
flat
|
||||||
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
|
round
|
||||||
<q-item-section side>
|
>
|
||||||
<div @click="onToggleFavoriteModule(module.name, $event)" class="row items-center">
|
<q-tooltip>{{ t('components.leftMenu.removeFromPinned') }}</q-tooltip>
|
||||||
<q-icon name="vn:pin_off"></q-icon>
|
</q-btn>
|
||||||
</div>
|
<q-btn
|
||||||
</q-item-section>
|
v-if="item.isPinned === false"
|
||||||
</q-item>
|
@click="togglePinned(item, $event)"
|
||||||
</div>
|
icon="vn:pin"
|
||||||
|
size="xs"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
>
|
||||||
|
<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
|
</template>
|
||||||
class="module"
|
<q-separator />
|
||||||
active-class="text-primary"
|
<q-expansion-item :label="t('moduleIndex.allModules')">
|
||||||
:label="t(`${module.name}.pageTitles.${module.title}`)"
|
<template v-for="item in items" :key="item.name">
|
||||||
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
|
<template v-if="item.children">
|
||||||
:to="{ name: module.stateName }"
|
<left-menu-item-group :item="item" group="modules">
|
||||||
>
|
<template #side>
|
||||||
<template #header>
|
<q-btn
|
||||||
<q-item-section avatar>
|
v-if="item.isPinned === true"
|
||||||
<q-icon :name="module.icon"></q-icon>
|
@click="togglePinned(item, $event)"
|
||||||
</q-item-section>
|
icon="vn:pin_off"
|
||||||
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
|
size="xs"
|
||||||
<q-item-section side>
|
flat
|
||||||
<div @click="onToggleFavoriteModule(module.name, $event)" class="row items-center">
|
round
|
||||||
<q-icon name="vn:pin_off"></q-icon>
|
>
|
||||||
</div>
|
<q-tooltip>{{ t('components.leftMenu.removeFromPinned') }}</q-tooltip>
|
||||||
</q-item-section>
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-if="item.isPinned === false"
|
||||||
|
@click="togglePinned(item, $event)"
|
||||||
|
icon="vn:pin"
|
||||||
|
size="xs"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ t('components.leftMenu.addToPinned') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
</left-menu-item-group>
|
||||||
</template>
|
</template>
|
||||||
<template v-for="section in module.children" :key="section.title">
|
</template>
|
||||||
<q-item
|
</q-expansion-item>
|
||||||
clickable
|
<q-separator />
|
||||||
v-ripple
|
</template>
|
||||||
active-class="text-primary"
|
<template v-if="$props.source === 'card'">
|
||||||
:to="{ name: section.stateName }"
|
<template v-for="item in items" :key="item.name">
|
||||||
v-if="!section.roles || !section.roles.length || hasAny(section.roles)"
|
<left-menu-item v-if="!item.children" :item="item" />
|
||||||
>
|
|
||||||
<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>
|
||||||
</template>
|
</template>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
|
||||||
<q-separator />
|
|
||||||
|
|
||||||
<q-expansion-item :label="t('moduleIndex.allModules')">
|
|
||||||
<q-list padding>
|
|
||||||
<template v-for="module in navigation.modules.value" :key="module.title">
|
|
||||||
<div class="module" v-if="!module.children">
|
|
||||||
<q-item
|
|
||||||
class="module"
|
|
||||||
clickable
|
|
||||||
v-ripple
|
|
||||||
active-class="text-primary"
|
|
||||||
:key="module.title"
|
|
||||||
:to="{ name: module.stateName }"
|
|
||||||
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
|
|
||||||
>
|
|
||||||
<q-item-section avatar :if="module.icon">
|
|
||||||
<q-icon :name="module.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>
|
|
||||||
</q-item>
|
|
||||||
</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 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-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>
|
|
@ -1,9 +1,10 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted } 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 { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
import UserPanel from 'src/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();
|
||||||
|
@ -11,6 +12,8 @@ const state = useState();
|
||||||
const user = state.getUser();
|
const user = state.getUser();
|
||||||
const token = session.getToken();
|
const token = session.getToken();
|
||||||
|
|
||||||
|
onMounted(() => (state.headerMounted.value = true));
|
||||||
|
|
||||||
function onToggleDrawer() {
|
function onToggleDrawer() {
|
||||||
state.drawer.value = !state.drawer.value;
|
state.drawer.value = !state.drawer.value;
|
||||||
}
|
}
|
||||||
|
@ -36,42 +39,17 @@ function onToggleDrawer() {
|
||||||
</router-link>
|
</router-link>
|
||||||
<q-toolbar-title shrink class="text-weight-bold">Salix</q-toolbar-title>
|
<q-toolbar-title shrink class="text-weight-bold">Salix</q-toolbar-title>
|
||||||
<q-space></q-space>
|
<q-space></q-space>
|
||||||
|
<div id="searchbar"></div>
|
||||||
|
<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">
|
||||||
<!-- <q-btn v-if="$q.screen.gt.xs" dense flat size="md" icon="add">
|
<div id="header-actions"></div>
|
||||||
<q-menu>
|
<q-btn id="pinnedModules" icon="apps" flat dense rounded>
|
||||||
<q-list style="min-width: 150px">
|
|
||||||
<q-item :to="{ path: '/customer/create' }" clickable>
|
|
||||||
<q-item-section>New customer</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable>
|
|
||||||
<q-item-section>New ticket</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn> -->
|
|
||||||
<!-- <q-btn v-if="$q.screen.gt.xs" dense flat round size="md" icon="notifications">
|
|
||||||
<q-badge color="red" text-color="white" floating> 2 </q-badge>
|
|
||||||
<q-tooltip bottom>
|
<q-tooltip bottom>
|
||||||
{{ t('globals.notifications') }}
|
{{ t('globals.pinnedModules') }}
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
<q-menu class="q-pa-md" style="min-width: 250px">
|
<PinnedModules />
|
||||||
<strong>Notifications</strong>
|
|
||||||
<q-separator />
|
|
||||||
<div style="text-align: center; font-size: 2em">
|
|
||||||
<q-spinner-puff color="orange" />
|
|
||||||
</div>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn> -->
|
|
||||||
<q-btn dense flat no-wrap id="favoriteModules">
|
|
||||||
<q-avatar size="lg">
|
|
||||||
<q-icon name="apps" size="s" />
|
|
||||||
</q-avatar>
|
|
||||||
<q-tooltip bottom>
|
|
||||||
{{ t('globals.favoriteModules') }}
|
|
||||||
</q-tooltip>
|
|
||||||
<FavoriteModules />
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn dense flat no-wrap id="user">
|
<q-btn rounded dense flat no-wrap id="user">
|
||||||
<q-avatar size="lg">
|
<q-avatar size="lg">
|
||||||
<q-img
|
<q-img
|
||||||
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
|
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
|
||||||
|
@ -79,7 +57,6 @@ function onToggleDrawer() {
|
||||||
>
|
>
|
||||||
</q-img>
|
</q-img>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
<q-icon name="arrow_drop_down" size="s" />
|
|
||||||
<q-tooltip bottom>
|
<q-tooltip bottom>
|
||||||
{{ t('globals.userPanel') }}
|
{{ t('globals.userPanel') }}
|
||||||
</q-tooltip>
|
</q-tooltip>
|
|
@ -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,97 +2,103 @@ 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: '/',
|
path: '/customer',
|
||||||
name: 'Main',
|
name: 'Customer',
|
||||||
|
meta: {
|
||||||
|
title: 'customers',
|
||||||
|
icon: 'vn:client',
|
||||||
|
},
|
||||||
|
menus: {
|
||||||
|
main: ['CustomerList', 'CustomerCreate'],
|
||||||
|
card: ['CustomerBasicData'],
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '',
|
||||||
name: 'Dashboard',
|
name: 'CustomerMain',
|
||||||
meta: { title: 'dashboard', icon: 'dashboard' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/customer',
|
|
||||||
name: 'Customer',
|
|
||||||
meta: {
|
|
||||||
title: 'customers',
|
|
||||||
icon: 'vn:client'
|
|
||||||
},
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: 'list',
|
||||||
name: 'CustomerMain',
|
name: 'CustomerList',
|
||||||
children: [
|
meta: {
|
||||||
{
|
title: 'list',
|
||||||
path: 'list',
|
icon: 'view_list',
|
||||||
name: 'CustomerList',
|
},
|
||||||
meta: {
|
},
|
||||||
title: 'list',
|
{
|
||||||
icon: 'view_list',
|
path: 'create',
|
||||||
}
|
name: 'CustomerCreate',
|
||||||
},
|
meta: {
|
||||||
{
|
title: 'createCustomer',
|
||||||
path: 'create',
|
icon: 'vn:addperson',
|
||||||
name: 'CustomerCreate',
|
},
|
||||||
meta: {
|
},
|
||||||
title: 'createCustomer',
|
],
|
||||||
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;
|
||||||
|
|
||||||
it('should return the proper formated object without the children property', async () => {
|
navigation = useNavigationStore();
|
||||||
const expectedMenuItem = {
|
navigation.modules = ['customer']; // I should mock to have just one module but isn´t working
|
||||||
stateName: 'Dashboard',
|
navigation.fetchPinned = jest.fn().mockReturnValue(Promise.resolve(true));
|
||||||
name: 'dashboard',
|
navigation.getModules = jest.fn().mockReturnValue({
|
||||||
roles: [],
|
value: [
|
||||||
icon: 'dashboard',
|
{
|
||||||
title: 'dashboard'
|
name: 'customer',
|
||||||
}
|
title: 'customer.pageTitles.customers',
|
||||||
|
icon: 'vn:customer',
|
||||||
const firstMenuItem = vm.navigation.modules.value[0];
|
module: 'customer',
|
||||||
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',
|
{
|
||||||
title: 'list',
|
name: 'CustomerList',
|
||||||
icon: 'view_list',
|
title: 'customer.pageTitles.list',
|
||||||
stateName: 'CustomerList'
|
icon: 'view_list',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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,6 +1,6 @@
|
||||||
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
|
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
|
||||||
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
|
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
|
||||||
import Paginate from '../Paginate.vue';
|
import Paginate from '../PaginateData.vue';
|
||||||
|
|
||||||
const mockPush = jest.fn();
|
const mockPush = jest.fn();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
address: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
||||||
|
|
||||||
|
const { dialogRef, onDialogOK } = useDialogPluginComponent();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const address = ref($props.address);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
|
||||||
|
async function confirm() {
|
||||||
|
isLoading.value = true;
|
||||||
|
await $props.send(address.value);
|
||||||
|
isLoading.value = false;
|
||||||
|
|
||||||
|
onDialogOK();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-dialog ref="dialogRef" persistent>
|
||||||
|
<q-card class="q-pa-sm">
|
||||||
|
<q-card-section class="row items-center q-pb-none">
|
||||||
|
<span class="text-h6 text-grey">{{ t('sendEmailNotification') }}</span>
|
||||||
|
<q-space />
|
||||||
|
<q-btn icon="close" flat round dense v-close-popup />
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="row items-center">
|
||||||
|
{{ t('notifyAddress') }}
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pt-none">
|
||||||
|
<q-input dense v-model="address" rounded outlined autofocus />
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn :label="t('globals.cancel')" color="primary" flat v-close-popup />
|
||||||
|
<q-btn :label="t('globals.confirm')" color="primary" :loading="isLoading" @click="confirm" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.q-card {
|
||||||
|
min-width: 350px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"sendEmailNotification": "Send email notification",
|
||||||
|
"notifyAddress": "The notification will be sent to the following address"
|
||||||
|
},
|
||||||
|
"es": {
|
||||||
|
"sendEmailNotification": "Enviar notificación por correo",
|
||||||
|
"notifyAddress": "La notificación se enviará a la siguiente dirección"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</i18n>
|
|
@ -45,6 +45,7 @@ const { t } = useI18n();
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="$props.data" class="body q-py-sm">
|
<div v-if="$props.data" class="body q-py-sm">
|
||||||
|
<slot name="before" />
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item-label header class="ellipsis text-h5" :lines="1">
|
<q-item-label header class="ellipsis text-h5" :lines="1">
|
||||||
{{ $props.description }}
|
{{ $props.description }}
|
||||||
|
@ -55,6 +56,7 @@ const { t } = useI18n();
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
<slot name="body" />
|
<slot name="body" />
|
||||||
|
<slot name="after" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Skeleton -->
|
<!-- Skeleton -->
|
||||||
|
@ -98,9 +100,5 @@ const { t } = useI18n();
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
#descriptor-skeleton .q-card__actions {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
maxLength: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="fetchedTags">
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="inline-tag" :class="{ empty: !$props.item.value5 }">{{ $props.item.value5 }}</div>
|
||||||
|
<div class="inline-tag" :class="{ empty: !$props.item.value6 }">{{ $props.item.value6 }}</div>
|
||||||
|
<div class="inline-tag" :class="{ empty: !$props.item.value7 }">{{ $props.item.value7 }}</div>
|
||||||
|
<div class="inline-tag" :class="{ empty: !$props.item.value8 }">{{ $props.item.value8 }}</div>
|
||||||
|
<div class="inline-tag" :class="{ empty: !$props.item.value9 }">{{ $props.item.value9 }}</div>
|
||||||
|
<div class="inline-tag" :class="{ empty: !$props.item.value10 }">{{ $props.item.value10 }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.fetchedTags {
|
||||||
|
align-items: center;
|
||||||
|
.wrap {
|
||||||
|
width: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-tag {
|
||||||
|
height: 1rem;
|
||||||
|
margin: 0.05rem;
|
||||||
|
color: $secondary;
|
||||||
|
text-align: center;
|
||||||
|
font-size: smaller;
|
||||||
|
padding: 1px;
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid $color-spacer;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 4rem;
|
||||||
|
max-width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
border: 1px solid $color-spacer-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<div id="descriptor-skeleton">
|
||||||
|
<div class="col q-pl-sm q-pa-sm">
|
||||||
|
<q-skeleton type="text" square height="45px" />
|
||||||
|
<q-skeleton type="text" square height="18px" />
|
||||||
|
<q-skeleton type="text" square height="18px" />
|
||||||
|
<q-skeleton type="text" square height="18px" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-card-actions>
|
||||||
|
<q-skeleton size="40px" />
|
||||||
|
<q-skeleton size="40px" />
|
||||||
|
<q-skeleton size="40px" />
|
||||||
|
<q-skeleton size="40px" />
|
||||||
|
<q-skeleton size="40px" />
|
||||||
|
</q-card-actions>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
#descriptor-skeleton .q-card__actions {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script setup>
|
||||||
|
import { nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isHeaderMounted = ref(false);
|
||||||
|
nextTick(() => {
|
||||||
|
isHeaderMounted.value = document.querySelector($props.to) !== null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<teleport v-if="isHeaderMounted" :to="$props.to">
|
||||||
|
<slot />
|
||||||
|
</teleport>
|
||||||
|
</template>
|
|
@ -1,43 +0,0 @@
|
||||||
import { describe, expect, it } from '@jest/globals';
|
|
||||||
import { useNavigation } from '../useNavigation';
|
|
||||||
const navigation = useNavigation();
|
|
||||||
|
|
||||||
describe('useNavigation', () => {
|
|
||||||
it('should return the routes for all modules', async () => {
|
|
||||||
expect(navigation.modules.value.length).toBeGreaterThan(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a proper formated object without the children property', async () => {
|
|
||||||
const expectedMenuItem = {
|
|
||||||
stateName: 'Dashboard',
|
|
||||||
name: 'dashboard',
|
|
||||||
roles: [],
|
|
||||||
icon: 'dashboard',
|
|
||||||
title: 'dashboard'
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstMenuItem = 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 () => {
|
|
||||||
const expectedMenuItem = [{
|
|
||||||
name: 'CustomerList',
|
|
||||||
title: 'list',
|
|
||||||
icon: 'view_list',
|
|
||||||
stateName: 'CustomerList'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CustomerCreate',
|
|
||||||
title: 'createCustomer',
|
|
||||||
icon: 'vn:addperson',
|
|
||||||
stateName: 'CustomerCreate',
|
|
||||||
roles: ['developer']
|
|
||||||
}];
|
|
||||||
|
|
||||||
const secondMenuItem = navigation.modules.value[1]
|
|
||||||
expect(secondMenuItem.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 };
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { useSession } from './useSession';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
|
export function usePrintService() {
|
||||||
|
const quasar = useQuasar();
|
||||||
|
const { getToken } = useSession();
|
||||||
|
|
||||||
|
function sendEmail(path, params) {
|
||||||
|
return axios.post(path, params).then(() =>
|
||||||
|
quasar.notify({
|
||||||
|
message: 'Notification sent',
|
||||||
|
type: 'positive',
|
||||||
|
icon: 'check',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openReport(path, params) {
|
||||||
|
params = Object.assign(
|
||||||
|
{
|
||||||
|
access_token: getToken(),
|
||||||
|
},
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
const query = new URLSearchParams(params).toString();
|
||||||
|
|
||||||
|
window.open(`api/${path}?${query}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendEmail,
|
||||||
|
openReport,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ const user = ref({
|
||||||
|
|
||||||
const roles = ref([]);
|
const roles = ref([]);
|
||||||
const drawer = ref(true);
|
const drawer = ref(true);
|
||||||
|
const headerMounted = ref(false);
|
||||||
|
|
||||||
export function useState() {
|
export function useState() {
|
||||||
function getUser() {
|
function getUser() {
|
||||||
|
@ -67,6 +68,7 @@ export function useState() {
|
||||||
set,
|
set,
|
||||||
get,
|
get,
|
||||||
unset,
|
unset,
|
||||||
drawer
|
drawer,
|
||||||
|
headerMounted
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
// app global css in SCSS form
|
// app global css in SCSS form
|
||||||
@import './icons.scss';
|
@import './icons.scss';
|
||||||
|
|
||||||
|
.body--dark {
|
||||||
|
.q-card--dark {
|
||||||
|
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2), 0 2px 2px rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-layout__shadow::after {
|
||||||
|
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0px 10px rgba(0, 0, 0, 0.24) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
cursor: pointer
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link:hover {
|
.link:hover {
|
||||||
color: $orange-4;
|
color: $orange-4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,3 +22,9 @@ $positive: #21ba45;
|
||||||
$negative: #c10015;
|
$negative: #c10015;
|
||||||
$info: #31ccec;
|
$info: #31ccec;
|
||||||
$warning: #f2c037;
|
$warning: #f2c037;
|
||||||
|
|
||||||
|
$color-spacer-light: rgba(255, 255, 255, .12);
|
||||||
|
$color-spacer:rgba(255, 255, 255, .3);
|
||||||
|
$border-thin-light: 1px solid $color-spacer-light;
|
||||||
|
|
||||||
|
$spacing-md: 16px;
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default function (value) {
|
||||||
|
if (value == null || value === '') return '-';
|
||||||
|
return value;
|
||||||
|
}
|
|
@ -2,10 +2,14 @@ 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';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
toLowerCase,
|
toLowerCase,
|
||||||
|
toLowerCamel,
|
||||||
toDate,
|
toDate,
|
||||||
toCurrency,
|
toCurrency,
|
||||||
toPercentage,
|
toPercentage,
|
||||||
|
dashIfEmpty,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,16 +9,18 @@ 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',
|
||||||
|
dataDeleted: 'Data deleted',
|
||||||
add: 'Add',
|
add: 'Add',
|
||||||
create: 'Create',
|
create: 'Create',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
remove: 'Remove',
|
remove: 'Remove',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
confirm: 'Confirm',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
yes: 'Yes',
|
yes: 'Yes',
|
||||||
no: 'No',
|
no: 'No',
|
||||||
|
@ -27,10 +29,10 @@ export default {
|
||||||
confirmRemove: 'You are about to delete this row. Are you sure?',
|
confirmRemove: 'You are about to delete this row. Are you sure?',
|
||||||
rowAdded: 'Row added',
|
rowAdded: 'Row added',
|
||||||
rowRemoved: 'Row removed',
|
rowRemoved: 'Row removed',
|
||||||
pleaseWait: 'Please wait...'
|
pleaseWait: 'Please wait...',
|
||||||
},
|
},
|
||||||
moduleIndex: {
|
moduleIndex: {
|
||||||
allModules: 'All modules'
|
allModules: 'All modules',
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
statusUnauthorized: 'Access denied',
|
statusUnauthorized: 'Access denied',
|
||||||
|
@ -46,12 +48,12 @@ export default {
|
||||||
keepLogin: 'Keep me logged in',
|
keepLogin: 'Keep me logged in',
|
||||||
loginSuccess: 'You have successfully logged in',
|
loginSuccess: 'You have successfully logged in',
|
||||||
loginError: 'Invalid username or password',
|
loginError: 'Invalid username or password',
|
||||||
fieldRequired: 'This field is required'
|
fieldRequired: 'This field is required',
|
||||||
},
|
},
|
||||||
dashboard: {
|
dashboard: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
dashboard: 'Dashboard',
|
dashboard: 'Dashboard',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
customer: {
|
customer: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
|
@ -59,13 +61,13 @@ export default {
|
||||||
list: 'List',
|
list: 'List',
|
||||||
createCustomer: 'Create customer',
|
createCustomer: 'Create customer',
|
||||||
summary: 'Summary',
|
summary: 'Summary',
|
||||||
basicData: 'Basic Data'
|
basicData: 'Basic Data',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
phone: 'Phone',
|
phone: 'Phone',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
customerOrders: 'Display customer orders',
|
customerOrders: 'Display customer orders',
|
||||||
moreOptions: 'More options'
|
moreOptions: 'More options',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
customerList: 'Customer list',
|
customerList: 'Customer list',
|
||||||
|
@ -79,7 +81,7 @@ export default {
|
||||||
isFrozen: 'Customer is frozen',
|
isFrozen: 'Customer is frozen',
|
||||||
hasDebt: 'Customer has debt',
|
hasDebt: 'Customer has debt',
|
||||||
notChecked: 'Customer not checked',
|
notChecked: 'Customer not checked',
|
||||||
noWebAccess: 'Web access is disabled'
|
noWebAccess: 'Web access is disabled',
|
||||||
},
|
},
|
||||||
summary: {
|
summary: {
|
||||||
basicData: 'Basic data',
|
basicData: 'Basic data',
|
||||||
|
@ -146,8 +148,8 @@ export default {
|
||||||
phone: 'Phone',
|
phone: 'Phone',
|
||||||
mobile: 'Mobile',
|
mobile: 'Mobile',
|
||||||
salesPerson: 'Sales person',
|
salesPerson: 'Sales person',
|
||||||
contactChannel: 'Contact channel'
|
contactChannel: 'Contact channel',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
ticket: {
|
ticket: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
|
@ -156,7 +158,7 @@ export default {
|
||||||
createTicket: 'Create ticket',
|
createTicket: 'Create ticket',
|
||||||
summary: 'Summary',
|
summary: 'Summary',
|
||||||
basicData: 'Basic Data',
|
basicData: 'Basic Data',
|
||||||
boxing: 'Boxing'
|
boxing: 'Boxing',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
nickname: 'Nickname',
|
nickname: 'Nickname',
|
||||||
|
@ -164,7 +166,7 @@ export default {
|
||||||
shipped: 'Shipped',
|
shipped: 'Shipped',
|
||||||
landed: 'Landed',
|
landed: 'Landed',
|
||||||
salesPerson: 'Sales person',
|
salesPerson: 'Sales person',
|
||||||
total: 'Total'
|
total: 'Total',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
ticketId: 'Ticket ID',
|
ticketId: 'Ticket ID',
|
||||||
|
@ -174,7 +176,7 @@ export default {
|
||||||
agency: 'Agency',
|
agency: 'Agency',
|
||||||
shipped: 'Shipped',
|
shipped: 'Shipped',
|
||||||
warehouse: 'Warehouse',
|
warehouse: 'Warehouse',
|
||||||
customerCard: 'Customer card'
|
customerCard: 'Customer card',
|
||||||
},
|
},
|
||||||
boxing: {
|
boxing: {
|
||||||
expedition: 'Expedition',
|
expedition: 'Expedition',
|
||||||
|
@ -183,8 +185,51 @@ export default {
|
||||||
worker: 'Worker',
|
worker: 'Worker',
|
||||||
selectTime: 'Select time:',
|
selectTime: 'Select time:',
|
||||||
selectVideo: 'Select video:',
|
selectVideo: 'Select video:',
|
||||||
notFound: 'No videos available'
|
notFound: 'No videos available',
|
||||||
}
|
},
|
||||||
|
summary: {
|
||||||
|
state: 'State',
|
||||||
|
salesPerson: 'Sales person',
|
||||||
|
agency: 'Agency',
|
||||||
|
zone: 'Zone',
|
||||||
|
warehouse: 'Warehouse',
|
||||||
|
route: 'Route',
|
||||||
|
invoice: 'Invoice',
|
||||||
|
shipped: 'Shipped',
|
||||||
|
landed: 'Landed',
|
||||||
|
packages: 'Packages',
|
||||||
|
consigneePhone: 'Consignee phone',
|
||||||
|
consigneeMobile: 'Consignee mobile',
|
||||||
|
clientPhone: 'Client phone',
|
||||||
|
clientMobile: 'Client mobile',
|
||||||
|
consignee: 'Consignee',
|
||||||
|
subtotal: 'Subtotal',
|
||||||
|
vat: 'VAT',
|
||||||
|
total: 'Total',
|
||||||
|
saleLines: 'Line items',
|
||||||
|
item: 'Item',
|
||||||
|
visible: 'Visible',
|
||||||
|
available: 'Available',
|
||||||
|
quantity: 'Quantity',
|
||||||
|
description: 'Description',
|
||||||
|
price: 'Price',
|
||||||
|
discount: 'Discount',
|
||||||
|
amount: 'Amount',
|
||||||
|
packing: 'Packing',
|
||||||
|
hasComponentLack: 'Component lack',
|
||||||
|
itemShortage: 'Not visible',
|
||||||
|
claim: 'Claim',
|
||||||
|
reserved: 'Reserved',
|
||||||
|
created: 'Created',
|
||||||
|
package: 'Package',
|
||||||
|
taxClass: 'Tax class',
|
||||||
|
services: 'Services',
|
||||||
|
changeState: 'Change state',
|
||||||
|
requester: 'Requester',
|
||||||
|
atender: 'Atender',
|
||||||
|
request: 'Request',
|
||||||
|
goTo: 'Go to',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
claim: {
|
claim: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
|
@ -194,21 +239,21 @@ export default {
|
||||||
rmaList: 'RMA',
|
rmaList: 'RMA',
|
||||||
summary: 'Summary',
|
summary: 'Summary',
|
||||||
basicData: 'Basic Data',
|
basicData: 'Basic Data',
|
||||||
rma: 'RMA'
|
rma: 'RMA',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
customer: 'Customer',
|
customer: 'Customer',
|
||||||
assignedTo: 'Assigned',
|
assignedTo: 'Assigned',
|
||||||
created: 'Created',
|
created: 'Created',
|
||||||
state: 'State'
|
state: 'State',
|
||||||
},
|
},
|
||||||
rmaList: {
|
rmaList: {
|
||||||
code: 'Code',
|
code: 'Code',
|
||||||
records: 'records'
|
records: 'records',
|
||||||
},
|
},
|
||||||
rma: {
|
rma: {
|
||||||
user: 'User',
|
user: 'User',
|
||||||
created: 'Created'
|
created: 'Created',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
claimId: 'Claim ID',
|
claimId: 'Claim ID',
|
||||||
|
@ -217,7 +262,7 @@ export default {
|
||||||
state: 'State',
|
state: 'State',
|
||||||
ticketId: 'Ticket ID',
|
ticketId: 'Ticket ID',
|
||||||
customerSummary: 'Customer summary',
|
customerSummary: 'Customer summary',
|
||||||
claimedTicket: 'Claimed ticket'
|
claimedTicket: 'Claimed ticket',
|
||||||
},
|
},
|
||||||
summary: {
|
summary: {
|
||||||
customer: 'Customer',
|
customer: 'Customer',
|
||||||
|
@ -237,7 +282,7 @@ export default {
|
||||||
actions: 'Actions',
|
actions: 'Actions',
|
||||||
responsibility: 'Responsibility',
|
responsibility: 'Responsibility',
|
||||||
company: 'Company',
|
company: 'Company',
|
||||||
person: 'Employee/Customer'
|
person: 'Employee/Customer',
|
||||||
},
|
},
|
||||||
basicData: {
|
basicData: {
|
||||||
customer: 'Customer',
|
customer: 'Customer',
|
||||||
|
@ -246,7 +291,50 @@ export default {
|
||||||
state: 'State',
|
state: 'State',
|
||||||
packages: 'Packages',
|
packages: 'Packages',
|
||||||
picked: 'Picked',
|
picked: 'Picked',
|
||||||
returnOfMaterial: 'Return of material authorization (RMA)'
|
returnOfMaterial: 'Return of material authorization (RMA)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invoiceOut: {
|
||||||
|
pageTitles: {
|
||||||
|
invoiceOuts: 'InvoiceOuts',
|
||||||
|
list: 'List',
|
||||||
|
createInvoiceOut: 'Create invoice out',
|
||||||
|
summary: 'Summary',
|
||||||
|
basicData: 'Basic Data',
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
ref: 'Reference',
|
||||||
|
issued: 'Issued',
|
||||||
|
amount: 'Amount',
|
||||||
|
client: 'Client',
|
||||||
|
created: 'Created',
|
||||||
|
company: 'Company',
|
||||||
|
dued: 'Due date',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
issued: 'Issued',
|
||||||
|
amount: 'Amount',
|
||||||
|
client: 'Client',
|
||||||
|
company: 'Company',
|
||||||
|
customerCard: 'Customer card',
|
||||||
|
ticketList: 'Ticket List',
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
issued: 'Issued',
|
||||||
|
created: 'Created',
|
||||||
|
dued: 'Due',
|
||||||
|
booked: 'Booked',
|
||||||
|
company: 'Company',
|
||||||
|
taxBreakdown: 'Tax breakdown',
|
||||||
|
type: 'Type',
|
||||||
|
taxableBase: 'Taxable base',
|
||||||
|
rate: 'Rate',
|
||||||
|
fee: 'Fee',
|
||||||
|
tickets: 'Tickets',
|
||||||
|
ticketId: 'Ticket id',
|
||||||
|
nickname: 'Alias',
|
||||||
|
shipped: 'Shipped',
|
||||||
|
totalWithVat: 'Amount',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
worker: {
|
worker: {
|
||||||
|
@ -295,6 +383,7 @@ export default {
|
||||||
subscribed: 'Subscribed to the notification',
|
subscribed: 'Subscribed to the notification',
|
||||||
unsubscribed: 'Unsubscribed from the notification',
|
unsubscribed: 'Unsubscribed from the notification',
|
||||||
},
|
},
|
||||||
|
imageNotFound: 'Image not found',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
topbar: {},
|
topbar: {},
|
||||||
|
@ -306,12 +395,16 @@ export default {
|
||||||
noData: 'No data to display',
|
noData: 'No data to display',
|
||||||
openCard: 'View card',
|
openCard: 'View card',
|
||||||
openSummary: 'Open summary',
|
openSummary: 'Open summary',
|
||||||
viewDescription: 'View description'
|
viewDescription: 'View description',
|
||||||
},
|
},
|
||||||
cardDescriptor: {
|
cardDescriptor: {
|
||||||
mainList: 'Main list',
|
mainList: 'Main list',
|
||||||
summary: 'Summary',
|
summary: 'Summary',
|
||||||
moreOptions: 'More options'
|
moreOptions: 'More options',
|
||||||
}
|
},
|
||||||
}
|
leftMenu: {
|
||||||
|
addToPinned: 'Add to pinned',
|
||||||
|
removeFromPinned: 'Remove from pinned',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,16 +9,18 @@ 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',
|
||||||
|
dataDeleted: 'Data deleted',
|
||||||
add: 'Añadir',
|
add: 'Añadir',
|
||||||
create: 'Crear',
|
create: 'Crear',
|
||||||
save: 'Guardar',
|
save: 'Guardar',
|
||||||
remove: 'Eliminar',
|
remove: 'Eliminar',
|
||||||
reset: 'Restaurar',
|
reset: 'Restaurar',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
|
confirm: 'Confirmar',
|
||||||
back: 'Volver',
|
back: 'Volver',
|
||||||
yes: 'Si',
|
yes: 'Si',
|
||||||
no: 'No',
|
no: 'No',
|
||||||
|
@ -27,10 +29,10 @@ export default {
|
||||||
confirmRemove: 'Vas a eliminar este registro. ¿Continuar?',
|
confirmRemove: 'Vas a eliminar este registro. ¿Continuar?',
|
||||||
rowAdded: 'Fila añadida',
|
rowAdded: 'Fila añadida',
|
||||||
rowRemoved: 'Fila eliminada',
|
rowRemoved: 'Fila eliminada',
|
||||||
pleaseWait: 'Por favor, espera...'
|
pleaseWait: 'Por favor, espera...',
|
||||||
},
|
},
|
||||||
moduleIndex: {
|
moduleIndex: {
|
||||||
allModules: 'Todos los módulos'
|
allModules: 'Todos los módulos',
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
statusUnauthorized: 'Acceso denegado',
|
statusUnauthorized: 'Acceso denegado',
|
||||||
|
@ -46,12 +48,12 @@ export default {
|
||||||
keepLogin: 'Mantener sesión iniciada',
|
keepLogin: 'Mantener sesión iniciada',
|
||||||
loginSuccess: 'Inicio de sesión correcto',
|
loginSuccess: 'Inicio de sesión correcto',
|
||||||
loginError: 'Nombre de usuario o contraseña incorrectos',
|
loginError: 'Nombre de usuario o contraseña incorrectos',
|
||||||
fieldRequired: 'Este campo es obligatorio'
|
fieldRequired: 'Este campo es obligatorio',
|
||||||
},
|
},
|
||||||
dashboard: {
|
dashboard: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
dashboard: 'Tablón',
|
dashboard: 'Tablón',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
customer: {
|
customer: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
|
@ -59,13 +61,13 @@ export default {
|
||||||
list: 'Listado',
|
list: 'Listado',
|
||||||
createCustomer: 'Crear cliente',
|
createCustomer: 'Crear cliente',
|
||||||
summary: 'Resumen',
|
summary: 'Resumen',
|
||||||
basicData: 'Datos básicos'
|
basicData: 'Datos básicos',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
phone: 'Teléfono',
|
phone: 'Teléfono',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
customerOrders: 'Mostrar órdenes del cliente',
|
customerOrders: 'Mostrar órdenes del cliente',
|
||||||
moreOptions: 'Más opciones'
|
moreOptions: 'Más opciones',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
customerId: 'ID cliente',
|
customerId: 'ID cliente',
|
||||||
|
@ -78,7 +80,7 @@ export default {
|
||||||
isFrozen: 'El cliente está congelado',
|
isFrozen: 'El cliente está congelado',
|
||||||
hasDebt: 'El cliente tiene riesgo',
|
hasDebt: 'El cliente tiene riesgo',
|
||||||
notChecked: 'El cliente no está comprobado',
|
notChecked: 'El cliente no está comprobado',
|
||||||
noWebAccess: 'El acceso web está desactivado'
|
noWebAccess: 'El acceso web está desactivado',
|
||||||
},
|
},
|
||||||
summary: {
|
summary: {
|
||||||
basicData: 'Datos básicos',
|
basicData: 'Datos básicos',
|
||||||
|
@ -145,8 +147,8 @@ export default {
|
||||||
phone: 'Teléfono',
|
phone: 'Teléfono',
|
||||||
mobile: 'Móvil',
|
mobile: 'Móvil',
|
||||||
salesPerson: 'Comercial',
|
salesPerson: 'Comercial',
|
||||||
contactChannel: 'Canal de contacto'
|
contactChannel: 'Canal de contacto',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
ticket: {
|
ticket: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
|
@ -155,7 +157,7 @@ export default {
|
||||||
createTicket: 'Crear ticket',
|
createTicket: 'Crear ticket',
|
||||||
summary: 'Resumen',
|
summary: 'Resumen',
|
||||||
basicData: 'Datos básicos',
|
basicData: 'Datos básicos',
|
||||||
boxing: 'Encajado'
|
boxing: 'Encajado',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
nickname: 'Alias',
|
nickname: 'Alias',
|
||||||
|
@ -163,7 +165,7 @@ export default {
|
||||||
shipped: 'Enviado',
|
shipped: 'Enviado',
|
||||||
landed: 'Entregado',
|
landed: 'Entregado',
|
||||||
salesPerson: 'Comercial',
|
salesPerson: 'Comercial',
|
||||||
total: 'Total'
|
total: 'Total',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
ticketId: 'ID ticket',
|
ticketId: 'ID ticket',
|
||||||
|
@ -173,7 +175,7 @@ export default {
|
||||||
agency: 'Agencia',
|
agency: 'Agencia',
|
||||||
shipped: 'Enviado',
|
shipped: 'Enviado',
|
||||||
warehouse: 'Almacén',
|
warehouse: 'Almacén',
|
||||||
customerCard: 'Ficha del cliente'
|
customerCard: 'Ficha del cliente',
|
||||||
},
|
},
|
||||||
boxing: {
|
boxing: {
|
||||||
expedition: 'Expedición',
|
expedition: 'Expedición',
|
||||||
|
@ -182,8 +184,51 @@ export default {
|
||||||
worker: 'Trabajador',
|
worker: 'Trabajador',
|
||||||
selectTime: 'Seleccionar hora:',
|
selectTime: 'Seleccionar hora:',
|
||||||
selectVideo: 'Seleccionar vídeo:',
|
selectVideo: 'Seleccionar vídeo:',
|
||||||
notFound: 'No hay vídeos disponibles'
|
notFound: 'No hay vídeos disponibles',
|
||||||
}
|
},
|
||||||
|
summary: {
|
||||||
|
state: 'Estado',
|
||||||
|
salesPerson: 'Comercial',
|
||||||
|
agency: 'Agencia',
|
||||||
|
zone: 'Zona',
|
||||||
|
warehouse: 'Almacén',
|
||||||
|
route: 'Ruta',
|
||||||
|
invoice: 'Factura',
|
||||||
|
shipped: 'Enviado',
|
||||||
|
landed: 'Entregado',
|
||||||
|
packages: 'Bultos',
|
||||||
|
consigneePhone: 'Tel. consignatario',
|
||||||
|
consigneeMobile: 'Móv. consignatario',
|
||||||
|
clientPhone: 'Tel. cliente',
|
||||||
|
clientMobile: 'Móv. cliente',
|
||||||
|
consignee: 'Consignatario',
|
||||||
|
subtotal: 'Subtotal',
|
||||||
|
vat: 'IVA',
|
||||||
|
total: 'Total',
|
||||||
|
saleLines: 'Líneas del pedido',
|
||||||
|
item: 'Artículo',
|
||||||
|
visible: 'Visible',
|
||||||
|
available: 'Disponible',
|
||||||
|
quantity: 'Cantidad',
|
||||||
|
description: 'Descripción',
|
||||||
|
price: 'Precio',
|
||||||
|
discount: 'Descuento',
|
||||||
|
amount: 'Importe',
|
||||||
|
packing: 'Encajado',
|
||||||
|
hasComponentLack: 'Faltan componentes',
|
||||||
|
itemShortage: 'No visible',
|
||||||
|
claim: 'Reclamación',
|
||||||
|
reserved: 'Reservado',
|
||||||
|
created: 'Fecha creación',
|
||||||
|
package: 'Embalaje',
|
||||||
|
taxClass: 'Tipo IVA',
|
||||||
|
services: 'Servicios',
|
||||||
|
changeState: 'Cambiar estado',
|
||||||
|
requester: 'Solicitante',
|
||||||
|
atender: 'Comprador',
|
||||||
|
request: 'Petición de compra',
|
||||||
|
goTo: 'Ir a',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
claim: {
|
claim: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
|
@ -193,21 +238,21 @@ export default {
|
||||||
rmaList: 'RMA',
|
rmaList: 'RMA',
|
||||||
summary: 'Resumen',
|
summary: 'Resumen',
|
||||||
basicData: 'Datos básicos',
|
basicData: 'Datos básicos',
|
||||||
rma: 'RMA'
|
rma: 'RMA',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
customer: 'Cliente',
|
customer: 'Cliente',
|
||||||
assignedTo: 'Asignada a',
|
assignedTo: 'Asignada a',
|
||||||
created: 'Creada',
|
created: 'Creada',
|
||||||
state: 'Estado'
|
state: 'Estado',
|
||||||
},
|
},
|
||||||
rmaList: {
|
rmaList: {
|
||||||
code: 'Código',
|
code: 'Código',
|
||||||
records: 'registros'
|
records: 'registros',
|
||||||
},
|
},
|
||||||
rma: {
|
rma: {
|
||||||
user: 'Usuario',
|
user: 'Usuario',
|
||||||
created: 'Creado'
|
created: 'Creado',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
claimId: 'ID reclamación',
|
claimId: 'ID reclamación',
|
||||||
|
@ -216,7 +261,7 @@ export default {
|
||||||
state: 'Estado',
|
state: 'Estado',
|
||||||
ticketId: 'ID ticket',
|
ticketId: 'ID ticket',
|
||||||
customerSummary: 'Resumen del cliente',
|
customerSummary: 'Resumen del cliente',
|
||||||
claimedTicket: 'Ticket reclamado'
|
claimedTicket: 'Ticket reclamado',
|
||||||
},
|
},
|
||||||
summary: {
|
summary: {
|
||||||
customer: 'Cliente',
|
customer: 'Cliente',
|
||||||
|
@ -236,7 +281,7 @@ export default {
|
||||||
actions: 'Acciones',
|
actions: 'Acciones',
|
||||||
responsibility: 'Responsabilidad',
|
responsibility: 'Responsabilidad',
|
||||||
company: 'Empresa',
|
company: 'Empresa',
|
||||||
person: 'Comercial/Cliente'
|
person: 'Comercial/Cliente',
|
||||||
},
|
},
|
||||||
basicData: {
|
basicData: {
|
||||||
customer: 'Cliente',
|
customer: 'Cliente',
|
||||||
|
@ -245,15 +290,59 @@ export default {
|
||||||
state: 'Estado',
|
state: 'Estado',
|
||||||
packages: 'Bultos',
|
packages: 'Bultos',
|
||||||
picked: 'Recogida',
|
picked: 'Recogida',
|
||||||
returnOfMaterial: 'Autorización de retorno de materiales (RMA)'
|
returnOfMaterial: 'Autorización de retorno de materiales (RMA)',
|
||||||
}
|
},
|
||||||
}, worker: {
|
},
|
||||||
|
invoiceOut: {
|
||||||
|
pageTitles: {
|
||||||
|
invoiceOuts: 'Fact. emitidas',
|
||||||
|
list: 'Listado',
|
||||||
|
createInvoiceOut: 'Crear fact. emitida',
|
||||||
|
summary: 'Resumen',
|
||||||
|
basicData: 'Datos básicos',
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
ref: 'Referencia',
|
||||||
|
issued: 'Fecha emisión',
|
||||||
|
amount: 'Importe',
|
||||||
|
client: 'Cliente',
|
||||||
|
created: 'Fecha creación',
|
||||||
|
company: 'Empresa',
|
||||||
|
dued: 'Fecha vencimineto',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
issued: 'Fecha emisión',
|
||||||
|
amount: 'Importe',
|
||||||
|
client: 'Cliente',
|
||||||
|
company: 'Empresa',
|
||||||
|
customerCard: 'Ficha del cliente',
|
||||||
|
ticketList: 'Listado de tickets',
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
issued: 'Fecha',
|
||||||
|
created: 'Fecha creación',
|
||||||
|
dued: 'Vencimiento',
|
||||||
|
booked: 'Contabilizada',
|
||||||
|
company: 'Empresa',
|
||||||
|
taxBreakdown: 'Desglose impositivo',
|
||||||
|
type: 'Tipo',
|
||||||
|
taxableBase: 'Base imp.',
|
||||||
|
rate: 'Tarifa',
|
||||||
|
fee: 'Cuota',
|
||||||
|
tickets: 'Tickets',
|
||||||
|
ticketId: 'Id ticket',
|
||||||
|
nickname: 'Alias',
|
||||||
|
shipped: 'F. envío',
|
||||||
|
totalWithVat: 'Importe',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
worker: {
|
||||||
pageTitles: {
|
pageTitles: {
|
||||||
workers: 'Trabajadores',
|
workers: 'Trabajadores',
|
||||||
list: 'Listado',
|
list: 'Listado',
|
||||||
basicData: 'Datos básicos',
|
basicData: 'Datos básicos',
|
||||||
summary: 'Resumen',
|
summary: 'Resumen',
|
||||||
notifications: 'Notificaciones'
|
notifications: 'Notificaciones',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
name: 'Nombre',
|
name: 'Nombre',
|
||||||
|
@ -293,6 +382,7 @@ export default {
|
||||||
subscribed: 'Te has suscrito a la notificación',
|
subscribed: 'Te has suscrito a la notificación',
|
||||||
unsubscribed: 'Te has dado de baja de la notificación',
|
unsubscribed: 'Te has dado de baja de la notificación',
|
||||||
},
|
},
|
||||||
|
imageNotFound: 'No se ha encontrado la imagen',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
topbar: {},
|
topbar: {},
|
||||||
|
@ -304,12 +394,16 @@ export default {
|
||||||
noData: 'Sin datos que mostrar',
|
noData: 'Sin datos que mostrar',
|
||||||
openCard: 'Ver ficha',
|
openCard: 'Ver ficha',
|
||||||
openSummary: 'Abrir detalles',
|
openSummary: 'Abrir detalles',
|
||||||
viewDescription: 'Ver descripción'
|
viewDescription: 'Ver descripción',
|
||||||
},
|
},
|
||||||
cardDescriptor: {
|
cardDescriptor: {
|
||||||
mainList: 'Listado principal',
|
mainList: 'Listado principal',
|
||||||
summary: 'Resumen',
|
summary: 'Resumen',
|
||||||
moreOptions: 'Más opciones',
|
moreOptions: 'Más opciones',
|
||||||
}
|
},
|
||||||
}
|
leftMenu: {
|
||||||
|
addToPinned: 'Añadir a fijados',
|
||||||
|
removeFromPinned: 'Eliminar de fijados',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import Navbar from 'src/components/Navbar.vue';
|
import { useQuasar } from 'quasar';
|
||||||
|
import Navbar from 'src/components/NavBar.vue';
|
||||||
|
|
||||||
|
const quasar = useQuasar();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-layout view="hHh LpR fFf">
|
<q-layout view="hHh LpR fFf">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
|
<q-footer v-if="quasar.platform.is.mobile"></q-footer>
|
||||||
</q-layout>
|
</q-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
import FetchData from 'src/components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import FormModel from 'src/components/FormModel.vue';
|
import FormModel from 'components/FormModel.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useState } from 'composables/useState';
|
||||||
import { useState } from 'src/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>
|
||||||
|
|
|
@ -4,8 +4,10 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toDate } from 'src/filters';
|
import { toDate } from 'src/filters';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
|
import TicketDescriptorPopover from 'pages/Ticket/Card/TicketDescriptorPopover.vue';
|
||||||
import TicketDescriptorPopover from 'src/pages/Ticket/Card/TicketDescriptorPopover.vue';
|
import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue';
|
||||||
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||||
|
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -57,7 +59,11 @@ function stateColor(code) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<skeleton-descriptor v-if="!claim" />
|
||||||
<card-descriptor v-if="claim" module="Claim" :data="claim" :description="claim.client.name">
|
<card-descriptor v-if="claim" module="Claim" :data="claim" :description="claim.client.name">
|
||||||
|
<template #menu>
|
||||||
|
<claim-descriptor-menu v-if="claim" :claim="claim" />
|
||||||
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item>
|
<q-item>
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
<script setup>
|
||||||
|
import axios from 'axios';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { usePrintService } from 'composables/usePrintService';
|
||||||
|
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
claim: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { openReport, sendEmail } = usePrintService();
|
||||||
|
|
||||||
|
const claim = ref($props.claim);
|
||||||
|
|
||||||
|
function openPickupOrder() {
|
||||||
|
const id = claim.value.id;
|
||||||
|
openReport(`Claims/${id}/claim-pickup-pdf`, {
|
||||||
|
recipientId: claim.value.clientFk,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmPickupOrder() {
|
||||||
|
const customer = claim.value.client;
|
||||||
|
quasar.dialog({
|
||||||
|
component: SendEmailDialog,
|
||||||
|
componentProps: {
|
||||||
|
address: customer.email,
|
||||||
|
send: sendPickupOrder,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPickupOrder(address) {
|
||||||
|
const id = claim.value.id;
|
||||||
|
const customer = claim.value.client;
|
||||||
|
return sendEmail(`Claims/${id}/claim-pickup-email`, {
|
||||||
|
recipientId: customer.id,
|
||||||
|
recipient: address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const showConfirmDialog = ref(false);
|
||||||
|
async function deleteClaim() {
|
||||||
|
const id = claim.value.id;
|
||||||
|
await axios.delete(`Claims/${id}`);
|
||||||
|
quasar.notify({
|
||||||
|
message: t('globals.dataDeleted'),
|
||||||
|
type: 'positive',
|
||||||
|
icon: 'check',
|
||||||
|
});
|
||||||
|
await router.push({ name: 'ClaimList' });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-item v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="summarize" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('pickupOrder') }}</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon name="keyboard_arrow_right" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-menu anchor="top end" self="top start" auto-close>
|
||||||
|
<q-list>
|
||||||
|
<q-item @click="openPickupOrder" v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="picture_as_pdf" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('openPickupOrder') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item @click="confirmPickupOrder" v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="send" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('sendPickupOrder') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
<q-item @click="showConfirmDialog = true" v-ripple clickable>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="delete" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ t('deleteClaim') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-dialog v-model="showConfirmDialog">
|
||||||
|
<q-card class="q-pa-sm">
|
||||||
|
<q-card-section class="row items-center q-pb-none">
|
||||||
|
<span class="text-h6 text-grey">{{ t('confirmDeletion') }}</span>
|
||||||
|
<q-space />
|
||||||
|
<q-btn icon="close" flat round dense v-close-popup />
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="row items-center">{{ t('confirmDeletionMessage') }}</q-card-section>
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn :label="t('globals.cancel')" color="primary" flat v-close-popup />
|
||||||
|
<q-btn :label="t('globals.confirm')" color="primary" @click="deleteClaim" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"pickupOrder": "Pickup order",
|
||||||
|
"openPickupOrder": "Open pickup order",
|
||||||
|
"sendPickupOrder": "Send pickup order",
|
||||||
|
"deleteClaim": "Delete claim",
|
||||||
|
"confirmDeletion": "Confirm deletion",
|
||||||
|
"confirmDeletionMessage": "Are you sure you want to delete this claim?"
|
||||||
|
},
|
||||||
|
"es": {
|
||||||
|
"pickupOrder": "Orden de recogida",
|
||||||
|
"openPickupOrder": "Abrir orden de recogida",
|
||||||
|
"sendPickupOrder": "Enviar orden de recogida",
|
||||||
|
"deleteClaim": "Eliminar reclamación",
|
||||||
|
"confirmDeletion": "Confirmar eliminación",
|
||||||
|
"confirmDeletionMessage": "Seguro que quieres eliminar esta reclamación?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</i18n>
|
|
@ -4,8 +4,9 @@ import { useI18n } from 'vue-i18n';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Paginate from 'src/components/Paginate.vue';
|
import Paginate from 'src/components/PaginateData.vue';
|
||||||
import FetchData from 'src/components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import TeleportSlot from 'components/ui/TeleportSlot';
|
||||||
import { toDate } from 'src/filters';
|
import { toDate } from 'src/filters';
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
|
@ -77,54 +78,44 @@ function hide() {
|
||||||
ref="fetcher"
|
ref="fetcher"
|
||||||
:url="`Claims/${route.params.id}`"
|
:url="`Claims/${route.params.id}`"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
@on-fetch="($data) => (claim = $data)"
|
@on-fetch="(data) => (claim = data)"
|
||||||
auto-load
|
auto-load
|
||||||
/>
|
/>
|
||||||
<div class="sticky-page">
|
<paginate :data="claim.rmas">
|
||||||
<q-page-sticky expand position="top">
|
<template #body="{ rows }">
|
||||||
<q-toolbar class="bg-grey-9">
|
<q-card class="card">
|
||||||
<q-space />
|
<template v-for="row of rows" :key="row.id">
|
||||||
<div class="q-gutter-md">
|
<q-item class="q-pa-none items-start">
|
||||||
<q-btn icon="add" :label="t('globals.add')" color="primary" @click="addRow()" />
|
<q-item-section class="q-pa-md">
|
||||||
</div>
|
<q-list>
|
||||||
</q-toolbar>
|
<q-item class="q-pa-none">
|
||||||
</q-page-sticky>
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('claim.rma.user') }}</q-item-label>
|
||||||
|
<q-item-label>{{ row.worker.user.name }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item class="q-pa-none">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('claim.rma.created') }}</q-item-label>
|
||||||
|
<q-item-label>
|
||||||
|
{{ toDate(row.created, { timeStyle: 'medium' }) }}
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-item-section>
|
||||||
|
<q-card-actions vertical class="justify-between">
|
||||||
|
<q-btn flat round color="orange" icon="vn:bin" @click="confirmRemove(row.id)">
|
||||||
|
<q-tooltip>{{ t('globals.remove') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
</paginate>
|
||||||
|
|
||||||
<paginate :data="claim.rmas">
|
|
||||||
<template #body="{ rows }">
|
|
||||||
<q-card class="card">
|
|
||||||
<template v-for="row of rows" :key="row.id">
|
|
||||||
<q-item class="q-pa-none items-start">
|
|
||||||
<q-item-section class="q-pa-md">
|
|
||||||
<q-list>
|
|
||||||
<q-item class="q-pa-none">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label caption>{{ t('claim.rma.user') }}</q-item-label>
|
|
||||||
<q-item-label>{{ row.worker.user.name }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item class="q-pa-none">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label caption>{{ t('claim.rma.created') }}</q-item-label>
|
|
||||||
<q-item-label>
|
|
||||||
{{ toDate(row.created, { timeStyle: 'medium' }) }}
|
|
||||||
</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-item-section>
|
|
||||||
<q-card-actions vertical class="justify-between">
|
|
||||||
<q-btn flat round color="orange" icon="vn:bin" @click="confirmRemove(row.id)">
|
|
||||||
<q-tooltip>{{ t('globals.remove') }}</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-item>
|
|
||||||
<q-separator />
|
|
||||||
</template>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
</paginate>
|
|
||||||
</div>
|
|
||||||
<q-dialog v-model="confirmShown" persistent @hide="hide">
|
<q-dialog v-model="confirmShown" persistent @hide="hide">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
|
@ -138,6 +129,20 @@ function hide() {
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
<teleport-slot v-if="!quasar.platform.is.mobile" to="#header-actions">
|
||||||
|
<div class="row q-gutter-x-sm">
|
||||||
|
<q-btn @click="addRow()" icon="add" color="primary" dense rounded>
|
||||||
|
<q-tooltip bottom> {{ t('globals.add') }} </q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-separator vertical />
|
||||||
|
</div>
|
||||||
|
</teleport-slot>
|
||||||
|
|
||||||
|
<teleport-slot to=".q-footer">
|
||||||
|
<q-tabs align="justify" inline-label narrow-indicator>
|
||||||
|
<q-tab @click="addRow()" icon="add_circle" :label="t('globals.add')" />
|
||||||
|
</q-tabs>
|
||||||
|
</teleport-slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { toDate, toCurrency } from 'src/filters';
|
import { toDate, toCurrency } from 'src/filters';
|
||||||
import SkeletonSummary from 'src/components/SkeletonSummary';
|
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
|
||||||
|
|
||||||
onMounted(() => fetch());
|
onMounted(() => fetch());
|
||||||
|
|
||||||
|
|
|
@ -19,3 +19,11 @@ const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||||
<claim-summary v-if="$props.id" :id="$props.id" />
|
<claim-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>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import Paginate from 'src/components/Paginate.vue';
|
import Paginate from 'src/components/PaginateData.vue';
|
||||||
import { toDate } from 'src/filters/index';
|
import { toDate } from 'src/filters/index';
|
||||||
import ClaimSummaryDialog from './Card/ClaimSummaryDialog.vue';
|
import ClaimSummaryDialog from './Card/ClaimSummaryDialog.vue';
|
||||||
import CustomerDescriptorPopover from 'src/pages/Customer/Card/CustomerDescriptorPopover.vue';
|
import CustomerDescriptorPopover from 'src/pages/Customer/Card/CustomerDescriptorPopover.vue';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import LeftMenu from 'src/components/LeftMenu.vue';
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Paginate from 'src/components/Paginate.vue';
|
import Paginate from 'src/components/PaginateData.vue';
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
|
||||||
|
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
|
||||||
|
import ClaimDescriptorMenu from '../Card/ClaimDescriptorMenu.vue';
|
||||||
|
|
||||||
|
const mockPush = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('vue-router', () => ({
|
||||||
|
useRouter: () => ({
|
||||||
|
push: mockPush,
|
||||||
|
currentRoute: {
|
||||||
|
value: {
|
||||||
|
params: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ClaimDescriptorMenu', () => {
|
||||||
|
let vm;
|
||||||
|
beforeAll(() => {
|
||||||
|
vm = createWrapper(ClaimDescriptorMenu, {
|
||||||
|
propsData: {
|
||||||
|
claim: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).vm;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteClaim()', () => {
|
||||||
|
it('should delete the claim', async () => {
|
||||||
|
jest.spyOn(axios, 'delete').mockResolvedValue({ data: true });
|
||||||
|
jest.spyOn(vm.quasar, 'notify');
|
||||||
|
|
||||||
|
await vm.deleteClaim();
|
||||||
|
|
||||||
|
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
|
||||||
|
{ 'type': 'positive' }
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,8 +4,8 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
import FetchData from 'src/components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import FormModel from 'src/components/FormModel.vue';
|
import FormModel from 'components/FormModel.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -33,7 +33,7 @@ const filterOptions = {
|
||||||
const id = row.id;
|
const id = row.id;
|
||||||
const name = row.name.toLowerCase();
|
const name = row.name.toLowerCase();
|
||||||
|
|
||||||
const idMatches = id == search;
|
const idMatches = id === search;
|
||||||
const nameMatches = name.indexOf(search) > -1;
|
const nameMatches = name.indexOf(search) > -1;
|
||||||
|
|
||||||
return idMatches || nameMatches;
|
return idMatches || nameMatches;
|
||||||
|
@ -48,9 +48,8 @@ const filterOptions = {
|
||||||
@on-fetch="setWorkers"
|
@on-fetch="setWorkers"
|
||||||
auto-load
|
auto-load
|
||||||
/>
|
/>
|
||||||
<fetch-data url="ContactChannels" @on-fetch="($data) => (contactChannels = $data)" auto-load />
|
<fetch-data url="ContactChannels" @on-fetch="(data) => contactChannels = data" auto-load />
|
||||||
<fetch-data url="BusinessTypes" @on-fetch="($data) => (businessTypes = $data)" auto-load />
|
<fetch-data url="BusinessTypes" @on-fetch="(data) => businessTypes = data" auto-load />
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<q-card>
|
<q-card>
|
||||||
<form-model :url="`Clients/${route.params.id}`" model="customer">
|
<form-model :url="`Clients/${route.params.id}`" model="customer">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toCurrency } from 'src/filters';
|
import { toCurrency } from 'src/filters';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||||
|
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -34,6 +35,7 @@ async function fetch() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<skeleton-descriptor v-if="!customer" />
|
||||||
<card-descriptor v-if="customer" module="Customer" :data="customer" :description="customer.name">
|
<card-descriptor v-if="customer" module="Customer" :data="customer" :description="customer.name">
|
||||||
<!-- <template #menu>
|
<!-- <template #menu>
|
||||||
<q-item clickable v-ripple>Option 1</q-item>
|
<q-item clickable v-ripple>Option 1</q-item>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { toCurrency, toPercentage, toDate } from 'src/filters';
|
import { toCurrency, toPercentage, toDate } from 'src/filters';
|
||||||
import SkeletonSummary from 'src/components/SkeletonSummary';
|
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
|
||||||
|
|
||||||
onMounted(() => fetch());
|
onMounted(() => fetch());
|
||||||
|
|
||||||
|
|
|
@ -19,3 +19,11 @@ const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||||
<customer-summary v-if="$props.id" :id="$props.id" />
|
<customer-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>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import Paginate from 'src/components/Paginate.vue';
|
import Paginate from 'src/components/PaginateData.vue';
|
||||||
import CustomerSummaryDialog from './Card/CustomerSummaryDialog.vue';
|
import CustomerSummaryDialog from './Card/CustomerSummaryDialog.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import LeftMenu from 'src/components/LeftMenu.vue';
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useState } from 'src/composables/useState';
|
|
||||||
import LeftMenu from 'src/components/LeftMenu.vue';
|
|
||||||
import { useNavigation } from 'src/composables/useNavigation';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const state = useState();
|
|
||||||
const modules = useNavigation();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
|
||||||
<q-scroll-area class="fit text-grey-8">
|
|
||||||
<LeftMenu />
|
|
||||||
</q-scroll-area>
|
|
||||||
</q-drawer>
|
|
||||||
<q-page-container>
|
|
||||||
<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="col-12 col-md" v-if="modules.favorites.value.length">
|
|
||||||
<div class="text-h6 text-grey-8 q-mb-sm">{{ t('globals.favoriteModules') }}</div>
|
|
||||||
<q-card class="row flex-container">
|
|
||||||
<div
|
|
||||||
v-for="module of modules.favorites.value"
|
|
||||||
:key="module.title"
|
|
||||||
class="row no-wrap q-pa-xs flex-item"
|
|
||||||
>
|
|
||||||
<q-btn
|
|
||||||
align="evenly"
|
|
||||||
padding="16px"
|
|
||||||
flat
|
|
||||||
stack
|
|
||||||
size="lg"
|
|
||||||
:icon="module.icon"
|
|
||||||
color="orange-6"
|
|
||||||
class="col-4 button"
|
|
||||||
:to="{ name: module.stateName }"
|
|
||||||
>
|
|
||||||
<div class="text-center text-primary button-text">
|
|
||||||
{{ t(`${module.name}.pageTitles.${module.title}`) }}
|
|
||||||
</div>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-page>
|
|
||||||
</q-page-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.flex-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.flex-item {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
.button {
|
|
||||||
width: 100%;
|
|
||||||
line-height: normal;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.button-text {
|
|
||||||
font-size: 10px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useState } from 'src/composables/useState';
|
||||||
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
|
import { useNavigationStore } from 'src/stores/useNavigationStore';
|
||||||
|
|
||||||
|
const state = useState();
|
||||||
|
const navigation = useNavigationStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
navigation.fetchPinned();
|
||||||
|
});
|
||||||
|
|
||||||
|
const pinnedModules = computed(() => navigation.getPinnedModules());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
||||||
|
<q-scroll-area class="fit text-grey-8">
|
||||||
|
<LeftMenu />
|
||||||
|
</q-scroll-area>
|
||||||
|
</q-drawer>
|
||||||
|
<q-page-container>
|
||||||
|
<q-page class="q-pa-md">
|
||||||
|
<div class="row items-start wrap q-col-gutter-md q-mb-lg">
|
||||||
|
<div class="col-12 col-md">
|
||||||
|
<div class="text-h6 text-grey-8 q-mb-sm">{{ t('globals.pinnedModules') }}</div>
|
||||||
|
<q-card class="row flex-container q-pa-md">
|
||||||
|
<div class="text-grey-5" v-if="pinnedModules.length === 0">
|
||||||
|
{{ t('pinnedInfo') }}
|
||||||
|
</div>
|
||||||
|
<template v-if="pinnedModules.length">
|
||||||
|
<div v-for="item of pinnedModules" :key="item.title" class="row no-wrap q-pa-xs flex-item">
|
||||||
|
<q-btn
|
||||||
|
align="evenly"
|
||||||
|
padding="16px"
|
||||||
|
flat
|
||||||
|
stack
|
||||||
|
size="lg"
|
||||||
|
:icon="item.icon"
|
||||||
|
color="orange-6"
|
||||||
|
class="col-4 button"
|
||||||
|
:to="{ name: item.name }"
|
||||||
|
>
|
||||||
|
<div class="text-center text-primary button-text">
|
||||||
|
{{ t(item.title) }}
|
||||||
|
</div>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</q-page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.flex-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.flex-item {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
line-height: normal;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.button-text {
|
||||||
|
font-size: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"pinnedInfo": "Your pinned modules will be shown here..."
|
||||||
|
},
|
||||||
|
"es": {
|
||||||
|
"pinnedInfo": "Tus módulos fijados aparecerán aquí..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</i18n>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup>
|
||||||
|
import { useState } from 'src/composables/useState';
|
||||||
|
import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue';
|
||||||
|
|
||||||
|
const state = useState();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
||||||
|
<q-scroll-area class="fit">
|
||||||
|
<InvoiceOutDescriptor />
|
||||||
|
</q-scroll-area>
|
||||||
|
</q-drawer>
|
||||||
|
<q-page-container>
|
||||||
|
<q-page class="q-pa-md">
|
||||||
|
<router-view></router-view>
|
||||||
|
</q-page>
|
||||||
|
</q-page-container>
|
||||||
|
</template>
|
|
@ -0,0 +1,103 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { toCurrency, toDate } from 'src/filters';
|
||||||
|
import axios from 'axios';
|
||||||
|
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
|
||||||
|
import CustomerDescriptorPopover from 'src/pages/Customer/Card/CustomerDescriptorPopover.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const entityId = computed(() => {
|
||||||
|
return $props.id || route.params.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
const invoiceOut = ref();
|
||||||
|
async function fetch() {
|
||||||
|
const filter = {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'company',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'code'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name', 'email'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = { params: { filter } };
|
||||||
|
const { data } = await axios.get(`InvoiceOuts/${entityId.value}`, options);
|
||||||
|
if (data) invoiceOut.value = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filter = computed(() => {
|
||||||
|
return invoiceOut.value ? JSON.stringify({ refFk: invoiceOut.value.ref }) : null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<card-descriptor v-if="invoiceOut" module="InvoiceOut" :data="invoiceOut" :description="invoiceOut.ref">
|
||||||
|
<template #body>
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.card.issued') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(invoiceOut.issued) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.card.amount') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toCurrency(invoiceOut.amount) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section v-if="invoiceOut.company">
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.card.client') }}</q-item-label>
|
||||||
|
<q-item-label class="link">
|
||||||
|
{{ invoiceOut.client.name }}
|
||||||
|
<q-popup-proxy>
|
||||||
|
<customer-descriptor-popover :id="invoiceOut.client.id" />
|
||||||
|
</q-popup-proxy>
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section v-if="invoiceOut.company">
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.card.company') }}</q-item-label>
|
||||||
|
<q-item-label>{{ invoiceOut.company.code }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
<q-card-actions>
|
||||||
|
<q-btn
|
||||||
|
size="md"
|
||||||
|
icon="vn:client"
|
||||||
|
color="primary"
|
||||||
|
:to="{ name: 'CustomerCard', params: { id: invoiceOut.client.id } }"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ t('invoiceOut.card.customerCard') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn size="md" icon="vn:ticket" color="primary" :to="{ name: 'TicketList', params: { q: filter } }">
|
||||||
|
<q-tooltip>{{ t('invoiceOut.card.ticketList') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
</template>
|
||||||
|
</card-descriptor>
|
||||||
|
</template>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup>
|
||||||
|
import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-card>
|
||||||
|
<invoiceOut-descriptor v-if="$props.id" :id="$props.id" />
|
||||||
|
</q-card>
|
||||||
|
</template>
|
|
@ -0,0 +1,200 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { toCurrency, toDate } from 'src/filters';
|
||||||
|
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
|
||||||
|
onMounted(() => fetch());
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const entityId = computed(() => $props.id || route.params.id);
|
||||||
|
|
||||||
|
const invoiceOut = ref(null);
|
||||||
|
const tax = ref(null);
|
||||||
|
const tikets = ref(null);
|
||||||
|
|
||||||
|
function fetch() {
|
||||||
|
const id = entityId.value;
|
||||||
|
|
||||||
|
axios.get(`InvoiceOuts/${id}/summary`).then(({ data }) => {
|
||||||
|
invoiceOut.value = data.invoiceOut;
|
||||||
|
tax.value = data.invoiceOut.taxesBreakdown;
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.get(`InvoiceOuts/${id}/getTickets`).then(({ data }) => {
|
||||||
|
tikets.value = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxColumns = ref([
|
||||||
|
{
|
||||||
|
name: 'item',
|
||||||
|
label: 'invoiceOut.summary.type',
|
||||||
|
field: (row) => row.name,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landed',
|
||||||
|
label: 'invoiceOut.summary.taxableBase',
|
||||||
|
field: (row) => row.taxableBase,
|
||||||
|
format: (value) => toCurrency(value),
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quantity',
|
||||||
|
label: 'invoiceOut.summary.rate',
|
||||||
|
field: (row) => row.rate,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invoiceOuted',
|
||||||
|
label: 'invoiceOut.summary.fee',
|
||||||
|
field: (row) => row.vat,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ticketsColumns = ref([
|
||||||
|
{
|
||||||
|
name: 'item',
|
||||||
|
label: 'invoiceOut.summary.ticketId',
|
||||||
|
field: (row) => row.id,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quantity',
|
||||||
|
label: 'invoiceOut.summary.nickname',
|
||||||
|
field: (row) => row.nickname,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landed',
|
||||||
|
label: 'invoiceOut.summary.shipped',
|
||||||
|
field: (row) => row.shipped,
|
||||||
|
format: (value) => toDate(value),
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landed',
|
||||||
|
label: 'invoiceOut.summary.totalWithVat',
|
||||||
|
field: (row) => row.totalWithVat,
|
||||||
|
format: (value) => toCurrency(value),
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="summary container">
|
||||||
|
<q-card>
|
||||||
|
<skeleton-summary v-if="!invoiceOut" />
|
||||||
|
<template v-if="invoiceOut">
|
||||||
|
<div class="header bg-primary q-pa-sm q-mb-md">
|
||||||
|
{{ invoiceOut.ref }} - {{ invoiceOut.client.socialName }}
|
||||||
|
</div>
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.summary.issued') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(invoiceOut.issued) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.summary.dued') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(invoiceOut.dued) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.summary.created') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(invoiceOut.created) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.summary.booked') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(invoiceOut.booked) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.summary.company') }}</q-item-label>
|
||||||
|
<q-item-label>{{ invoiceOut.company.code }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
<q-card-section class="q-pa-md">
|
||||||
|
<h6>{{ t('invoiceOut.summary.taxBreakdown') }}</h6>
|
||||||
|
<q-table :columns="taxColumns" :rows="tax" flat>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ t(col.label) }}
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pa-md">
|
||||||
|
<h6>{{ t('invoiceOut.summary.tickets') }}</h6>
|
||||||
|
<q-table :columns="ticketsColumns" :rows="tikets" flat>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ t(col.label) }}
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-card-section>
|
||||||
|
</template>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-card {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 950px;
|
||||||
|
max-width: 950px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slider-container {
|
||||||
|
max-width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.q-slider {
|
||||||
|
.q-slider__marker-labels:nth-child(1) {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
.q-slider__marker-labels:nth-child(2) {
|
||||||
|
transform: none;
|
||||||
|
left: auto !important;
|
||||||
|
right: 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-dialog .summary {
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<script setup>
|
||||||
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
|
import InvoiceOutSummary from './InvoiceOutSummary.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits([...useDialogPluginComponent.emits]);
|
||||||
|
|
||||||
|
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-dialog ref="dialogRef" @hide="onDialogHide">
|
||||||
|
<invoiceOut-summary v-if="$props.id" :id="$props.id" />
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.q-dialog .summary .header {
|
||||||
|
position: sticky;
|
||||||
|
z-index: $z-max;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,83 @@
|
||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import Paginate from 'src/components/PaginateData.vue';
|
||||||
|
import InvoiceOutSummaryDialog from './Card/InvoiceOutSummaryDialog.vue';
|
||||||
|
import { toDate, toCurrency } from 'src/filters/index';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
function navigate(id) {
|
||||||
|
router.push({ path: `/invoiceOut/${id}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewSummary(id) {
|
||||||
|
quasar.dialog({
|
||||||
|
component: InvoiceOutSummaryDialog,
|
||||||
|
componentProps: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-page class="q-pa-md">
|
||||||
|
<paginate url="/InvoiceOuts/filter" sort-by="issued DESC, id DESC" auto-load>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<q-card class="card" v-for="row of rows" :key="row.id">
|
||||||
|
<q-item class="q-pa-none items-start cursor-pointer q-hoverable" v-ripple clickable>
|
||||||
|
<q-item-section class="q-pa-md" @click="navigate(row.id)">
|
||||||
|
<div class="text-h6">{{ row.ref }}</div>
|
||||||
|
<q-item-label caption>#{{ row.id }}</q-item-label>
|
||||||
|
<q-list>
|
||||||
|
<q-item class="q-pa-none">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.list.issued') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(row.issued) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.list.amount') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toCurrency(row.amount) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item class="q-pa-none">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.list.client') }}</q-item-label>
|
||||||
|
<q-item-label>{{ row.clientSocialName }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.list.created') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(row.created) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item class="q-pa-none">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.list.company') }}</q-item-label>
|
||||||
|
<q-item-label>{{ row.companyCode }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('invoiceOut.list.dued') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(row.dued) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-item-section>
|
||||||
|
<q-separator vertical />
|
||||||
|
<q-card-actions vertical class="justify-between">
|
||||||
|
<q-btn flat round color="orange" icon="arrow_circle_right" @click="navigate(row.id)">
|
||||||
|
<q-tooltip>{{ t('components.smartCard.openCard') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn flat round color="grey-7" icon="preview" @click="viewSummary(row.id)">
|
||||||
|
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-item>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
</paginate>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script setup>
|
||||||
|
import { useState } from 'src/composables/useState';
|
||||||
|
import LeftMenu from 'src/components/LeftMenu.vue';
|
||||||
|
|
||||||
|
const state = useState();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
||||||
|
<q-scroll-area class="fit text-grey-8">
|
||||||
|
<LeftMenu />
|
||||||
|
</q-scroll-area>
|
||||||
|
</q-drawer>
|
||||||
|
<q-page-container>
|
||||||
|
<router-view></router-view>
|
||||||
|
</q-page-container>
|
||||||
|
</template>
|
|
@ -1,6 +1,6 @@
|
||||||
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
|
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
|
||||||
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
|
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
|
||||||
import Login from '../Login.vue';
|
import Login from '../LoginMain.vue';
|
||||||
|
|
||||||
const mockPush = jest.fn();
|
const mockPush = jest.fn();
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,11 @@ const { t } = useI18n();
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
<i18n>
|
<i18n>
|
||||||
{
|
{
|
||||||
'en': {
|
"en": {
|
||||||
'notFound': 'Oops. Nothing here...'
|
"notFound": "Oops. Nothing here..."
|
||||||
},
|
},
|
||||||
'es': {
|
"es": {
|
||||||
'notFound': 'Vaya. Nada por aquí...'
|
"notFound": "Vaya. Nada por aquí..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
|
@ -79,69 +79,71 @@ async function getVideoList(expeditionId, timed) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-drawer show-if-above side="right">
|
<teleport to=".q-layout">
|
||||||
<q-scroll-area class="fit">
|
<q-drawer show-if-above side="right">
|
||||||
<q-list bordered separator style="max-width: 318px">
|
<q-scroll-area class="fit">
|
||||||
<q-item v-if="lastExpedition && videoList.length">
|
<q-list bordered separator style="max-width: 318px">
|
||||||
<q-item-section>
|
<q-item v-if="lastExpedition && videoList.length">
|
||||||
<q-item-label class="text-h6">
|
<q-item-section>
|
||||||
{{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ time.max }})
|
<q-item-label class="text-h6">
|
||||||
</q-item-label>
|
{{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ time.max }})
|
||||||
<q-range
|
</q-item-label>
|
||||||
v-model="time"
|
<q-range
|
||||||
@change="getVideoList(lastExpedition, time)"
|
v-model="time"
|
||||||
:min="0"
|
@change="getVideoList(lastExpedition, time)"
|
||||||
:max="24"
|
:min="0"
|
||||||
:step="1"
|
:max="24"
|
||||||
:left-label-value="time.min + ':00'"
|
:step="1"
|
||||||
:right-label-value="time.max + ':00'"
|
:left-label-value="time.min + ':00'"
|
||||||
label
|
:right-label-value="time.max + ':00'"
|
||||||
markers
|
label
|
||||||
snap
|
markers
|
||||||
color="orange"
|
snap
|
||||||
/>
|
color="orange"
|
||||||
</q-item-section>
|
/>
|
||||||
</q-item>
|
</q-item-section>
|
||||||
<q-item v-if="lastExpedition && videoList.length">
|
</q-item>
|
||||||
<q-item-section>
|
<q-item v-if="lastExpedition && videoList.length">
|
||||||
<q-select
|
<q-item-section>
|
||||||
color="orange"
|
<q-select
|
||||||
v-model="slide"
|
color="orange"
|
||||||
:options="videoList"
|
v-model="slide"
|
||||||
:label="t('ticket.boxing.selectVideo')"
|
:options="videoList"
|
||||||
emit-value
|
:label="t('ticket.boxing.selectVideo')"
|
||||||
map-options
|
emit-value
|
||||||
>
|
map-options
|
||||||
<template #prepend>
|
>
|
||||||
<q-icon name="schedule" />
|
<template #prepend>
|
||||||
</template>
|
<q-icon name="schedule" />
|
||||||
</q-select>
|
</template>
|
||||||
</q-item-section>
|
</q-select>
|
||||||
</q-item>
|
</q-item-section>
|
||||||
<q-item
|
</q-item>
|
||||||
v-for="expedition in expeditions"
|
<q-item
|
||||||
:key="expedition.id"
|
v-for="expedition in expeditions"
|
||||||
@click="getVideoList(expedition.id)"
|
:key="expedition.id"
|
||||||
clickable
|
@click="getVideoList(expedition.id)"
|
||||||
v-ripple
|
clickable
|
||||||
>
|
v-ripple
|
||||||
<q-item-section>
|
>
|
||||||
<q-item-label class="text-h6">#{{ expedition.id }}</q-item-label>
|
<q-item-section>
|
||||||
</q-item-section>
|
<q-item-label class="text-h6">#{{ expedition.id }}</q-item-label>
|
||||||
<q-item-section>
|
</q-item-section>
|
||||||
<q-item-label caption>{{ t('ticket.boxing.created') }}</q-item-label>
|
<q-item-section>
|
||||||
<q-item-label>
|
<q-item-label caption>{{ t('ticket.boxing.created') }}</q-item-label>
|
||||||
{{ date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') }}
|
<q-item-label>
|
||||||
</q-item-label>
|
{{ date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') }}
|
||||||
<q-item-label caption>{{ t('ticket.boxing.item') }}</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label>{{ expedition.packagingItemFk }}</q-item-label>
|
<q-item-label caption>{{ t('ticket.boxing.item') }}</q-item-label>
|
||||||
<q-item-label caption>{{ t('ticket.boxing.worker') }}</q-item-label>
|
<q-item-label>{{ expedition.packagingItemFk }}</q-item-label>
|
||||||
<q-item-label>{{ expedition.userName }}</q-item-label>
|
<q-item-label caption>{{ t('ticket.boxing.worker') }}</q-item-label>
|
||||||
</q-item-section>
|
<q-item-label>{{ expedition.userName }}</q-item-label>
|
||||||
</q-item>
|
</q-item-section>
|
||||||
</q-list>
|
</q-item>
|
||||||
</q-scroll-area>
|
</q-list>
|
||||||
</q-drawer>
|
</q-scroll-area>
|
||||||
|
</q-drawer>
|
||||||
|
</teleport>
|
||||||
|
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-carousel animated v-model="slide" height="max-content">
|
<q-carousel animated v-model="slide" height="max-content">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -4,8 +4,9 @@ import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { toDate } from 'src/filters';
|
import { toDate } from 'src/filters';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
|
|
||||||
import CustomerDescriptorPopover from 'src/pages/Customer/Card/CustomerDescriptorPopover.vue';
|
import CustomerDescriptorPopover from 'src/pages/Customer/Card/CustomerDescriptorPopover.vue';
|
||||||
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||||
|
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -42,6 +43,7 @@ function stateColor(state) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<skeleton-descriptor v-if="!ticket" />
|
||||||
<card-descriptor v-if="ticket" module="Ticket" :data="ticket" :description="ticket.client.name">
|
<card-descriptor v-if="ticket" module="Ticket" :data="ticket" :description="ticket.client.name">
|
||||||
<!-- <template #menu>
|
<!-- <template #menu>
|
||||||
<q-item clickable v-ripple>Option 1</q-item>
|
<q-item clickable v-ripple>Option 1</q-item>
|
||||||
|
|
|
@ -0,0 +1,553 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, computed, onUpdated } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { dashIfEmpty, toDate, toCurrency } from 'src/filters';
|
||||||
|
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||||
|
|
||||||
|
onMounted(() => fetch());
|
||||||
|
onUpdated(() => fetch());
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const entityId = computed(() => $props.id || route.params.id);
|
||||||
|
|
||||||
|
const ticket = ref();
|
||||||
|
const salesLines = ref(null);
|
||||||
|
const editableStates = ref([]);
|
||||||
|
async function fetch() {
|
||||||
|
const { data } = await axios.get(`Tickets/${entityId.value}/summary`);
|
||||||
|
if (data) {
|
||||||
|
ticket.value = data;
|
||||||
|
salesLines.value = data.sales;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stateColor(state) {
|
||||||
|
if (state.code === 'OK') return 'text-green';
|
||||||
|
if (state.code === 'FREE') return 'text-blue-3';
|
||||||
|
if (state.alertLevel === 1) return 'text-primary';
|
||||||
|
if (state.alertLevel === 0) return 'text-red';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formattedAddress() {
|
||||||
|
if (!ticket.value) return '';
|
||||||
|
|
||||||
|
const address = this.ticket.address;
|
||||||
|
const postcode = address.postalCode;
|
||||||
|
const province = address.province ? `(${address.province.name})` : '';
|
||||||
|
|
||||||
|
return `${address.street} - ${postcode} - ${address.city} ${province}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditable() {
|
||||||
|
try {
|
||||||
|
return !this.ticket.ticketState.state.alertLevel;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeState(value) {
|
||||||
|
if (!this.ticket.id) return;
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
ticketFk: this.ticket.id,
|
||||||
|
code: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
await axios.post(`TicketTrackings/changeState`, formData);
|
||||||
|
await router.go(route.fullPath);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<fetch-data url="States/editableStates" @on-fetch="(data) => (editableStates = data)" auto-load />
|
||||||
|
<div class="summary container">
|
||||||
|
<q-card>
|
||||||
|
<skeleton-summary v-if="!ticket" />
|
||||||
|
<template v-if="ticket">
|
||||||
|
<div class="header bg-primary q-pa-sm q-mb-md">
|
||||||
|
<span>
|
||||||
|
Ticket #{{ ticket.id }} - {{ ticket.client.name }} ({{ ticket.client.id }}) -
|
||||||
|
{{ ticket.nickname }}
|
||||||
|
</span>
|
||||||
|
<q-btn-dropdown
|
||||||
|
side
|
||||||
|
top
|
||||||
|
color="orange-11"
|
||||||
|
text-color="black"
|
||||||
|
:label="t('ticket.summary.changeState')"
|
||||||
|
:disable="!isEditable()"
|
||||||
|
>
|
||||||
|
<q-list>
|
||||||
|
<q-virtual-scroll
|
||||||
|
style="max-height: 300px"
|
||||||
|
:items="editableStates"
|
||||||
|
separator
|
||||||
|
v-slot="{ item, index }"
|
||||||
|
>
|
||||||
|
<q-item :key="index" dense clickable v-close-popup @click="changeState(item.code)">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ item.name }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-virtual-scroll>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="row q-pa-md q-col-gutter-md q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.state') }}</q-item-label>
|
||||||
|
<q-item-label :class="stateColor(ticket.ticketState.state)">
|
||||||
|
{{ ticket.ticketState.state.name }}
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.salesPerson') }}</q-item-label>
|
||||||
|
<q-item-label class="link">{{ ticket.client.salesPersonUser.name }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.agency') }}</q-item-label>
|
||||||
|
<q-item-label>{{ ticket.agencyMode.name }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.zone') }}</q-item-label>
|
||||||
|
<q-item-label class="link">{{ ticket.routeFk }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.warehouse') }}</q-item-label>
|
||||||
|
<q-item-label>{{ ticket.warehouse.name }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.invoice') }}</q-item-label>
|
||||||
|
<q-item-label v-if="ticket.refFk" class="link">{{ ticket.refFk }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.shipped') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(ticket.shipped) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.landed') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toDate(ticket.landed) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.packages') }}</q-item-label>
|
||||||
|
<q-item-label>{{ ticket.packages }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.consigneePhone') }}</q-item-label>
|
||||||
|
<q-item-label>{{ ticket.address.phone }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.consigneeMobile') }}</q-item-label>
|
||||||
|
<q-item-label>{{ ticket.address.mobile }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.clientPhone') }}</q-item-label>
|
||||||
|
<q-item-label>{{ ticket.client.phone }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.clientMobile') }}</q-item-label>
|
||||||
|
<q-item-label>{{ ticket.client.mobile }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.consignee') }}</q-item-label>
|
||||||
|
<q-item-label>{{ formattedAddress() }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<q-list>
|
||||||
|
<q-item v-for="note in ticket.notes" :key="note.id">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>
|
||||||
|
{{ note.observationType.description }}
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label>
|
||||||
|
{{ note.description }}
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<q-list class="taxes">
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.subtotal') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toCurrency(ticket.totalWithoutVat) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.vat') }}</q-item-label>
|
||||||
|
<q-item-label>{{
|
||||||
|
toCurrency(ticket.totalWithVat - ticket.totalWithoutVat)
|
||||||
|
}}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>{{ t('ticket.summary.total') }}</q-item-label>
|
||||||
|
<q-item-label>{{ toCurrency(ticket.totalWithVat) }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-pa-md" v-if="salesLines.length > 0">
|
||||||
|
<div class="col">
|
||||||
|
<q-list>
|
||||||
|
<q-item-label header class="text-h6">
|
||||||
|
{{ t('ticket.summary.saleLines') }}
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'TicketBasicData', params: { id: entityId } }"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<q-icon name="open_in_new" />
|
||||||
|
</router-link>
|
||||||
|
</q-item-label>
|
||||||
|
<q-table :rows="ticket.sales" flat>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th auto-width></q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.item') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.visible') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.available') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.discount') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.amount') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.packing') }}</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
<template #body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-td>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="xs"
|
||||||
|
icon="vn:claims"
|
||||||
|
v-if="props.row.claim"
|
||||||
|
color="primary"
|
||||||
|
:to="{ name: 'ClaimCard', params: { id: props.row.claim.claimFk } }"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
>{{ t('ticket.summary.claim') }}:
|
||||||
|
{{ props.row.claim.claimFk }}</q-tooltip
|
||||||
|
>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="xs"
|
||||||
|
icon="vn:claims"
|
||||||
|
v-if="props.row.claimBeginning"
|
||||||
|
color="primary"
|
||||||
|
:to="{
|
||||||
|
name: 'ClaimCard',
|
||||||
|
params: { id: props.row.claimBeginning.claimFk },
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
>{{ t('ticket.summary.claim') }}:
|
||||||
|
{{ props.row.claimBeginning.claimFk }}</q-tooltip
|
||||||
|
>
|
||||||
|
</q-btn>
|
||||||
|
<q-icon
|
||||||
|
name="warning"
|
||||||
|
v-show="props.row.visible < 0"
|
||||||
|
size="xs"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
>{{ t('ticket.summary.visible') }}:
|
||||||
|
{{ props.row.visible }}</q-tooltip
|
||||||
|
>
|
||||||
|
</q-icon>
|
||||||
|
<q-icon
|
||||||
|
name="vn:reserva"
|
||||||
|
v-show="props.row.reserved"
|
||||||
|
size="xs"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ t('ticket.summary.reserved') }}</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
<q-icon
|
||||||
|
name="vn:unavailable"
|
||||||
|
v-show="props.row.itemShortage"
|
||||||
|
size="xs"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ t('ticket.summary.itemShortage') }}</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
<q-icon
|
||||||
|
name="vn:components"
|
||||||
|
v-show="props.row.hasComponentLack"
|
||||||
|
size="xs"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ t('ticket.summary.hasComponentLack') }}</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
</q-td>
|
||||||
|
<q-td class="link">{{ props.row.itemFk }}</q-td>
|
||||||
|
<q-td>{{ props.row.visible }}</q-td>
|
||||||
|
<q-td>{{ props.row.available }}</q-td>
|
||||||
|
<q-td>{{ props.row.quantity }}</q-td>
|
||||||
|
<q-td>
|
||||||
|
<div class="fetched-tags">
|
||||||
|
<span>{{ props.row.item.name }}</span>
|
||||||
|
<span v-if="props.row.item.subName" class="subName">{{
|
||||||
|
props.row.item.subName
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<fetched-tags :item="props.row.item" :max-length="5"></fetched-tags>
|
||||||
|
</q-td>
|
||||||
|
<q-td>{{ props.row.price }}</q-td>
|
||||||
|
<q-td>{{ props.row.discount }} %</q-td>
|
||||||
|
<q-td
|
||||||
|
>{{
|
||||||
|
toCurrency(
|
||||||
|
props.row.quantity *
|
||||||
|
props.row.price *
|
||||||
|
((100 - props.row.discount) / 100)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</q-td>
|
||||||
|
<q-td>{{ dashIfEmpty(props.row.item.itemPackingTypeFk) }}</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-pa-md" v-if="ticket.packagings.length > 0 || ticket.services.length > 0">
|
||||||
|
<div class="col" v-if="ticket.packagings.length > 0">
|
||||||
|
<q-list>
|
||||||
|
<q-item-label header class="text-h6">
|
||||||
|
{{ t('ticket.summary.packages') }}
|
||||||
|
<q-icon name="open_in_new" />
|
||||||
|
</q-item-label>
|
||||||
|
<q-table :rows="ticket.packagings" flat>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.created') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.package') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
<template #body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-td>{{ toDate(props.row.created) }}</q-td>
|
||||||
|
<q-td>{{ props.row.packaging.item.name }}</q-td>
|
||||||
|
<q-td>{{ props.row.quantity }}</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
<div class="col" v-if="ticket.services.length > 0">
|
||||||
|
<q-list>
|
||||||
|
<q-item-label header class="text-h6">
|
||||||
|
{{ t('ticket.summary.services') }}
|
||||||
|
<q-icon name="open_in_new" />
|
||||||
|
</q-item-label>
|
||||||
|
<q-table :rows="ticket.services" flat>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.taxClass') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.amount') }}</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
<template #body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-td>{{ props.row.quantity }}</q-td>
|
||||||
|
<q-td>{{ props.row.description }}</q-td>
|
||||||
|
<q-td>{{ toCurrency(props.row.price) }}</q-td>
|
||||||
|
<q-td>{{ props.row.taxClass.description }}</q-td>
|
||||||
|
<q-td>{{ toCurrency(props.row.quantity * props.row.price) }}</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-pa-md" v-if="ticket.requests.length > 0">
|
||||||
|
<div class="col">
|
||||||
|
<q-list>
|
||||||
|
<q-item-label header class="text-h6">
|
||||||
|
{{ t('ticket.summary.request') }}
|
||||||
|
<q-icon name="open_in_new" />
|
||||||
|
</q-item-label>
|
||||||
|
<q-table :rows="ticket.requests" flat>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.created') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.requester') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.atender') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
|
||||||
|
<q-th auto-width>{{ t('ticket.summary.item') }}</q-th>
|
||||||
|
<q-th auto-width>Ok</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
<template #body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-td>{{ props.row.description }}</q-td>
|
||||||
|
<q-td>{{ toDate(props.row.created) }}</q-td>
|
||||||
|
<q-td>{{ props.row.requester.user.name }}</q-td>
|
||||||
|
<q-td>{{ props.row.atender.user.name }}</q-td>
|
||||||
|
<q-td>{{ props.row.quantity }}</q-td>
|
||||||
|
<q-td>{{ toCurrency(props.row.price) }}</q-td>
|
||||||
|
<q-td v-if="!props.row.sale">-</q-td>
|
||||||
|
<q-td v-if="props.row.sale" class="link">{{ props.row.sale.itemFk }}</q-td>
|
||||||
|
<q-td><q-checkbox v-model="props.row.isOk" :disable="true" /></q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-card {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
.q-list {
|
||||||
|
.q-item__label--header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fetched-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
& span {
|
||||||
|
flex-basis: 50%;
|
||||||
|
}
|
||||||
|
& span.subName {
|
||||||
|
flex-basis: 50%;
|
||||||
|
color: $secondary;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.q-table__container {
|
||||||
|
text-align: left;
|
||||||
|
.q-icon {
|
||||||
|
padding: 2%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.taxes {
|
||||||
|
border: $border-thin-light;
|
||||||
|
text-align: right;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.col {
|
||||||
|
min-width: 250px;
|
||||||
|
padding-left: 1.5%;
|
||||||
|
padding-right: 1.5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: 18px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-btn {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-dialog .summary {
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<script setup>
|
||||||
|
import { useDialogPluginComponent } from 'quasar';
|
||||||
|
import TicketSummary from './TicketSummary.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits([...useDialogPluginComponent.emits]);
|
||||||
|
|
||||||
|
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-dialog ref="dialogRef" @hide="onDialogHide">
|
||||||
|
<ticket-summary v-if="$props.id" :id="$props.id" />
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.q-dialog .summary .header {
|
||||||
|
position: sticky;
|
||||||
|
z-index: $z-max;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -17,7 +17,7 @@ jest.mock('vue-router', () => ({
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// #4836 - Investigate how to test q-drawer outside
|
// #4836 - Investigate how to test q-drawer outside
|
||||||
// q-layout or how to teleport q-drawer inside
|
// q-layout or how to teleport q-drawer inside
|
||||||
xdescribe('TicketBoxing', () => {
|
xdescribe('TicketBoxing', () => {
|
||||||
let vm;
|
let vm;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import Paginate from 'src/components/Paginate.vue';
|
import Paginate from 'src/components/PaginateData.vue';
|
||||||
import { toDate, toCurrency } from 'src/filters/index';
|
import { toDate, toCurrency } from 'src/filters/index';
|
||||||
// import TicketSummary from './Card/TicketSummary.vue';
|
import TicketSummaryDialog from './Card/TicketSummaryDialog.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const quasar = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
|
@ -48,15 +49,13 @@ function navigate(id) {
|
||||||
router.push({ path: `/ticket/${id}` });
|
router.push({ path: `/ticket/${id}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const preview = ref({
|
function viewSummary(id) {
|
||||||
shown: false,
|
quasar.dialog({
|
||||||
});
|
component: TicketSummaryDialog,
|
||||||
|
componentProps: {
|
||||||
function showPreview(id) {
|
id,
|
||||||
preview.value.shown = true;
|
},
|
||||||
preview.value.data = {
|
});
|
||||||
customerId: id,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -108,42 +107,16 @@ function showPreview(id) {
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-separator vertical />
|
<q-separator vertical />
|
||||||
<q-card-actions vertical class="justify-between">
|
<q-card-actions vertical class="justify-between">
|
||||||
<!-- <q-btn color="grey-7" round flat icon="more_vert">
|
|
||||||
<q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip>
|
|
||||||
<q-menu cover auto-close>
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="add" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>Add a note</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="history" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>Display customer history</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn> -->
|
|
||||||
|
|
||||||
<q-btn flat round color="orange" icon="arrow_circle_right" @click="navigate(row.id)">
|
<q-btn flat round color="orange" icon="arrow_circle_right" @click="navigate(row.id)">
|
||||||
<q-tooltip>{{ t('components.smartCard.openCard') }}</q-tooltip>
|
<q-tooltip>{{ t('components.smartCard.openCard') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn flat round color="grey-7" icon="preview" @click="showPreview(row.id)">
|
<q-btn flat round color="grey-7" icon="preview" @click="viewSummary(row.id)">
|
||||||
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
|
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<!-- <q-btn flat round color="grey-7" icon="vn:ticket">
|
|
||||||
<q-tooltip>{{ t('customer.list.customerOrders') }}</q-tooltip>
|
|
||||||
</q-btn> -->
|
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-card>
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
</paginate>
|
</paginate>
|
||||||
</q-page>
|
</q-page>
|
||||||
<!-- <q-dialog v-model="preview.shown">
|
|
||||||
<customer-summary :customer-id="preview.data.customerId" />
|
|
||||||
</q-dialog> -->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import LeftMenu from 'src/components/LeftMenu.vue';
|
import LeftMenu from 'components/LeftMenu.vue';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -14,8 +14,8 @@ const $props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
await fetch();
|
fetch();
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -53,13 +53,15 @@ const filter = {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const { data } = await axios.get(`Workers/${entityId.value}`, {
|
axios
|
||||||
params: {
|
.get(`Workers/${entityId.value}`, {
|
||||||
filter: JSON.stringify(filter),
|
params: {
|
||||||
},
|
filter: JSON.stringify(filter),
|
||||||
});
|
},
|
||||||
|
})
|
||||||
if (data) worker.value = data;
|
.then((response) => {
|
||||||
|
worker.value = response.data;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sipExtension() {
|
function sipExtension() {
|
||||||
|
@ -73,50 +75,50 @@ function getWorkerAvatar() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<card-descriptor v-if="worker" module="Worker" :data="worker" :description="worker.user.nickname">
|
<card-descriptor v-if="worker" module="Worker" :data="worker" :description="worker.user.name">
|
||||||
|
<template #before>
|
||||||
|
<q-img :src="getWorkerAvatar()" class="q-mb-md">
|
||||||
|
<template #error>
|
||||||
|
<div class="absolute-full bg-grey-10 text-center q-pa-md flex flex-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-grey-5" style="opacity: 0.4; font-size: 5vh">
|
||||||
|
<q-icon name="vn:claims" />
|
||||||
|
</div>
|
||||||
|
<div class="text-grey-5" style="opacity: 0.4">
|
||||||
|
{{ t('worker.imageNotFound') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-img>
|
||||||
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<q-img :src="getWorkerAvatar()" class="q-mb-md" />
|
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="person" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption> {{ t('worker.card.name') }} </q-item-label>
|
<q-item-label caption> {{ t('worker.card.name') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.user.nickname }}</q-item-label>
|
<q-item-label>{{ worker.user.nickname }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="email" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption> {{ t('worker.card.email') }} </q-item-label>
|
<q-item-label caption> {{ t('worker.card.email') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.user.email }}</q-item-label>
|
<q-item-label>{{ worker.user.email }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="work" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption> {{ t('worker.list.department') }} </q-item-label>
|
<q-item-label caption> {{ t('worker.list.department') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.department.department.name }}</q-item-label>
|
<q-item-label>{{ worker.department.department.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="phone" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption> {{ t('worker.card.phone') }} </q-item-label>
|
<q-item-label caption> {{ t('worker.card.phone') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.phone }}</q-item-label>
|
<q-item-label>{{ worker.phone }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="extension" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.summary.sipExtension') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.summary.sipExtension') }} </q-item-label>
|
||||||
<q-item-label>{{ sipExtension() }}</q-item-label>
|
<q-item-label>{{ sipExtension() }}</q-item-label>
|
||||||
|
|
|
@ -91,7 +91,9 @@ function openDescriptorDialog(id) {
|
||||||
<q-card>
|
<q-card>
|
||||||
<skeleton-summary v-if="!worker" />
|
<skeleton-summary v-if="!worker" />
|
||||||
<template v-if="worker">
|
<template v-if="worker">
|
||||||
<div class="header bg-primary q-pa-sm q-mb-md">{{ worker.id }} - {{ worker.firstName }}</div>
|
<div class="header bg-primary q-pa-sm q-mb-md">
|
||||||
|
{{ worker.id }} - {{ worker.firstName }} {{ worker.lastName }}
|
||||||
|
</div>
|
||||||
<div class="row q-pa-md q-col-gutter-md q-mb-md">
|
<div class="row q-pa-md q-col-gutter-md q-mb-md">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-list>
|
<q-list>
|
||||||
|
@ -99,58 +101,38 @@ function openDescriptorDialog(id) {
|
||||||
{{ t('worker.summary.basicData') }}
|
{{ t('worker.summary.basicData') }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="grid_3x3" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption> ID </q-item-label>
|
<q-item-label caption> ID </q-item-label>
|
||||||
<q-item-label>{{ worker.id }}</q-item-label>
|
<q-item-label>{{ worker.id }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="person" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.card.name') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.card.name') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.user.nickname }}</q-item-label>
|
<q-item-label>{{ worker.user.nickname }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="work" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.list.department') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.list.department') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.department.department.name }}</q-item-label>
|
<q-item-label>{{ worker.department.department.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="email" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.list.email') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.list.email') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.user.email }}</q-item-label>
|
<q-item-label>{{ worker.user.email }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item class="items-start cursor-pointer q-hoverable" v-if="worker.boss">
|
<q-item class="items-start cursor-pointer q-hoverable" v-if="worker.boss">
|
||||||
<q-item-section side @click="openDescriptorDialog(worker.bossFk)">
|
|
||||||
<q-icon name="person" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section @click="openDescriptorDialog(worker.bossFk)">
|
<q-item-section @click="openDescriptorDialog(worker.bossFk)">
|
||||||
<q-item-label caption>{{ t('worker.summary.boss') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.summary.boss') }} </q-item-label>
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
<a>
|
{{ worker.boss.name }}
|
||||||
{{ worker.boss.name }}
|
|
||||||
</a>
|
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="grid_3x3" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.summary.phoneExtension') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.summary.phoneExtension') }} </q-item-label>
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
|
@ -159,21 +141,17 @@ function openDescriptorDialog(id) {
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="phone" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.summary.entPhone') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.summary.entPhone') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.phone }}</q-item-label>
|
<q-item-label>{{ worker.phone == '' ? worker.phone : '-' }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="phone_android" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.summary.personalPhone') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.summary.personalPhone') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.client.phone }}</q-item-label>
|
<q-item-label>{{
|
||||||
|
worker.client.phone == '' ? worker.client.phone : '-'
|
||||||
|
}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
@ -184,36 +162,24 @@ function openDescriptorDialog(id) {
|
||||||
{{ t('worker.summary.userData') }}
|
{{ t('worker.summary.userData') }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="grid_3x3" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption> {{ t('worker.summary.userId') }} </q-item-label>
|
<q-item-label caption> {{ t('worker.summary.userId') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.user.id }}</q-item-label>
|
<q-item-label>{{ worker.user.id }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="person" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.card.name') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.card.name') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.user.nickname }}</q-item-label>
|
<q-item-label>{{ worker.user.nickname }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="group" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.summary.role') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.summary.role') }} </q-item-label>
|
||||||
<q-item-label>{{ worker.user.role.name }}</q-item-label>
|
<q-item-label>{{ worker.user.role.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="extension" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.summary.sipExtension') }} </q-item-label>
|
<q-item-label caption>{{ t('worker.summary.sipExtension') }} </q-item-label>
|
||||||
<q-item-label>{{ sipExtension() }}</q-item-label>
|
<q-item-label>{{ sipExtension() }}</q-item-label>
|
||||||
|
@ -227,6 +193,13 @@ function openDescriptorDialog(id) {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import Paginate from 'src/components/Paginate.vue';
|
import Paginate from 'src/components/PaginateData.vue';
|
||||||
import WorkerSummaryDialog from './Card/WorkerSummaryDialog.vue';
|
import WorkerSummaryDialog from './Card/WorkerSummaryDialog.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -52,21 +52,23 @@ function viewSummary(id) {
|
||||||
<q-card class="card" v-for="row in rows" :key="row.id">
|
<q-card class="card" v-for="row in rows" :key="row.id">
|
||||||
<q-item class="q-pa-none items-start cursor-pointer q-hoverable" v-ripple clickable>
|
<q-item class="q-pa-none items-start cursor-pointer q-hoverable" v-ripple clickable>
|
||||||
<q-item-section class="q-pa-md" @click="navigate(row.id)">
|
<q-item-section class="q-pa-md" @click="navigate(row.id)">
|
||||||
<q-item-label caption>#{{ row.id }}</q-item-label>
|
|
||||||
<q-item-label class="text-h6">{{ row.user.nickname }}</q-item-label>
|
<q-item-label class="text-h6">{{ row.user.nickname }}</q-item-label>
|
||||||
|
<q-item-label caption>#{{ row.id }}</q-item-label>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item class="q-pa-none">
|
<q-item class="q-pa-none">
|
||||||
<q-item-section class="q-pa-md">
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.list.name') }}</q-item-label>
|
<q-item-label caption>{{ t('worker.list.name') }}</q-item-label>
|
||||||
<q-item-label>{{ row.user.name }}</q-item-label>
|
<q-item-label>{{ row.user.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section class="q-pa-md">
|
</q-item>
|
||||||
|
<q-item class="q-pa-none">
|
||||||
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.list.email') }}</q-item-label>
|
<q-item-label caption>{{ t('worker.list.email') }}</q-item-label>
|
||||||
<q-item-label>{{ row.user.email }}</q-item-label>
|
<q-item-label>{{ row.user.email }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item class="q-pa-none">
|
<q-item class="q-pa-none">
|
||||||
<q-item-section class="q-pa-md">
|
<q-item-section>
|
||||||
<q-item-label caption>{{ t('worker.list.department') }}</q-item-label>
|
<q-item-label caption>{{ t('worker.list.department') }}</q-item-label>
|
||||||
<q-item-label>{{ row.department.department.name }}</q-item-label>
|
<q-item-label>{{ row.department.department.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
@ -75,14 +77,12 @@ function viewSummary(id) {
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-separator vertical />
|
<q-separator vertical />
|
||||||
<q-card-actions vertical class="justify-between">
|
<q-card-actions vertical class="justify-between">
|
||||||
<q-item-section side class="q-pa-md">
|
<q-btn flat round color="orange" icon="preview" @click="viewSummary(row.id)">
|
||||||
<q-btn flat round color="orange" icon="preview" @click="viewSummary(row.id)">
|
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
|
||||||
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
|
</q-btn>
|
||||||
</q-btn>
|
<q-btn flat round color="grey-7" icon="schedule" @click="navigate(row.id)">
|
||||||
<q-btn flat round color="grey-7" icon="schedule" @click="navigate(row.id)">
|
<q-tooltip>{{ t('worker.list.schedule') }}</q-tooltip>
|
||||||
<q-tooltip>{{ t('worker.list.schedule') }}</q-tooltip>
|
</q-btn>
|
||||||
</q-btn>
|
|
||||||
</q-item-section>
|
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
|
@ -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();
|
||||||
|
@ -25,8 +25,8 @@ export default route(function (/* { store, ssrContext } */) {
|
||||||
const createHistory = process.env.SERVER
|
const createHistory = process.env.SERVER
|
||||||
? createMemoryHistory
|
? createMemoryHistory
|
||||||
: process.env.VUE_ROUTER_MODE === 'history'
|
: process.env.VUE_ROUTER_MODE === 'history'
|
||||||
? createWebHistory
|
? createWebHistory
|
||||||
: createWebHashHistory;
|
: createWebHashHistory;
|
||||||
|
|
||||||
const Router = createRouter({
|
const Router = createRouter({
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
|
@ -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,13 @@
|
||||||
|
import Customer from './customer';
|
||||||
|
import Ticket from './ticket';
|
||||||
|
import Claim from './claim';
|
||||||
|
import InvoiceOut from './invoiceOut';
|
||||||
|
import Worker from './worker';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
Customer,
|
||||||
|
Ticket,
|
||||||
|
Claim,
|
||||||
|
InvoiceOut,
|
||||||
|
Worker
|
||||||
|
]
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
path: '/invoiceOut',
|
||||||
|
name: 'InvoiceOut',
|
||||||
|
meta: {
|
||||||
|
title: 'invoiceOuts',
|
||||||
|
icon: 'vn:invoice-out'
|
||||||
|
},
|
||||||
|
component: RouterView,
|
||||||
|
redirect: { name: 'InvoiceOutMain' },
|
||||||
|
menus: {
|
||||||
|
main: ['InvoiceOutList']
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'InvoiceOutMain',
|
||||||
|
component: () => import('src/pages/InvoiceOut/InvoiceOutMain.vue'),
|
||||||
|
redirect: { name: 'InvoiceOutList' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'list',
|
||||||
|
name: 'InvoiceOutList',
|
||||||
|
meta: {
|
||||||
|
title: 'list',
|
||||||
|
icon: 'view_list',
|
||||||
|
},
|
||||||
|
component: () => import('src/pages/InvoiceOut/InvoiceOutList.vue'),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'InvoiceOutCard',
|
||||||
|
path: ':id',
|
||||||
|
component: () => import('src/pages/InvoiceOut/Card/InvoiceOutCard.vue'),
|
||||||
|
redirect: { name: 'InvoiceOutSummary' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'InvoiceOutSummary',
|
||||||
|
path: 'summary',
|
||||||
|
meta: {
|
||||||
|
title: 'summary'
|
||||||
|
},
|
||||||
|
component: () => import('src/pages/InvoiceOut/Card/InvoiceOutSummary.vue'),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
|
@ -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'),
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,31 +1,34 @@
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Worker',
|
|
||||||
path: '/worker',
|
path: '/worker',
|
||||||
|
name: 'Worker',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'workers',
|
title: 'workers',
|
||||||
icon: 'vn:worker'
|
icon: 'vn:worker',
|
||||||
},
|
},
|
||||||
component: RouterView,
|
component: RouterView,
|
||||||
redirect: { name: 'WorkerMain' },
|
redirect: { name: 'WorkerMain' },
|
||||||
|
menus: {
|
||||||
|
main: ['WorkerList'],
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'WorkerMain',
|
|
||||||
path: '',
|
path: '',
|
||||||
|
name: 'WorkerMain',
|
||||||
component: () => import('src/pages/Worker/WorkerMain.vue'),
|
component: () => import('src/pages/Worker/WorkerMain.vue'),
|
||||||
redirect: { name: 'WorkerList' },
|
redirect: { name: 'WorkerList' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'WorkerList',
|
|
||||||
path: 'list',
|
path: 'list',
|
||||||
|
name: 'WorkerList',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'list',
|
title: 'list',
|
||||||
icon: 'view_list',
|
icon: 'view_list',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Worker/WorkerList.vue'),
|
component: () => import('src/pages/Worker/WorkerList.vue'),
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'WorkerCard',
|
name: 'WorkerCard',
|
||||||
|
@ -37,7 +40,7 @@ export default {
|
||||||
name: 'WorkerSummary',
|
name: 'WorkerSummary',
|
||||||
path: 'summary',
|
path: 'summary',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'summary'
|
title: 'summary',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Worker/Card/WorkerSummary.vue'),
|
component: () => import('src/pages/Worker/Card/WorkerSummary.vue'),
|
||||||
},
|
},
|
||||||
|
@ -45,11 +48,11 @@ export default {
|
||||||
name: 'WorkerNotificationsManager',
|
name: 'WorkerNotificationsManager',
|
||||||
path: 'notifications',
|
path: 'notifications',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'notifications'
|
title: 'notifications',
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Worker/Card/WorkerNotificationsManager.vue'),
|
component: () => import('src/pages/Worker/Card/WorkerNotificationsManager.vue'),
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,13 +2,14 @@ import customer from './modules/customer';
|
||||||
import ticket from './modules/ticket';
|
import ticket from './modules/ticket';
|
||||||
import claim from './modules/claim';
|
import claim from './modules/claim';
|
||||||
import worker from './modules/worker';
|
import worker from './modules/worker';
|
||||||
|
import invoiceOut from './modules/invoiceOut';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
meta: { title: 'logIn' },
|
meta: { title: 'logIn' },
|
||||||
component: () => import('../pages/Login/Login.vue')
|
component: () => import('../pages/Login/LoginMain.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
@ -20,20 +21,21 @@ const routes = [
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
meta: { title: 'dashboard', icon: 'dashboard' },
|
meta: { title: 'dashboard', icon: 'dashboard' },
|
||||||
component: () => import('../pages/Dashboard/Dashboard.vue'),
|
component: () => import('../pages/Dashboard/DashboardMain.vue'),
|
||||||
},
|
},
|
||||||
// Module routes
|
// Module routes
|
||||||
customer,
|
customer,
|
||||||
ticket,
|
ticket,
|
||||||
claim,
|
claim,
|
||||||
worker,
|
worker,
|
||||||
|
invoiceOut,
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
name: 'NotFound',
|
name: 'NotFound',
|
||||||
component: () => import('../pages/NotFound.vue'),
|
component: () => import('../pages/NotFound.vue'),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
|
@ -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', 'worker'];
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
describe('WorkerList', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport(1280, 720);
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit('/#/worker/list');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load workers', () => {
|
||||||
|
cy.get('div[class="q-item__label text-h6"]').eq(0).should('have.text', 'Jessica Jones');
|
||||||
|
cy.get('div[class="q-item__label text-h6"]').eq(1).should('have.text', 'Bruce Banner');
|
||||||
|
cy.get('div[class="q-item__label text-h6"]').eq(2).should('have.text', 'Charles Xavier');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open the worker summary', () => {
|
||||||
|
cy.get('div[class="q-item__section column q-item__section--side justify-center q-pa-md"]').eq(0).click();
|
||||||
|
cy.get('div[class="header bg-primary q-pa-sm q-mb-md"').should('have.text', '1110 - Jessica Jones');
|
||||||
|
cy.get('div[class="q-item__label q-item__label--header text-h6"]').eq(0).should('have.text', 'Basic data');
|
||||||
|
cy.get('div[class="q-item__label q-item__label--header text-h6"]').eq(1).should('have.text', 'User data');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
describe('WorkerSummary', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport(1280, 720)
|
||||||
|
cy.login('developer')
|
||||||
|
cy.visit('/#/worker/19/summary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load worker summary', () => {
|
||||||
|
cy.get('div[class="header bg-primary q-pa-sm q-mb-md"').should('have.text', '19 - salesBoss');
|
||||||
|
cy.get('div[class="q-item__label q-item__label--header text-h6"]').eq(0).should('have.text', 'Basic data');
|
||||||
|
cy.get('div[class="q-item__label q-item__label--header text-h6"]').eq(1).should('have.text', 'User data');
|
||||||
|
cy.get('div[class="q-item__section column q-item__section--main justify-center"]').eq(0).should('have.text', 'NamesalesBossNick');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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