adminforth 2.4.0-next.285 → 2.4.0-next.287
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
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
142
|
-
@click="clear"
|
|
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/dist/spa/src/utils.ts
CHANGED
|
@@ -481,3 +481,11 @@ export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Reco
|
|
|
481
481
|
|
|
482
482
|
return evaluatePredicate(c.showIf);
|
|
483
483
|
}
|
|
484
|
+
|
|
485
|
+
export function btoa_function(source: string): string {
|
|
486
|
+
return btoa(source);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function atob_function(source: string): string {
|
|
490
|
+
return atob(source);
|
|
491
|
+
}
|