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/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/components/ItemsFilterPanel.vue b/src/components/ItemsFilterPanel.vue index dc2a34435..48f607a30 100644 --- a/src/components/ItemsFilterPanel.vue +++ b/src/components/ItemsFilterPanel.vue @@ -282,7 +282,7 @@ const setCategoryList = (data) => { +
+
+ {{ t('No data to display') }} +
+
@@ -405,7 +413,7 @@ defineExpose({ fab color="primary" icon="add" - shortcut="+" + v-shortcut @click="showFormDialog()" class="fill-icon" > diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index fdf2e52ee..d870e487f 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -268,7 +268,7 @@ async function applyFilter() { filter.where.and.push(selectedFilters.value); } - paginate.value.fetch(filter); + paginate.value.fetch({ filter }); } function setDate(type) { @@ -404,7 +404,7 @@ watch( ref="paginate" :data-key="`${model}Log`" :url="`${model}Logs`" - :filter="filter" + :user-filter="filter" :skeleton="false" auto-load @on-fetch="setLogTree" diff --git a/src/components/common/__tests__/VnNotes.spec.js b/src/components/common/__tests__/VnNotes.spec.js index 8f24a7f14..2603bf03c 100644 --- a/src/components/common/__tests__/VnNotes.spec.js +++ b/src/components/common/__tests__/VnNotes.spec.js @@ -1,51 +1,78 @@ -import { describe, it, expect, vi, beforeAll, afterEach, beforeEach } from 'vitest'; +import { + describe, + it, + expect, + vi, + beforeAll, + afterEach, + beforeEach, + afterAll, +} from 'vitest'; import { createWrapper, axios } from 'app/test/vitest/helper'; import VnNotes from 'src/components/ui/VnNotes.vue'; +import vnDate from 'src/boot/vnDate'; describe('VnNotes', () => { let vm; let wrapper; let spyFetch; let postMock; - let expectedBody; - const mockData= {name: 'Tony', lastName: 'Stark', text: 'Test Note', observationTypeFk: 1}; - - function generateExpectedBody() { - expectedBody = {...vm.$props.body, ...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk }}; - } - - async function setTestParams(text, observationType, type){ - vm.newNote.text = text; - vm.newNote.observationTypeFk = observationType; - wrapper.setProps({ selectType: type }); - } - - beforeAll(async () => { - vi.spyOn(axios, 'get').mockReturnValue({ data: [] }); - + let patchMock; + let expectedInsertBody; + let expectedUpdateBody; + const defaultOptions = { + url: '/test', + body: { name: 'Tony', lastName: 'Stark' }, + selectType: false, + saveUrl: null, + justInput: false, + }; + function generateWrapper( + options = defaultOptions, + text = null, + observationType = null, + ) { + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); wrapper = createWrapper(VnNotes, { - propsData: { - url: '/test', - body: { name: 'Tony', lastName: 'Stark' }, - } + propsData: options, }); wrapper = wrapper.wrapper; vm = wrapper.vm; - }); + vm.newNote.text = text; + vm.newNote.observationTypeFk = observationType; + } + + function createSpyFetch() { + spyFetch = vi.spyOn(vm.$refs.vnPaginateRef, 'fetch'); + } + + function generateExpectedBody() { + expectedInsertBody = { + ...vm.$props.body, + ...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk }, + }; + expectedUpdateBody = { ...vm.$props.body, ...{ notes: vm.newNote.text } }; + } beforeEach(() => { - postMock = vi.spyOn(axios, 'post').mockResolvedValue(mockData); - spyFetch = vi.spyOn(vm.vnPaginateRef, 'fetch').mockImplementation(() => vi.fn()); + postMock = vi.spyOn(axios, 'post'); + patchMock = vi.spyOn(axios, 'patch'); }); afterEach(() => { vi.clearAllMocks(); - expectedBody = {}; + expectedInsertBody = {}; + expectedUpdateBody = {}; + }); + + afterAll(() => { + vi.restoreAllMocks(); }); describe('insert', () => { - it('should not call axios.post and vnPaginateRef.fetch if newNote.text is null', async () => { - await setTestParams( null, null, true ); + it('should not call axios.post and vnPaginateRef.fetch when newNote.text is null', async () => { + generateWrapper({ selectType: true }); + createSpyFetch(); await vm.insert(); @@ -53,8 +80,9 @@ describe('VnNotes', () => { expect(spyFetch).not.toHaveBeenCalled(); }); - it('should not call axios.post and vnPaginateRef.fetch if newNote.text is empty', async () => { - await setTestParams( "", null, false ); + it('should not call axios.post and vnPaginateRef.fetch when newNote.text is empty', async () => { + generateWrapper(null, ''); + createSpyFetch(); await vm.insert(); @@ -62,8 +90,9 @@ describe('VnNotes', () => { expect(spyFetch).not.toHaveBeenCalled(); }); - it('should not call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is true', async () => { - await setTestParams( "Test Note", null, true ); + it('should not call axios.post and vnPaginateRef.fetch when observationTypeFk is null and selectType is true', async () => { + generateWrapper({ selectType: true }, 'Test Note'); + createSpyFetch(); await vm.insert(); @@ -71,37 +100,57 @@ describe('VnNotes', () => { expect(spyFetch).not.toHaveBeenCalled(); }); - it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is false', async () => { - await setTestParams( "Test Note", null, false ); - + it('should call axios.post and vnPaginateRef.fetch when observationTypeFk is missing and selectType is false', async () => { + generateWrapper(null, 'Test Note'); + createSpyFetch(); generateExpectedBody(); await vm.insert(); - expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody); - expect(spyFetch).toHaveBeenCalled(); - }); - - it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is setted and selectType is false', async () => { - await setTestParams( "Test Note", 1, false ); - - generateExpectedBody(); - - await vm.insert(); - - expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody); + expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody); expect(spyFetch).toHaveBeenCalled(); }); it('should call axios.post and vnPaginateRef.fetch when newNote is valid', async () => { - await setTestParams( "Test Note", 1, true ); - + generateWrapper({ selectType: true }, 'Test Note', 1); + createSpyFetch(); generateExpectedBody(); - + await vm.insert(); - expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody); + expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedInsertBody); expect(spyFetch).toHaveBeenCalled(); }); }); -}); \ No newline at end of file + + describe('update', () => { + it('should call axios.patch with saveUrl when saveUrl is set and justInput is true', async () => { + generateWrapper({ + url: '/business', + justInput: true, + saveUrl: '/saveUrlTest', + }); + generateExpectedBody(); + + await vm.update(); + + expect(patchMock).toHaveBeenCalledWith(vm.$props.saveUrl, expectedUpdateBody); + }); + + it('should call axios.patch with url when saveUrl is not set and justInput is true', async () => { + generateWrapper({ + url: '/business', + body: { workerFk: 1110 }, + justInput: true, + }); + generateExpectedBody(); + + await vm.update(); + + expect(patchMock).toHaveBeenCalledWith( + `${vm.$props.url}/${vm.$props.body.workerFk}`, + expectedUpdateBody, + ); + }); + }); +}); diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 2d7da8729..9d108c1f8 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -15,6 +15,10 @@ const props = defineProps({ type: Object, default: null, }, + userFilter: { + type: Object, + default: null, + }, entityId: { type: [Number, String], default: null, @@ -34,6 +38,7 @@ const isSummary = ref(); const arrayData = useArrayData(props.dataKey, { url: props.url, filter: props.filter, + userFilter: props.userFilter, skip: 0, }); const { store } = arrayData; diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index 1690a94ba..5b1d6e726 100644 --- a/src/components/ui/VnNotes.vue +++ b/src/components/ui/VnNotes.vue @@ -1,6 +1,6 @@