diff --git a/src/pages/Item/ItemList.vue b/src/pages/Item/ItemList.vue index bd1bf75e5..39e190147 100644 --- a/src/pages/Item/ItemList.vue +++ b/src/pages/Item/ItemList.vue @@ -12,6 +12,7 @@ import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import ItemSummary from '../Item/Card/ItemSummary.vue'; import VnPaginate from 'components/ui/VnPaginate.vue'; +import ItemListFilter from './ItemListFilter.vue'; import { useStateStore } from 'stores/useStateStore'; import { toDateFormat } from 'src/filters/date.js'; @@ -69,7 +70,7 @@ const exprBuilder = (param, value) => { } }; -const params = reactive({}); +const params = reactive({ isFloramondo: false, isActive: true }); const applyColumnFilter = async (col) => { try { @@ -442,6 +443,11 @@ onUnmounted(() => (stateStore.rightDrawer = false)); <QSpace /> <div id="st-actions"></div> </QToolbar> + <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> + <QScrollArea class="fit text-grey-8"> + <ItemListFilter data-key="ItemList" /> + </QScrollArea> + </QDrawer> <QPage class="column items-center q-pa-md"> <VnPaginate ref="paginateRef" diff --git a/src/pages/Item/ItemListFilter.vue b/src/pages/Item/ItemListFilter.vue new file mode 100644 index 000000000..a51f9ebb4 --- /dev/null +++ b/src/pages/Item/ItemListFilter.vue @@ -0,0 +1,460 @@ +<script setup> +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { onMounted } from 'vue'; +import { useStateStore } from 'stores/useStateStore'; + +import FetchData from 'components/FetchData.vue'; +import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import { QCheckbox } from 'quasar'; + +import { useArrayData } from 'composables/useArrayData'; +import { useValidator } from 'src/composables/useValidator'; +import axios from 'axios'; + +const { t } = useI18n(); + +const props = defineProps({ + dataKey: { + type: String, + required: true, + }, +}); + +const stateStore = useStateStore(); +const validationsStore = useValidator(); + +const itemTypesRef = ref(null); +const categoriesOptions = ref([]); +const itemTypesOptions = ref([]); +const buyersOptions = ref([]); +const suppliersOptions = ref([]); +const tagValues = ref([]); +const fieldFiltersValues = ref([]); +const moreFields = ref([]); + +const arrayData = useArrayData(props.dataKey); +const itemTypesFilter = { + fields: ['id', 'name', 'categoryFk'], + include: 'category', + order: 'name ASC', + where: {}, +}; + +const onCategoryChange = async (categoryFk, search) => { + if (!categoryFk) { + itemTypesFilter.where.categoryFk = null; + delete itemTypesFilter.where.categoryFk; + } else { + itemTypesFilter.where.categoryFk = categoryFk; + } + search(); + await itemTypesRef.value.fetch(); +}; + +const getSelectedTagValues = async (tag) => { + try { + tag.value = null; + const filter = { + fields: ['value'], + order: 'value ASC', + }; + + const params = { filter: JSON.stringify(filter) }; + const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, { + params, + }); + tag.valueOptions = data; + } catch (err) { + console.error('Error getting selected tag values'); + } +}; + +const applyTags = (params, search) => { + params.tags = tagValues.value + .filter((tag) => tag.selectedTag && tag.value) + .map((tag) => ({ + tagFk: tag.selectedTag.id, + tagName: tag.selectedTag.name, + value: tag.value, + })); + search(); +}; + +const removeTag = (index, params, search) => { + (tagValues.value || []).splice(index, 1); + applyTags(params, search); +}; + +const applyFieldFilters = (params) => { + fieldFiltersValues.value.forEach((fieldFilter) => { + if ( + fieldFilter.selectedField && + (fieldFilter.value !== null || + fieldFilter.value !== '' || + fieldFilter.value !== undefined) + ) { + params[fieldFilter.name] = fieldFilter.value; + } + }); + arrayData.applyFilter({ params }); +}; + +const removeFieldFilter = (index, params, search) => { + delete params[fieldFiltersValues.value[index].name]; + (fieldFiltersValues.value || []).splice(index, 1); + applyFieldFilters(params, search); +}; + +onMounted(async () => { + stateStore.rightDrawer = true; + if (arrayData.store?.userParams?.categoryFk) + itemTypesFilter.where.categoryFk = arrayData.store?.userParams?.categoryFk; + await itemTypesRef.value.fetch(); + const { models } = validationsStore; + const properties = models.Item?.properties || {}; + + const _moreFields = ['id', 'description', 'name', 'isActive']; + + _moreFields.forEach((field) => { + let prop = properties[field]; + const label = t(`params.${field}`); + moreFields.value.push({ + name: field, + label, + type: prop ? prop.type : null, + }); + }); + + // Fill fieldFiltersValues with existent userParams + if (arrayData.store?.userParams) { + fieldFiltersValues.value = Object.entries(arrayData.store?.userParams) + .filter(([key, value]) => value && _moreFields.includes(key)) + .map(([key, value]) => ({ + name: key, + value, + selectedField: moreFields.value.find((field) => field.name === key), + })); + } +}); +</script> + +<template> + <FetchData + url="ItemCategories" + :filter="{ fields: ['id', 'name'], order: 'name ASC' }" + @on-fetch="(data) => (categoriesOptions = data)" + auto-load + /> + <FetchData + ref="itemTypesRef" + url="ItemTypes" + :filter="itemTypesFilter" + @on-fetch="(data) => (itemTypesOptions = data)" + /> + <FetchData + url="TicketRequests/getItemTypeWorker" + :filter="{ fields: ['id', 'nickname'], order: 'nickname ASC' }" + @on-fetch="(data) => (buyersOptions = data)" + auto-load + /> + <FetchData + url="Suppliers" + :filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC' }" + @on-fetch="(data) => (suppliersOptions = data)" + auto-load + /> + <FetchData + url="Tags" + :filter="{ fields: ['id', 'name', 'isFree'] }" + auto-load + @on-fetch="(data) => (tagOptions = data)" + /> + <VnFilterPanel + :data-key="props.dataKey" + :search-button="true" + :hidden-tags="tagValues.length === 0 ? ['tags'] : []" + > + <template #tags="{ tag, formatFn }"> + <div> + <strong>{{ t(`params.${tag.label}`) }}</strong> + <span v-if="tag.label !== 'tags'">: {{ formatFn(tag.value) }}</span> + </div> + </template> + <template #body="{ params, searchFn }"> + <QItem> + <QItemSection> + <VnInput + v-model="params.search" + :label="t('params.search')" + is-outlined + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelect + :label="t('params.categoryFk')" + v-model="params.categoryFk" + @update:model-value=" + ($event) => onCategoryChange($event, searchFn) + " + :options="categoriesOptions" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelect + :label="t('params.typeFk')" + v-model="params.typeFk" + @update:model-value="searchFn()" + :options="itemTypesOptions" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel>{{ scope.opt?.name }}</QItemLabel> + <QItemLabel caption>{{ + scope.opt?.category?.name + }}</QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelect + :label="t('params.buyerFk')" + v-model="params.buyerFk" + @update:model-value="searchFn()" + :options="buyersOptions" + option-value="id" + option-label="nickname" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelect + :label="t('params.supplierFk')" + v-model="params.supplierFk" + @update:model-value="searchFn()" + :options="suppliersOptions" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel>{{ + scope.opt?.name + ': ' + scope.opt?.nickname + }}</QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </QItemSection> + </QItem> + <!-- Tags filter --> + <QItem class="row items-center"> + <QItemLabel> + {{ t('params.tags') }} + </QItemLabel> + <QIcon + name="add_circle" + class="fill-icon-on-hover q-ml-md" + size="sm" + color="primary" + @click="tagValues.push({})" + /> + </QItem> + <QItem + v-for="(tag, index) in tagValues" + :key="index" + class="row items-center" + > + <QItemSection class="col"> + <VnSelect + :label="t('params.tag')" + v-model="tag.selectedTag" + :options="tagOptions" + option-label="name" + dense + outlined + rounded + :emit-value="false" + use-input + :is-clearable="false" + @update:model-value="getSelectedTagValues(tag)" + /> + </QItemSection> + <QItemSection class="col"> + <VnSelect + v-if="!tag?.selectedTag?.isFree && tag.valueOptions" + :label="t('params.tag')" + v-model="tag.value" + :options="tag.valueOptions || []" + option-value="value" + option-label="value" + dense + outlined + rounded + emit-value + use-input + :disable="!tag" + :is-clearable="false" + @update:model-value="applyTags(params, searchFn)" + /> + <VnInput + v-else + v-model="tag.value" + :label="t('params.value')" + :disable="!tag" + is-outlined + :is-clearable="false" + @keydown.enter.prevent="applyTags(params, searchFn)" + /> + </QItemSection> + <QIcon + name="delete" + class="fill-icon-on-hover q-ml-xs" + size="sm" + color="primary" + @click="removeTag(index, params, searchFn)" + /> + </QItem> + <!-- Filter fields --> + <QItem class="row items-center"> + <QItemLabel> + {{ t('More fields') }} + </QItemLabel> + <QIcon + name="add_circle" + class="fill-icon-on-hover q-ml-md" + size="sm" + color="primary" + @click="fieldFiltersValues.push({})" + /> + </QItem> + <QItem + v-for="(fieldFilter, index) in fieldFiltersValues" + :key="index" + class="row items-center" + > + <QItemSection class="col"> + <VnSelect + :label="t('params.tag')" + :model-value="fieldFilter.selectedField" + :options="moreFields" + option-label="label" + dense + outlined + rounded + :emit-value="false" + use-input + :is-clearable="false" + @update:model-value=" + ($event) => { + fieldFilter.name = $event.name; + fieldFilter.value = null; + fieldFilter.selectedField = $event; + } + " + /> + </QItemSection> + <QItemSection class="col"> + <QCheckbox + v-if="fieldFilter.selectedField?.type === 'boolean'" + v-model="fieldFilter.value" + :label="t('params.value')" + @update:model-value="applyFieldFilters(params, searchFn)" + /> + <VnInput + v-else + v-model="fieldFilter.value" + :label="t('params.value')" + :disable="!fieldFilter.selectedField" + is-outlined + @keydown.enter="applyFieldFilters(params, searchFn)" + /> + </QItemSection> + <QIcon + name="delete" + class="fill-icon-on-hover q-ml-xs" + size="sm" + color="primary" + @click="removeFieldFilter(index, params, searchFn)" + /> + </QItem> + <QItem> + <QItemSection> + <QCheckbox + :label="t('params.isFloramondo')" + v-model="params.isFloramondo" + toggle-indeterminate + @update:model-value="searchFn()" + /> + </QItemSection> + </QItem> + </template> + </VnFilterPanel> +</template> + +<i18n> +en: + params: + search: General search + categoryFk: Category + typeFk: Type + buyerFk: Buyer + supplierFk: Supplier + tags: Tags + tag: Tag + value: Value + isFloramondo: Floramondo + isActive: Active + description: Description + name: Name + id: Id +es: + More fields: Más campos + params: + search: Búsqueda general + categoryFk: Reino + typeFk: Tipo + buyerFk: Comprador + supplierFk: Proveedor + tags: Etiquetas + tag: Etiqueta + value: Valor + isFloramondo: Floramondo + isActive: Activo + description: Descripción + name: Nombre + id: Id +</i18n>