adminforth 2.4.0-next.286 → 2.4.0-next.288

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,27 +4,20 @@
4
4
  :min="minFormatted"
5
5
  :max="maxFormatted"
6
6
  type="number" aria-describedby="helper-text-explanation"
7
- class="bg-lightRangePickerInputBackground border border-lightRangePickerInputBorder text-lightRangePickerInputText placeholder-lightRangePickerInputPlaceholder text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-darkRangePickerInputBackground dark:border-darkRangePickerInputBorder dark:placeholder-darkRangePickerInputPlaceholder dark:text-darkRangePickerInputText dark:focus:ring-blue-500 dark:focus:border-blue-500"
7
+ class="flex-1 bg-lightRangePickerInputBackground border border-lightRangePickerInputBorder text-lightRangePickerInputText placeholder-lightRangePickerInputPlaceholder text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-darkRangePickerInputBackground dark:border-darkRangePickerInputBorder dark:placeholder-darkRangePickerInputPlaceholder dark:text-darkRangePickerInputText dark:focus:ring-blue-500 dark:focus:border-blue-500"
8
8
  :placeholder="$t('From')"
9
9
  v-model="start"
10
10
  >
11
-
11
+ <p>_</p>
12
12
  <input
13
13
  :min="minFormatted"
14
14
  :max="maxFormatted"
15
15
  type="number" aria-describedby="helper-text-explanation"
16
- class="bg-lightRangePickerInputBackground border border-lightRangePickerInputBorder text-lightRangePickerInputText placeholder-lightRangePickerInputPlaceholder text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-darkRangePickerInputBackground dark:border-darkRangePickerInputBorder dark:placeholder-darkRangePickerInputPlaceholder dark:text-darkRangePickerInputText dark:focus:ring-blue-500 dark:focus:border-blue-500"
16
+ class="flex-1 bg-lightRangePickerInputBackground border border-lightRangePickerInputBorder text-lightRangePickerInputText placeholder-lightRangePickerInputPlaceholder text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-darkRangePickerInputBackground dark:border-darkRangePickerInputBorder dark:placeholder-darkRangePickerInputPlaceholder dark:text-darkRangePickerInputText dark:focus:ring-blue-500 dark:focus:border-blue-500"
17
17
  :placeholder="$t('To')"
18
18
  v-model="end"
19
19
  >
20
20
 
21
- <button
22
- v-if="isChanged"
23
- type="button"
24
- class="flex items-center p-0.5 ml-auto px-3 text-sm font-medium text-lightRangePickerButtonText focus:outline-none bg-lightRangePickerButtonBackground rounded border border-lightRangePickerButtonBorder hover:bg-lightRangePickerButtonBackgroundHover hover:text-lightRangePickerButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightRangePickerFocusRing dark:focus:ring-darkRangePickerFocusRing dark:bg-darkRangePickerButtonBackground dark:text-darkRangePickerButtonText dark:border-darkRangePickerButtonBorder dark:hover:text-darkRangePickerButtonTextHover dark:hover:bg-darkRangePickerButtonBackgroundHover disabled:opacity-50 disabled:cursor-not-allowed"
25
- @click="clear">Clear
26
- </button>
27
-
28
21
  <div v-if="min && max" class="w-full px-2.5">
29
22
  <vue-slider
30
23
  class="custom-slider"
@@ -112,12 +105,6 @@ watch([minFormatted,maxFormatted], () => {
112
105
  setSliderValues(minFormatted.value, maxFormatted.value)
113
106
  })
114
107
 
115
- const clear = () => {
116
- start.value = ''
117
- end.value = ''
118
- setSliderValues('', '')
119
- }
120
-
121
108
  function setSliderValues(start: any, end: any) {
122
109
  sliderValue.value = [start || minFormatted.value, end || maxFormatted.value];
123
110
  }
@@ -20,116 +20,131 @@
20
20
  <div class="py-4 ">
21
21
  <ul class="space-y-3 font-medium">
22
22
  <li v-for="c in columnsWithFilter" :key="c">
23
- <p class="dark:text-gray-400">{{ c.label }}</p>
24
- <component
25
- v-if="c.components?.filter"
26
- :is="getCustomComponent(c.components.filter)"
27
- :meta="c?.components?.list?.meta"
28
- :column="c"
29
- class="w-full"
30
- @update:modelValue="(filtersArray) => {
31
- filtersStore.filters = filtersStore.filters.filter(f => f.field !== c.name);
32
-
33
- for (const f of filtersArray) {
34
- filtersStore.filters.push({ field: c.name, ...f });
35
- }
36
- console.log('filtersStore.filters', filtersStore.filters);
37
- emits('update:filters', [...filtersStore.filters]);
38
- }"
39
- :modelValue="filtersStore.filters.filter(f => f.field === c.name)"
40
- />
41
- <Select
42
- v-else-if="c.foreignResource"
43
- :multiple="c.filterOptions.multiselect"
44
- class="w-full"
45
- :options="columnOptions[c.name] || []"
46
- :searchDisabled="!c.foreignResource.searchableFields"
47
- @scroll-near-end="loadMoreOptions(c.name)"
48
- @search="(searchTerm) => {
49
- if (c.foreignResource.searchableFields && onSearchInput[c.name]) {
50
- onSearchInput[c.name](searchTerm);
51
- }
52
- }"
53
- @update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions.multiselect ? 'in' : 'eq', value: c.filterOptions.multiselect ? ($event.length ? $event : undefined) : $event || undefined })"
54
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value || (c.filterOptions.multiselect ? [] : '')"
55
- >
56
- <template #extra-item v-if="columnLoadingState[c.name]?.loading">
57
- <div class="text-center text-gray-400 dark:text-gray-300 py-2 flex items-center justify-center gap-2">
58
- <Spinner class="w-4 h-4" />
59
- {{ $t('Loading...') }}
60
- </div>
61
- </template>
62
- </Select>
63
- <Select
64
- :multiple="c.filterOptions.multiselect"
65
- class="w-full"
66
- v-else-if="c.type === 'boolean'"
67
- :options="[
68
- { label: $t('Yes'), value: true },
69
- { label: $t('No'), value: false },
70
- // if field is not required, undefined might be there, and user might want to filter by it
71
- ...(c.required ? [] : [ { label: $t('Unset'), value: undefined } ])
72
- ]"
73
- @update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions.multiselect ? 'in' : 'eq', value: c.filterOptions.multiselect ? ($event.length ? $event : undefined) : $event })"
74
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value !== undefined
75
- ? filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value
76
- : (c.filterOptions.multiselect ? [] : '')"
77
- />
78
-
79
- <Select
80
- :multiple="c.filterOptions.multiselect"
81
- class="w-full"
82
- v-else-if="c.enum"
83
- :options="c.enum"
84
- @update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions.multiselect ? 'in' : 'eq', value: c.filterOptions.multiselect ? ($event.length ? $event : undefined) : $event || undefined })"
85
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value || (c.filterOptions.multiselect ? [] : '')"
86
- />
87
-
88
- <Input
89
- v-else-if="['string', 'text', 'json', 'richtext', 'unknown'].includes(c.type)"
90
- type="text"
91
- full-width
92
- :placeholder="$t('Search')"
93
- @update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions?.substringSearch ? 'ilike' : 'eq', value: $event || undefined })"
94
- :modelValue="getFilterItem({ column: c, operator: c.filterOptions?.substringSearch ? 'ilike' : 'eq' })"
95
- />
96
-
97
- <CustomDateRangePicker
98
- v-else-if="['datetime', 'date', 'time'].includes(c.type)"
99
- :column="c"
100
- :valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
101
- @update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: $event || undefined })"
102
- :valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
103
- @update:valueEnd="onFilterInput[c.name]({ column: c, operator: 'lte', value: $event || undefined })"
104
- />
105
-
106
- <CustomRangePicker
107
- v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
108
- :min="getFilterMinValue(c.name)"
109
- :max="getFilterMaxValue(c.name)"
110
- :valueStart="getFilterItem({ column: c, operator: 'gte' })"
111
- @update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: ($event !== '' && $event !== null) ? $event : undefined })"
112
- :valueEnd="getFilterItem({ column: c, operator: 'lte' })"
113
- @update:valueEnd="onFilterInput[c.name]({ column: c, operator: 'lte', value: ($event !== '' && $event !== null) ? $event : undefined })"
114
- />
115
-
116
- <div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
117
- <Input
118
- type="number"
119
- aria-describedby="helper-text-explanation"
120
- :placeholder="$t('From')"
121
- @update:modelValue="onFilterInput[c.name]({ column: c, operator: 'gte', value: ($event !== '' && $event !== null) ? $event : undefined })"
122
- :modelValue="getFilterItem({ column: c, operator: 'gte' })"
123
- />
124
- <Input
125
- type="number"
126
- aria-describedby="helper-text-explanation"
127
- :placeholder="$t('To')"
128
- @update:modelValue="onFilterInput[c.name]({ column: c, operator: 'lte', value: ($event !== '' && $event !== null) ? $event : undefined })"
129
- :modelValue="getFilterItem({ column: c, operator: 'lte' })"
130
- />
131
- </div>
132
-
23
+ <div class="flex flex-col">
24
+ <div class="flex justify-between items-center">
25
+ <p class="dark:text-gray-400 h-7 my-1">{{ c.label }}</p>
26
+ <Tooltip v-if="filtersStore.filters.find(f => f.field === c.name)">
27
+ <button
28
+ class=" flex items-center justify-center w-7 h-7 my-1 hover:border rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
29
+ :disabled="!filtersStore.filters.find(f => f.field === c.name)"
30
+ @click="filtersStore.clearFilter(c.name);"
31
+ >
32
+ <IconCloseOutline />
33
+ </button>
34
+ <template #tooltip>
35
+ Clear filter
36
+ </template>
37
+ </Tooltip>
38
+ </div>
39
+ <component
40
+ v-if="c.components?.filter"
41
+ :is="getCustomComponent(c.components.filter)"
42
+ :meta="c?.components?.list?.meta"
43
+ :column="c"
44
+ class="w-full"
45
+ @update:modelValue="(filtersArray) => {
46
+ filtersStore.filters = filtersStore.filters.filter(f => f.field !== c.name);
47
+
48
+ for (const f of filtersArray) {
49
+ filtersStore.filters.push({ field: c.name, ...f });
50
+ }
51
+ console.log('filtersStore.filters', filtersStore.filters);
52
+ emits('update:filters', [...filtersStore.filters]);
53
+ }"
54
+ :modelValue="filtersStore.filters.filter(f => f.field === c.name)"
55
+ />
56
+ <Select
57
+ v-else-if="c.foreignResource"
58
+ :multiple="c.filterOptions.multiselect"
59
+ class="w-full"
60
+ :options="columnOptions[c.name] || []"
61
+ :searchDisabled="!c.foreignResource.searchableFields"
62
+ @scroll-near-end="loadMoreOptions(c.name)"
63
+ @search="(searchTerm) => {
64
+ if (c.foreignResource.searchableFields && onSearchInput[c.name]) {
65
+ onSearchInput[c.name](searchTerm);
66
+ }
67
+ }"
68
+ @update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions.multiselect ? 'in' : 'eq', value: c.filterOptions.multiselect ? ($event.length ? $event : undefined) : $event || undefined })"
69
+ :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value || (c.filterOptions.multiselect ? [] : '')"
70
+ >
71
+ <template #extra-item v-if="columnLoadingState[c.name]?.loading">
72
+ <div class="text-center text-gray-400 dark:text-gray-300 py-2 flex items-center justify-center gap-2">
73
+ <Spinner class="w-4 h-4" />
74
+ {{ $t('Loading...') }}
75
+ </div>
76
+ </template>
77
+ </Select>
78
+ <Select
79
+ :multiple="c.filterOptions.multiselect"
80
+ class="w-full"
81
+ v-else-if="c.type === 'boolean'"
82
+ :options="[
83
+ { label: $t('Yes'), value: true },
84
+ { label: $t('No'), value: false },
85
+ // if field is not required, undefined might be there, and user might want to filter by it
86
+ ...(c.required ? [] : [ { label: $t('Unset'), value: undefined } ])
87
+ ]"
88
+ @update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions.multiselect ? 'in' : 'eq', value: c.filterOptions.multiselect ? ($event.length ? $event : undefined) : $event })"
89
+ :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value !== undefined
90
+ ? filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value
91
+ : (c.filterOptions.multiselect ? [] : '')"
92
+ />
93
+
94
+ <Select
95
+ :multiple="c.filterOptions.multiselect"
96
+ class="w-full"
97
+ v-else-if="c.enum"
98
+ :options="c.enum"
99
+ @update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions.multiselect ? 'in' : 'eq', value: c.filterOptions.multiselect ? ($event.length ? $event : undefined) : $event || undefined })"
100
+ :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value || (c.filterOptions.multiselect ? [] : '')"
101
+ />
102
+
103
+ <Input
104
+ v-else-if="['string', 'text', 'json', 'richtext', 'unknown'].includes(c.type)"
105
+ type="text"
106
+ full-width
107
+ :placeholder="$t('Search')"
108
+ @update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions?.substringSearch ? 'ilike' : 'eq', value: $event || undefined })"
109
+ :modelValue="getFilterItem({ column: c, operator: c.filterOptions?.substringSearch ? 'ilike' : 'eq' })"
110
+ />
111
+
112
+ <CustomDateRangePicker
113
+ v-else-if="['datetime', 'date', 'time'].includes(c.type)"
114
+ :column="c"
115
+ :valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
116
+ @update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: $event || undefined })"
117
+ :valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
118
+ @update:valueEnd="onFilterInput[c.name]({ column: c, operator: 'lte', value: $event || undefined })"
119
+ />
120
+
121
+ <CustomRangePicker
122
+ v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
123
+ :min="getFilterMinValue(c.name)"
124
+ :max="getFilterMaxValue(c.name)"
125
+ :valueStart="getFilterItem({ column: c, operator: 'gte' })"
126
+ @update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: ($event !== '' && $event !== null) ? $event : undefined })"
127
+ :valueEnd="getFilterItem({ column: c, operator: 'lte' })"
128
+ @update:valueEnd="onFilterInput[c.name]({ column: c, operator: 'lte', value: ($event !== '' && $event !== null) ? $event : undefined })"
129
+ />
130
+
131
+ <div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
132
+ <Input
133
+ type="number"
134
+ aria-describedby="helper-text-explanation"
135
+ :placeholder="$t('From')"
136
+ @update:modelValue="onFilterInput[c.name]({ column: c, operator: 'gte', value: ($event !== '' && $event !== null) ? $event : undefined })"
137
+ :modelValue="getFilterItem({ column: c, operator: 'gte' })"
138
+ />
139
+ <Input
140
+ type="number"
141
+ aria-describedby="helper-text-explanation"
142
+ :placeholder="$t('To')"
143
+ @update:modelValue="onFilterInput[c.name]({ column: c, operator: 'lte', value: ($event !== '' && $event !== null) ? $event : undefined })"
144
+ :modelValue="getFilterItem({ column: c, operator: 'lte' })"
145
+ />
146
+ </div>
147
+ </div>
133
148
  </li>
134
149
  </ul>
135
150
  </div>
@@ -138,8 +153,8 @@
138
153
  <button
139
154
  :disabled="!filtersStore.visibleFiltersCount"
140
155
  type="button"
141
- class="flex items-center py-1 px-3 text-sm font-medium text-lightFiltersClearAllButtonText focus:outline-none bg-lightFiltersClearAllButtonBackground rounded border border-lightFiltersClearAllButtonBorder hover:bg-lightFiltersClearAllButtonBackgroundHover hover:text-lightFiltersClearAllButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightFiltersClearAllButtonFocus dark:focus:ring-darkFiltersClearAllButtonFocus dark:bg-darkFiltersClearAllButtonBackground dark:text-darkFiltersClearAllButtonText dark:border-darkFiltersClearAllButtonBorder dark:hover:text-darkFiltersClearAllButtonTextHover dark:hover:bg-darkFiltersClearAllButtonBackgroundHover disabled:opacity-50 disabled:cursor-not-allowed"
142
- @click="clear">{{ $t('Clear all') }}</button>
156
+ class="flex gap-1 items-center py-1 pr-3 text-sm font-medium text-lightFiltersClearAllButtonText focus:outline-none bg-lightFiltersClearAllButtonBackground rounded border border-lightFiltersClearAllButtonBorder hover:bg-lightFiltersClearAllButtonBackgroundHover hover:text-lightFiltersClearAllButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightFiltersClearAllButtonFocus dark:focus:ring-darkFiltersClearAllButtonFocus dark:bg-darkFiltersClearAllButtonBackground dark:text-darkFiltersClearAllButtonText dark:border-darkFiltersClearAllButtonBorder dark:hover:text-darkFiltersClearAllButtonTextHover dark:hover:bg-darkFiltersClearAllButtonBackgroundHover disabled:opacity-50 disabled:cursor-not-allowed"
157
+ @click="clear"><IconCloseOutline class="ml-3"/> {{ $t('Clear all') }}</button>
143
158
 
144
159
  </div>
145
160
  </div>
@@ -162,6 +177,8 @@ import Input from '@/afcl/Input.vue';
162
177
  import Select from '@/afcl/Select.vue';
163
178
  import Spinner from '@/afcl/Spinner.vue';
164
179
  import debounce from 'debounce';
180
+ import { Tooltip } from '@/afcl';
181
+ import { IconCloseOutline } from '@iconify-prerendered/vue-flowbite';
165
182
 
166
183
  const filtersStore = useFiltersStore();
167
184
  const { t } = useI18n();
@@ -22,6 +22,9 @@ export const useFiltersStore = defineStore('filters', () => {
22
22
  const getFilters = () => {
23
23
  return filters.value;
24
24
  }
25
+ const clearFilter = (fieldName: string) => {
26
+ filters.value = filters.value.filter(f => f.field !== fieldName);
27
+ }
25
28
  const clearFilters = () => {
26
29
  filters.value = [];
27
30
  }
@@ -49,6 +52,7 @@ export const useFiltersStore = defineStore('filters', () => {
49
52
  setSort,
50
53
  getSort,
51
54
  visibleFiltersCount,
52
- shouldFilterBeHidden
55
+ shouldFilterBeHidden,
56
+ clearFilter
53
57
  }
54
58
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "2.4.0-next.286",
3
+ "version": "2.4.0-next.288",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",