Skip to content

Commit f62a387

Browse files
author
cnouguier
committed
wip: Enhance collection filtering #1151
1 parent 37d6a20 commit f62a387

File tree

5 files changed

+414
-0
lines changed

5 files changed

+414
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<template>
2+
<div>
3+
<q-fab
4+
v-if="hasOptions"
5+
icon="las la-filter"
6+
color="grey-9"
7+
flat
8+
direction="down"
9+
:vertical-actions-align="responsiveAlignment"
10+
padding="xs"
11+
@show="enableTooltip = false"
12+
@hide="enableTooltip = true"
13+
>
14+
<template v-for="tag in options" :key="tag.name">
15+
<q-fab-action
16+
:label="$tie(tag.label)"
17+
:color="tag.color"
18+
:text-color="tag.textColor"
19+
padding="1px"
20+
square
21+
@click="onTagAdded(tag)"
22+
/>
23+
</template>
24+
</q-fab>
25+
<q-tooltip v-if="enableTooltip">
26+
{{ $t('C3XTagsFilterAction.FILTER') }}
27+
</q-tooltip>
28+
</div>
29+
</template>
30+
31+
<script setup>
32+
import _ from 'lodash'
33+
import { ref, computed } from 'vue'
34+
import { useCurrentActivity, useScreen } from '../../composables'
35+
36+
// Props
37+
const props = defineProps({
38+
alignment: {
39+
type: [String, Object],
40+
default: 'center'
41+
}
42+
})
43+
44+
// Data
45+
const { Screen } = useScreen()
46+
const { CurrentActivityContext } = useCurrentActivity()
47+
const tagsFilter = CurrentActivityContext.state.tagsFilter
48+
const enableTooltip = ref(true)
49+
50+
// Computed
51+
const selection = computed(() => {
52+
return tagsFilter.selection
53+
})
54+
const options = computed(() => {
55+
return _.difference(tagsFilter.options, tagsFilter.selection)
56+
})
57+
const hasOptions = computed(() => {
58+
return !_.isEmpty(options.value)
59+
})
60+
const responsiveAlignment = computed(() => {
61+
if (_.isString(props.alignment)) return props.alignment
62+
return _.get(props.alignment, Screen.name)
63+
})
64+
65+
// Function
66+
function onTagAdded (tag) {
67+
Object.assign(CurrentActivityContext.state.tagsFilter, { selection: _.concat(selection.value, [tag]) })
68+
}
69+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<template>
2+
<KAction
3+
v-if="hasTimeRange"
4+
id="time-filter-action"
5+
icon="las la-clock"
6+
tooltip="C3XTimeFilterAction.FILTER"
7+
:handler="showTimeRangeSlider"
8+
/>
9+
</template>
10+
11+
<script setup>
12+
import { computed } from 'vue'
13+
import { useCurrentActivity } from '../../composables'
14+
15+
// Props
16+
defineProps({
17+
service: {
18+
type: String,
19+
default: 'events'
20+
}
21+
})
22+
23+
// Data
24+
const { CurrentActivityContext } = useCurrentActivity()
25+
const { state } = CurrentActivityContext
26+
27+
// Computed
28+
const hasTimeRange = computed(() => {
29+
return state.timeFilter && state.timeFilter.min !== state.timeFilter.max
30+
})
31+
32+
// Functions
33+
async function showTimeRangeSlider () {
34+
Object.assign(state.timeFilter, {
35+
start: state.timeFilter.min,
36+
end: state.timeFilter.max
37+
})
38+
}
39+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<template>
2+
<div class="q-pa-xs" :style="innerStyle">
3+
<!--
4+
Input area
5+
-->
6+
<q-input
7+
:label="pattern ? undefined: $tie(label)"
8+
v-model="pattern"
9+
clearable
10+
borderless
11+
dense
12+
debounce="500"
13+
@update:model-value="onSearch"
14+
>
15+
<template v-slot:before>
16+
<div class="row">
17+
<q-icon class="q-pl-xs" dense name="search" />
18+
<template v-for="item in items" :key="item.name">
19+
<q-chip
20+
square
21+
dense
22+
removable
23+
color="primary"
24+
text-color="white"
25+
size="md"
26+
@remove="onItemRemoved(item._id)"
27+
>
28+
<q-icon v-if="getItemIcon(item)" class="q-pr-sm" :name="getItemIcon(item)" />
29+
{{ getItemLabel(item) }}
30+
</q-chip>
31+
</template>
32+
</div>
33+
</template>
34+
</q-input>
35+
<!--
36+
Options menu
37+
-->
38+
<q-menu
39+
v-if="options.length > 0"
40+
no-focus
41+
no-refocus
42+
v-model="hasOptions"
43+
>
44+
<q-list style="min-width: 100px">
45+
<template v-for="option in options" :key="option.name">
46+
<q-item
47+
clickable
48+
@click="onItemSelected(option)"
49+
v-close-popup>
50+
<q-item-section v-if="getItemIcon(option)" avatar>
51+
<q-icon :name="getItemIcon(option)" />
52+
</q-item-section>
53+
<q-item-section>
54+
<q-item-label>{{ getItemLabel(option) }}</q-item-label>
55+
<q-item-label caption>{{ getItemDescription(option) }}</q-item-label>
56+
</q-item-section>
57+
</q-item>
58+
</template>
59+
</q-list>
60+
</q-menu>
61+
</div>
62+
</template>
63+
64+
<script setup>
65+
import _ from 'lodash'
66+
import { ref, computed } from 'vue'
67+
import { useCurrentActivity } from '../../composables'
68+
import { Search } from '../../search.js'
69+
70+
// Props
71+
const props = defineProps({
72+
label: {
73+
type: String,
74+
default: 'KSearchFilter.SEARCH_LABEL'
75+
},
76+
fields: {
77+
type: [String, Array],
78+
default: 'name'
79+
},
80+
services: {
81+
type: Array,
82+
default: () => []
83+
}
84+
})
85+
86+
// Data
87+
const { CurrentActivityContext } = useCurrentActivity()
88+
const { state } = CurrentActivityContext
89+
const options = ref([]
90+
91+
)
92+
// Computed
93+
const items = computed(() => {
94+
return state.searchFilter.items
95+
})
96+
const hasOptions = computed({
97+
get: function () {
98+
return this.options.length > 0
99+
},
100+
set: function (value) {
101+
if (!value) this.options = []
102+
}
103+
})
104+
105+
// Functions
106+
function getItemLabel (item) {
107+
return _.get(item, item.field)
108+
}
109+
function getItemDescription (item) {
110+
return _.get(item, this.description)
111+
}
112+
function getItemIcon (item) {
113+
return _.get(item, 'icon.name', _.get(item, 'icon'))
114+
}
115+
function onItemSelected (item) {
116+
this.items.push(item)
117+
this.pattern = ''
118+
this.$store.patch('filter', { items: this.items, pattern: this.pattern })
119+
}
120+
function onItemRemoved (itemId) {
121+
_.set(state.searchFilter, 'items', _.without(items.value, itemId))
122+
}
123+
async function onSearch (pattern) {
124+
// take about a null pattern received when clearing the input
125+
if (_.isNull(pattern)) pattern = ''
126+
// update the pattern
127+
this.pattern = pattern
128+
this.$store.patch('filter', { pattern: this.pattern })
129+
// run the search if the pattern is not empty
130+
if (!_.isEmpty(pattern)) {
131+
const results = await Search.query(props.services, pattern)
132+
if (results.length > 0) {
133+
options.value = _.differenceWith(results, this.items, (item1, item2) => {
134+
return item1._id === item2._id
135+
})
136+
}
137+
}
138+
}
139+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<div
3+
v-if="hasSelection"
4+
class="q-pl-xs row justify-center items-center q-gutter-x-xs k-tags-filter"
5+
>
6+
<template v-for="tag in selection" :key="tag.name">
7+
<KChip
8+
v-bind="tag"
9+
removable
10+
@remove="onRemoved(tag)"
11+
square
12+
:dense="dense"
13+
/>
14+
</template>
15+
<KAction
16+
id="clear-tags"
17+
icon="cancel"
18+
color="grey-7"
19+
size="12px"
20+
:handler="onClear"
21+
/>
22+
</div>
23+
</template>
24+
25+
<script setup>
26+
import _ from 'lodash'
27+
import { computed } from 'vue'
28+
import { useCurrentActivity, useScreen } from '../../composables'
29+
import KAction from '../action/KAction.vue'
30+
31+
// Data
32+
const { dense } = useScreen()
33+
const { CurrentActivityContext } = useCurrentActivity()
34+
const { state } = CurrentActivityContext
35+
36+
// Computed
37+
const hasSelection = computed(() => {
38+
return state.tagsFilter && !_.isEmpty(selection.value)
39+
})
40+
const selection = computed(() => {
41+
return state.tagsFilter.selection
42+
})
43+
44+
// Functions
45+
function onRemoved (tag) {
46+
_.set(state.tagsFilter, 'selection', _.without(selection.value, tag))
47+
}
48+
function onClear () {
49+
_.set(state.tagsFilter, 'selection', [])
50+
}
51+
</script>
52+
53+
<style lang="scss" scoped>
54+
.k-tags-filter {
55+
padding-left: 8px;
56+
padding-right: 4px;
57+
border-radius: 24px;
58+
border: 1px solid lightgrey;
59+
background-color: #dedede
60+
}
61+
</style>

0 commit comments

Comments
 (0)