11<template lang="pug">
22q-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 }}
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+ |
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'
138173import type { QTableProps } from ' quasar'
139174import dayjs from ' dayjs'
140175
141176type 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 >
0 commit comments