diff --git a/CHANGELOG.md b/CHANGELOG.md index e110e4cd6..a7797f810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,246 @@ +# Version 25.00 - 2025-01-14 + +### Added 🆕 + +- chore: refs #7056 move test by:jorgep +- feat: refs #7050 7050 add object check by:Jtubau +- feat: refs #7050 7050 add test to isEmpty() by:Jtubau +- feat: refs #7056 add tests in FormModel by:jorgep +- feat: refs #7056 update route meta information and add FormModel tests by:jorgep +- feat: refs #7074 tests for fns setData(), parseDms() and showFormDialog() (7074-makeFrontTestToVnDmsList) by:Jtubau +- feat: refs #7079 created VnLocation front test by:provira +- feat: refs #7189 add Accept-Language header to axios requests by:jorgep +- feat: refs #7924 add custom inspection checkbox and localization support by:jgallego +- feat: refs #7924 update custom inspection label for clarity in English and Spanish locales by:jgallego +- feat: refs #8004 enhance FetchedTags component with column support and styling updates by:pablone +- feat: refs #8004 hide rightFilter by:pablone +- feat: refs #8246 added new field in list by:Jon +- feat: refs #8266 added descriptor to item name by:jtubau +- feat: refs #8293 add zone filter by:Jtubau +- feat: refs #8293 include zone data in each record by:Jtubau +- fix: refs #8004 more list style issues by:pablone +- fix: refs #8004 some style issues on all list by:pablone +- style: refs #8004 update layout and styling in FetchedTags and ItemList components by:pablone +- style: update CustomerBalance.vue to set label color by:jgallego + +### Changed 📦 + +- perf: order by:alexm +- perf: redirect transition list to card by:alexm +- perf: refs #8201 onDataSaved fetch by:Jon +- perf: revert processData by:alexm +- perf: simplify if by:alexm +- perf: simplify if (perf_redirectTransition) by:alexm +- refactor: item fixedPrice by:Jon +- refactor: refs #7050 refactorize by:jtubau +- refactor: refs #7050 removed blank spaces by:jtubau +- refactor: refs #7052 move EditTableCellValueForm tests to a new location and enhance test coverage by:jgallego +- refactor: refs #7052 remove unnecessary console logs from EditTableCellValueForm tests by:jgallego +- refactor: refs #7074 move dms constant to global scope by:Jtubau +- refactor: refs #7079 removed useless code by:provira +- refactor: refs #7924 simplify custom inspection icon rendering in ExtraCommunity.vue by:jgallego +- refactor: refs #8004 remove console log from CardSummary component on mount by:pablone +- refactor: refs #8004 remove consoleLogs by:pablone +- refactor: refs #8004 remove unused stateStore import in InvoiceInList.vue by:pablone +- refactor: refs #8004 remove unused travelFilterRef and chip definition in TravelList.vue by:pablone +- refactor: refs #8004 replace VnSelect with VnSelectWorker in CustomerList component by:pablone +- refactor: refs #8201 added onMounted to stablish the value to show icons by:Jon +- refactor: refs #8201 deleted condition by:Jon +- refactor: refs #8201 deleted log by:Jon +- refactor: refs #8201 deleted logs by:Jon +- refactor: refs #8266 8266 change expedition item name by:Jtubau +- refactor: refs #8266 change expedition label by:Jtubau +- refactor: refs #8293 remove redundant attributes by:Jtubau +- refactor: refs #8320 changed folder names from "specs" to "**tests**" by:provira +- refactor: refs #8320 moved front tests to their respective sections by:provira +- refactor: refs #8813 removed unused class property by:provira + +### Fixed 🛠️ + +- fix: discount class by:PAU ROVIRA ROSALENY +- fix: duplicate transalation after test to dev by:alexm +- fix: fix translations by:carlossa +- fix: redirect to sales when confirming lines by:Jon +- fix: refs #7050 delete import added by mistake by:Jtubau +- fix: refs #7133 handleSalesModelValue function to handle empty input by:jorgep +- fix: refs #7189 update user language on sessionStorage by:jorgep +- fix: refs #7935 remove unused 'companyFk' column from InvoiceInList component by:jorgep +- fix: refs #8004 more list style issues by:pablone +- fix: refs #8004 some style issues on all list by:pablone +- fix: refs #8004 update label for daysOnward in TravelFilter component and add translations by:pablone +- fix: refs #8004 vnTable card with and add permanent labels by:pablone +- fix: refs #8201 added onDataSaved emi to refetch when cahnges are made by:Jon +- fix: refs #8201 use arrayData to fix the error by:Jon +- fix: refs #8314 space between label and value by:jtubau +- fix: refs #8813 fixed ClaimLines format by:provira +- fix: update button sizes in ExtraCommunity.vue for better visibility by:jgallego +- perf: revert processData by:alexm +- refactor: item fixedPrice by:Jon + +# Version 24.52 - 2024-01-07 + +### Added 🆕 + +- chore: refs #8197 remove console log by:alexm +- chore: refs #8197 replace name by:alexm +- chore: refs #8197 unnecessary file by:alexm +- feat: #8110 apply mixin in quasar components by:Javier Segarra +- feat(Account & AccountRole): refs #8197 add VnCardMain by:alexm +- feat: addDptoLink by:Jtubau +- feat: added restore ticket function in ticket descriptor menu by:Jon +- feat: add support service wip by:jorgep +- feat: focus menu searchbar by:jorgep +- feat: make additional data object by:jorgep +- feat: message to grant access by:jorgep +- feat: refs #6583 add default param by:jorgep +- feat: refs #6583 add destination opt filter by:jorgep +- feat: refs #6583 add icon by:jorgep +- feat: refs #6583 add locale by:jorgep +- feat: refs #7072 added test to computed fn total by:Jtubau +- feat: refs #7235 update invoice out global form to fetch config based on serial type by:jgallego +- feat: refs #7301 add exclude inventory supplier from list by:pablone +- feat: refs #7301 enhance VnDateBadge styling and improve ItemLastEntries component by:pablone +- feat: refs #7882 Added distribution point by:guillermo +- feat: refs #7882 Added longitude & latitude by:guillermo +- feat: refs #7936 add autocomplete on tab fn by:jorgep +- feat: refs #7936 add company filter by:jorgep +- feat: refs #7936 add currency check before fetching by:jorgep +- feat: refs #7936 add dueDated field by:jorgep +- feat: refs #7936 add number validation to VnInputNumber & new daysAgo filter in InvoiceInFilter by:jorgep +- feat: refs #7936 add optionCaption by:jorgep +- feat: refs #7936 add row click navigation to InvoiceInSerial by:jorgep +- feat: refs #7936 add unit tests by:jorgep +- feat: refs #7936 add useAccountShortToStandard composable by:jorgep +- feat: refs #7936 calculate exchange & update taxable base by:jorgep +- feat: refs #7936 enhance downloadFile function to support opening in a new tab by:jorgep +- feat: refs #7936 enhance getTotal fn & add unit tests by:jorgep +- feat: refs #7936 enhance vn-select by:jorgep +- feat: refs #7936 improve optionLabel logic in InvoiceInVat component for better handling of numeric values by:jorgep +- feat: refs #7936 limit decimal places by:jorgep +- feat: refs #7936 make fields required by:jorgep +- feat: refs #7936 show country code & isVies fields by:jorgep +- feat: refs #7936 show id & value by:jorgep +- feat: refs #7936 simplify optionLabel wip by:jorgep +- feat: refs #7936 update 'isVies' label to use global translation key by:jorgep +- feat: refs #7936 update option labels in InvoiceIn components for better clarity by:jorgep +- feat: refs #7936 use default invoice data by:jorgep +- feat: refs #8001 change request by:robert +- feat: refs #8001 ticketExpeditionGrafana by:robert +- feat: refs #8194 created VnSelectWorker component and use it in Lilium by:Jon +- feat: refs #8197 better leftMenu and VnCardMain improvements by:alexm +- feat: refs #8197 default leftMenu by:alexm +- feat: refs #8197 default sectionName by:alexm +- feat: refs #8197 keepData in VnSection by:alexm +- feat: refs #8197 vnTableFilter by:alexm +- feat: refs #8197 working rightMenu by:alexm +- feat: remove re-fetch when add element by:Javier Segarra +- feat: remove search after category by:Javier Segarra +- feat: requested changes in item module by:Jon +- feat: update quantity by:Javier Segarra +- feat(VnPaginate): refs #8197 hold data when change to Card by:alexm + +### Changed 📦 + +- perf: #6896 REMOVE COMMENTS by:Javier Segarra +- perf: qFormMixin by:Javier Segarra +- perf: qFormMixin improvement by:Javier Segarra +- perf: refs #8194 select worker component by:Jon +- perf: refs #8197 perf by:alexm +- perf: remove comments by:Javier Segarra +- perf: remove unused variables (origin/warmfix_noUsedVars) by:Javier Segarra +- refactor: added again search emit by:Jon +- refactor: add useCau composable by:jorgep +- refactor: deleted log by:Jon +- refactor: deleted onUnmounted code by:Jon +- refactor: deleted useless hidden tag by:Jon +- refactor: deleted warnings and corrected itemTag by:Jon +- refactor: drop logic by:jorgep +- refactor: ignore params when searching by id on searchbar (origin/VnSearchbar-SearchRemoveParams) by:Jon +- refactor: log error by:Jon +- refactor: refs #7936 locale by:jorgep +- refactor: refs #7936 simplify getTotal fn by:jorgep +- refactor: refs #7936 update label capitalization and replace invoice type options by:jorgep +- refactor: refs #8194 deleted unnecessary label by:Jon +- refactor: refs #8194 modified select worker template by:Jon +- refactor: refs #8194 modified select worker to allow no one filter from monitor ticket by:Jon +- refactor: refs #8194 moved translation to the correct place by:Jon +- refactor: refs #8194 requested changes by:Jon +- refactor: refs #8194 structure changes in component and related files by:Jon +- refactor: refs #8197 adapt AccountAcls to VnCardMain by:alexm +- refactor: refs #8197 adapt AccountAlias by:alexm +- refactor: refs #8197 adapt Ticket to VnCardMain by:alexm +- refactor: refs #8197 backward compatible (8197-VnCardMain_backwardCompatibility) by:alexm +- refactor: refs #8197 rename VnSectionMain to VnModule and VnCardMain to VnSection by:alexm +- refactor: refs #8288 changed invoice out spanish translation by:provira +- refactor: use locale keys by:jorgep +- refactor: use teleport to avoid qdrawer overlapping by:Jon +- refactor: use VnSelectWorker by:Jon + +### Fixed 🛠️ + +- fix: account by:carlossa +- fix: account create by:carlossa +- fix: accountList create by:carlossa +- fix(AccountList): use $refs by:alexm +- fix: add data-key by:alexm +- fix: addLocales by:Jtubau +- fix: dated field by:Jon +- fix: e2e by:jorgep +- fix: fix department filter by:carlossa +- fix: fixed translations by:Javier Segarra +- fix: fixed translations by:provira +- fix: get total from api by:Javier Segarra +- fix: handle non-object options by:jorgep +- fix: monitorPayMethodFilter by:carlossa +- fix: orderBy priority by:Javier Segarra +- fix: prevent null by:jorgep +- fix: redirection vnTable VnTableFilter by:alexm +- fix: refs #6389 fix filter trad by:carlossa +- fix: refs #6389 fix front, filters, itp by:carlossa +- fix: refs #6389 front add packing filter by:carlossa +- fix: refs #6389 front by:carlossa +- fix: refs #6389 front filters by:carlossa +- fix: refs #6389 ipt by:carlossa +- fix: refs #6389 packing by:carlossa +- fix: refs #6583 update checkbox for filtering by destination in TicketAdvanceFilter by:jorgep +- fix: refs #7031 add test e2e by:carlossa +- fix: refs #7031 fix zoneTest by:carlossa +- fix: refs #7301 unnecessary console logs from ItemLastEntries.vue by:pablone +- fix: refs #7936 changes by:jorgep +- fix: refs #7936 decimal places & locale by:jorgep +- fix: refs #7936 descriptor & dueday by:jorgep +- fix: refs #7936 exclude disabled els on tab by:jorgep +- fix: refs #7936 format tax calculation to two decimal places by:jorgep +- fix: refs #7936 improve error handling by:jorgep +- fix: refs #7936 redirection by:jorgep +- fix: refs #7936 rollback by:jorgep +- fix: refs #7936 serial by:jorgep +- fix: refs #7936 tabulation wip by:jorgep +- fix: refs #7936 test by:jorgep +- fix: refs #8114 clean by:carlossa +- fix: refs #8114 fix agencyList by:carlossa +- fix: refs #8114 fix lifeCycle hooks by:carlossa +- fix: refs #8114 fix pr by:carlossa +- fix: refs #8114 fix removeAddress by:carlossa +- fix: refs #8114 orderList by:carlossa +- fix: refs #8114 remove logs by:carlossa +- fix: refs #8197 mapKey (origin/8197-perf_vnTableInside, 8197-perf_vnTableInside) by:alexm +- fix: refs #8197 redirection (8197-perf_redirection) by:alexm +- fix: refs #8197 staticParams and redirect by:alexm +- fix: refs #8197 vnPaginate onFetch emit by:alexm +- fix: refs #8197 vnPaginate when change :id by:alexm +- fix: refs #8197 vnTableFilter in vnTable by:alexm +- fix: refs #8315 ticketBoxing test by:alexm +- fix: remove url by:carlossa +- fix: rollback by:jorgep +- fix: test by:jorgep +- fix(VnDmsList): refs #8197 add mapKey by:alexm +- revert: refs #8197 arrayData changes by:alexm +- test: refs #8197 fix e2e by:alexm +- test: refs #8315 fix claimDevelopment fixtures by:alexm +- test: refs #8315 fix clientList by:alexm +- test: refs #8315 fix VnSelect in e2e by:alexm + # Version 24.50 - 2024-12-10 ### Added 🆕 diff --git a/package.json b/package.json index 04b75a0b0..6259f4acd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "24.50.0", + "version": "25.02.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -25,6 +25,7 @@ "axios": "^1.4.0", "chromium": "^3.0.3", "croppie": "^2.6.5", + "moment": "^2.30.1", "pinia": "^2.1.3", "quasar": "^2.14.5", "validator": "^13.9.0", @@ -64,4 +65,4 @@ "vite": "^5.1.4", "vitest": "^0.31.1" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83dfa0469..7bf640347 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ dependencies: croppie: specifier: ^2.6.5 version: 2.6.5 + moment: + specifier: ^2.30.1 + version: 2.30.1 pinia: specifier: ^2.1.3 version: 2.1.7(typescript@5.5.4)(vue@3.4.19) @@ -832,8 +835,8 @@ packages: vue-i18n: optional: true dependencies: - '@intlify/message-compiler': 10.0.0 - '@intlify/shared': 10.0.0 + '@intlify/message-compiler': 11.0.0-rc.1 + '@intlify/shared': 11.0.0-rc.1 jsonc-eslint-parser: 1.4.1 source-map: 0.6.1 vue-i18n: 9.9.1(vue@3.4.19) @@ -847,11 +850,11 @@ packages: '@intlify/message-compiler': 9.9.1 '@intlify/shared': 9.9.1 - /@intlify/message-compiler@10.0.0: - resolution: {integrity: sha512-OcaWc63NC/9p1cMdgoNKBj4d61BH8sUW1Hfs6YijTd9656ZR4rNqXAlRnBrfS5ABq0vjQjpa8VnyvH9hK49yBw==} + /@intlify/message-compiler@11.0.0-rc.1: + resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==} engines: {node: '>= 16'} dependencies: - '@intlify/shared': 10.0.0 + '@intlify/shared': 11.0.0-rc.1 source-map-js: 1.0.2 dev: true @@ -862,8 +865,8 @@ packages: '@intlify/shared': 9.9.1 source-map-js: 1.0.2 - /@intlify/shared@10.0.0: - resolution: {integrity: sha512-6ngLfI7DOTew2dcF9WMJx+NnMWghMBhIiHbGg+wRvngpzD5KZJZiJVuzMsUQE1a5YebEmtpTEfUrDp/NqVGdiw==} + /@intlify/shared@11.0.0-rc.1: + resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==} engines: {node: '>= 16'} dev: true @@ -887,7 +890,7 @@ packages: optional: true dependencies: '@intlify/bundle-utils': 4.0.0(vue-i18n@9.9.1) - '@intlify/shared': 10.0.0 + '@intlify/shared': 11.0.0-rc.1 '@rollup/pluginutils': 4.2.1 '@vue/compiler-sfc': 3.4.19 debug: 4.3.4(supports-color@8.1.1) @@ -4960,6 +4963,10 @@ packages: uuid: 8.3.2 dev: true + /moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + dev: false + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} diff --git a/test/vitest/__tests__/boot/axios.spec.js b/src/boot/__tests__/axios.spec.js similarity index 97% rename from test/vitest/__tests__/boot/axios.spec.js rename to src/boot/__tests__/axios.spec.js index 19d396ec5..b3b6f98c6 100644 --- a/test/vitest/__tests__/boot/axios.spec.js +++ b/src/boot/__tests__/axios.spec.js @@ -1,4 +1,3 @@ -import { Notify } from 'quasar'; import { onRequest, onResponseError } from 'src/boot/axios'; import { describe, expect, it, vi } from 'vitest'; @@ -27,6 +26,7 @@ describe('Axios boot', () => { expect(resultConfig).toEqual( expect.objectContaining({ headers: { + 'Accept-Language': 'en-US', Authorization: 'DEFAULT_TOKEN', }, }) diff --git a/src/boot/axios.js b/src/boot/axios.js index aee38e887..3f9fadee5 100644 --- a/src/boot/axios.js +++ b/src/boot/axios.js @@ -3,12 +3,12 @@ import { useSession } from 'src/composables/useSession'; import { Router } from 'src/router'; import useNotify from 'src/composables/useNotify.js'; import { useStateQueryStore } from 'src/stores/useStateQueryStore'; +import { i18n } from 'src/boot/i18n'; const session = useSession(); const { notify } = useNotify(); const stateQuery = useStateQueryStore(); const baseUrl = '/api/'; - axios.defaults.baseURL = baseUrl; const axiosNoError = axios.create({ baseURL: baseUrl }); @@ -16,6 +16,7 @@ const onRequest = (config) => { const token = session.getToken(); if (token.length && !config.headers.Authorization) { config.headers.Authorization = token; + config.headers['Accept-Language'] = i18n.global.locale.value; } stateQuery.add(config); return config; diff --git a/src/boot/i18n.js b/src/boot/i18n.js index b23b6d5fd..85d0772a3 100644 --- a/src/boot/i18n.js +++ b/src/boot/i18n.js @@ -1,9 +1,11 @@ import { boot } from 'quasar/wrappers'; import { createI18n } from 'vue-i18n'; import messages from 'src/i18n'; +import { useState } from 'src/composables/useState'; +const user = useState().getUser(); const i18n = createI18n({ - locale: navigator.language || navigator.userLanguage, + locale: user.value.lang || navigator.language || navigator.userLanguage, fallbackLocale: 'en', globalInjection: true, messages, diff --git a/src/boot/mainShortcutMixin.js b/src/boot/mainShortcutMixin.js new file mode 100644 index 000000000..481077e37 --- /dev/null +++ b/src/boot/mainShortcutMixin.js @@ -0,0 +1,36 @@ +import routes from 'src/router/modules'; +import { useRouter } from 'vue-router'; + +let isNotified = false; + +export default { + created: function () { + const router = useRouter(); + const keyBindingMap = routes + .filter((route) => route.meta.keyBinding) + .reduce((map, route) => { + map['Key' + route.meta.keyBinding.toUpperCase()] = route.path; + return map; + }, {}); + + const handleKeyDown = (event) => { + const { ctrlKey, altKey, code } = event; + + if (ctrlKey && altKey && keyBindingMap[code] && !isNotified) { + event.preventDefault(); + router.push(keyBindingMap[code]); + isNotified = true; + } + }; + + const handleKeyUp = (event) => { + const { ctrlKey, altKey } = event; + if (!ctrlKey || !altKey) { + isNotified = false; + } + }; + + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + }, +}; diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index 8d009dbea..97d80c670 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -1,30 +1,51 @@ -import { getCurrentInstance } from 'vue'; - +function focusFirstInput(input) { + input.focus(); +} export default { mounted: function () { - const vm = getCurrentInstance(); - if (vm.type.name === 'QForm') { - if (!['searchbarForm', 'filterPanelForm'].includes(this.$el?.id)) { - // TODO: AUTOFOCUS IS NOT FOCUSING - const that = this; - this.$el.addEventListener('keyup', function (evt) { - if (evt.key === 'Enter' && !that.$attrs['prevent-submit']) { - const input = evt.target; - if (input.type == 'textarea' && evt.shiftKey) { - evt.preventDefault(); - let { selectionStart, selectionEnd } = input; - input.value = - input.value.substring(0, selectionStart) + - '\n' + - input.value.substring(selectionEnd); - selectionStart = selectionEnd = selectionStart + 1; - return; - } - evt.preventDefault(); - that.onSubmit(); - } - }); + const that = this; + + const form = document.querySelector('.q-form#formModel'); + if (!form) return; + try { + const inputsFormCard = form.querySelectorAll( + `input:not([disabled]):not([type="checkbox"])` + ); + if (inputsFormCard.length) { + focusFirstInput(inputsFormCard[0]); } + const textareas = document.querySelectorAll( + '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"])' + ); + const input = inputs[0]; + if (!input) return; + + focusFirstInput(input); + } catch (error) { + console.error(error); } + form.addEventListener('keyup', function (evt) { + if (evt.key === 'Enter' && !that.$attrs['prevent-submit']) { + const input = evt.target; + if (input.type == 'textarea' && evt.shiftKey) { + evt.preventDefault(); + let { selectionStart, selectionEnd } = input; + input.value = + input.value.substring(0, selectionStart) + + '\n' + + input.value.substring(selectionEnd); + selectionStart = selectionEnd = selectionStart + 1; + return; + } + evt.preventDefault(); + that.onSubmit(); + } + }); }, }; diff --git a/src/boot/quasar.js b/src/boot/quasar.js index 01fe68d8b..547517682 100644 --- a/src/boot/quasar.js +++ b/src/boot/quasar.js @@ -1,15 +1,18 @@ +import axios from 'axios'; import { boot } from 'quasar/wrappers'; import qFormMixin from './qformMixin'; import keyShortcut from './keyShortcut'; -import useNotify from 'src/composables/useNotify.js'; -import { CanceledError } from 'axios'; - -const { notify } = useNotify(); +import { QForm } from 'quasar'; +import { QLayout } from 'quasar'; +import mainShortcutMixin from './mainShortcutMixin'; +import { useCau } from 'src/composables/useCau'; export default boot(({ app }) => { - app.mixin(qFormMixin); + QForm.mixins = [qFormMixin]; + QLayout.mixins = [mainShortcutMixin]; + app.directive('shortcut', keyShortcut); - app.config.errorHandler = (error) => { + app.config.errorHandler = async (error) => { let message; const response = error.response; const responseData = response?.data; @@ -40,12 +43,12 @@ export default boot(({ app }) => { } console.error(error); - if (error instanceof CanceledError) { + if (error instanceof axios.CanceledError) { const env = process.env.NODE_ENV; if (env && env !== 'development') return; message = 'Duplicate request'; } - notify(message ?? 'globals.error', 'negative', 'error'); + await useCau(response, message); }; }); diff --git a/src/components/CreateNewPostcodeForm.vue b/src/components/CreateNewPostcodeForm.vue index c656fcb2f..26b79b1bc 100644 --- a/src/components/CreateNewPostcodeForm.vue +++ b/src/components/CreateNewPostcodeForm.vue @@ -55,13 +55,6 @@ async function setCountry(countryFk, data) { } // Province - -async function handleProvinces(data) { - provincesOptions.value = data; - if (postcodeFormData.countryFk) { - await fetchTowns(); - } -} async function setProvince(id, data) { if (data.provinceFk === id) return; const newProvince = provincesOptions.value.find((province) => province.id == id); diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 7fdb54bc4..940b72ff0 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -127,7 +127,7 @@ function resetData(data) { originalData.value = JSON.parse(JSON.stringify(data)); formData.value = JSON.parse(JSON.stringify(data)); - if (watchChanges.value) watchChanges.value(); //destoy watcher + if (watchChanges.value) watchChanges.value(); //destroy watcher watchChanges.value = watch(formData, () => (hasChanges.value = true), { deep: true }); } @@ -270,10 +270,8 @@ function getChanges() { function isEmpty(obj) { if (obj == null) return true; - if (obj === undefined) return true; - if (Object.keys(obj).length === 0) return true; - - if (obj.length > 0) return false; + if (Array.isArray(obj)) return !obj.length; + return !Object.keys(obj).length; } async function reload(params) { diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index c569f2553..ea1ea53f2 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -1,7 +1,7 @@ - - - -en: - Go to Salix: Go to Salix -es: - Go to Salix: Ir a Salix - diff --git a/src/components/UserPanel.vue b/src/components/UserPanel.vue index 810f63044..a0ef73a1f 100644 --- a/src/components/UserPanel.vue +++ b/src/components/UserPanel.vue @@ -87,10 +87,10 @@ async function saveDarkMode(value) { async function saveLanguage(value) { const query = `/VnUsers/${user.value.id}`; try { - await axios.patch(query, { - lang: value, - }); + await axios.patch(query, { lang: value }); + user.value.lang = value; + useState().setUser(user.value); onDataSaved(); } catch (error) { onDataError(); diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index 999133130..426f5c716 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -32,7 +32,10 @@ const $props = defineProps({ defineExpose({ addFilter, props: $props }); const model = defineModel(undefined, { required: true }); -const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl }); +const arrayData = useArrayData( + $props.dataKey, + $props.searchUrl ? { searchUrl: $props.searchUrl } : null +); const columnFilter = computed(() => $props.column?.columnFilter); const updateEvent = { 'update:modelValue': addFilter }; diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 9ab080276..07992f616 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1,20 +1,21 @@ + diff --git a/test/vitest/__tests__/components/VnTable.spec.js b/src/components/VnTable/__tests__/VnTable.spec.js similarity index 82% rename from test/vitest/__tests__/components/VnTable.spec.js rename to src/components/VnTable/__tests__/VnTable.spec.js index 162df727d..74ba06987 100644 --- a/test/vitest/__tests__/components/VnTable.spec.js +++ b/src/components/VnTable/__tests__/VnTable.spec.js @@ -1,4 +1,4 @@ -import { describe, expect, it, beforeAll, beforeEach } from 'vitest'; +import { describe, expect, it, beforeAll, beforeEach, vi } from 'vitest'; import { createWrapper } from 'app/test/vitest/helper'; import VnTable from 'src/components/VnTable/VnTable.vue'; @@ -13,6 +13,15 @@ describe('VnTable', () => { }, }); vm = wrapper.vm; + + vi.mock('src/composables/useFilterParams', () => { + return { + useFilterParams: vi.fn(() => ({ + params: {}, + orders: {}, + })), + }; + }); }); beforeEach(() => (vm.selected = [])); diff --git a/src/components/__tests__/CrudModel.spec.js b/src/components/__tests__/CrudModel.spec.js new file mode 100644 index 000000000..e0afd30ad --- /dev/null +++ b/src/components/__tests__/CrudModel.spec.js @@ -0,0 +1,248 @@ +import { createWrapper, axios } from 'app/test/vitest/helper'; +import CrudModel from 'components/CrudModel.vue'; +import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; + +describe('CrudModel', () => { + let wrapper; + let vm; + let data; + beforeAll(() => { + wrapper = createWrapper(CrudModel, { + global: { + stubs: [ + 'vnPaginate', + 'useState', + 'arrayData', + 'useStateStore', + 'vue-i18n', + ], + mocks: { + validate: vi.fn(), + }, + }, + propsData: { + dataRequired: { + fk: 1, + }, + dataKey: 'crudModelKey', + model: 'crudModel', + url: 'crudModelUrl', + saveFn: '', + }, + }); + wrapper=wrapper.wrapper; + vm=wrapper.vm; + }); + + beforeEach(() => { + vm.fetch([]); + vm.watchChanges = null; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('insert()', () => { + it('should new element in list with index 0 if formData not has data', () => { + vm.insert(); + + expect(vm.formData.length).toEqual(1); + expect(vm.formData[0].fk).toEqual(1); + expect(vm.formData[0].$index).toEqual(0); + }); + }); + + describe('getChanges()', () => { + it('should return correct updates and creates', async () => { + vm.fetch([ + { id: 1, name: 'New name one' }, + { id: 2, name: 'New name two' }, + { id: 3, name: 'Bruce Wayne' }, + ]); + + vm.originalData = [ + { id: 1, name: 'Tony Starks' }, + { id: 2, name: 'Jessica Jones' }, + { id: 3, name: 'Bruce Wayne' }, + ]; + + vm.insert(); + const result = vm.getChanges(); + + const expected = { + creates: [ + { + $index: 3, + fk: 1, + }, + ], + updates: [ + { + data: { + name: 'New name one', + }, + where: { + id: 1, + }, + }, + { + data: { + name: 'New name two', + }, + where: { + id: 2, + }, + }, + ], + }; + + expect(result).toEqual(expected); + }); + }); + + describe('getDifferences()', () => { + it('should return the differences between two objects', async () => { + const obj1 = { + a: 1, + b: 2, + c: 3, + }; + const obj2 = { + a: null, + b: 4, + d: 5, + }; + + const result = vm.getDifferences(obj1, obj2); + + expect(result).toEqual({ + a: null, + b: 4, + d: 5, + }); + }); + }); + + describe('isEmpty()', () => { + let dummyObj; + let dummyArray; + let result; + it('should return true if object si null', async () => { + dummyObj = null; + result = vm.isEmpty(dummyObj); + + expect(result).toBe(true); + }); + + it('should return true if object si undefined', async () => { + dummyObj = undefined; + result = vm.isEmpty(dummyObj); + + expect(result).toBe(true); + }); + + it('should return true if object is empty', async () => { + dummyObj ={}; + result = vm.isEmpty(dummyObj); + + expect(result).toBe(true); + }); + + it('should return false if object is not empty', async () => { + dummyObj = {a:1, b:2, c:3}; + result = vm.isEmpty(dummyObj); + + expect(result).toBe(false); + }); + + it('should return true if array is empty', async () => { + dummyArray = []; + result = vm.isEmpty(dummyArray); + + expect(result).toBe(true); + }); + + it('should return false if array is not empty', async () => { + dummyArray = [1,2,3]; + result = vm.isEmpty(dummyArray); + + expect(result).toBe(false); + }) + }); + + describe('resetData()', () => { + it('should add $index to elements in data[] and sets originalData and formData with data', async () => { + data = [{ + name: 'Tony', + lastName: 'Stark', + age: 42, + }]; + + vm.resetData(data); + + expect(vm.originalData).toEqual(data); + expect(vm.originalData[0].$index).toEqual(0); + expect(vm.formData).toEqual(data); + expect(vm.formData[0].$index).toEqual(0); + expect(vm.watchChanges).not.toBeNull(); + }); + + it('should dont do nothing if data is null', async () => { + vm.resetData(null); + + expect(vm.watchChanges).toBeNull(); + }); + + it('should set originalData and formatData with data and generate watchChanges', async () => { + data = { + name: 'Tony', + lastName: 'Stark', + age: 42, + }; + + vm.resetData(data); + + expect(vm.originalData).toEqual(data); + expect(vm.formData).toEqual(data); + expect(vm.watchChanges).not.toBeNull(); + }); + }); + + describe('saveChanges()', () => { + data = [{ + name: 'Tony', + lastName: 'Stark', + age: 42, + }]; + + it('should call saveFn if exists', async () => { + await wrapper.setProps({ saveFn: vi.fn() }); + + vm.saveChanges(data); + + expect(vm.saveFn).toHaveBeenCalledOnce(); + expect(vm.isLoading).toBe(false); + expect(vm.hasChanges).toBe(false); + + await wrapper.setProps({ saveFn: '' }); + }); + + it("should use default url if there's not saveFn", async () => { + const postMock =vi.spyOn(axios, 'post'); + + vm.formData = [{ + name: 'Bruce', + lastName: 'Wayne', + age: 45, + }] + + await vm.saveChanges(data); + + expect(postMock).toHaveBeenCalledWith(vm.url + '/crud', data); + expect(vm.isLoading).toBe(false); + expect(vm.hasChanges).toBe(false); + expect(vm.originalData).toEqual(JSON.parse(JSON.stringify(vm.formData))); + }); + }); +}); diff --git a/src/components/__tests__/EditTableCellValueForm.spec.js b/src/components/__tests__/EditTableCellValueForm.spec.js new file mode 100644 index 000000000..fa47d8f73 --- /dev/null +++ b/src/components/__tests__/EditTableCellValueForm.spec.js @@ -0,0 +1,56 @@ +import { createWrapper, axios } from 'app/test/vitest/helper'; +import EditForm from 'components/EditTableCellValueForm.vue'; +import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; + +const fieldA = 'fieldA'; +const fieldB = 'fieldB'; + +describe('EditForm', () => { + let vm; + const mockRows = [ + { id: 1, itemFk: 101 }, + { id: 2, itemFk: 102 }, + ]; + const mockFieldsOptions = [ + { label: 'Field A', field: fieldA, component: 'input', attrs: {} }, + { label: 'Field B', field: fieldB, component: 'date', attrs: {} }, + ]; + const editUrl = '/api/edit'; + + beforeAll(() => { + vi.spyOn(axios, 'post').mockResolvedValue({ status: 200 }); + vm = createWrapper(EditForm, { + props: { + rows: mockRows, + fieldsOptions: mockFieldsOptions, + editUrl, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('onSubmit()', () => { + it('should call axios.post with the correct parameters in the payload', async () => { + const selectedField = { field: fieldA, component: 'input', attrs: {} }; + const newValue = 'Test Value'; + + vm.selectedField = selectedField; + vm.newValue = newValue; + + await vm.onSubmit(); + + const payload = axios.post.mock.calls[0][1]; + + expect(axios.post).toHaveBeenCalledWith(editUrl, expect.any(Object)); + expect(payload.field).toEqual(fieldA); + expect(payload.newValue).toEqual(newValue); + + expect(payload.lines).toEqual(expect.arrayContaining(mockRows)); + + expect(vm.isLoading).toEqual(false); + }); + }); +}); diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js new file mode 100644 index 000000000..e35684bc3 --- /dev/null +++ b/src/components/__tests__/FormModel.spec.js @@ -0,0 +1,149 @@ +import { describe, expect, it, beforeAll, vi, afterAll } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import FormModel from 'src/components/FormModel.vue'; + +describe('FormModel', () => { + const model = 'mockModel'; + const url = 'mockUrl'; + const formInitialData = { mockKey: 'mockVal' }; + + describe('modelValue', () => { + it('should use the provided model', () => { + const { vm } = mount({ propsData: { model } }); + expect(vm.modelValue).toBe(model); + }); + + it('should use the route meta title when model is not provided', () => { + const { vm } = mount({}); + expect(vm.modelValue).toBe('formModel_mockTitle'); + }); + }); + + describe('onMounted()', () => { + let mockGet; + + beforeAll(() => { + mockGet = vi.spyOn(axios, 'get').mockResolvedValue({ data: {} }); + }); + + afterAll(() => { + mockGet.mockRestore(); + }); + + it('should not fetch when has formInitialData', () => { + mount({ propsData: { url, model, autoLoad: true, formInitialData } }); + expect(mockGet).not.toHaveBeenCalled(); + }); + + it('should fetch when there is url and auto-load', () => { + mount({ propsData: { url, model, autoLoad: true } }); + expect(mockGet).toHaveBeenCalled(); + }); + + it('should not observe changes', () => { + const { vm } = mount({ + propsData: { url, model, observeFormChanges: false, formInitialData }, + }); + + expect(vm.hasChanges).toBe(true); + vm.reset(); + expect(vm.hasChanges).toBe(true); + }); + + it('should observe changes', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData }, + }); + vm.state.set(model, formInitialData); + expect(vm.hasChanges).toBe(false); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + expect(vm.hasChanges).toBe(true); + vm.formData.mockKey = 'mockVal'; + }); + }); + + describe('trimData()', () => { + let vm; + beforeAll(() => { + vm = mount({}).vm; + }); + + it('should trim whitespace from string values', () => { + const data = { key1: ' value1 ', key2: ' value2 ' }; + const trimmedData = vm.trimData(data); + expect(trimmedData).toEqual({ key1: 'value1', key2: 'value2' }); + }); + + it('should not modify non-string values', () => { + const data = { key1: 123, key2: true, key3: null, key4: undefined }; + const trimmedData = vm.trimData(data); + expect(trimmedData).toEqual(data); + }); + }); + + describe('save()', async () => { + it('should not call if there are not changes', async () => { + const { vm } = mount({ propsData: { url, model } }); + + await vm.save(); + expect(vm.hasChanges).toBe(false); + }); + + it('should call axios.patch with the right data', async () => { + const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + const { vm } = mount({ propsData: { url, model, formInitialData } }); + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spy).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; + }); + + it('should call axios.post with the right data', async () => { + const spy = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} }); + const { vm } = mount({ + propsData: { url, model, formInitialData, urlCreate: 'mockUrlCreate' }, + }); + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spy).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; + }); + + it('should use the saveFn', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData, saveFn: () => {} }, + }); + const spyPatch = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + const spySaveFn = vi.spyOn(vm.$props, 'saveFn'); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spyPatch).not.toHaveBeenCalled(); + expect(spySaveFn).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; + }); + + it('should reload the data after save', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData, reload: true }, + }); + vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + vm.formData.mockKey = 'mockVal'; + }); + }); +}); + +function mount({ propsData = {} }) { + return createWrapper(FormModel, { + propsData, + }); +} diff --git a/test/vitest/__tests__/components/Leftmenu.spec.js b/src/components/__tests__/Leftmenu.spec.js similarity index 100% rename from test/vitest/__tests__/components/Leftmenu.spec.js rename to src/components/__tests__/Leftmenu.spec.js diff --git a/src/components/common/RightMenu.vue b/src/components/common/RightMenu.vue index 3aa1891f9..32dc2874d 100644 --- a/src/components/common/RightMenu.vue +++ b/src/components/common/RightMenu.vue @@ -2,7 +2,11 @@ import { ref, onMounted, useSlots } from 'vue'; import { useI18n } from 'vue-i18n'; import { useStateStore } from 'stores/useStateStore'; +import { useQuasar } from 'quasar'; +const { t } = useI18n(); +const quasar = useQuasar(); +const stateStore = useStateStore(); const slots = useSlots(); const hasContent = ref(false); const rightPanel = ref(null); @@ -11,7 +15,6 @@ onMounted(() => { rightPanel.value = document.querySelector('#right-panel'); if (!rightPanel.value) return; - // Check if there's content to display const observer = new MutationObserver(() => { hasContent.value = rightPanel.value.childNodes.length; }); @@ -21,12 +24,9 @@ onMounted(() => { childList: true, attributes: true, }); - - if (!slots['right-panel'] && !hasContent.value) stateStore.rightDrawer = false; + if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile) + stateStore.rightDrawer = false; }); - -const { t } = useI18n(); -const stateStore = useStateStore(); diff --git a/src/components/common/VnCardBeta.vue b/src/components/common/VnCardBeta.vue new file mode 100644 index 000000000..349956be9 --- /dev/null +++ b/src/components/common/VnCardBeta.vue @@ -0,0 +1,67 @@ + + diff --git a/src/components/common/VnDateBadge.vue b/src/components/common/VnDateBadge.vue new file mode 100644 index 000000000..83d39937a --- /dev/null +++ b/src/components/common/VnDateBadge.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/common/VnDmsList.vue b/src/components/common/VnDmsList.vue index 52dd6ef79..ed3cadc6b 100644 --- a/src/components/common/VnDmsList.vue +++ b/src/components/common/VnDmsList.vue @@ -297,7 +297,7 @@ defineExpose({ ref="dmsRef" :data-key="$props.model" :url="$props.model" - :filter="dmsFilter" + :user-filter="dmsFilter" :order="['dmsFk DESC']" :auto-load="true" @on-fetch="setData" diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index eb9dc0f38..db8e25195 100644 --- a/src/components/common/VnInputDate.vue +++ b/src/components/common/VnInputDate.vue @@ -106,7 +106,8 @@ const manageDate = (date) => { :class="{ required: isRequired }" :rules="mixinRules" :clearable="false" - @click="isPopupOpen = true" + @click="isPopupOpen = !isPopupOpen" + @keydown="isPopupOpen = false" hide-bottom-space > { - es: Open date: Abrir fecha diff --git a/src/components/common/VnInputNumber.vue b/src/components/common/VnInputNumber.vue index 1cad6c245..165cfae3d 100644 --- a/src/components/common/VnInputNumber.vue +++ b/src/components/common/VnInputNumber.vue @@ -1,13 +1,28 @@ - diff --git a/src/components/common/VnInputTime.vue b/src/components/common/VnInputTime.vue index 6724c00b5..cb1f723ba 100644 --- a/src/components/common/VnInputTime.vue +++ b/src/components/common/VnInputTime.vue @@ -80,7 +80,8 @@ function dateToTime(newDate) { :class="{ required: isRequired }" style="min-width: 100px" :rules="mixinRules" - @click="isPopupOpen = false" + @click="isPopupOpen = !isPopupOpen" + @keydown="isPopupOpen = false" type="time" hide-bottom-space > @@ -100,12 +101,6 @@ function dateToTime(newDate) { isPopupOpen = false; " /> - props.location, + (newValue) => { + if (!modelValue.value) return; + modelValue.value = formatLocation(newValue) ?? null; + } +); + const mixinRules = [requiredFieldRule]; const locationProperties = [ 'postcode', @@ -26,7 +34,7 @@ const locationProperties = [ (obj) => obj.country?.name, ]; -const formatLocation = (obj, properties) => { +const formatLocation = (obj, properties = locationProperties) => { const parts = properties.map((prop) => { if (typeof prop === 'string') { return obj[prop]; @@ -43,9 +51,7 @@ const formatLocation = (obj, properties) => { return filteredParts.join(', '); }; -const modelValue = computed(() => - props.location ? formatLocation(props.location, locationProperties) : null -); +const modelValue = ref(props.location ? formatLocation(props.location) : null); function showLabel(data) { const dataProperties = [ diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 8c71c0997..9eca3c711 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -238,6 +238,7 @@ async function openPointRecord(id, modelLog) { pointRecord.value = parseProps(propNames, locale, data); } async function setLogTree(data) { + if (!data) return; logTree.value = getLogTree(data); } diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnModule.vue similarity index 52% rename from src/components/common/VnSectionMain.vue rename to src/components/common/VnModule.vue index 15be6ad9a..038ee1d60 100644 --- a/src/components/common/VnSectionMain.vue +++ b/src/components/common/VnModule.vue @@ -1,8 +1,8 @@ - - diff --git a/src/components/ui/VnConfirm.vue b/src/components/ui/VnConfirm.vue index 0b1913383..a02b56bdb 100644 --- a/src/components/ui/VnConfirm.vue +++ b/src/components/ui/VnConfirm.vue @@ -98,6 +98,7 @@ function cancel() { /> -import { onMounted, ref, computed, watch } from 'vue'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; -import { useRoute } from 'vue-router'; import toDate from 'filters/toDate'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; +import { useFilterParams } from 'src/composables/useFilterParams'; +import { useRoute } from 'vue-router'; -const { t } = useI18n(); +const { t, te } = useI18n(); +const route = useRoute(); const $props = defineProps({ modelValue: { type: Object, @@ -55,63 +57,35 @@ const $props = defineProps({ type: Boolean, default: true, }, + arrayData: { + type: Object, + default: null, + }, }); const emit = defineEmits([ 'update:modelValue', 'refresh', 'clear', + 'search', 'init', 'remove', 'setUserParams', ]); -const arrayData = useArrayData($props.dataKey, { - exprBuilder: $props.exprBuilder, - searchUrl: $props.searchUrl, - navigate: $props.redirect ? {} : null, -}); -const route = useRoute(); +const arrayData = + $props.arrayData ?? + useArrayData($props.dataKey, { + exprBuilder: $props.exprBuilder, + searchUrl: $props.searchUrl, + navigate: $props.redirect ? {} : null, + }); + const store = arrayData.store; -const userParams = ref({}); +const userParams = ref(useFilterParams($props.dataKey).params); +const userOrders = ref(useFilterParams($props.dataKey).orders); -defineExpose({ search, sanitizer, params: userParams }); - -onMounted(() => { - if (!userParams.value) userParams.value = $props.modelValue ?? {}; - emit('init', { params: userParams.value }); -}); - -function setUserParams(watchedParams) { - if (!watchedParams || Object.keys(watchedParams).length == 0) return; - - if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); - if (typeof watchedParams?.filter == 'string') - watchedParams.filter = JSON.parse(watchedParams.filter); - - watchedParams = { ...watchedParams, ...watchedParams.filter?.where }; - const order = watchedParams.filter?.order; - - delete watchedParams.filter; - userParams.value = sanitizer(watchedParams); - emit('setUserParams', userParams.value, order); -} - -watch( - () => route.query[$props.searchUrl], - (val, oldValue) => (val || oldValue) && setUserParams(val) -); - -watch( - () => arrayData.store.userParams, - (val, oldValue) => (val || oldValue) && setUserParams(val), - { immediate: true } -); - -watch( - () => $props.modelValue, - (val) => (userParams.value = val ?? {}) -); +defineExpose({ search, params: userParams, remove }); const isLoading = ref(false); async function search(evt) { @@ -122,10 +96,9 @@ async function search(evt) { isLoading.value = true; const filter = { ...userParams.value, ...$props.modelValue }; store.userParamsChanged = true; - const { params: newParams } = await arrayData.addFilter({ + await arrayData.addFilter({ params: filter, }); - userParams.value = newParams; if (!$props.showAll && !Object.values(filter).length) store.data = []; emit('search'); @@ -138,7 +111,7 @@ async function clearFilters() { try { isLoading.value = true; store.userParamsChanged = true; - arrayData.reset(['skip', 'filter.skip', 'page']); + arrayData.resetPagination(); // Filtrar los params no removibles const removableFilters = Object.keys(userParams.value).filter((param) => $props.unremovableParams.includes(param) @@ -148,9 +121,8 @@ async function clearFilters() { for (const key of removableFilters) { newParams[key] = userParams.value[key]; } - userParams.value = {}; - userParams.value = { ...newParams }; // Actualizar los params con los removibles - await arrayData.applyFilter({ params: userParams.value }); + + await arrayData.applyFilter({ params: { ...newParams } }); if (!$props.showAll) { store.data = []; @@ -213,20 +185,13 @@ function formatValue(value) { return `"${value}"`; } -function sanitizer(params) { - for (const [key, value] of Object.entries(params)) { - if (key === 'and' && Array.isArray(value)) { - value.forEach((item) => { - Object.assign(params, item); - }); - delete params[key]; - } else if (value && typeof value === 'object') { - const param = Object.values(value)[0]; - if (typeof param == 'string') params[key] = param.replaceAll('%', ''); - } - } - return params; -} +const getLocale = (label) => { + const param = label.split('.').at(-1); + const globalLocale = `globals.params.${param}`; + if (te(globalLocale)) return t(globalLocale); + else if (te(t(`params.${param}`))); + else return t(`${route.meta.moduleName}.params.${param}`); +};