From cad6b077f066ee15018d2f904698feeb8ef4666d Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sat, 15 Feb 2025 17:45:06 +0100 Subject: [PATCH 01/30] fix: ticketfilter from and to --- src/components/ui/VnSearchbar.vue | 10 ++++++++++ src/composables/useArrayData.js | 15 +++++++++++---- src/pages/Ticket/TicketList.vue | 3 +++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 30e4135e2..98be77d09 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -69,6 +69,10 @@ const props = defineProps({ type: Boolean, default: true, }, + excludeParams: { + type: Object, + default: null, + }, }); const searchText = ref(); @@ -135,6 +139,12 @@ async function search() { }; delete filter.params.search; } + if (props.excludeParams) { + filter.params = { + ...filter.params, + exclude: props.excludeParams, + }; + } await arrayData.applyFilter(filter); searchText.value = undefined; } diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index bd3cecf08..250756c59 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -74,12 +74,13 @@ export function useArrayData(key, userOptions) { } } - async function fetch({ append = false, updateRouter = true }) { + async function fetch(fetchOptions) { + let { append = false, updateRouter = true } = fetchOptions; if (!store.url) return; cancelRequest(); canceller = new AbortController(); - const { params, limit } = setCurrentFilter(); + let { params, limit } = setCurrentFilter(); let exprFilter; if (store?.exprBuilder) { @@ -97,7 +98,10 @@ export function useArrayData(key, userOptions) { if (!params?.filter?.order?.length) delete params?.filter?.order; params.filter = JSON.stringify(params.filter); - + if (fetchOptions?.exclude) { + params = { ...params, ...fetchOptions.exclude }; + delete params.exclude; + } store.isLoading = true; const response = await axios.get(store.url, { signal: canceller.signal, @@ -145,8 +149,11 @@ export function useArrayData(key, userOptions) { async function applyFilter({ filter, params }, fetchOptions = {}) { if (filter) store.userFilter = filter; store.filter = {}; + if (params?.exclude) { + fetchOptions = { ...fetchOptions, exclude: params.exclude }; + delete params.exclude; + } if (params) store.userParams = { ...params }; - const response = await fetch(fetchOptions); return response; } diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 8df19c0d9..b16472764 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -445,6 +445,9 @@ function setReference(data) { :array-data-props="{ url: 'Tickets/filter', order: ['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id'], + label: t('Search items'), + excludeParams: { ...userParams }, + searchRemoveParams: true, exprBuilder, }" > From 3b3332f15cd6ec6431f01f31fc2f4655353e5676 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sat, 15 Feb 2025 23:59:22 +0100 Subject: [PATCH 02/30] feat: use clientFK in dialog --- .../Customer/composables/getAddresses.js | 8 +++---- src/pages/Ticket/TicketList.vue | 22 ++++++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/pages/Customer/composables/getAddresses.js b/src/pages/Customer/composables/getAddresses.js index e65e64455..5f18530e7 100644 --- a/src/pages/Customer/composables/getAddresses.js +++ b/src/pages/Customer/composables/getAddresses.js @@ -1,15 +1,15 @@ import axios from 'axios'; -export async function getAddresses(clientId, _filter = {}) { +export async function getAddresses(clientId, _filter = {}) { if (!clientId) return; const filter = { ..._filter, - fields: ['nickname', 'street', 'city', 'id'], + fields: ['nickname', 'street', 'city', 'id', 'isActive'], where: { isActive: true }, - order: 'nickname ASC', + order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'], }; const params = { filter: JSON.stringify(filter) }; return await axios.get(`Clients/${clientId}/addresses`, { params, }); -}; \ No newline at end of file +} diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index b16472764..6490f3b8e 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -1,6 +1,6 @@ <script setup> import axios from 'axios'; -import { computed, ref, onBeforeMount } from 'vue'; +import { computed, ref, onBeforeMount, watch } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { useStateStore } from 'stores/useStateStore'; import { useI18n } from 'vue-i18n'; @@ -425,6 +425,23 @@ function setReference(data) { dialogData.value.value.description = newDescription; } + +const formInitialData = ref({}); +watch( + () => route.query.table, + (newValue) => { + if (newValue) { + const clientId = +JSON.parse(newValue)?.clientFk; + if (!clientFk) return; + formInitialData.value = { + clientId, + }; + if (tableRef.value) tableRef.value.create.formInitialData = { clientId }; + onClientSelected({ clientId }); + } + }, + { immediate: true }, +); </script> <template> @@ -462,11 +479,10 @@ function setReference(data) { urlCreate: 'Tickets/new', title: t('ticketList.createTicket'), onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: { clientId: null }, + formInitialData, }" default-mode="table" :columns="columns" - :user-params="userParams" :right-search="false" redirect="ticket" v-model:selected="selectedRows" From 70fe95661abb3b275f90c227a684c19aa92cdb10 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 16 Feb 2025 00:00:24 +0100 Subject: [PATCH 03/30] style: remove optionId and optionLabel --- src/pages/Ticket/TicketList.vue | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 6490f3b8e..d8eb91fc9 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -560,11 +560,9 @@ watch( :label="t('ticketList.client')" v-model="data.clientId" :options="clientsOptions" - option-value="id" - option-label="name" hide-selected required - @update:model-value="(client) => onClientSelected(data)" + @update:model-value="() => onClientSelected(data)" :sort-by="'id ASC'" > <template #option="scope"> @@ -586,7 +584,6 @@ watch( :label="t('basicData.address')" v-model="data.addressId" :options="addressesOptions" - option-value="id" option-label="nickname" hide-selected map-options @@ -655,8 +652,6 @@ watch( :label="t('globals.warehouse')" v-model="data.warehouseId" :options="warehousesOptions" - option-value="id" - option-label="name" hide-selected required @update:model-value="() => fetchAvailableAgencies(data)" @@ -716,7 +711,6 @@ watch( :label="t('ticketList.company')" v-model="dialogData.companyFk" :options="companiesOptions" - option-value="id" option-label="code" hide-selected > @@ -727,7 +721,6 @@ watch( :label="t('ticketList.bank')" v-model="dialogData.bankFk" :options="accountingOptions" - option-value="id" option-label="bank" hide-selected @update:model-value="setReference" From b998aab6dd02b13996bd576c2864960cbe6b9e47 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 16 Feb 2025 00:46:20 +0100 Subject: [PATCH 04/30] test: add test --- src/pages/Ticket/TicketList.vue | 2 +- .../integration/ticket/ticketList.spec.js | 38 +++++++++++++++++-- test/cypress/support/commands.js | 3 ++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index d8eb91fc9..ed2aad37c 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -432,7 +432,7 @@ watch( (newValue) => { if (newValue) { const clientId = +JSON.parse(newValue)?.clientFk; - if (!clientFk) return; + if (!clientId) return; formInitialData.value = { clientId, }; diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 2984a4ee4..8c03462da 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -1,16 +1,16 @@ /// <reference types="cypress" /> describe('TicketList', () => { - const firstRow = 'tbody > :nth-child(1)'; + const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); cy.visit('/#/ticket/list'); + cy.domContentLoad(); }); const searchResults = (search) => { - cy.dataCy('vn-searchbar').find('input').focus(); - if (search) cy.dataCy('vn-searchbar').find('input').type(search); + if (search) cy.typeSearchbar().type(search); cy.dataCy('vn-searchbar').find('input').type('{enter}'); cy.dataCy('ticketListTable').should('exist'); cy.get(firstRow).should('exist'); @@ -27,7 +27,7 @@ describe('TicketList', () => { cy.window().then((win) => { cy.stub(win, 'open').as('windowOpen'); }); - cy.get(firstRow).find('.q-btn:first').click(); + cy.get(firstRow).should('be.visible').find('.q-btn:first').click(); cy.get('@windowOpen').should('be.calledWithMatch', /\/ticket\/\d+\/sale/); }); @@ -38,6 +38,36 @@ describe('TicketList', () => { cy.get('.summaryBody').should('exist'); }); + it.only('Filter client and create ticket', () => { + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketSearchbar'); + searchResults(); + cy.wait('@ticketSearchbar').then((interception) => { + const { query } = interception.request; + cy.log('Request query:', query); + expect(query).to.have.property('from'); + expect(query).to.have.property('to'); + expect(query).to.not.have.property('clientFk'); + }); + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); + cy.get('[data-cy="Customer ID_input"]').clear('1'); + cy.get('[data-cy="Customer ID_input"]').type('1101{enter}'); + cy.wait('@ticketFilter').then((interception) => { + const { query } = interception.request; + cy.log('Request query:', query); + expect(query).to.not.have.property('from'); + expect(query).to.not.have.property('to'); + expect(query).to.have.property('clientFk'); + }); + cy.get('[data-cy="vnTableCreateBtn"] > .q-btn__content > .q-icon').click(); + cy.get('[data-cy="Customer_select"]').should('have.value', 'Bruce Wayne'); + cy.get('[data-cy="Address_select"]').click(); + + cy.selectOptionBeta().click(); + cy.get('[data-cy="Address_select"]').should('have.value', 'Bruce Wayne'); + // cy.get('[role="listbox"] .q-item:nth-child(1)>.q-item__section--avatar > i') + // .should('have.text', 'star') + // .click(); + }); it('Client list create new client', () => { cy.dataCy('vnTableCreateBtn').should('exist'); cy.dataCy('vnTableCreateBtn').click(); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 2c93fbf84..c4e2c29ca 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -365,3 +365,6 @@ Cypress.Commands.add('clickButtonWithIcon', (iconClass) => { Cypress.Commands.add('clickButtonWithText', (buttonText) => { cy.get('.q-btn').contains(buttonText).click(); }); +Cypress.Commands.add('selectOptionBeta', (index = 1) => { + cy.get(`[role="listbox"] .q-item:nth-child(${index})`).click(); +}); From ab3ac4fdebdc063ec6e80ff95b1a069ba0ba9490 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 16 Feb 2025 00:50:39 +0100 Subject: [PATCH 05/30] fix: remove bad code --- src/pages/Ticket/TicketList.vue | 1 - test/cypress/integration/ticket/ticketList.spec.js | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index ed2aad37c..fa03b3f6d 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -462,7 +462,6 @@ watch( :array-data-props="{ url: 'Tickets/filter', order: ['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id'], - label: t('Search items'), excludeParams: { ...userParams }, searchRemoveParams: true, exprBuilder, diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 8c03462da..4164d373e 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -38,12 +38,11 @@ describe('TicketList', () => { cy.get('.summaryBody').should('exist'); }); - it.only('Filter client and create ticket', () => { + it('filter client and create ticket', () => { cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketSearchbar'); searchResults(); cy.wait('@ticketSearchbar').then((interception) => { const { query } = interception.request; - cy.log('Request query:', query); expect(query).to.have.property('from'); expect(query).to.have.property('to'); expect(query).to.not.have.property('clientFk'); @@ -53,7 +52,6 @@ describe('TicketList', () => { cy.get('[data-cy="Customer ID_input"]').type('1101{enter}'); cy.wait('@ticketFilter').then((interception) => { const { query } = interception.request; - cy.log('Request query:', query); expect(query).to.not.have.property('from'); expect(query).to.not.have.property('to'); expect(query).to.have.property('clientFk'); @@ -64,9 +62,6 @@ describe('TicketList', () => { cy.selectOptionBeta().click(); cy.get('[data-cy="Address_select"]').should('have.value', 'Bruce Wayne'); - // cy.get('[role="listbox"] .q-item:nth-child(1)>.q-item__section--avatar > i') - // .should('have.text', 'star') - // .click(); }); it('Client list create new client', () => { cy.dataCy('vnTableCreateBtn').should('exist'); From 1972e921df1d96db346e1487efc6ff396a337f19 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 16 Feb 2025 00:52:37 +0100 Subject: [PATCH 06/30] test: fix getAddresses --- .../Customer/composables/__tests__/getAddresses.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Customer/composables/__tests__/getAddresses.spec.js b/src/pages/Customer/composables/__tests__/getAddresses.spec.js index 9e04a83cc..8c90bf281 100644 --- a/src/pages/Customer/composables/__tests__/getAddresses.spec.js +++ b/src/pages/Customer/composables/__tests__/getAddresses.spec.js @@ -17,9 +17,9 @@ describe('getAddresses', () => { expect(axios.get).toHaveBeenCalledWith(`Clients/${clientId}/addresses`, { params: { filter: JSON.stringify({ - fields: ['nickname', 'street', 'city', 'id'], + fields: ['nickname', 'street', 'city', 'id', 'isActive'], where: { isActive: true }, - order: 'nickname ASC', + order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'], }), }, }); @@ -30,4 +30,4 @@ describe('getAddresses', () => { expect(axios.get).not.toHaveBeenCalled(); }); -}); \ No newline at end of file +}); From 2ec5c2b49fe4ab6ae0da7dcbad82b2e0ff6bcfe5 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 16 Feb 2025 03:18:10 +0100 Subject: [PATCH 07/30] fix: ticketList columnfilter --- src/pages/Ticket/TicketList.vue | 68 ++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index fa03b3f6d..cdc122004 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -121,12 +121,16 @@ const columns = computed(() => [ { align: 'left', name: 'shipped', + component: 'time', + columnFilter: false, label: t('ticketList.hour'), format: (row) => toTimeFormat(row.shipped), }, { align: 'left', name: 'zoneLanding', + component: 'time', + columnFilter: false, label: t('ticketList.closure'), format: (row, dashIfEmpty) => dashIfEmpty(toTimeFormat(row.zoneLanding)), }, @@ -146,9 +150,16 @@ const columns = computed(() => [ }, { align: 'left', - name: 'province', + name: 'provinceFk', label: t('ticketList.province'), - columnClass: 'expand', + component: 'select', + attrs: { + url: 'Provinces', + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.province), }, { align: 'left', @@ -182,9 +193,21 @@ const columns = computed(() => [ }, { align: 'left', - name: 'warehouse', - label: t('ticketList.warehouse'), - columnClass: 'expand', + name: 'warehouseFk', + label: t('globals.warehouse'), + component: 'select', + attrs: { + url: 'warehouses', + fields: ['id', 'name'], + optionLabel: 'name', + optionValue: 'id', + }, + format: (row) => row.warehouse, + columnField: { + component: null, + }, + cardVisible: false, + create: false, }, { align: 'left', @@ -216,6 +239,7 @@ const columns = computed(() => [ { title: t('components.smartCard.viewSummary'), icon: 'preview', + isPrimary: true, action: (row, evt) => { if (evt && evt.ctrlKey) { const url = router.resolve({ @@ -232,7 +256,7 @@ const columns = computed(() => [ function resetAgenciesSelector(formData) { agenciesOptions.value = []; - if(formData) formData.agencyModeId = null; + if (formData) formData.agencyModeId = null; } function redirectToLines(id) { @@ -240,7 +264,7 @@ function redirectToLines(id) { window.open(url, '_blank'); } -const onClientSelected = async (formData) => { +const onClientSelected = async (formData) => { resetAgenciesSelector(formData); await fetchClient(formData); await fetchAddresses(formData); @@ -248,14 +272,12 @@ const onClientSelected = async (formData) => { const fetchAvailableAgencies = async (formData) => { resetAgenciesSelector(formData); - const response= await getAgencies(formData, selectedClient.value); + const response = await getAgencies(formData, selectedClient.value); if (!response) return; - - const { options, agency } = response - if(options) - agenciesOptions.value = options; - if(agency) - formData.agencyModeId = agency; + + const { options, agency } = response; + if (options) agenciesOptions.value = options; + if (agency) formData.agencyModeId = agency; }; const fetchClient = async (formData) => { @@ -330,7 +352,7 @@ function openBalanceDialog(ticket) { const description = ref([]); const firstTicketClientId = checkedTickets[0].clientFk; const isSameClient = checkedTickets.every( - (ticket) => ticket.clientFk === firstTicketClientId + (ticket) => ticket.clientFk === firstTicketClientId, ); if (!isSameClient) { @@ -369,7 +391,7 @@ async function onSubmit() { description: dialogData.value.value.description, clientFk: dialogData.value.value.clientFk, email: email[0].email, - } + }, ); if (data) notify('globals.dataSaved', 'positive'); @@ -388,32 +410,32 @@ function setReference(data) { switch (data) { case 1: newDescription = `${t( - 'ticketList.creditCard' + 'ticketList.creditCard', )}, ${dialogData.value.value.description.replace( /^(Credit Card, |Cash, |Transfers, )/, - '' + '', )}`; break; case 2: newDescription = `${t( - 'ticketList.cash' + 'ticketList.cash', )}, ${dialogData.value.value.description.replace( /^(Credit Card, |Cash, |Transfers, )/, - '' + '', )}`; break; case 3: newDescription = `${newDescription.replace( /^(Credit Card, |Cash, |Transfers, )/, - '' + '', )}`; break; case 4: newDescription = `${t( - 'ticketList.transfers' + 'ticketList.transfers', )}, ${dialogData.value.value.description.replace( /^(Credit Card, |Cash, |Transfers, )/, - '' + '', )}`; break; case 3317: From 33d6662f97a6afb148e2ab799809a962ab1b2e0e Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 16 Feb 2025 23:35:56 +0100 Subject: [PATCH 08/30] feat: same searchbar logic filter in VnFilterPanel --- src/components/common/VnSection.vue | 5 +- src/components/ui/VnFilterPanel.vue | 46 ++++++++++++--- src/components/ui/VnSearchbar.vue | 3 +- src/pages/Ticket/TicketFilter.vue | 56 +++++++++++++++++-- src/pages/Ticket/TicketList.vue | 6 +- .../integration/ticket/tickeFilter.spec.js | 44 +++++++++++++++ .../integration/ticket/ticketList.spec.js | 8 +-- test/cypress/support/commands.js | 5 ++ 8 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 test/cypress/integration/ticket/tickeFilter.spec.js diff --git a/src/components/common/VnSection.vue b/src/components/common/VnSection.vue index ef65b841f..03871c3b1 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -2,7 +2,7 @@ import RightAdvancedMenu from './RightAdvancedMenu.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue'; -import { onBeforeMount, onMounted, onUnmounted, computed, ref } from 'vue'; +import { onBeforeMount, onMounted, onUnmounted, computed, ref, provide } from 'vue'; import { useArrayData } from 'src/composables/useArrayData'; import { useRoute, useRouter } from 'vue-router'; import { useHasContent } from 'src/composables/useHasContent'; @@ -52,10 +52,12 @@ const router = useRouter(); let arrayData; const sectionValue = computed(() => $props.section ?? $props.dataKey); const isMainSection = ref(false); +const searchbarRef = ref(null); const searchbarId = 'section-searchbar'; const advancedMenuSlot = 'advanced-menu'; const hasContent = useHasContent(`#${searchbarId}`); +provide('searchbar', () => searchbarRef.value?.search()); onBeforeMount(() => { if ($props.dataKey) @@ -90,6 +92,7 @@ function checkIsMain() { <template> <slot name="searchbar"> <VnSearchbar + ref="searchbarRef" v-if="searchBar && !hasContent" v-bind="arrayDataProps" :data-key="dataKey" diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 93f069cc6..da01d7174 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref, computed, provide, inject, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; import toDate from 'filters/toDate'; @@ -14,6 +14,10 @@ const $props = defineProps({ type: Object, default: () => {}, }, + searchBarRef: { + type: Object, + default: () => {}, + }, dataKey: { type: String, required: true, @@ -61,6 +65,14 @@ const $props = defineProps({ type: Object, default: null, }, + requiredParams: { + type: [Array, Object], + default: () => [], + }, + useSearchbar: { + type: [Boolean, Function], + default: false, + }, }); const emit = defineEmits([ @@ -84,13 +96,29 @@ const arrayData = const store = arrayData.store; const userParams = ref(useFilterParams($props.dataKey).params); const userOrders = ref(useFilterParams($props.dataKey).orders); - +const searchbar = ref(null); defineExpose({ search, params: userParams, remove }); - +onMounted(() => { + searchbar.value = inject('searchbar'); +}); const isLoading = ref(false); async function search(evt) { try { - if (evt && $props.disableSubmitEvent) return; + if ($props.useSearchbar) { + if (!searchbar.value) { + console.error('Searchbar not found'); + return; + } + if (typeof $props.useSearchbar === 'function') { + $props.useSearchbar(userParams.value); + + if (Object.keys(userParams.value).length == 0) { + searchbar.value(); + return; + } + } + } + if (evt && $props.disableSubmitEvent) debugger; store.filter.where = {}; isLoading.value = true; @@ -114,7 +142,7 @@ async function clearFilters() { arrayData.resetPagination(); // Filtrar los params no removibles const removableFilters = Object.keys(userParams.value).filter((param) => - $props.unremovableParams.includes(param) + $props.unremovableParams.includes(param), ); const newParams = {}; // Conservar solo los params que no son removibles @@ -162,13 +190,13 @@ const formatTags = (tags) => { const tags = computed(() => { const filteredTags = tagsList.value.filter( - (tag) => !($props.customTags || []).includes(tag.label) + (tag) => !($props.customTags || []).includes(tag.label), ); return formatTags(filteredTags); }); const customTags = computed(() => - tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label)) + tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.label)), ); async function remove(key) { @@ -191,7 +219,9 @@ const getLocale = (label) => { if (te(globalLocale)) return t(globalLocale); else if (te(t(`params.${param}`))); else { - const camelCaseModuleName = route.meta.moduleName.charAt(0).toLowerCase() + route.meta.moduleName.slice(1); + const camelCaseModuleName = + route.meta.moduleName.charAt(0).toLowerCase() + + route.meta.moduleName.slice(1); return t(`${camelCaseModuleName}.params.${param}`); } }; diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 98be77d09..c33f80da8 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, ref, computed, watch } from 'vue'; +import { onMounted, ref, computed, watch, provide } from 'vue'; import { useQuasar } from 'quasar'; import { useArrayData } from 'composables/useArrayData'; import VnInput from 'src/components/common/VnInput.vue'; @@ -148,6 +148,7 @@ async function search() { await arrayData.applyFilter(filter); searchText.value = undefined; } +defineExpose({ search }); </script> <template> <Teleport to="#searchbar" v-if="state.isHeaderMounted()"> diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index 4b50892b0..d4d56d20f 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -1,5 +1,5 @@ <script setup> -import { ref } from 'vue'; +import { computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; @@ -8,6 +8,8 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; +import { Notify } from 'quasar'; +import useNotify from 'src/composables/useNotify'; const { t } = useI18n(); const props = defineProps({ @@ -15,6 +17,10 @@ const props = defineProps({ type: String, required: true, }, + searchBarRef: { + type: Object, + default: () => ({}), + }, }); const provinces = ref([]); @@ -22,6 +28,7 @@ const states = ref([]); const agencies = ref([]); const warehouses = ref([]); const groupedStates = ref([]); +const { notify } = useNotify(); const getGroupedStates = (data) => { for (const state of data) { @@ -32,6 +39,29 @@ const getGroupedStates = (data) => { }); } }; +const from = Date.vnNew(); +from.setHours(0, 0, 0, 0); +from.setDate(from.getDate() - 7); +const to = Date.vnNew(); +to.setHours(23, 59, 0, 0); +to.setDate(to.getDate() + 1); +const userParams = computed(() => { + from.value = from.toISOString(); + to.value = to.toISOString(); + return { from, to }; +}); +function validateDateRange(params) { + const hasFrom = 'from' in params; + const hasTo = 'to' in params; + + if (hasFrom !== hasTo) { + notify(t(`dateRangeMustHaveBothFrom`), 'negative'); + + throw new Error(t(`dateRangeMustHaveBothFrom`)); + } + + return hasFrom && hasTo; +} </script> <template> @@ -48,7 +78,13 @@ const getGroupedStates = (data) => { /> <FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> - <VnFilterPanel :data-key="props.dataKey" :search-button="true"> + <VnFilterPanel + :searchBarRef="$props.searchBarRef" + :data-key="props.dataKey" + :search-button="true" + :use-searchbar="validateDateRange" + :requiredParams="{ ...userParams }" + > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> <strong>{{ t(`params.${tag.label}`) }}: </strong> @@ -74,10 +110,20 @@ const getGroupedStates = (data) => { </QItem> <QItem> <QItemSection> - <VnInputDate v-model="params.from" :label="t('From')" is-outlined /> + <VnInputDate + v-model="params.from" + :label="t('From')" + is-outlined + data-cy="From_date" + /> </QItemSection> <QItemSection> - <VnInputDate v-model="params.to" :label="t('To')" is-outlined /> + <VnInputDate + v-model="params.to" + :label="t('To')" + is-outlined + data-cy="To_date" + /> </QItemSection> </QItem> <QItem> @@ -288,6 +334,7 @@ const getGroupedStates = (data) => { <i18n> en: + dateRangeMustHaveBothFrom: The date range must have both 'from' and 'to' params: search: Contains clientFk: Customer @@ -315,6 +362,7 @@ en: DELIVERED: Delivered ON_PREVIOUS: ON_PREVIOUS es: + dateRangeMustHaveBothFrom: El rango de fechas debe tener 'desde' y 'hasta' params: search: Contiene clientFk: Cliente diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index cdc122004..03db94732 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -478,6 +478,7 @@ watch( auto-load /> <VnSection + ref="sectionRef" :data-key="dataKey" :columns="columns" prefix="card" @@ -490,7 +491,10 @@ watch( }" > <template #advanced-menu> - <TicketFilter data-key="TicketList" /> + <TicketFilter + data-key="TicketList" + :searchbarRef="$refs.sectionRef?.$refs.searchbarRef" + /> </template> <template #body> <VnTable diff --git a/test/cypress/integration/ticket/tickeFilter.spec.js b/test/cypress/integration/ticket/tickeFilter.spec.js new file mode 100644 index 000000000..b2bf78743 --- /dev/null +++ b/test/cypress/integration/ticket/tickeFilter.spec.js @@ -0,0 +1,44 @@ +/// <reference types="cypress" /> +describe('TicketFilter', () => { + const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; + + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/list'); + cy.domContentLoad(); + }); + + it.only('use search button', function () { + cy.waitForElement('.q-page'); + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); + cy.searchBtnFilterPanel(); + cy.wait('@ticketFilter').then(({ request }) => { + const { query } = request; + expect(query).to.have.property('from'); + expect(query).to.have.property('to'); + }); + cy.on('uncaught:exception', () => { + return false; + }); + cy.get('.q-field__control-container > [data-cy="From_date"]').type( + '14-02-2025{enter}', + ); + cy.get('.q-notification').should( + 'contain', + `The date range must have both 'from' and 'to'`, + ); + + cy.get('.q-field__control-container > [data-cy="To_date"]').type( + '16/02/2025{enter}', + ); + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); + cy.searchBtnFilterPanel(); + cy.wait('@ticketFilter').then(({ request }) => { + const { query } = request; + expect(query).to.have.property('from'); + expect(query).to.have.property('to'); + }); + cy.location('href').should('contain', '#/ticket/999999'); + }); +}); diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 4164d373e..800ce6aaa 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -41,8 +41,8 @@ describe('TicketList', () => { it('filter client and create ticket', () => { cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketSearchbar'); searchResults(); - cy.wait('@ticketSearchbar').then((interception) => { - const { query } = interception.request; + cy.wait('@ticketSearchbar').then(({ request }) => { + const { query } = request; expect(query).to.have.property('from'); expect(query).to.have.property('to'); expect(query).to.not.have.property('clientFk'); @@ -50,8 +50,8 @@ describe('TicketList', () => { cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); cy.get('[data-cy="Customer ID_input"]').clear('1'); cy.get('[data-cy="Customer ID_input"]').type('1101{enter}'); - cy.wait('@ticketFilter').then((interception) => { - const { query } = interception.request; + cy.wait('@ticketFilter').then(({ request }) => { + const { query } = request; expect(query).to.not.have.property('from'); expect(query).to.not.have.property('to'); expect(query).to.have.property('clientFk'); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index c4e2c29ca..4606ea56c 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -368,3 +368,8 @@ Cypress.Commands.add('clickButtonWithText', (buttonText) => { Cypress.Commands.add('selectOptionBeta', (index = 1) => { cy.get(`[role="listbox"] .q-item:nth-child(${index})`).click(); }); +Cypress.Commands.add('searchBtnFilterPanel', () => { + cy.get( + '.q-scrollarea__content > .q-btn--standard > .q-btn__content > .q-icon', + ).click(); +}); From 443a2747ccf852d9d43f618c67e350ed1db010cd Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 16 Feb 2025 23:46:05 +0100 Subject: [PATCH 09/30] style: remove proposal --- src/components/ui/VnFilterPanel.vue | 8 -------- src/pages/Ticket/TicketFilter.vue | 11 ----------- src/pages/Ticket/TicketList.vue | 5 +---- test/cypress/integration/ticket/tickeFilter.spec.js | 2 -- test/cypress/integration/ticket/ticketList.spec.js | 10 +++++----- 5 files changed, 6 insertions(+), 30 deletions(-) diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index da01d7174..5ebba5028 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -14,10 +14,6 @@ const $props = defineProps({ type: Object, default: () => {}, }, - searchBarRef: { - type: Object, - default: () => {}, - }, dataKey: { type: String, required: true, @@ -65,10 +61,6 @@ const $props = defineProps({ type: Object, default: null, }, - requiredParams: { - type: [Array, Object], - default: () => [], - }, useSearchbar: { type: [Boolean, Function], default: false, diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index d4d56d20f..549618e55 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -17,10 +17,6 @@ const props = defineProps({ type: String, required: true, }, - searchBarRef: { - type: Object, - default: () => ({}), - }, }); const provinces = ref([]); @@ -45,11 +41,6 @@ from.setDate(from.getDate() - 7); const to = Date.vnNew(); to.setHours(23, 59, 0, 0); to.setDate(to.getDate() + 1); -const userParams = computed(() => { - from.value = from.toISOString(); - to.value = to.toISOString(); - return { from, to }; -}); function validateDateRange(params) { const hasFrom = 'from' in params; const hasTo = 'to' in params; @@ -79,11 +70,9 @@ function validateDateRange(params) { <FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> <VnFilterPanel - :searchBarRef="$props.searchBarRef" :data-key="props.dataKey" :search-button="true" :use-searchbar="validateDateRange" - :requiredParams="{ ...userParams }" > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 03db94732..ad8865a57 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -491,10 +491,7 @@ watch( }" > <template #advanced-menu> - <TicketFilter - data-key="TicketList" - :searchbarRef="$refs.sectionRef?.$refs.searchbarRef" - /> + <TicketFilter data-key="TicketList" /> </template> <template #body> <VnTable diff --git a/test/cypress/integration/ticket/tickeFilter.spec.js b/test/cypress/integration/ticket/tickeFilter.spec.js index b2bf78743..408c5a19f 100644 --- a/test/cypress/integration/ticket/tickeFilter.spec.js +++ b/test/cypress/integration/ticket/tickeFilter.spec.js @@ -1,7 +1,5 @@ /// <reference types="cypress" /> describe('TicketFilter', () => { - const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; - beforeEach(() => { cy.login('developer'); cy.viewport(1920, 1080); diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 800ce6aaa..e6ddc2fa1 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -48,8 +48,8 @@ describe('TicketList', () => { expect(query).to.not.have.property('clientFk'); }); cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); - cy.get('[data-cy="Customer ID_input"]').clear('1'); - cy.get('[data-cy="Customer ID_input"]').type('1101{enter}'); + cy.dataCy('Customer ID_input').clear('1'); + cy.dataCy('Customer ID_input').type('1101{enter}'); cy.wait('@ticketFilter').then(({ request }) => { const { query } = request; expect(query).to.not.have.property('from'); @@ -57,11 +57,11 @@ describe('TicketList', () => { expect(query).to.have.property('clientFk'); }); cy.get('[data-cy="vnTableCreateBtn"] > .q-btn__content > .q-icon').click(); - cy.get('[data-cy="Customer_select"]').should('have.value', 'Bruce Wayne'); - cy.get('[data-cy="Address_select"]').click(); + cy.dataCy('Customer_select').should('have.value', 'Bruce Wayne'); + cy.dataCy('Address_select').click(); cy.selectOptionBeta().click(); - cy.get('[data-cy="Address_select"]').should('have.value', 'Bruce Wayne'); + cy.dataCy('Address_select').should('have.value', 'Bruce Wayne'); }); it('Client list create new client', () => { cy.dataCy('vnTableCreateBtn').should('exist'); From 3e3713a9376757da87b2621ad71d6659d5d97346 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 16 Feb 2025 23:47:31 +0100 Subject: [PATCH 10/30] perf: remove unnussed import --- src/components/ui/VnSearchbar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index c33f80da8..f4b4f0fe8 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, ref, computed, watch, provide } from 'vue'; +import { onMounted, ref, computed, watch } from 'vue'; import { useQuasar } from 'quasar'; import { useArrayData } from 'composables/useArrayData'; import VnInput from 'src/components/common/VnInput.vue'; From c0823b0f48e92a60bbffed2b1385900490eaac30 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 17 Feb 2025 19:33:29 +0100 Subject: [PATCH 11/30] perf: comments --- src/components/ui/VnFilterPanel.vue | 14 ++++++++------ src/pages/Ticket/TicketFilter.vue | 3 +-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 5ebba5028..7af226bff 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, provide, inject, onMounted } from 'vue'; +import { ref, computed, inject, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; import toDate from 'filters/toDate'; @@ -89,28 +89,30 @@ const store = arrayData.store; const userParams = ref(useFilterParams($props.dataKey).params); const userOrders = ref(useFilterParams($props.dataKey).orders); const searchbar = ref(null); -defineExpose({ search, params: userParams, remove }); +const isLoading = ref(false); + onMounted(() => { searchbar.value = inject('searchbar'); }); -const isLoading = ref(false); + +defineExpose({ search, params: userParams, remove }); + async function search(evt) { try { if ($props.useSearchbar) { if (!searchbar.value) { - console.error('Searchbar not found'); return; } if (typeof $props.useSearchbar === 'function') { $props.useSearchbar(userParams.value); - if (Object.keys(userParams.value).length == 0) { + if (!Object.keys(userParams.value).length) { searchbar.value(); return; } } } - if (evt && $props.disableSubmitEvent) debugger; + if (evt && $props.disableSubmitEvent) return; store.filter.where = {}; isLoading.value = true; diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index 549618e55..254b89e60 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -1,5 +1,5 @@ <script setup> -import { computed, ref } from 'vue'; +import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; @@ -8,7 +8,6 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; -import { Notify } from 'quasar'; import useNotify from 'src/composables/useNotify'; const { t } = useI18n(); From 7f370dc29c4381d0c4f51a6d33e3a8ae32bf9496 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 17 Feb 2025 22:57:11 +0100 Subject: [PATCH 12/30] test: improve getOption command --- .../integration/client/clientAddress.spec.js | 2 +- .../cypress/integration/ticket/tickeFilter.spec.js | 2 +- test/cypress/integration/ticket/ticketList.spec.js | 2 +- .../integration/vnComponent/UserPanel.spec.js | 14 ++++---------- .../integration/vnComponent/VnLocation.spec.js | 10 +++++----- test/cypress/support/commands.js | 6 +++++- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/test/cypress/integration/client/clientAddress.spec.js b/test/cypress/integration/client/clientAddress.spec.js index 434180047..4d6186679 100644 --- a/test/cypress/integration/client/clientAddress.spec.js +++ b/test/cypress/integration/client/clientAddress.spec.js @@ -18,7 +18,7 @@ describe('Client consignee', () => { const addressName = 'test'; cy.dataCy('Consignee_input').type(addressName); cy.dataCy('Location_select').click(); - cy.get('[role="listbox"] .q-item:nth-child(1)').click(); + cy.getOption(); cy.dataCy('Street address_input').type('TEST ADDRESS'); cy.get('.q-btn-group > .q-btn--standard').click(); cy.location('href').should('contain', '#/customer/1107/address'); diff --git a/test/cypress/integration/ticket/tickeFilter.spec.js b/test/cypress/integration/ticket/tickeFilter.spec.js index 408c5a19f..59abb0164 100644 --- a/test/cypress/integration/ticket/tickeFilter.spec.js +++ b/test/cypress/integration/ticket/tickeFilter.spec.js @@ -7,7 +7,7 @@ describe('TicketFilter', () => { cy.domContentLoad(); }); - it.only('use search button', function () { + it('use search button', function () { cy.waitForElement('.q-page'); cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); cy.searchBtnFilterPanel(); diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index e6ddc2fa1..d0ea14779 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -60,7 +60,7 @@ describe('TicketList', () => { cy.dataCy('Customer_select').should('have.value', 'Bruce Wayne'); cy.dataCy('Address_select').click(); - cy.selectOptionBeta().click(); + cy.getOption().click(); cy.dataCy('Address_select').should('have.value', 'Bruce Wayne'); }); it('Client list create new client', () => { diff --git a/test/cypress/integration/vnComponent/UserPanel.spec.js b/test/cypress/integration/vnComponent/UserPanel.spec.js index e83d07954..25724e873 100644 --- a/test/cypress/integration/vnComponent/UserPanel.spec.js +++ b/test/cypress/integration/vnComponent/UserPanel.spec.js @@ -18,7 +18,7 @@ describe('UserPanel', () => { cy.get(userWarehouse).should('have.value', 'VNL').click(); // Actualizo la opción - getOption(3); + cy.getOption(3); //Compruebo la notificación cy.get('.q-notification').should('be.visible'); @@ -26,7 +26,7 @@ describe('UserPanel', () => { //Restauro el valor cy.get(userWarehouse).click(); - getOption(2); + cy.getOption(2); }); it('should notify when update user company', () => { const userCompany = @@ -39,7 +39,7 @@ describe('UserPanel', () => { cy.get(userCompany).should('have.value', 'Warehouse One').click(); //Actualizo la opción - getOption(2); + cy.getOption(2); //Compruebo la notificación cy.get('.q-notification').should('be.visible'); @@ -47,12 +47,6 @@ describe('UserPanel', () => { //Restauro el valor cy.get(userCompany).click(); - getOption(1); + cy.getOption(1); }); }); - -function getOption(index) { - cy.waitForElement('[role="listbox"]'); - const option = `[role="listbox"] .q-item:nth-child(${index})`; - cy.get(option).click(); -} diff --git a/test/cypress/integration/vnComponent/VnLocation.spec.js b/test/cypress/integration/vnComponent/VnLocation.spec.js index 751b3a065..9074fc089 100644 --- a/test/cypress/integration/vnComponent/VnLocation.spec.js +++ b/test/cypress/integration/vnComponent/VnLocation.spec.js @@ -40,7 +40,7 @@ describe('VnLocation', () => { cy.selectOption(countrySelector, country); cy.dataCy('locationProvince').type(`${province}{enter}`); cy.get( - `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(3) ` + `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(3) `, ).click(); cy.dataCy('locationProvince').should('have.value', province); }); @@ -87,9 +87,9 @@ describe('VnLocation', () => { .get(':nth-child(1)') .should('have.length.at.least', 2); cy.get( - firstOption.concat(' > .q-item__section > .q-item__label--caption') + firstOption.concat(' > .q-item__section > .q-item__label--caption'), ).should('have.text', postCodeLabel); - cy.get(firstOption).click(); + cy.getOption(); cy.get('.q-btn-group > .q-btn--standard > .q-btn__content > .q-icon').click(); cy.reload(); cy.waitForElement('.q-form'); @@ -103,7 +103,7 @@ describe('VnLocation', () => { cy.get('.q-card > h1').should('have.text', 'New postcode'); cy.selectOption( `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix}`, - province + province, ); cy.get(dialogInputs).eq(0).clear(); cy.get(dialogInputs).eq(0).type(postCode); @@ -156,7 +156,7 @@ describe('VnLocation', () => { cy.get(createLocationButton).click(); cy.selectOption( `${createForm.prefix} > :nth-child(5) > :nth-child(3) `, - 'España' + 'España', ); cy.dataCy('Province_icon').click(); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 4606ea56c..fab881620 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -362,12 +362,16 @@ Cypress.Commands.add('clickButtonWith', (type, value) => { Cypress.Commands.add('clickButtonWithIcon', (iconClass) => { cy.get(`.q-icon.${iconClass}`).parent().click(); }); + Cypress.Commands.add('clickButtonWithText', (buttonText) => { cy.get('.q-btn').contains(buttonText).click(); }); -Cypress.Commands.add('selectOptionBeta', (index = 1) => { + +Cypress.Commands.add('getOption', (index = 1) => { + cy.waitForElement('[role="listbox"]'); cy.get(`[role="listbox"] .q-item:nth-child(${index})`).click(); }); + Cypress.Commands.add('searchBtnFilterPanel', () => { cy.get( '.q-scrollarea__content > .q-btn--standard > .q-btn__content > .q-icon', From df794391ec8d97852b5457b4434964ab72a23cc8 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 17 Feb 2025 23:08:57 +0100 Subject: [PATCH 13/30] feat: agency in ticketlist sort data --- .../Route/Agency/composables/getAgencies.js | 18 ++++++++++-------- src/pages/Ticket/TicketFilter.vue | 7 ++++++- src/pages/Ticket/TicketList.vue | 3 +++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/pages/Route/Agency/composables/getAgencies.js b/src/pages/Route/Agency/composables/getAgencies.js index 850f87456..2462ec718 100644 --- a/src/pages/Route/Agency/composables/getAgencies.js +++ b/src/pages/Route/Agency/composables/getAgencies.js @@ -1,11 +1,11 @@ import axios from 'axios'; -import agency from 'src/router/modules/agency'; export async function getAgencies(formData, client, _filter = {}) { if (!formData.warehouseId || !formData.addressId || !formData.landed) return; - + const filter = { - ..._filter + order: ['name ASC'], + ..._filter, }; let defaultAgency = null; @@ -18,9 +18,11 @@ export async function getAgencies(formData, client, _filter = {}) { const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params }); - if(data && client) { - defaultAgency = data.find((agency) => agency.agencyModeFk === client.defaultAddress.agencyModeFk ); - }; - - return {options: data, agency: defaultAgency} + if (data && client) { + defaultAgency = data.find( + (agency) => agency.agencyModeFk === client.defaultAddress.agencyModeFk, + ); + } + + return { options: data, agency: defaultAgency }; } diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index 254b89e60..722db879d 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -66,7 +66,12 @@ function validateDateRange(params) { " auto-load /> - <FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load /> + <FetchData + url="AgencyModes" + :sort-by="['name ASC']" + @on-fetch="(data) => (agencies = data)" + auto-load + /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> <VnFilterPanel :data-key="props.dataKey" diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index ad8865a57..aba05980e 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -651,6 +651,9 @@ watch( {{ scope.opt?.city }} </span> </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> </QItemSection> </QItem> </template> From e6e21b61bdbc7d02b57243e8d786233ec4d66173 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 17 Feb 2025 23:27:22 +0100 Subject: [PATCH 14/30] perf: orderList --- src/pages/Order/OrderList.vue | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index 21cb5ed7e..3876d21e2 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -152,11 +152,23 @@ onMounted(() => { }); async function fetchClientAddress(id, formData = {}) { - const { data } = await axios.get( - `Clients/${id}/addresses?filter[order]=isActive DESC` - ); + const { data } = await axios.get(`Clients/${id}/addresses`, { + params: { + filter: JSON.stringify({ + include: [ + { + relation: 'client', + scope: { + fields: ['defaultAddressFk'], + }, + }, + ], + order: ['isActive DESC'], + }), + }, + }); addressOptions.value = data; - formData.addressId = data.defaultAddressFk; + formData.addressId = data[0].client.defaultAddressFk; fetchAgencies(formData); } @@ -164,7 +176,13 @@ async function fetchAgencies({ landed, addressId }) { if (!landed || !addressId) return (agencyList.value = []); const { data } = await axios.get('Agencies/landsThatDay', { - params: { addressFk: addressId, landed }, + params: { + filter: JSON.stringify({ + order: ['agencyMode DESC', 'agencyModeFk ASC'], + }), + addressFk: addressId, + landed, + }, }); agencyList.value = data; } @@ -255,6 +273,7 @@ const getDateColor = (date) => { </template> </VnSelect> <VnSelect + :disable="!data.clientFk" v-model="data.addressId" :options="addressOptions" :label="t('module.address')" @@ -281,6 +300,9 @@ const getDateColor = (date) => { {{ scope.opt?.street }}, {{ scope.opt?.city }} </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> </QItemSection> </QItem> </template> From 3ca73d03a063cf53ec12fb58c87fba519ce69545 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Tue, 18 Feb 2025 00:45:52 +0100 Subject: [PATCH 15/30] test: fix --- .../composables/__tests__/getAgencies.spec.js | 23 +++++++++++-------- .../Route/Agency/composables/getAgencies.js | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js b/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js index ccf7872cb..99966569c 100644 --- a/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js +++ b/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js @@ -27,14 +27,17 @@ describe('getAgencies', () => { landed: 'true', }; const filter = { - fields: ['nickname', 'street', 'city', 'id'], + fields: ['name', 'street', 'city', 'id'], where: { isActive: true }, - order: 'nickname ASC', + order: ['name ASC'], }; await getAgencies(formData, null, filter); - expect(axios.get).toHaveBeenCalledWith('Agencies/getAgenciesWithWarehouse', generateParams(formData, filter)); + expect(axios.get).toHaveBeenCalledWith( + 'Agencies/getAgenciesWithWarehouse', + generateParams(formData, filter), + ); }); it('should not call API when formData is missing required landed field', async () => { @@ -64,19 +67,19 @@ describe('getAgencies', () => { it('should return options and agency when default agency is found', async () => { const formData = { warehouseId: '123', addressId: '456', landed: 'true' }; const client = { defaultAddress: { agencyModeFk: 'Agency1' } }; - + const { options, agency } = await getAgencies(formData, client); - + expect(options).toEqual(response.data); expect(agency).toEqual(response.data[0]); - }); + }); - it('should return options and agency when client is not provided', async () => { + it('should return options and agency when client is not provided', async () => { const formData = { warehouseId: '123', addressId: '456', landed: 'true' }; - + const { options, agency } = await getAgencies(formData); - + expect(options).toEqual(response.data); expect(agency).toBeNull(); - }); + }); }); diff --git a/src/pages/Route/Agency/composables/getAgencies.js b/src/pages/Route/Agency/composables/getAgencies.js index 2462ec718..f837f54e9 100644 --- a/src/pages/Route/Agency/composables/getAgencies.js +++ b/src/pages/Route/Agency/composables/getAgencies.js @@ -4,8 +4,8 @@ export async function getAgencies(formData, client, _filter = {}) { if (!formData.warehouseId || !formData.addressId || !formData.landed) return; const filter = { - order: ['name ASC'], ..._filter, + order: ['name ASC'], }; let defaultAgency = null; From 89673b05db578087d027d1fd7987ac27059fe152 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Tue, 18 Feb 2025 12:55:40 +0100 Subject: [PATCH 16/30] fix: country addressEdit --- src/pages/Customer/components/CustomerAddressEdit.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index d650bbbda..1ea107f66 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -233,7 +233,7 @@ function handleLocation(data, location) { postcode: data.postalCode, city: data.city, province: data.province, - country: data.province.country, + country: data.province?.country, }" @update:model-value="(location) => handleLocation(data, location)" ></VnLocation> From f62f72832a0a27d0762820a69855929da881cac4 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Wed, 19 Feb 2025 00:03:28 +0100 Subject: [PATCH 17/30] perf: rename prop --- src/components/ui/VnFilterPanel.vue | 23 +++++++++++-------- src/pages/Ticket/TicketFilter.vue | 2 +- .../integration/ticket/tickeFilter.spec.js | 17 ++++++++++---- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 7af226bff..8f2c9f05e 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -61,9 +61,12 @@ const $props = defineProps({ type: Object, default: null, }, - useSearchbar: { - type: [Boolean, Function], - default: false, + searchbarOptions: { + type: Object, + default: () => ({ + use: false, + validateFn: null, + }), }, }); @@ -99,17 +102,17 @@ defineExpose({ search, params: userParams, remove }); async function search(evt) { try { - if ($props.useSearchbar) { + if ($props.searchbarOptions.use) { if (!searchbar.value) { return; } - if (typeof $props.useSearchbar === 'function') { - $props.useSearchbar(userParams.value); + if (typeof $props.searchbarOptions.validateFn === 'function') { + $props.searchbarOptions.validateFn(userParams.value); + } - if (!Object.keys(userParams.value).length) { - searchbar.value(); - return; - } + if (!Object.keys(userParams.value).length) { + searchbar.value(); + return; } } if (evt && $props.disableSubmitEvent) return; diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index 722db879d..a7205b6a6 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -76,7 +76,7 @@ function validateDateRange(params) { <VnFilterPanel :data-key="props.dataKey" :search-button="true" - :use-searchbar="validateDateRange" + :searchbar-options="{ use: true, validateFn: validateDateRange }" > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> diff --git a/test/cypress/integration/ticket/tickeFilter.spec.js b/test/cypress/integration/ticket/tickeFilter.spec.js index 59abb0164..c92bae844 100644 --- a/test/cypress/integration/ticket/tickeFilter.spec.js +++ b/test/cypress/integration/ticket/tickeFilter.spec.js @@ -19,16 +19,16 @@ describe('TicketFilter', () => { cy.on('uncaught:exception', () => { return false; }); - cy.get('.q-field__control-container > [data-cy="From_date"]').type( - '14-02-2025{enter}', - ); + cy.get('.q-field__control-container > [data-cy="From_date"]') + .type(`${today()} `) + .type('{enter}'); cy.get('.q-notification').should( 'contain', `The date range must have both 'from' and 'to'`, ); cy.get('.q-field__control-container > [data-cy="To_date"]').type( - '16/02/2025{enter}', + `${today()}{enter}`, ); cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); cy.searchBtnFilterPanel(); @@ -40,3 +40,12 @@ describe('TicketFilter', () => { cy.location('href').should('contain', '#/ticket/999999'); }); }); +function today() { + // return new Date().toISOString().split('T')[0]; + + return new Intl.DateTimeFormat('es-ES', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }).format(new Date()); +} From f33d396d825e4cd3bef33f5748aed68637d34db9 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Wed, 19 Feb 2025 00:12:01 +0100 Subject: [PATCH 18/30] perf: minor changes --- src/pages/Route/Agency/composables/getAgencies.js | 14 ++++++++------ src/pages/Ticket/TicketList.vue | 1 - 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pages/Route/Agency/composables/getAgencies.js b/src/pages/Route/Agency/composables/getAgencies.js index f837f54e9..180ac943e 100644 --- a/src/pages/Route/Agency/composables/getAgencies.js +++ b/src/pages/Route/Agency/composables/getAgencies.js @@ -8,7 +8,7 @@ export async function getAgencies(formData, client, _filter = {}) { order: ['name ASC'], }; - let defaultAgency = null; + let agency = null; let params = { filter: JSON.stringify(filter), warehouseFk: formData.warehouseId, @@ -16,13 +16,15 @@ export async function getAgencies(formData, client, _filter = {}) { landed: formData.landed, }; - const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params }); + const { data: options } = await axios.get('Agencies/getAgenciesWithWarehouse', { + params, + }); - if (data && client) { - defaultAgency = data.find( - (agency) => agency.agencyModeFk === client.defaultAddress.agencyModeFk, + if (options && client) { + agency = options.find( + ({ agencyModeFk }) => agencyModeFk === client.defaultAddress.agencyModeFk, ); } - return { options: data, agency: defaultAgency }; + return { options, agency }; } diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index aba05980e..1fe6baf00 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -478,7 +478,6 @@ watch( auto-load /> <VnSection - ref="sectionRef" :data-key="dataKey" :columns="columns" prefix="card" From 61374493bdb150f9beaebf11119b8c0871203283 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Wed, 19 Feb 2025 12:19:59 +0100 Subject: [PATCH 19/30] perf: default params --- src/composables/useArrayData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 805e9cf85..657390688 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -76,7 +76,7 @@ export function useArrayData(key, userOptions) { } async function fetch(fetchOptions) { - let { append = false, updateRouter = true } = fetchOptions; + let { append = false, updateRouter = true } = fetchOptions ?? {}; if (!store.url) return; cancelRequest(); From 57c0171bdd570caccc228a2e8baee2ee02fd5bff Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Fri, 21 Feb 2025 08:38:15 +0100 Subject: [PATCH 20/30] fix: transfer style --- src/pages/Ticket/Card/TicketTransferProxy.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Ticket/Card/TicketTransferProxy.vue b/src/pages/Ticket/Card/TicketTransferProxy.vue index 3f3f018df..7d5d82f85 100644 --- a/src/pages/Ticket/Card/TicketTransferProxy.vue +++ b/src/pages/Ticket/Card/TicketTransferProxy.vue @@ -42,7 +42,7 @@ const transferRef = ref(null); /> </div> - <div v-else> + <div style="display: flex; flex-direction: row" v-else> <TicketTransfer ref="transferRef" :ticket="$props.ticket" From 93326db2d97bdea10efe59114d0a7067f365d814 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 23 Feb 2025 13:11:43 +0100 Subject: [PATCH 21/30] fix: call filterpanel from searchbar --- src/components/common/VnSection.vue | 26 ++++++++++++--- src/components/ui/VnFilterPanel.vue | 49 +++++++++++++++++---------- src/components/ui/VnSearchbar.vue | 51 ++++++++++++++++++++++------- src/composables/useArrayData.js | 15 +++++++-- src/pages/Ticket/TicketFilter.vue | 29 ++++++++++++---- src/pages/Ticket/TicketList.vue | 23 ++++++------- 6 files changed, 138 insertions(+), 55 deletions(-) diff --git a/src/components/common/VnSection.vue b/src/components/common/VnSection.vue index 6677fa312..1b1c18d7d 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -2,7 +2,7 @@ import RightAdvancedMenu from './RightAdvancedMenu.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue'; -import { onBeforeMount, onMounted, onUnmounted, computed, ref, provide } from 'vue'; +import { onBeforeMount, onMounted, onUnmounted, computed, ref, inject, watch } from 'vue'; import { useArrayData } from 'src/composables/useArrayData'; import { useRoute, useRouter } from 'vue-router'; import { useHasContent } from 'src/composables/useHasContent'; @@ -36,6 +36,10 @@ const $props = defineProps({ type: Object, default: null, }, + filterPanelRef: { + type: Object, + default: null, + }, redirect: { type: Boolean, default: true, @@ -52,12 +56,13 @@ const router = useRouter(); let arrayData; const sectionValue = computed(() => $props.section ?? $props.dataKey); const isMainSection = ref(false); -const searchbarRef = ref(null); const searchbarId = 'section-searchbar'; const advancedMenuSlot = 'advanced-menu'; const hasContent = useHasContent(`#${searchbarId}`); -provide('searchbar', () => searchbarRef.value?.search()); +// const filterPanel = ref(inject('filterPanel', null)); + +// filterPanel.value = inject('filterPanel', null); onBeforeMount(() => { if ($props.dataKey) @@ -69,14 +74,26 @@ onBeforeMount(() => { }); checkIsMain(); }); +// const filterPanel = ref(inject('filterPanel', null)); onMounted(() => { const unsubscribe = router.afterEach(() => { checkIsMain(); }); + // filterPanel.value = inject('filterPanel', null); onUnmounted(unsubscribe); }); +watch( + () => inject('filterPanel'), + (newValue) => { + if (newValue) { + debugger; + // hacer algo cuando el valor esté disponible + } + }, + { immediate: true }, +); onUnmounted(() => { if (arrayData) arrayData.destroy(); }); @@ -90,9 +107,10 @@ function checkIsMain() { } </script> <template> + <pre>{{ filterPanelRef }}</pre> <slot name="searchbar"> <VnSearchbar - ref="searchbarRef" + :filterPanel="filterPanelRef" v-if="searchBar && !hasContent" v-bind="arrayDataProps" :data-key="dataKey" diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index d09fbf3e5..20570ad50 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, inject, onMounted } from 'vue'; +import { ref, computed, inject, onMounted, provide } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; import toDate from 'filters/toDate'; @@ -61,12 +61,13 @@ const $props = defineProps({ type: Object, default: null, }, - searchbarOptions: { + validations: { + type: Array, + default: () => [], + }, + excludeParams: { type: Object, - default: () => ({ - use: false, - validateFn: null, - }), + default: null, }, }); @@ -93,7 +94,7 @@ const userParams = ref(useFilterParams($props.dataKey).params); const userOrders = ref(useFilterParams($props.dataKey).orders); const searchbar = ref(null); const isLoading = ref(false); - +const excludeParams = ref($props.excludeParams); onMounted(() => { searchbar.value = inject('searchbar'); }); @@ -102,25 +103,36 @@ defineExpose({ search, params: userParams, remove }); async function search(evt) { try { - if ($props.searchbarOptions.use) { - if (!searchbar.value) { - return; - } - if (typeof $props.searchbarOptions.validateFn === 'function') { - $props.searchbarOptions.validateFn(userParams.value); - } + // if ($props.searchbarOptions.use) { + // // if (!searchbar.value) { + // // return; + // // } + const validations = $props.validations.every((validation) => { + return validation(userParams.value); + }); + // $props.searchbarOptions.validateFn(userParams.value); - if (!Object.keys(userParams.value).length) { - searchbar.value(); - return; - } + if (!validations) { + return; } + + if (Object.keys(userParams.value).length) { + excludeParams.value = null; + } + + // } if (evt && $props.disableSubmitEvent) return; store.filter.where = {}; isLoading.value = true; const filter = { ...userParams.value, ...$props.modelValue }; store.userParamsChanged = true; + if (excludeParams.value) { + filter.params = { + ...filter.params, + exclude: excludeParams.value, + }; + } await arrayData.addFilter({ params: filter, }); @@ -131,6 +143,7 @@ async function search(evt) { isLoading.value = false; } } +provide('filterPanel', search); async function clearFilters() { try { diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index f4b4f0fe8..3f7399a2b 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, ref, computed, watch } from 'vue'; +import { onMounted, ref, computed, watch, inject, onUpdated } from 'vue'; import { useQuasar } from 'quasar'; import { useArrayData } from 'composables/useArrayData'; import VnInput from 'src/components/common/VnInput.vue'; @@ -69,9 +69,13 @@ const props = defineProps({ type: Boolean, default: true, }, - excludeParams: { + filterPanelOptions: { + type: Boolean, + default: true, + }, + filterPanel: { type: Object, - default: null, + default: true, }, }); @@ -101,6 +105,29 @@ const to = computed(() => { return url; }); +// watch( +// () => filterPanel.value, +// (newValue) => { +// if (newValue) { +// // hacer algo cuando el valor esté disponible +// filterPanel.value = newValue; +// } +// }, +// { immediate: true }, +// ); + +const filterPanelRef = ref(null); +const filterPanel = ref(null); +watch( + () => filterPanelRef.value, + (newValue) => { + if (newValue) { + // hacer algo cuando el valor esté disponible + filterPanelRef.value = newValue; + } + }, + { immediate: true }, +); watch( () => props.dataKey, (val) => { @@ -108,6 +135,12 @@ watch( store = arrayData.store; }, ); +watch( + () => props.filterPanel, + (val) => { + filterPanel.value = val; + }, +); onMounted(() => { const params = store.userParams; @@ -120,7 +153,10 @@ async function search() { arrayData.resetPagination(); let filter = { params: { search: searchText.value } }; - + if (props.filterPanelOptions && filterPanel.value) { + filterPanel.value.filterPanelRef.search(filter); + return; + } if (!props.searchRemoveParams || !searchText.value) { filter = { params: { @@ -139,16 +175,9 @@ async function search() { }; delete filter.params.search; } - if (props.excludeParams) { - filter.params = { - ...filter.params, - exclude: props.excludeParams, - }; - } await arrayData.applyFilter(filter); searchText.value = undefined; } -defineExpose({ search }); </script> <template> <Teleport to="#searchbar" v-if="state.isHeaderMounted()"> diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 657390688..b5ebba83c 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -165,14 +165,19 @@ export function useArrayData(key, userOptions) { async function addFilter({ filter, params }) { if (filter) store.filter = filter; - + let exclude = {}; + if (params?.params?.exclude) { + exclude = params.params.exclude; + // params = { ...params, ...params.exclude }; + delete params.params.exclude; + } let userParams = { ...store.userParams, ...params }; userParams = sanitizerParams(userParams, store?.exprBuilder); store.userParams = userParams; resetPagination(); - await fetch({}); + await fetch({ exclude }); return { filter, params }; } @@ -224,7 +229,11 @@ export function useArrayData(key, userOptions) { function sanitizerParams(params, exprBuilder) { for (const param in params) { - if (params[param] === '' || params[param] === null) { + if ( + params[param] === '' || + params[param] === null || + !Object(params[param]).length + ) { delete store.userParams[param]; delete params[param]; if (store.filter?.where) { diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index a7205b6a6..cdca48101 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -1,6 +1,8 @@ <script setup> -import { ref } from 'vue'; +import { ref, computed, provide } from 'vue'; import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; +import { toDateString } from 'src/filters'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; @@ -17,14 +19,29 @@ const props = defineProps({ required: true, }, }); +const route = useRoute(); +const userParams = { + from: null, + to: null, +}; +const filterPanelRef = ref(null); +// Proveer específicamente el filterPanel +provide('filterPanel', filterPanelRef); +defineExpose({ filterPanelRef }); const provinces = ref([]); const states = ref([]); const agencies = ref([]); const warehouses = ref([]); const groupedStates = ref([]); const { notify } = useNotify(); - +const initializeFromQuery = computed(() => { + const query = route.query.table ? JSON.parse(route.query.table) : {}; + from.value = query.from || from.toISOString(); + to.value = query.to || to.toISOString(); + Object.assign(userParams, { from, to }); + return userParams; +}); const getGroupedStates = (data) => { for (const state of data) { groupedStates.value.push({ @@ -46,11 +63,9 @@ function validateDateRange(params) { if (hasFrom !== hasTo) { notify(t(`dateRangeMustHaveBothFrom`), 'negative'); - - throw new Error(t(`dateRangeMustHaveBothFrom`)); } - return hasFrom && hasTo; + return (hasFrom && hasTo) || (!hasFrom && !hasTo); } </script> @@ -74,9 +89,11 @@ function validateDateRange(params) { /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> <VnFilterPanel + ref="filterPanelRef" :data-key="props.dataKey" :search-button="true" - :searchbar-options="{ use: true, validateFn: validateDateRange }" + :validations="[validateDateRange]" + :exclude-params="initializeFromQuery" > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 1fe6baf00..f49fc2294 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -44,22 +44,13 @@ from.setDate(from.getDate() - 7); const to = Date.vnNew(); to.setHours(23, 59, 0, 0); to.setDate(to.getDate() + 1); -const userParams = { - from: null, - to: null, -}; + onBeforeMount(() => { - initializeFromQuery(); + // initializeFromQuery(); stateStore.rightDrawer = true; if (!route.query.createForm) return; onClientSelected(JSON.parse(route.query.createForm)); }); -const initializeFromQuery = () => { - const query = route.query.table ? JSON.parse(route.query.table) : {}; - from.value = query.from || from.toISOString(); - to.value = query.to || to.toISOString(); - Object.assign(userParams, { from, to }); -}; const selectedRows = ref([]); const hasSelectedRows = computed(() => selectedRows.value.length > 0); @@ -464,6 +455,7 @@ watch( }, { immediate: true }, ); +const filterPanelRef = ref(null); </script> <template> @@ -484,13 +476,18 @@ watch( :array-data-props="{ url: 'Tickets/filter', order: ['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id'], - excludeParams: { ...userParams }, + filterPanelOptions: true, + filterPanel: filterPanelRef, searchRemoveParams: true, exprBuilder, }" > <template #advanced-menu> - <TicketFilter data-key="TicketList" /> + <TicketFilter + ref="filterPanelRef" + data-key="TicketList" + :excludeParams="{ ...userParams }" + /> </template> <template #body> <VnTable From 1ce2009ca8b180bb8c8c6d6712811c9acce5bc48 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 23 Feb 2025 13:11:57 +0100 Subject: [PATCH 22/30] test: rename test --- .../ticket/{tickeFilter.spec.js => ticketFilter.spec.js} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename test/cypress/integration/ticket/{tickeFilter.spec.js => ticketFilter.spec.js} (93%) diff --git a/test/cypress/integration/ticket/tickeFilter.spec.js b/test/cypress/integration/ticket/ticketFilter.spec.js similarity index 93% rename from test/cypress/integration/ticket/tickeFilter.spec.js rename to test/cypress/integration/ticket/ticketFilter.spec.js index c92bae844..10973c5c5 100644 --- a/test/cypress/integration/ticket/tickeFilter.spec.js +++ b/test/cypress/integration/ticket/ticketFilter.spec.js @@ -11,7 +11,7 @@ describe('TicketFilter', () => { cy.waitForElement('.q-page'); cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); cy.searchBtnFilterPanel(); - cy.wait('@ticketFilter').then(({ request }) => { + cy.waitRequest('@ticketFilter', ({ request }) => { const { query } = request; expect(query).to.have.property('from'); expect(query).to.have.property('to'); @@ -40,12 +40,12 @@ describe('TicketFilter', () => { cy.location('href').should('contain', '#/ticket/999999'); }); }); -function today() { +function today(date) { // return new Date().toISOString().split('T')[0]; return new Intl.DateTimeFormat('es-ES', { day: '2-digit', month: '2-digit', year: 'numeric', - }).format(new Date()); + }).format(date ?? new Date()); } From aff783eb2eb3d6edcc8d97af4761f2003fd45f83 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 23 Feb 2025 14:06:25 +0100 Subject: [PATCH 23/30] perf: remove comments --- src/components/common/VnSection.vue | 23 +------------------- src/components/ui/VnFilterPanel.vue | 13 +----------- src/components/ui/VnSearchbar.vue | 33 +++-------------------------- 3 files changed, 5 insertions(+), 64 deletions(-) diff --git a/src/components/common/VnSection.vue b/src/components/common/VnSection.vue index 1b1c18d7d..4bd17124f 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -2,7 +2,7 @@ import RightAdvancedMenu from './RightAdvancedMenu.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue'; -import { onBeforeMount, onMounted, onUnmounted, computed, ref, inject, watch } from 'vue'; +import { onBeforeMount, onMounted, onUnmounted, computed, ref } from 'vue'; import { useArrayData } from 'src/composables/useArrayData'; import { useRoute, useRouter } from 'vue-router'; import { useHasContent } from 'src/composables/useHasContent'; @@ -36,10 +36,6 @@ const $props = defineProps({ type: Object, default: null, }, - filterPanelRef: { - type: Object, - default: null, - }, redirect: { type: Boolean, default: true, @@ -60,9 +56,6 @@ const isMainSection = ref(false); const searchbarId = 'section-searchbar'; const advancedMenuSlot = 'advanced-menu'; const hasContent = useHasContent(`#${searchbarId}`); -// const filterPanel = ref(inject('filterPanel', null)); - -// filterPanel.value = inject('filterPanel', null); onBeforeMount(() => { if ($props.dataKey) @@ -74,26 +67,14 @@ onBeforeMount(() => { }); checkIsMain(); }); -// const filterPanel = ref(inject('filterPanel', null)); onMounted(() => { const unsubscribe = router.afterEach(() => { checkIsMain(); }); - // filterPanel.value = inject('filterPanel', null); onUnmounted(unsubscribe); }); -watch( - () => inject('filterPanel'), - (newValue) => { - if (newValue) { - debugger; - // hacer algo cuando el valor esté disponible - } - }, - { immediate: true }, -); onUnmounted(() => { if (arrayData) arrayData.destroy(); }); @@ -107,10 +88,8 @@ function checkIsMain() { } </script> <template> - <pre>{{ filterPanelRef }}</pre> <slot name="searchbar"> <VnSearchbar - :filterPanel="filterPanelRef" v-if="searchBar && !hasContent" v-bind="arrayDataProps" :data-key="dataKey" diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 20570ad50..6f5f68a94 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, inject, onMounted, provide } from 'vue'; +import { ref, computed, provide } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; import toDate from 'filters/toDate'; @@ -92,25 +92,16 @@ const arrayData = const store = arrayData.store; const userParams = ref(useFilterParams($props.dataKey).params); const userOrders = ref(useFilterParams($props.dataKey).orders); -const searchbar = ref(null); const isLoading = ref(false); const excludeParams = ref($props.excludeParams); -onMounted(() => { - searchbar.value = inject('searchbar'); -}); defineExpose({ search, params: userParams, remove }); async function search(evt) { try { - // if ($props.searchbarOptions.use) { - // // if (!searchbar.value) { - // // return; - // // } const validations = $props.validations.every((validation) => { return validation(userParams.value); }); - // $props.searchbarOptions.validateFn(userParams.value); if (!validations) { return; @@ -120,7 +111,6 @@ async function search(evt) { excludeParams.value = null; } - // } if (evt && $props.disableSubmitEvent) return; store.filter.where = {}; @@ -143,7 +133,6 @@ async function search(evt) { isLoading.value = false; } } -provide('filterPanel', search); async function clearFilters() { try { diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 3f7399a2b..7dcad95db 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, ref, computed, watch, inject, onUpdated } from 'vue'; +import { onMounted, ref, computed, watch } from 'vue'; import { useQuasar } from 'quasar'; import { useArrayData } from 'composables/useArrayData'; import VnInput from 'src/components/common/VnInput.vue'; @@ -69,10 +69,6 @@ const props = defineProps({ type: Boolean, default: true, }, - filterPanelOptions: { - type: Boolean, - default: true, - }, filterPanel: { type: Object, default: true, @@ -93,6 +89,7 @@ if (props.redirect) }; let arrayData = useArrayData(props.dataKey, arrayDataProps); let store = arrayData.store; +const filterPanel = ref(props.filterPanel); const to = computed(() => { const url = { path: route.path, query: { ...(route.query ?? {}) } }; const searchUrl = arrayData.store.searchUrl; @@ -104,30 +101,6 @@ const to = computed(() => { if (searchUrl) url.query[searchUrl] = JSON.stringify(currentFilter); return url; }); - -// watch( -// () => filterPanel.value, -// (newValue) => { -// if (newValue) { -// // hacer algo cuando el valor esté disponible -// filterPanel.value = newValue; -// } -// }, -// { immediate: true }, -// ); - -const filterPanelRef = ref(null); -const filterPanel = ref(null); -watch( - () => filterPanelRef.value, - (newValue) => { - if (newValue) { - // hacer algo cuando el valor esté disponible - filterPanelRef.value = newValue; - } - }, - { immediate: true }, -); watch( () => props.dataKey, (val) => { @@ -153,7 +126,7 @@ async function search() { arrayData.resetPagination(); let filter = { params: { search: searchText.value } }; - if (props.filterPanelOptions && filterPanel.value) { + if (filterPanel.value) { filterPanel.value.filterPanelRef.search(filter); return; } From 2725571ee15e66bcf3a8ad182720ddb89a311325 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 23 Feb 2025 14:06:31 +0100 Subject: [PATCH 24/30] perf: remove comments --- src/pages/Ticket/TicketFilter.vue | 2 -- src/pages/Ticket/TicketList.vue | 2 -- test/cypress/support/commands.js | 4 ++++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index cdca48101..e0b5835ca 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -26,8 +26,6 @@ const userParams = { }; const filterPanelRef = ref(null); -// Proveer específicamente el filterPanel -provide('filterPanel', filterPanelRef); defineExpose({ filterPanelRef }); const provinces = ref([]); const states = ref([]); diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index f49fc2294..6830d319e 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -46,7 +46,6 @@ to.setHours(23, 59, 0, 0); to.setDate(to.getDate() + 1); onBeforeMount(() => { - // initializeFromQuery(); stateStore.rightDrawer = true; if (!route.query.createForm) return; onClientSelected(JSON.parse(route.query.createForm)); @@ -476,7 +475,6 @@ const filterPanelRef = ref(null); :array-data-props="{ url: 'Tickets/filter', order: ['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id'], - filterPanelOptions: true, filterPanel: filterPanelRef, searchRemoveParams: true, exprBuilder, diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 4470b6027..7c8aacc8a 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -396,3 +396,7 @@ Cypress.Commands.add('searchBtnFilterPanel', () => { '.q-scrollarea__content > .q-btn--standard > .q-btn__content > .q-icon', ).click(); }); + +Cypress.Commands.add('waitRequest', (alias, cb) => { + cy.wait(alias).then(cb); +}); From 7daa97999b95289887698e9bda9049ef41231b8b Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 23 Feb 2025 14:08:28 +0100 Subject: [PATCH 25/30] perf: remove comments --- src/pages/Ticket/TicketFilter.vue | 3 +-- src/pages/Ticket/TicketList.vue | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index e0b5835ca..0493fe8b4 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -1,8 +1,7 @@ <script setup> -import { ref, computed, provide } from 'vue'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; -import { toDateString } from 'src/filters'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 6830d319e..6e9d23492 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -59,6 +59,8 @@ const companiesOptions = ref([]); const accountingOptions = ref([]); const amountToReturn = ref(); const dataKey = 'TicketList'; +const filterPanelRef = ref(null); +const formInitialData = ref({}); const columns = computed(() => [ { @@ -189,8 +191,6 @@ const columns = computed(() => [ attrs: { url: 'warehouses', fields: ['id', 'name'], - optionLabel: 'name', - optionValue: 'id', }, format: (row) => row.warehouse, columnField: { @@ -438,7 +438,6 @@ function setReference(data) { dialogData.value.value.description = newDescription; } -const formInitialData = ref({}); watch( () => route.query.table, (newValue) => { @@ -454,7 +453,6 @@ watch( }, { immediate: true }, ); -const filterPanelRef = ref(null); </script> <template> From ed43f413f5c6436fc88c7c9db57d21dbd4ab2a16 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 23 Feb 2025 20:57:22 +0100 Subject: [PATCH 26/30] perf: remove comments --- src/components/ui/VnFilterPanel.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 6f5f68a94..d8ac750d5 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, provide } from 'vue'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; import toDate from 'filters/toDate'; From 403159629bd41e7f9a3a53fb853cca8c5c4c1921 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 24 Feb 2025 02:32:27 +0100 Subject: [PATCH 27/30] feat: ticketVolum 6 cols --- src/pages/Ticket/Card/TicketVolume.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Ticket/Card/TicketVolume.vue b/src/pages/Ticket/Card/TicketVolume.vue index 71b16f878..db78094cf 100644 --- a/src/pages/Ticket/Card/TicketVolume.vue +++ b/src/pages/Ticket/Card/TicketVolume.vue @@ -142,7 +142,7 @@ onMounted(() => (stateStore.rightDrawer = true)); <template #column-concept="{ row }"> <span>{{ row.item.name }}</span> <span class="color-vn-label q-pl-md">{{ row.item.subName }}</span> - <FetchedTags :item="row.item" /> + <FetchedTags :item="row.item" :columns="6" /> </template> <template #column-volume="{ rowIndex }"> <span>{{ packingTypeVolume?.[rowIndex]?.volume }}</span> From 43bbf05adfa1ffecede95d14877682ca2f7d8083 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 24 Feb 2025 02:32:50 +0100 Subject: [PATCH 28/30] perf: apply search --- src/components/ui/VnFilterPanel.vue | 2 +- src/components/ui/VnSearchbar.vue | 2 +- src/composables/useArrayData.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index d8ac750d5..c6bc11e2b 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -115,7 +115,7 @@ async function search(evt) { store.filter.where = {}; isLoading.value = true; - const filter = { ...userParams.value, ...$props.modelValue }; + const filter = { ...userParams.value, ...$props.modelValue, ...evt }; store.userParamsChanged = true; if (excludeParams.value) { filter.params = { diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 7dcad95db..064baec20 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -126,7 +126,7 @@ async function search() { arrayData.resetPagination(); let filter = { params: { search: searchText.value } }; - if (filterPanel.value) { + if (filterPanel?.value?.filterPanelRef) { filterPanel.value.filterPanelRef.search(filter); return; } diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index b5ebba83c..1d86fc8e6 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -100,8 +100,8 @@ export function useArrayData(key, userOptions) { params.filter = JSON.stringify(params.filter); if (fetchOptions?.exclude) { - params = { ...params, ...fetchOptions.exclude }; delete params.exclude; + params = { ...params.params, ...fetchOptions.exclude }; } store.isLoading = true; const response = await axios.get(store.url, { @@ -232,7 +232,7 @@ export function useArrayData(key, userOptions) { if ( params[param] === '' || params[param] === null || - !Object(params[param]).length + !Object.keys(params[param]).length ) { delete store.userParams[param]; delete params[param]; From 2117cbfb55386e0eccbabaf653b0fc7848b6dd33 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 24 Feb 2025 12:22:42 +0100 Subject: [PATCH 29/30] fix: date ticketExpedition --- src/pages/Ticket/Card/TicketExpedition.vue | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/Ticket/Card/TicketExpedition.vue b/src/pages/Ticket/Card/TicketExpedition.vue index f8084ff2f..a41d492ed 100644 --- a/src/pages/Ticket/Card/TicketExpedition.vue +++ b/src/pages/Ticket/Card/TicketExpedition.vue @@ -105,6 +105,9 @@ const columns = computed(() => [ name: 'created', align: 'left', cardVisible: true, + columnFilter: { + component: 'date', + }, format: (row) => toDateTimeFormat(row.created), }, { @@ -201,7 +204,7 @@ const getExpeditionState = async (expedition) => { const openGrafana = (expeditionFk) => { useOpenURL( - `https://grafana.verdnatura.es/d/de1njb6p5answd/control-de-expediciones?orgId=1&var-expeditionFk=${expeditionFk}` + `https://grafana.verdnatura.es/d/de1njb6p5answd/control-de-expediciones?orgId=1&var-expeditionFk=${expeditionFk}`, ); }; @@ -287,7 +290,7 @@ onMounted(async () => { openConfirmationModal( '', t('expedition.removeExpeditionSubtitle'), - deleteExpedition + deleteExpedition, ) " > @@ -302,7 +305,6 @@ onMounted(async () => { url="Expeditions/filter" search-url="expeditions" :columns="columns" - :filter="expeditionsFilter" v-model:selected="selectedRows" :table="{ 'row-key': 'id', @@ -316,6 +318,8 @@ onMounted(async () => { return { id: value }; case 'packageItemName': return { packagingItemFk: value }; + case 'created': + return { 'e.created': { gte: value } }; } } " From b4dad7a29b94947915d8918ccbc89ae4ea8e28e7 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Sun, 2 Mar 2025 23:22:01 +0100 Subject: [PATCH 30/30] revert: filter logic moved to other branch --- src/components/ui/VnFilterPanel.vue | 31 ++----------------- src/components/ui/VnSearchbar.vue | 22 ++++---------- src/composables/useArrayData.js | 30 +++++-------------- src/pages/Ticket/TicketFilter.vue | 46 ++--------------------------- src/pages/Ticket/TicketList.vue | 21 ++++++++----- 5 files changed, 30 insertions(+), 120 deletions(-) diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index c6bc11e2b..d6b525dc8 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -61,14 +61,6 @@ const $props = defineProps({ type: Object, default: null, }, - validations: { - type: Array, - default: () => [], - }, - excludeParams: { - type: Object, - default: null, - }, }); const emit = defineEmits([ @@ -92,37 +84,18 @@ const arrayData = const store = arrayData.store; const userParams = ref(useFilterParams($props.dataKey).params); const userOrders = ref(useFilterParams($props.dataKey).orders); -const isLoading = ref(false); -const excludeParams = ref($props.excludeParams); defineExpose({ search, params: userParams, remove }); +const isLoading = ref(false); async function search(evt) { try { - const validations = $props.validations.every((validation) => { - return validation(userParams.value); - }); - - if (!validations) { - return; - } - - if (Object.keys(userParams.value).length) { - excludeParams.value = null; - } - if (evt && $props.disableSubmitEvent) return; store.filter.where = {}; isLoading.value = true; - const filter = { ...userParams.value, ...$props.modelValue, ...evt }; + const filter = { ...userParams.value, ...$props.modelValue }; store.userParamsChanged = true; - if (excludeParams.value) { - filter.params = { - ...filter.params, - exclude: excludeParams.value, - }; - } await arrayData.addFilter({ params: filter, }); diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 064baec20..8607d9694 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -69,10 +69,6 @@ const props = defineProps({ type: Boolean, default: true, }, - filterPanel: { - type: Object, - default: true, - }, }); const searchText = ref(); @@ -89,7 +85,6 @@ if (props.redirect) }; let arrayData = useArrayData(props.dataKey, arrayDataProps); let store = arrayData.store; -const filterPanel = ref(props.filterPanel); const to = computed(() => { const url = { path: route.path, query: { ...(route.query ?? {}) } }; const searchUrl = arrayData.store.searchUrl; @@ -101,6 +96,7 @@ const to = computed(() => { if (searchUrl) url.query[searchUrl] = JSON.stringify(currentFilter); return url; }); + watch( () => props.dataKey, (val) => { @@ -108,12 +104,6 @@ watch( store = arrayData.store; }, ); -watch( - () => props.filterPanel, - (val) => { - filterPanel.value = val; - }, -); onMounted(() => { const params = store.userParams; @@ -126,10 +116,7 @@ async function search() { arrayData.resetPagination(); let filter = { params: { search: searchText.value } }; - if (filterPanel?.value?.filterPanelRef) { - filterPanel.value.filterPanelRef.search(filter); - return; - } + if (!props.searchRemoveParams || !searchText.value) { filter = { params: { @@ -217,8 +204,9 @@ async function search() { } :deep(.q-field--focused) { - .q-icon { - color: black; + .q-icon, + .q-placeholder { + color: var(--vn-black-text-color); } } diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 1d86fc8e6..fcc61972a 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -75,13 +75,12 @@ export function useArrayData(key, userOptions) { } } - async function fetch(fetchOptions) { - let { append = false, updateRouter = true } = fetchOptions ?? {}; + async function fetch({ append = false, updateRouter = true }) { if (!store.url) return; cancelRequest(); canceller = new AbortController(); - let { params, limit } = setCurrentFilter(); + const { params, limit } = setCurrentFilter(); let exprFilter; if (store?.exprBuilder) { @@ -99,10 +98,7 @@ export function useArrayData(key, userOptions) { if (!params?.filter?.order?.length) delete params?.filter?.order; params.filter = JSON.stringify(params.filter); - if (fetchOptions?.exclude) { - delete params.exclude; - params = { ...params.params, ...fetchOptions.exclude }; - } + store.isLoading = true; const response = await axios.get(store.url, { signal: canceller.signal, @@ -154,30 +150,22 @@ export function useArrayData(key, userOptions) { async function applyFilter({ filter, params }, fetchOptions = {}) { if (filter) store.userFilter = filter; store.filter = {}; - if (params?.exclude) { - fetchOptions = { ...fetchOptions, exclude: params.exclude }; - delete params.exclude; - } if (params) store.userParams = { ...params }; + const response = await fetch(fetchOptions); return response; } async function addFilter({ filter, params }) { if (filter) store.filter = filter; - let exclude = {}; - if (params?.params?.exclude) { - exclude = params.params.exclude; - // params = { ...params, ...params.exclude }; - delete params.params.exclude; - } + let userParams = { ...store.userParams, ...params }; userParams = sanitizerParams(userParams, store?.exprBuilder); store.userParams = userParams; resetPagination(); - await fetch({ exclude }); + await fetch({}); return { filter, params }; } @@ -229,11 +217,7 @@ export function useArrayData(key, userOptions) { function sanitizerParams(params, exprBuilder) { for (const param in params) { - if ( - params[param] === '' || - params[param] === null || - !Object.keys(params[param]).length - ) { + if (params[param] === '' || params[param] === null) { delete store.userParams[param]; delete params[param]; if (store.filter?.where) { diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index a3193f352..5da2a858c 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -1,7 +1,6 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useRoute } from 'vue-router'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; @@ -9,7 +8,6 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; -import useNotify from 'src/composables/useNotify'; const { t } = useI18n(); const props = defineProps({ @@ -18,27 +16,13 @@ const props = defineProps({ required: true, }, }); -const route = useRoute(); -const userParams = { - from: null, - to: null, -}; -const filterPanelRef = ref(null); -defineExpose({ filterPanelRef }); const provinces = ref([]); const states = ref([]); const agencies = ref([]); const warehouses = ref([]); const groupedStates = ref([]); -const { notify } = useNotify(); -const initializeFromQuery = computed(() => { - const query = route.query.table ? JSON.parse(route.query.table) : {}; - from.value = query.from || from.toISOString(); - to.value = query.to || to.toISOString(); - Object.assign(userParams, { from, to }); - return userParams; -}); + const getGroupedStates = (data) => { for (const state of data) { groupedStates.value.push({ @@ -48,22 +32,6 @@ const getGroupedStates = (data) => { }); } }; -const from = Date.vnNew(); -from.setHours(0, 0, 0, 0); -from.setDate(from.getDate() - 7); -const to = Date.vnNew(); -to.setHours(23, 59, 0, 0); -to.setDate(to.getDate() + 1); -function validateDateRange(params) { - const hasFrom = 'from' in params; - const hasTo = 'to' in params; - - if (hasFrom !== hasTo) { - notify(t(`dateRangeMustHaveBothFrom`), 'negative'); - } - - return (hasFrom && hasTo) || (!hasFrom && !hasTo); -} </script> <template> @@ -85,13 +53,7 @@ function validateDateRange(params) { auto-load /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> - <VnFilterPanel - ref="filterPanelRef" - :data-key="props.dataKey" - :search-button="true" - :validations="[validateDateRange]" - :exclude-params="initializeFromQuery" - > + <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> <strong>{{ t(`params.${tag.label}`) }}: </strong> @@ -341,7 +303,6 @@ function validateDateRange(params) { <i18n> en: - dateRangeMustHaveBothFrom: The date range must have both 'from' and 'to' params: search: Contains clientFk: Customer @@ -370,7 +331,6 @@ en: DELIVERED: Delivered ON_PREVIOUS: ON_PREVIOUS es: - dateRangeMustHaveBothFrom: El rango de fechas debe tener 'desde' y 'hasta' params: search: Contiene clientFk: Cliente diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 01bb23807..ee092d40f 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -44,12 +44,22 @@ from.setDate(from.getDate() - 7); const to = Date.vnNew(); to.setHours(23, 59, 0, 0); to.setDate(to.getDate() + 1); - +const userParams = { + from: null, + to: null, +}; onBeforeMount(() => { + initializeFromQuery(); stateStore.rightDrawer = true; if (!route.query.createForm) return; onClientSelected(JSON.parse(route.query.createForm)); }); +const initializeFromQuery = () => { + const query = route.query.table ? JSON.parse(route.query.table) : {}; + from.value = query.from || from.toISOString(); + to.value = query.to || to.toISOString(); + Object.assign(userParams, { from, to }); +}; const selectedRows = ref([]); const hasSelectedRows = computed(() => selectedRows.value.length > 0); @@ -471,17 +481,11 @@ watch( :array-data-props="{ url: 'Tickets/filter', order: ['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id'], - filterPanel: filterPanelRef, - searchRemoveParams: true, exprBuilder, }" > <template #advanced-menu> - <TicketFilter - ref="filterPanelRef" - data-key="TicketList" - :excludeParams="{ ...userParams }" - /> + <TicketFilter data-key="TicketList" /> </template> <template #body> <VnTable @@ -495,6 +499,7 @@ watch( }" default-mode="table" :columns="columns" + :user-params="userParams" :right-search="false" redirect="ticket" v-model:selected="selectedRows"