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"