Implement tag filter

This commit is contained in:
Kevin Martinez 2024-01-05 13:04:06 -04:00
parent b622f9cca7
commit c997d19a5e
3 changed files with 91 additions and 35 deletions

View File

@ -4,6 +4,8 @@ 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';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
dataKey: { dataKey: {
@ -39,6 +41,10 @@ const props = defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
customTags: {
type: Array,
default: () => [],
},
}); });
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']); const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
@ -104,19 +110,26 @@ async function clearFilters() {
emit('clear'); emit('clear');
} }
const tags = computed(() => { const tagsList = computed(() =>
return Object.entries(userParams.value) Object.entries(userParams.value)
.filter(([key, value]) => value && !(props.hiddenTags || []).includes(key)) .filter(([key, value]) => value && !(props.hiddenTags || []).includes(key))
.map(([key, value]) => ({ .map(([key, value]) => ({
label: key, label: key,
value: value, value: value,
})); }))
}); );
const tags = computed(() =>
tagsList.value.filter((tag) => !(props.customTags || []).includes(tag.label))
);
const customTags = computed(() =>
tagsList.value.filter((tag) => (props.customTags || []).includes(tag.label))
);
async function remove(key) { async function remove(key) {
userParams.value[key] = null; userParams.value[key] = null;
await search(); await search();
emit('remove', key) emit('remove', key);
} }
function formatValue(value) { function formatValue(value) {
@ -172,21 +185,17 @@ function formatValue(value) {
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<div <div
v-if="tags.length === 0" v-if="tagsList.length === 0"
class="text-grey font-xs text-center full-width" class="text-grey font-xs text-center full-width"
> >
{{ t(`No filters applied`) }} {{ t(`No filters applied`) }}
</div> </div>
<div> <div>
<QChip <VnFilterPanelChip
:key="chip.label"
@remove="remove(chip.label)"
class="text-dark"
color="primary"
icon="label"
:removable="!unremovableParams.includes(chip.label)"
size="sm"
v-for="chip of tags" v-for="chip of tags"
:key="chip.label"
:removable="!unremovableParams.includes(chip.label)"
@remove="remove(chip.label)"
> >
<slot name="tags" :tag="chip" :format-fn="formatValue"> <slot name="tags" :tag="chip" :format-fn="formatValue">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
@ -194,7 +203,15 @@ function formatValue(value) {
<span>"{{ chip.value }}"</span> <span>"{{ chip.value }}"</span>
</div> </div>
</slot> </slot>
</QChip> </VnFilterPanelChip>
<slot
v-if="$slots.customTags"
name="customTags"
:params="userParams"
:tags="customTags"
:format-fn="formatValue"
:search-fn="search"
/>
</div> </div>
</QItem> </QItem>
<QSeparator /> <QSeparator />

View File

@ -0,0 +1,7 @@
<script setup></script>
<template>
<QChip class="text-dark" color="primary" icon="label" size="sm" v-bind="$attrs">
<slot />
</QChip>
</template>

View File

@ -1,12 +1,14 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import axios from 'axios';
import VnInput from 'components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import axios from 'axios'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
import { useRoute } from 'vue-router';
import VnInput from 'components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -97,6 +99,7 @@ function exprBuilder(param, value) {
const selectedTag = ref(null); const selectedTag = ref(null);
const tagValues = ref([{}]); const tagValues = ref([{}]);
const tagOptions = ref(null); const tagOptions = ref(null);
const isButtonDisabled = computed(()=> !selectedTag.value || tagValues.value.some(item => !item.value))
const applyTagFilter = (params, search) => { const applyTagFilter = (params, search) => {
if (!tagValues.value?.length) { if (!tagValues.value?.length) {
@ -104,14 +107,30 @@ const applyTagFilter = (params, search) => {
search(); search();
return; return;
} }
params.tagGroups = JSON.stringify({ if (!params.tagGroups) {
values: tagValues.value, params.tagGroups = [];
tagSelection: { }
...selectedTag.value, params.tagGroups.push(
orgShowField: selectedTag.value.name, JSON.stringify({
}, values: tagValues.value,
tagFk: selectedTag.value.tagFk, tagSelection: {
}); ...selectedTag.value,
orgShowField: selectedTag.value.name,
},
tagFk: selectedTag.value.tagFk,
})
);
search();
selectedTag.value = null;
tagValues.value = [{}]
};
const removeTagChip = (selection, params, search) => {
if (params.tagGroups) {
params.tagGroups = (params.tagGroups || []).filter(
(value) => value !== selection
);
}
search(); search();
}; };
@ -144,19 +163,12 @@ const onOrderChange = (value, params, search) => {
:data-key="props.dataKey" :data-key="props.dataKey"
:hidden-tags="['orderFk', 'orderBy']" :hidden-tags="['orderFk', 'orderBy']"
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:custom-tags="['tagGroups']"
@init="onFilterInit" @init="onFilterInit"
@remove="clearFilter" @remove="clearFilter"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div v-if="tag.label === 'tagGroups'"> <strong v-if="tag.label === 'categoryFk'">
<strong> {{ JSON.parse(tag?.value).tagSelection?.name }}: </strong>
<span>{{
(JSON.parse(tag?.value).values || [])
.map((item) => item.value)
.join(' | ')
}}</span>
</div>
<strong v-else-if="tag.label === 'categoryFk'">
{{ t(selectedCategory?.name || '') }} {{ t(selectedCategory?.name || '') }}
</strong> </strong>
<strong v-else-if="tag.label === 'typeFk'"> <strong v-else-if="tag.label === 'typeFk'">
@ -167,6 +179,25 @@ const onOrderChange = (value, params, search) => {
<span>{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
</div> </div>
</template> </template>
<template #customTags="{ tags: customTags, params, searchFn }">
<template v-for="tag in customTags" :key="tag.label">
<template v-if="tag.label === 'tagGroups'">
<VnFilterPanelChip
v-for="chip in tag.value"
:key="chip"
removable
@remove="removeTagChip(chip, params, searchFn)"
>
<strong> {{ JSON.parse(chip).tagSelection?.name }}: </strong>
<span>{{
(JSON.parse(chip).values || [])
.map((item) => item.value)
.join(' | ')
}}</span>
</VnFilterPanelChip>
</template>
</template>
</template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QList dense style="max-width: 256px"> <QList dense style="max-width: 256px">
<QItem class="category-filter q-mt-md"> <QItem class="category-filter q-mt-md">
@ -320,6 +351,7 @@ const onOrderChange = (value, params, search) => {
rounded rounded
type="button" type="button"
unelevated unelevated
:disable="isButtonDisabled"
@click.stop="applyTagFilter(params, searchFn)" @click.stop="applyTagFilter(params, searchFn)"
/> />
</QItemSection> </QItemSection>