diff --git a/cypress.config.js b/cypress.config.js index 1924144f6..a9e27fcfd 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -14,8 +14,8 @@ export default defineConfig({ downloadsFolder: 'test/cypress/downloads', video: false, specPattern: 'test/cypress/integration/**/*.spec.js', - experimentalRunAllSpecs: true, - watchForFileChanges: true, + experimentalRunAllSpecs: false, + watchForFileChanges: false, reporter: 'cypress-mochawesome-reporter', reporterOptions: { charts: true, diff --git a/package.json b/package.json index 17f39cad7..d23ed0ced 100644 --- a/package.json +++ b/package.json @@ -1,74 +1,74 @@ { - "name": "salix-front", - "version": "25.06.0", - "description": "Salix frontend", - "productName": "Salix", - "author": "Verdnatura", - "private": true, - "packageManager": "pnpm@8.15.1", - "type": "module", - "scripts": { - "resetDatabase": "cd ../salix && gulp docker", - "lint": "eslint --ext .js,.vue ./", - "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", - "test:e2e": "cypress open", - "test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run", - "test": "echo \"See package.json => scripts for available tests.\" && exit 0", - "test:unit": "vitest", - "test:unit:ci": "vitest run", - "commitlint": "commitlint --edit", - "prepare": "npx husky install", - "addReferenceTag": "node .husky/addReferenceTag.js", - "docs:dev": "vitepress dev docs", - "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs" - }, - "dependencies": { - "@quasar/cli": "^2.4.1", - "@quasar/extras": "^1.16.16", - "axios": "^1.4.0", - "chromium": "^3.0.3", - "croppie": "^2.6.5", - "moment": "^2.30.1", - "pinia": "^2.1.3", - "quasar": "^2.17.7", - "validator": "^13.9.0", - "vue": "^3.5.13", - "vue-i18n": "^9.3.0", - "vue-router": "^4.2.5" - }, - "devDependencies": { - "@commitlint/cli": "^19.2.1", - "@commitlint/config-conventional": "^19.1.0", - "@intlify/unplugin-vue-i18n": "^0.8.2", - "@pinia/testing": "^0.1.2", - "@quasar/app-vite": "^2.0.8", - "@quasar/quasar-app-extension-qcalendar": "^4.0.2", - "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0", - "@vue/test-utils": "^2.4.4", - "autoprefixer": "^10.4.14", - "cypress": "^13.6.6", - "cypress-mochawesome-reporter": "^3.8.2", - "eslint": "^9.18.0", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-cypress": "^4.1.0", - "eslint-plugin-vue": "^9.32.0", - "husky": "^8.0.0", - "postcss": "^8.4.23", - "prettier": "^3.4.2", - "sass": "^1.83.4", - "vitepress": "^1.6.3", - "vitest": "^0.34.0" - }, - "engines": { - "node": "^20 || ^18 || ^16", - "npm": ">= 8.1.2", - "yarn": ">= 1.21.1", - "bun": ">= 1.0.25" - }, - "overrides": { - "@vitejs/plugin-vue": "^5.2.1", - "vite": "^6.0.11", - "vitest": "^0.31.1" - } + "name": "salix-front", + "version": "25.08.0", + "description": "Salix frontend", + "productName": "Salix", + "author": "Verdnatura", + "private": true, + "packageManager": "pnpm@8.15.1", + "type": "module", + "scripts": { + "resetDatabase": "cd ../salix && gulp docker", + "lint": "eslint --ext .js,.vue ./", + "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", + "test:e2e": "cypress open", + "test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run", + "test": "echo \"See package.json => scripts for available tests.\" && exit 0", + "test:unit": "vitest", + "test:unit:ci": "vitest run", + "commitlint": "commitlint --edit", + "prepare": "npx husky install", + "addReferenceTag": "node .husky/addReferenceTag.js", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + }, + "dependencies": { + "@quasar/cli": "^2.4.1", + "@quasar/extras": "^1.16.16", + "axios": "^1.4.0", + "chromium": "^3.0.3", + "croppie": "^2.6.5", + "moment": "^2.30.1", + "pinia": "^2.1.3", + "quasar": "^2.17.7", + "validator": "^13.9.0", + "vue": "^3.5.13", + "vue-i18n": "^9.3.0", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@commitlint/cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", + "@intlify/unplugin-vue-i18n": "^0.8.2", + "@pinia/testing": "^0.1.2", + "@quasar/app-vite": "^2.0.8", + "@quasar/quasar-app-extension-qcalendar": "^4.0.2", + "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0", + "@vue/test-utils": "^2.4.4", + "autoprefixer": "^10.4.14", + "cypress": "^13.6.6", + "cypress-mochawesome-reporter": "^3.8.2", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-cypress": "^4.1.0", + "eslint-plugin-vue": "^9.32.0", + "husky": "^8.0.0", + "postcss": "^8.4.23", + "prettier": "^3.4.2", + "sass": "^1.83.4", + "vitepress": "^1.6.3", + "vitest": "^0.34.0" + }, + "engines": { + "node": "^20 || ^18 || ^16", + "npm": ">= 8.1.2", + "yarn": ">= 1.21.1", + "bun": ">= 1.0.25" + }, + "overrides": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.0.11", + "vitest": "^0.31.1" + } } \ No newline at end of file diff --git a/quasar.config.js b/quasar.config.js index 6d545c026..9467c92af 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -30,7 +30,6 @@ export default configure(function (/* ctx */) { // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli/boot-files boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'], - // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ['app.scss'], diff --git a/src/boot/defaults/constants.js b/src/boot/defaults/constants.js new file mode 100644 index 000000000..c96ceb2d1 --- /dev/null +++ b/src/boot/defaults/constants.js @@ -0,0 +1,2 @@ +export const langs = ['en', 'es']; +export const decimalPlaces = 2; diff --git a/src/boot/keyShortcut.js b/src/boot/keyShortcut.js index 5afb5b74a..6da06c8bf 100644 --- a/src/boot/keyShortcut.js +++ b/src/boot/keyShortcut.js @@ -1,6 +1,6 @@ export default { - mounted: function (el, binding) { - const shortcut = binding.value ?? '+'; + mounted(el, binding) { + const shortcut = binding.value || '+'; const { key, ctrl, alt, callback } = typeof shortcut === 'string' @@ -8,25 +8,24 @@ export default { key: shortcut, ctrl: true, alt: true, - callback: () => - document - .querySelector(`button[shortcut="${shortcut}"]`) - ?.click(), + callback: () => el?.click(), } : binding.value; + if (!el.hasAttribute('shortcut')) { + el.setAttribute('shortcut', key); + } + const handleKeydown = (event) => { if (event.key === key && (!ctrl || event.ctrlKey) && (!alt || event.altKey)) { callback(); } }; - // Attach the event listener to the window window.addEventListener('keydown', handleKeydown); - el._handleKeydown = handleKeydown; }, - unmounted: function (el) { + unmounted(el) { if (el._handleKeydown) { window.removeEventListener('keydown', el._handleKeydown); } diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index 97d80c670..cb31391b3 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -9,19 +9,19 @@ export default { if (!form) return; try { const inputsFormCard = form.querySelectorAll( - `input:not([disabled]):not([type="checkbox"])` + `input:not([disabled]):not([type="checkbox"])`, ); if (inputsFormCard.length) { focusFirstInput(inputsFormCard[0]); } const textareas = document.querySelectorAll( - 'textarea:not([disabled]), [contenteditable]:not([disabled])' + 'textarea:not([disabled]), [contenteditable]:not([disabled])', ); if (textareas.length) { focusFirstInput(textareas[textareas.length - 1]); } const inputs = document.querySelectorAll( - 'form#formModel input:not([disabled]):not([type="checkbox"])' + 'form#formModel input:not([disabled]):not([type="checkbox"])', ); const input = inputs[0]; if (!input) return; diff --git a/src/boot/quasar.js b/src/boot/quasar.js index 547517682..a8c397b83 100644 --- a/src/boot/quasar.js +++ b/src/boot/quasar.js @@ -51,4 +51,5 @@ export default boot(({ app }) => { await useCau(response, message); }; + app.provide('app', app); }); diff --git a/src/components/CreateBankEntityForm.vue b/src/components/CreateBankEntityForm.vue index 2da3aa994..7c4b94a6a 100644 --- a/src/components/CreateBankEntityForm.vue +++ b/src/components/CreateBankEntityForm.vue @@ -14,7 +14,7 @@ const { t } = useI18n(); const bicInputRef = ref(null); const state = useState(); -const customer = computed(() => state.get('customer')); +const customer = computed(() => state.get('Customer')); const countriesFilter = { fields: ['id', 'name', 'code'], diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index d569dfda1..93a2ac96a 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -64,6 +64,10 @@ const $props = defineProps({ type: Function, default: null, }, + beforeSaveFn: { + type: Function, + default: null, + }, goTo: { type: String, default: '', @@ -176,7 +180,11 @@ async function saveChanges(data) { hasChanges.value = false; return; } - const changes = data || getChanges(); + let changes = data || getChanges(); + if ($props.beforeSaveFn) { + changes = await $props.beforeSaveFn(changes, getChanges); + } + try { await axios.post($props.saveUrl || $props.url + '/crud', changes); } finally { @@ -229,12 +237,12 @@ async function remove(data) { componentProps: { title: t('globals.confirmDeletion'), message: t('globals.confirmDeletionMessage'), - newData, + data: { deletes: ids }, ids, + promise: saveChanges, }, }) .onOk(async () => { - await saveChanges({ deletes: ids }); newData = newData.filter((form) => !ids.some((id) => id == form[pk])); fetch(newData); }); @@ -374,6 +382,8 @@ watch(formUrl, async () => { @click="onSubmit" :disable="!hasChanges" :title="t('globals.save')" + v-shortcut="'s'" + shortcut="s" data-cy="crudModelDefaultSaveBtn" /> diff --git a/src/components/FilterTravelForm.vue b/src/components/FilterTravelForm.vue index 4d43c3810..765d97763 100644 --- a/src/components/FilterTravelForm.vue +++ b/src/components/FilterTravelForm.vue @@ -181,6 +181,7 @@ const selectTravel = ({ id }) => { color="primary" :disabled="isLoading" :loading="isLoading" + data-cy="save-filter-travel-form" /> { :no-data-label="t('Enter a new search')" class="q-mt-lg" @row-click="(_, row) => selectTravel(row)" + data-cy="table-filter-travel-form" > diff --git a/src/components/ItemsFilterPanel.vue b/src/components/ItemsFilterPanel.vue index 36123b834..f73753a6b 100644 --- a/src/components/ItemsFilterPanel.vue +++ b/src/components/ItemsFilterPanel.vue @@ -281,7 +281,7 @@ const setCategoryList = (data) => { { return locale.includes(normalizedSearch); }); }); - const filteredPinnedModules = computed(() => { if (!search.value) return pinnedModules.value; const normalizedSearch = search.value @@ -72,7 +71,7 @@ watch( items.value = []; getRoutes(); }, - { deep: true } + { deep: true }, ); function findMatches(search, item) { @@ -104,33 +103,40 @@ function addChildren(module, route, parent) { } 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 - ); - if (!moduleDef) continue; - item.children = []; - - addChildren(item.module, moduleDef, item.children); - } - - items.value = modules; + const handleRoutes = { + main: getMainRoutes, + card: getCardRoutes, + }; + try { + handleRoutes[props.source](); + } catch (error) { + throw new Error(`Method is not defined`); } +} +function getMainRoutes() { + const modules = Object.assign([], navigation.getModules().value); - if (props.source === 'card') { - const currentRoute = route.matched[1]; - const currentModule = toLowerCamel(currentRoute.name); - let moduleDef = routes.find( - (route) => toLowerCamel(route.name) === currentModule + for (const item of modules) { + const moduleDef = routes.find( + (route) => toLowerCamel(route.name) === item.module, ); + if (!moduleDef) continue; + item.children = []; - if (!moduleDef) return; - if (!moduleDef?.menus) moduleDef = betaGetRoutes(); - addChildren(currentModule, moduleDef, items.value); + addChildren(item.module, moduleDef, item.children); } + + items.value = modules; +} + +function getCardRoutes() { + const currentRoute = route.matched[1]; + const currentModule = toLowerCamel(currentRoute.name); + let moduleDef = routes.find((route) => toLowerCamel(route.name) === currentModule); + + if (!moduleDef) return; + if (!moduleDef?.menus) moduleDef = betaGetRoutes(); + addChildren(currentModule, moduleDef, items.value); } function betaGetRoutes() { @@ -223,9 +229,16 @@ const searchModule = () => {