feat: same searchbar logic filter in VnFilterPanel

This commit is contained in:
Javier Segarra 2025-02-16 23:35:56 +01:00
parent 2ec5c2b49f
commit 33d6662f97
8 changed files with 154 additions and 19 deletions

View File

@ -2,7 +2,7 @@
import RightAdvancedMenu from './RightAdvancedMenu.vue'; import RightAdvancedMenu from './RightAdvancedMenu.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnTableFilter from '../VnTable/VnTableFilter.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 { useArrayData } from 'src/composables/useArrayData';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useHasContent } from 'src/composables/useHasContent'; import { useHasContent } from 'src/composables/useHasContent';
@ -52,10 +52,12 @@ const router = useRouter();
let arrayData; let arrayData;
const sectionValue = computed(() => $props.section ?? $props.dataKey); const sectionValue = computed(() => $props.section ?? $props.dataKey);
const isMainSection = ref(false); const isMainSection = ref(false);
const searchbarRef = ref(null);
const searchbarId = 'section-searchbar'; const searchbarId = 'section-searchbar';
const advancedMenuSlot = 'advanced-menu'; const advancedMenuSlot = 'advanced-menu';
const hasContent = useHasContent(`#${searchbarId}`); const hasContent = useHasContent(`#${searchbarId}`);
provide('searchbar', () => searchbarRef.value?.search());
onBeforeMount(() => { onBeforeMount(() => {
if ($props.dataKey) if ($props.dataKey)
@ -90,6 +92,7 @@ function checkIsMain() {
<template> <template>
<slot name="searchbar"> <slot name="searchbar">
<VnSearchbar <VnSearchbar
ref="searchbarRef"
v-if="searchBar && !hasContent" v-if="searchBar && !hasContent"
v-bind="arrayDataProps" v-bind="arrayDataProps"
:data-key="dataKey" :data-key="dataKey"

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, provide, inject, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import toDate from 'filters/toDate'; import toDate from 'filters/toDate';
@ -14,6 +14,10 @@ const $props = defineProps({
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
searchBarRef: {
type: Object,
default: () => {},
},
dataKey: { dataKey: {
type: String, type: String,
required: true, required: true,
@ -61,6 +65,14 @@ const $props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
requiredParams: {
type: [Array, Object],
default: () => [],
},
useSearchbar: {
type: [Boolean, Function],
default: false,
},
}); });
const emit = defineEmits([ const emit = defineEmits([
@ -84,13 +96,29 @@ const arrayData =
const store = arrayData.store; const store = arrayData.store;
const userParams = ref(useFilterParams($props.dataKey).params); const userParams = ref(useFilterParams($props.dataKey).params);
const userOrders = ref(useFilterParams($props.dataKey).orders); const userOrders = ref(useFilterParams($props.dataKey).orders);
const searchbar = ref(null);
defineExpose({ search, params: userParams, remove }); defineExpose({ search, params: userParams, remove });
onMounted(() => {
searchbar.value = inject('searchbar');
});
const isLoading = ref(false); const isLoading = ref(false);
async function search(evt) { async function search(evt) {
try { 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 = {}; store.filter.where = {};
isLoading.value = true; isLoading.value = true;
@ -114,7 +142,7 @@ async function clearFilters() {
arrayData.resetPagination(); arrayData.resetPagination();
// Filtrar los params no removibles // Filtrar los params no removibles
const removableFilters = Object.keys(userParams.value).filter((param) => const removableFilters = Object.keys(userParams.value).filter((param) =>
$props.unremovableParams.includes(param) $props.unremovableParams.includes(param),
); );
const newParams = {}; const newParams = {};
// Conservar solo los params que no son removibles // Conservar solo los params que no son removibles
@ -162,13 +190,13 @@ const formatTags = (tags) => {
const tags = computed(() => { const tags = computed(() => {
const filteredTags = tagsList.value.filter( const filteredTags = tagsList.value.filter(
(tag) => !($props.customTags || []).includes(tag.label) (tag) => !($props.customTags || []).includes(tag.label),
); );
return formatTags(filteredTags); return formatTags(filteredTags);
}); });
const customTags = computed(() => 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) { async function remove(key) {
@ -191,7 +219,9 @@ const getLocale = (label) => {
if (te(globalLocale)) return t(globalLocale); if (te(globalLocale)) return t(globalLocale);
else if (te(t(`params.${param}`))); else if (te(t(`params.${param}`)));
else { 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}`); return t(`${camelCaseModuleName}.params.${param}`);
} }
}; };

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, ref, computed, watch } from 'vue'; import { onMounted, ref, computed, watch, provide } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
@ -148,6 +148,7 @@ async function search() {
await arrayData.applyFilter(filter); await arrayData.applyFilter(filter);
searchText.value = undefined; searchText.value = undefined;
} }
defineExpose({ search });
</script> </script>
<template> <template>
<Teleport to="#searchbar" v-if="state.isHeaderMounted()"> <Teleport to="#searchbar" v-if="state.isHeaderMounted()">

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; 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 VnInputDate from 'components/common/VnInputDate.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import { Notify } from 'quasar';
import useNotify from 'src/composables/useNotify';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -15,6 +17,10 @@ const props = defineProps({
type: String, type: String,
required: true, required: true,
}, },
searchBarRef: {
type: Object,
default: () => ({}),
},
}); });
const provinces = ref([]); const provinces = ref([]);
@ -22,6 +28,7 @@ const states = ref([]);
const agencies = ref([]); const agencies = ref([]);
const warehouses = ref([]); const warehouses = ref([]);
const groupedStates = ref([]); const groupedStates = ref([]);
const { notify } = useNotify();
const getGroupedStates = (data) => { const getGroupedStates = (data) => {
for (const state of 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> </script>
<template> <template>
@ -48,7 +78,13 @@ const getGroupedStates = (data) => {
/> />
<FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load /> <FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load />
<FetchData url="Warehouses" @on-fetch="(data) => (warehouses = 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 }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong> <strong>{{ t(`params.${tag.label}`) }}: </strong>
@ -74,10 +110,20 @@ const getGroupedStates = (data) => {
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <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>
<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> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -288,6 +334,7 @@ const getGroupedStates = (data) => {
<i18n> <i18n>
en: en:
dateRangeMustHaveBothFrom: The date range must have both 'from' and 'to'
params: params:
search: Contains search: Contains
clientFk: Customer clientFk: Customer
@ -315,6 +362,7 @@ en:
DELIVERED: Delivered DELIVERED: Delivered
ON_PREVIOUS: ON_PREVIOUS ON_PREVIOUS: ON_PREVIOUS
es: es:
dateRangeMustHaveBothFrom: El rango de fechas debe tener 'desde' y 'hasta'
params: params:
search: Contiene search: Contiene
clientFk: Cliente clientFk: Cliente

View File

@ -478,6 +478,7 @@ watch(
auto-load auto-load
/> />
<VnSection <VnSection
ref="sectionRef"
:data-key="dataKey" :data-key="dataKey"
:columns="columns" :columns="columns"
prefix="card" prefix="card"
@ -490,7 +491,10 @@ watch(
}" }"
> >
<template #advanced-menu> <template #advanced-menu>
<TicketFilter data-key="TicketList" /> <TicketFilter
data-key="TicketList"
:searchbarRef="$refs.sectionRef?.$refs.searchbarRef"
/>
</template> </template>
<template #body> <template #body>
<VnTable <VnTable

View File

@ -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');
});
});

View File

@ -41,8 +41,8 @@ describe('TicketList', () => {
it('filter client and create ticket', () => { it('filter client and create ticket', () => {
cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketSearchbar'); cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketSearchbar');
searchResults(); searchResults();
cy.wait('@ticketSearchbar').then((interception) => { cy.wait('@ticketSearchbar').then(({ request }) => {
const { query } = interception.request; const { query } = request;
expect(query).to.have.property('from'); expect(query).to.have.property('from');
expect(query).to.have.property('to'); expect(query).to.have.property('to');
expect(query).to.not.have.property('clientFk'); expect(query).to.not.have.property('clientFk');
@ -50,8 +50,8 @@ describe('TicketList', () => {
cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter');
cy.get('[data-cy="Customer ID_input"]').clear('1'); cy.get('[data-cy="Customer ID_input"]').clear('1');
cy.get('[data-cy="Customer ID_input"]').type('1101{enter}'); cy.get('[data-cy="Customer ID_input"]').type('1101{enter}');
cy.wait('@ticketFilter').then((interception) => { cy.wait('@ticketFilter').then(({ request }) => {
const { query } = interception.request; const { query } = request;
expect(query).to.not.have.property('from'); expect(query).to.not.have.property('from');
expect(query).to.not.have.property('to'); expect(query).to.not.have.property('to');
expect(query).to.have.property('clientFk'); expect(query).to.have.property('clientFk');

View File

@ -368,3 +368,8 @@ Cypress.Commands.add('clickButtonWithText', (buttonText) => {
Cypress.Commands.add('selectOptionBeta', (index = 1) => { Cypress.Commands.add('selectOptionBeta', (index = 1) => {
cy.get(`[role="listbox"] .q-item:nth-child(${index})`).click(); 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();
});