Skip to content

Commit d3adac9

Browse files
committed
feat: enhance filter functionality with new UI components and methods
1 parent 7fbefb3 commit d3adac9

File tree

3 files changed

+222
-37
lines changed

3 files changed

+222
-37
lines changed

apps/web/src/components/core/edit-filters.vue

Lines changed: 118 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
<template lang="pug">
22
q-card.transparent(style='min-width: 40vw; max-width: 80vw')
3-
q-toolbar.bg-primary.text-white(dense flat style='height: 32px;')
4-
q-toolbar-title(v-text='title')
3+
q-toolbar.bg-secondary.text-white(dense flat style='height: 32px;')
4+
q-toolbar-title
5+
span {{ title }}&nbsp;
6+
small
7+
i(v-if='initialFilter.label') <{{ initialFilter.label }}>
8+
q-chip(
9+
@click='copy(initialFilter.field || "")'
10+
v-if='initialFilter && initialFilter.field'
11+
:color='$q.dark.isActive ? "amber-9" : "amber-3"'
12+
text-color='black'
13+
size='xs'
14+
class='q-ml-sm'
15+
clickable
16+
) {{ initialFilter.field }}
17+
q-tooltip(anchor='top middle' self='bottom middle') Copier le champ
518
q-btn(
619
icon='mdi-close'
720
v-close-popup
@@ -12,19 +25,40 @@ q-card.transparent(style='min-width: 40vw; max-width: 80vw')
1225
q-card-section.q-pb-sm
1326
.flex.q-col-gutter-md
1427
q-select.col(
28+
ref='field'
1529
style='min-width: 180px; max-width: 220px'
1630
v-model='filter.key'
1731
:readonly='!!initialFilter?.field'
18-
:options='columns'
32+
:use-input='!initialFilter?.field'
33+
:options='filterOptions'
1934
label='Champ à filtrer'
2035
option-value='name'
2136
option-label='label'
37+
input-debounce="100"
38+
@new-value="createValue"
39+
@filter="filterFn"
2240
dense
2341
outlined
42+
use-chips
2443
autofocus
2544
map-options
2645
emit-value
2746
)
47+
template(v-slot:selected-item="scope")
48+
//- pre(v-html='JSON.stringify(scope)')
49+
q-chip(
50+
:class="[!columnExists(scope.opt.name || scope.opt) ? 'text-black' : '']"
51+
:color="!columnExists(scope.opt.name || scope.opt) ? ($q.dark.isActive ? 'amber-9' : 'amber-3') : ''"
52+
dense
53+
)
54+
| {{ scope.opt.label || scope.opt.name || scope.opt }}
55+
template(#no-option="{ inputValue }")
56+
q-item(clickable @click="triggerEnter")
57+
q-item-section
58+
q-item-label
59+
| Ajouter le filtre personnalisé
60+
| &nbsp;
61+
code <{{ inputValue }}>
2862
q-select.col-1(
2963
style='min-width: 130px'
3064
v-model='filter.operator'
@@ -135,12 +169,13 @@ q-card.transparent(style='min-width: 40vw; max-width: 80vw')
135169
</template>
136170

137171
<script lang="ts">
172+
import { copyToClipboard } from 'quasar'
138173
import type { QTableProps } from 'quasar'
139174
import dayjs from 'dayjs'
140175
141176
type Filter = {
142-
key: string
143177
operator: string
178+
key: string | undefined
144179
value: string | number | undefined
145180
146181
min?: string
@@ -199,6 +234,11 @@ export default defineNuxtComponent({
199234
},
200235
},
201236
},
237+
data() {
238+
return {
239+
filterOptions: [] as { name: string; label: string }[],
240+
}
241+
},
202242
setup({ columns, initialFilter, columnsType }) {
203243
console.log('initialFilter', initialFilter)
204244
const { fieldTypes, comparatorTypes, writeFilter } = useFiltersQuery(ref(columns), ref(columnsType))
@@ -286,7 +326,7 @@ export default defineNuxtComponent({
286326
const fieldType = ref<string>()
287327
const filter = ref<Filter>({
288328
operator,
289-
key: initialFilter?.field?.replace('[]', '') || '',
329+
key: initialFilter?.field?.replace('[]', '') || undefined,
290330
value: initialValue.value,
291331
292332
min: '',
@@ -312,7 +352,12 @@ export default defineNuxtComponent({
312352
return this.fieldType
313353
},
314354
availableComparators(): { label: string; value: string; prefix?: string; suffix?: string; multiplefields?: boolean }[] {
355+
if (!this.columnExists(this.filter.key || '')) {
356+
return this.comparatorTypes
357+
}
358+
315359
if (this.fieldType === undefined || this.fieldType === null) return []
360+
316361
return this.comparatorTypes.filter((comparator) => {
317362
return comparator.type.includes(this.fieldType!)
318363
})
@@ -331,11 +376,75 @@ export default defineNuxtComponent({
331376
return mapping
332377
},
333378
},
379+
methods: {
380+
columnExists(field: string) {
381+
return this.columns.find((col) => col.name === field)
382+
},
383+
mapAssign(col: { name: string; label?: string }) {
384+
return { name: col.name, label: col.label || col.name }
385+
},
386+
createValue(val: string, done: (newVal: string, mode: 'toggle' | 'add-unique' | 'add') => void) {
387+
if (val.length > 0) {
388+
if (this.filterOptions.find((opt) => opt.label === val)) {
389+
// If the value already exists in options, select it
390+
const existing = this.filterOptions.find((opt) => opt.label === val)!
391+
done(existing.name, 'toggle')
392+
} else {
393+
// Otherwise, add it to options and select it
394+
const newOption = { name: val, label: val }
395+
this.filterOptions.push(newOption)
396+
done(val, 'add-unique')
397+
}
398+
}
399+
},
400+
filterFn(val, update) {
401+
update(() => {
402+
if (val === '') {
403+
this.filterOptions = this.columns.map(this.mapAssign)
404+
} else {
405+
const needle = val.toLowerCase()
406+
this.filterOptions = this.columns.map(this.mapAssign).filter((v) => v.label.toLowerCase().indexOf(needle) > -1)
407+
}
408+
})
409+
},
410+
triggerEnter() {
411+
;(this.$refs.field as { focus: () => void }).focus()
412+
this.$nextTick(() => {
413+
const input = (this.$refs.field as { $el: HTMLElement }).$el.querySelector('input')
414+
if (input) {
415+
input.dispatchEvent(
416+
new KeyboardEvent('keydown', {
417+
key: 'Enter',
418+
code: 'Enter',
419+
keyCode: 13,
420+
which: 13,
421+
bubbles: true,
422+
}),
423+
)
424+
}
425+
})
426+
},
427+
async copy(text: string) {
428+
try {
429+
await copyToClipboard(text)
430+
this.$q.notify({
431+
message: 'Champ copié dans le presse-papier',
432+
color: 'positive',
433+
icon: 'mdi-clipboard-check',
434+
})
435+
} catch (e) {
436+
console.warn('copyToClipboard failed', e)
437+
this.$q.notify({
438+
message: 'Échec de la copie dans le presse-papier',
439+
color: 'negative',
440+
icon: 'mdi-close-circle',
441+
})
442+
}
443+
},
444+
},
334445
mounted() {
335-
// console.log('comparator', this.comparator)
336-
// console.log('optionsMapping', this.optionsMapping)
337-
// console.log('this.columnsType', this.columnsType)
338-
this.fieldType = this.columnsType.find((col) => col.name === this.filter.key)?.type || 'text'
446+
this.filterOptions = this.columns.map(this.mapAssign)
447+
this.fieldType = this.columnsType.find((col) => col.name === this.filter.key)?.type
339448
},
340449
})
341450
</script>

apps/web/src/components/core/pan-filters.vue

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,60 @@ q-toolbar(dense flat)
1717
stacked-label
1818
)
1919
q-space(v-if='mode === "advanced"')
20-
q-btn.q-ml-sm(
21-
v-if='mode === "complex" || mode === "advanced"'
22-
color='primary'
23-
icon='mdi-filter-variant'
24-
flat
25-
dense
26-
)
27-
span(v-text='mode === "advanced" ? "Ajouter un nouveau filtre" : ""')
20+
q-btn-group(flat)
21+
q-btn.q-ml-sm(
22+
v-if='mode === "complex" || mode === "advanced"'
23+
color='secondary'
24+
icon='mdi-filter-variant-plus'
25+
flat
26+
dense
27+
)
28+
q-tooltip.text-body2.bg-secondary(
29+
anchor='top middle'
30+
self='bottom middle'
31+
)
32+
span Il y a {{ countFilters }} filtre(s) actif(s)
33+
br
34+
small Cliquer pour ajouter des filtres
35+
span(v-text='mode === "advanced" ? "Ajouter un nouveau filtre" : ""')
36+
q-popup-proxy(
37+
anchor='bottom left'
38+
self='top middle'
39+
transition-show='scale'
40+
transition-hide='scale'
41+
)
42+
sesame-core-edit-filters(
43+
title='Ajouter un filtre'
44+
:columns='columns'
45+
:columns-type='columnsType'
46+
)
47+
q-separator(vertical)
48+
q-btn-dropdown.text-secondary(
49+
flat dense
50+
)
51+
q-list(dense)
52+
q-item(@click='removeAllFilters' clickable v-close-popup)
53+
q-item-section(avatar)
54+
q-icon(
55+
name='mdi-delete'
56+
color='negative'
57+
)
58+
q-item-section
59+
q-item-label Retirer tous les filtres
2860
q-badge.text-black(
61+
style='margin-right: 8px; margin-top: 4px;'
2962
v-if='countFilters > 0'
3063
color='warning'
64+
align='middle'
3165
floating
3266
) {{ countFilters }}
33-
q-popup-proxy(
34-
anchor='bottom left'
35-
self='top middle'
36-
transition-show='scale'
37-
transition-hide='scale'
38-
)
39-
sesame-core-edit-filters(
40-
title='Ajouter un filtre'
41-
:columns='columns'
42-
:columns-type='columnsType'
43-
)
4467
.flex.q-mt-sm(v-show='hasFilters')
4568
template(v-for='(filter, i) in getFilters' :key='filter.field')
4669
//- pre(v-html='JSON.stringify(filter)')
4770
q-chip(
71+
:class="[!columnExists(filter.field) ? 'text-black' : '']"
4872
@remove="removeFilter(filter)"
49-
:color="$q.dark.isActive ? 'grey-9' : 'grey-3'"
73+
:color="getFilterColor(filter)"
5074
removable
5175
clickable
5276
dense
@@ -63,11 +87,20 @@ q-toolbar(dense flat)
6387
transition-hide='scale'
6488
)
6589
sesame-core-edit-filters(
66-
:title='"Modifier le filtre (" + (filter.label || filter.field || "") + ")"'
90+
title='Modifier le filtre'
6791
:initial-filter='filter'
6892
:columns='columns'
6993
:columns-type='columnsType'
7094
)
95+
q-tooltip.text-body2(
96+
:class="getTooltipColor(filter)"
97+
anchor='top middle'
98+
self='bottom middle'
99+
)
100+
span(v-if='!columnExists(filter.field)')
101+
| Cliquer pour modifier le filtre&nbsp;
102+
small (Le champ "{{ filter.field }}" n'existe pas ou n'est pas reconnu)
103+
span(v-else) Cliquer pour modifier le filtre
71104
span.content-center(v-if='i < countFilters - 1') &amp;gt;
72105
</template>
73106

@@ -99,13 +132,14 @@ export default defineComponent({
99132
},
100133
},
101134
setup({ columns, columnsType }) {
102-
const { countFilters, hasFilters, getFilters, removeFilter } = useFiltersQuery(ref(columns), ref(columnsType))
135+
const { countFilters, hasFilters, getFilters, removeFilter, removeAllFilters } = useFiltersQuery(ref(columns), ref(columnsType))
103136
104137
return {
105138
countFilters,
106139
hasFilters,
107140
getFilters,
108141
removeFilter,
142+
removeAllFilters,
109143
}
110144
},
111145
computed: {
@@ -125,5 +159,26 @@ export default defineComponent({
125159
},
126160
},
127161
},
162+
methods: {
163+
columnExists(field: string) {
164+
return this.columns.find((col) => col.name === field)
165+
},
166+
getFilterColor(filter: { comparator: string; label: string; field: string; search?: string; value?: unknown }) {
167+
if (this.columnExists(filter.field)) {
168+
return this.$q.dark.isActive ? 'grey-9' : 'grey-3'
169+
}
170+
171+
return this.$q.dark.isActive ? 'amber-9' : 'amber-3'
172+
},
173+
getTooltipColor(filter: { comparator: string; label: string; field: string; search?: string; value?: unknown }) {
174+
const colors = [] as string[]
175+
if (!this.columnExists(filter.field)) {
176+
colors.push(this.$q.dark.isActive ? 'bg-amber-9' : 'bg-amber-3')
177+
colors.push('text-black')
178+
}
179+
180+
return colors.join(' ')
181+
},
182+
},
128183
})
129184
</script>

0 commit comments

Comments
 (0)