@adminforth/quick-filters 1.0.0 → 1.2.0
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.
- package/build.log +2 -2
- package/custom/FiltersArea.vue +77 -27
- package/dist/custom/FiltersArea.vue +77 -27
- package/dist/index.js +8 -11
- package/index.ts +8 -13
- package/package.json +1 -1
package/build.log
CHANGED
package/custom/FiltersArea.vue
CHANGED
|
@@ -77,8 +77,6 @@
|
|
|
77
77
|
|
|
78
78
|
<CustomRangePicker
|
|
79
79
|
v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
|
|
80
|
-
:min="getFilterMinValue(c.name)"
|
|
81
|
-
:max="getFilterMaxValue(c.name)"
|
|
82
80
|
:valueStart="getFilterItem({ column: c, operator: 'gte' })"
|
|
83
81
|
@update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: ($event !== '' && $event !== null) ? $event : undefined })"
|
|
84
82
|
:valueEnd="getFilterItem({ column: c, operator: 'lte' })"
|
|
@@ -105,21 +103,27 @@
|
|
|
105
103
|
</div>
|
|
106
104
|
</div>
|
|
107
105
|
</div>
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
<template v-if="freeCells > 0" v-for="n in freeCells-1" :key="'free-cell-' + n">
|
|
107
|
+
<div></div>
|
|
108
|
+
</template>
|
|
109
|
+
<template v-if="freeCells === 0" v-for="n in cols-1" :key="'free-cell2-' + n">
|
|
110
|
+
<div></div>
|
|
111
|
+
</template>
|
|
112
|
+
<div class="flex items-end justify-end">
|
|
110
113
|
<Button
|
|
111
|
-
class="mt-
|
|
114
|
+
class="mt-6 max-w-24"
|
|
112
115
|
@click="filtersStore.filters = [...filtersStore.filters.filter(f => filtersStore.shouldFilterBeHidden(f.field))]"
|
|
113
116
|
:disabled="filtersStore.filters.length === 0"
|
|
114
117
|
>
|
|
115
118
|
Clear all
|
|
116
119
|
</Button>
|
|
120
|
+
</div>
|
|
117
121
|
</div>
|
|
118
122
|
</div>
|
|
119
123
|
</template>
|
|
120
124
|
|
|
121
125
|
<script lang="ts" setup>
|
|
122
|
-
import { onMounted, computed, ref, reactive } from 'vue';
|
|
126
|
+
import { onMounted, onUnmounted, computed, ref, reactive, watch } from 'vue';
|
|
123
127
|
import { useFiltersStore } from '@/stores/filters';
|
|
124
128
|
import { callAdminForthApi, loadMoreForeignOptions, searchForeignOptions, createSearchInputHandlers } from '@/utils';
|
|
125
129
|
import { useRouter } from 'vue-router';
|
|
@@ -136,7 +140,10 @@ const columnOffsets = reactive({});
|
|
|
136
140
|
const columnEmptyResultsCount = reactive({});
|
|
137
141
|
const filtersStore = useFiltersStore();
|
|
138
142
|
const columnsMinMax = ref({});
|
|
139
|
-
const isExpanded = ref(
|
|
143
|
+
const isExpanded = ref(true);
|
|
144
|
+
const freeCells = ref(0);
|
|
145
|
+
const currentBreakpoint = ref("base");
|
|
146
|
+
const cols = ref(1);
|
|
140
147
|
|
|
141
148
|
const props = defineProps<{
|
|
142
149
|
meta: any,
|
|
@@ -146,37 +153,37 @@ const props = defineProps<{
|
|
|
146
153
|
|
|
147
154
|
const columnOptions = ref({});
|
|
148
155
|
const columnsWithFilter = computed(
|
|
149
|
-
() => props.resource.columns?.filter(column => column.showIn.filter && props.meta.options.columns.some(c => c.column === column.name)) || []
|
|
156
|
+
() => sortFilters(props.resource.columns?.filter(column => column.showIn.filter && props.meta.options.columns.some(c => c.column === column.name)), props.meta.options.columns) || []
|
|
150
157
|
);
|
|
151
158
|
|
|
152
159
|
onMounted(async () => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
// columnsMinMax.value = await callAdminForthApi({
|
|
161
|
+
// path: '/get_min_max_for_columns',
|
|
162
|
+
// method: 'POST',
|
|
163
|
+
// body: {
|
|
164
|
+
// resourceId: route.params.resourceId
|
|
165
|
+
// }
|
|
166
|
+
// });
|
|
167
|
+
currentBreakpoint.value = getBreakpoint(window.innerWidth);
|
|
168
|
+
calculateGridCols();
|
|
169
|
+
window.addEventListener('resize', () => {
|
|
170
|
+
const newBreakpoint = getBreakpoint(window.innerWidth)
|
|
171
|
+
if (newBreakpoint !== currentBreakpoint.value) {
|
|
172
|
+
currentBreakpoint.value = newBreakpoint;
|
|
173
|
+
calculateGridCols();
|
|
159
174
|
}
|
|
160
|
-
})
|
|
161
|
-
console.log('Fetched columnsMinMax:', columnsMinMax.value);
|
|
175
|
+
})
|
|
162
176
|
});
|
|
163
177
|
|
|
178
|
+
onUnmounted(() => {
|
|
179
|
+
window.removeEventListener('resize', getBreakpoint)
|
|
180
|
+
})
|
|
181
|
+
|
|
164
182
|
function getFilterItem({ column, operator }) {
|
|
165
183
|
const filterValue = filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value;
|
|
166
184
|
return filterValue !== undefined ? filterValue : '';
|
|
167
185
|
}
|
|
168
186
|
|
|
169
|
-
function getFilterMinValue(columnName) {
|
|
170
|
-
if(columnsMinMax.value && columnsMinMax.value[columnName]) {
|
|
171
|
-
return columnsMinMax.value[columnName]?.min
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function getFilterMaxValue(columnName) {
|
|
176
|
-
if(columnsMinMax.value && columnsMinMax.value[columnName]) {
|
|
177
|
-
return columnsMinMax.value[columnName]?.max
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
187
|
async function loadMoreOptions(columnName, searchTerm = '') {
|
|
181
188
|
return loadMoreForeignOptions({
|
|
182
189
|
columnName,
|
|
@@ -244,4 +251,47 @@ function setFilterItem({ column, operator, value }) {
|
|
|
244
251
|
}
|
|
245
252
|
}
|
|
246
253
|
|
|
254
|
+
|
|
255
|
+
function getBreakpoint(width) {
|
|
256
|
+
if (width >= 1536) return '2xl'
|
|
257
|
+
if (width >= 1280) return 'xl'
|
|
258
|
+
if (width >= 1024) return 'lg'
|
|
259
|
+
if (width >= 768) return 'md'
|
|
260
|
+
if (width >= 640) return 'sm'
|
|
261
|
+
return 'base'
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function calculateGridCols() {
|
|
265
|
+
const size = currentBreakpoint.value;
|
|
266
|
+
switch (size) {
|
|
267
|
+
case '2xl':
|
|
268
|
+
cols.value = 6;
|
|
269
|
+
break;
|
|
270
|
+
case 'xl':
|
|
271
|
+
cols.value = 3;
|
|
272
|
+
break;
|
|
273
|
+
case 'lg':
|
|
274
|
+
cols.value = 2;
|
|
275
|
+
break;
|
|
276
|
+
case 'md':
|
|
277
|
+
cols.value = 1;
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
cols.value = 1;
|
|
281
|
+
}
|
|
282
|
+
const rows = Math.ceil(columnsWithFilter.value.length / cols.value);
|
|
283
|
+
freeCells.value = (cols.value * rows) - columnsWithFilter.value.length;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function sortFilters(array, fieldsObject) {
|
|
287
|
+
let desiredOrder = [];
|
|
288
|
+
fieldsObject.forEach(element => {
|
|
289
|
+
desiredOrder.push(element.column);
|
|
290
|
+
});
|
|
291
|
+
const sortedObjects = array.sort(
|
|
292
|
+
(a, b) => desiredOrder.indexOf(a.name) - desiredOrder.indexOf(b.name)
|
|
293
|
+
);
|
|
294
|
+
return sortedObjects;
|
|
295
|
+
}
|
|
296
|
+
|
|
247
297
|
</script>
|
|
@@ -77,8 +77,6 @@
|
|
|
77
77
|
|
|
78
78
|
<CustomRangePicker
|
|
79
79
|
v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
|
|
80
|
-
:min="getFilterMinValue(c.name)"
|
|
81
|
-
:max="getFilterMaxValue(c.name)"
|
|
82
80
|
:valueStart="getFilterItem({ column: c, operator: 'gte' })"
|
|
83
81
|
@update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: ($event !== '' && $event !== null) ? $event : undefined })"
|
|
84
82
|
:valueEnd="getFilterItem({ column: c, operator: 'lte' })"
|
|
@@ -105,21 +103,27 @@
|
|
|
105
103
|
</div>
|
|
106
104
|
</div>
|
|
107
105
|
</div>
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
<template v-if="freeCells > 0" v-for="n in freeCells-1" :key="'free-cell-' + n">
|
|
107
|
+
<div></div>
|
|
108
|
+
</template>
|
|
109
|
+
<template v-if="freeCells === 0" v-for="n in cols-1" :key="'free-cell2-' + n">
|
|
110
|
+
<div></div>
|
|
111
|
+
</template>
|
|
112
|
+
<div class="flex items-end justify-end">
|
|
110
113
|
<Button
|
|
111
|
-
class="mt-
|
|
114
|
+
class="mt-6 max-w-24"
|
|
112
115
|
@click="filtersStore.filters = [...filtersStore.filters.filter(f => filtersStore.shouldFilterBeHidden(f.field))]"
|
|
113
116
|
:disabled="filtersStore.filters.length === 0"
|
|
114
117
|
>
|
|
115
118
|
Clear all
|
|
116
119
|
</Button>
|
|
120
|
+
</div>
|
|
117
121
|
</div>
|
|
118
122
|
</div>
|
|
119
123
|
</template>
|
|
120
124
|
|
|
121
125
|
<script lang="ts" setup>
|
|
122
|
-
import { onMounted, computed, ref, reactive } from 'vue';
|
|
126
|
+
import { onMounted, onUnmounted, computed, ref, reactive, watch } from 'vue';
|
|
123
127
|
import { useFiltersStore } from '@/stores/filters';
|
|
124
128
|
import { callAdminForthApi, loadMoreForeignOptions, searchForeignOptions, createSearchInputHandlers } from '@/utils';
|
|
125
129
|
import { useRouter } from 'vue-router';
|
|
@@ -136,7 +140,10 @@ const columnOffsets = reactive({});
|
|
|
136
140
|
const columnEmptyResultsCount = reactive({});
|
|
137
141
|
const filtersStore = useFiltersStore();
|
|
138
142
|
const columnsMinMax = ref({});
|
|
139
|
-
const isExpanded = ref(
|
|
143
|
+
const isExpanded = ref(true);
|
|
144
|
+
const freeCells = ref(0);
|
|
145
|
+
const currentBreakpoint = ref("base");
|
|
146
|
+
const cols = ref(1);
|
|
140
147
|
|
|
141
148
|
const props = defineProps<{
|
|
142
149
|
meta: any,
|
|
@@ -146,37 +153,37 @@ const props = defineProps<{
|
|
|
146
153
|
|
|
147
154
|
const columnOptions = ref({});
|
|
148
155
|
const columnsWithFilter = computed(
|
|
149
|
-
() => props.resource.columns?.filter(column => column.showIn.filter && props.meta.options.columns.some(c => c.column === column.name)) || []
|
|
156
|
+
() => sortFilters(props.resource.columns?.filter(column => column.showIn.filter && props.meta.options.columns.some(c => c.column === column.name)), props.meta.options.columns) || []
|
|
150
157
|
);
|
|
151
158
|
|
|
152
159
|
onMounted(async () => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
// columnsMinMax.value = await callAdminForthApi({
|
|
161
|
+
// path: '/get_min_max_for_columns',
|
|
162
|
+
// method: 'POST',
|
|
163
|
+
// body: {
|
|
164
|
+
// resourceId: route.params.resourceId
|
|
165
|
+
// }
|
|
166
|
+
// });
|
|
167
|
+
currentBreakpoint.value = getBreakpoint(window.innerWidth);
|
|
168
|
+
calculateGridCols();
|
|
169
|
+
window.addEventListener('resize', () => {
|
|
170
|
+
const newBreakpoint = getBreakpoint(window.innerWidth)
|
|
171
|
+
if (newBreakpoint !== currentBreakpoint.value) {
|
|
172
|
+
currentBreakpoint.value = newBreakpoint;
|
|
173
|
+
calculateGridCols();
|
|
159
174
|
}
|
|
160
|
-
})
|
|
161
|
-
console.log('Fetched columnsMinMax:', columnsMinMax.value);
|
|
175
|
+
})
|
|
162
176
|
});
|
|
163
177
|
|
|
178
|
+
onUnmounted(() => {
|
|
179
|
+
window.removeEventListener('resize', getBreakpoint)
|
|
180
|
+
})
|
|
181
|
+
|
|
164
182
|
function getFilterItem({ column, operator }) {
|
|
165
183
|
const filterValue = filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value;
|
|
166
184
|
return filterValue !== undefined ? filterValue : '';
|
|
167
185
|
}
|
|
168
186
|
|
|
169
|
-
function getFilterMinValue(columnName) {
|
|
170
|
-
if(columnsMinMax.value && columnsMinMax.value[columnName]) {
|
|
171
|
-
return columnsMinMax.value[columnName]?.min
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function getFilterMaxValue(columnName) {
|
|
176
|
-
if(columnsMinMax.value && columnsMinMax.value[columnName]) {
|
|
177
|
-
return columnsMinMax.value[columnName]?.max
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
187
|
async function loadMoreOptions(columnName, searchTerm = '') {
|
|
181
188
|
return loadMoreForeignOptions({
|
|
182
189
|
columnName,
|
|
@@ -244,4 +251,47 @@ function setFilterItem({ column, operator, value }) {
|
|
|
244
251
|
}
|
|
245
252
|
}
|
|
246
253
|
|
|
254
|
+
|
|
255
|
+
function getBreakpoint(width) {
|
|
256
|
+
if (width >= 1536) return '2xl'
|
|
257
|
+
if (width >= 1280) return 'xl'
|
|
258
|
+
if (width >= 1024) return 'lg'
|
|
259
|
+
if (width >= 768) return 'md'
|
|
260
|
+
if (width >= 640) return 'sm'
|
|
261
|
+
return 'base'
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function calculateGridCols() {
|
|
265
|
+
const size = currentBreakpoint.value;
|
|
266
|
+
switch (size) {
|
|
267
|
+
case '2xl':
|
|
268
|
+
cols.value = 6;
|
|
269
|
+
break;
|
|
270
|
+
case 'xl':
|
|
271
|
+
cols.value = 3;
|
|
272
|
+
break;
|
|
273
|
+
case 'lg':
|
|
274
|
+
cols.value = 2;
|
|
275
|
+
break;
|
|
276
|
+
case 'md':
|
|
277
|
+
cols.value = 1;
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
cols.value = 1;
|
|
281
|
+
}
|
|
282
|
+
const rows = Math.ceil(columnsWithFilter.value.length / cols.value);
|
|
283
|
+
freeCells.value = (cols.value * rows) - columnsWithFilter.value.length;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function sortFilters(array, fieldsObject) {
|
|
287
|
+
let desiredOrder = [];
|
|
288
|
+
fieldsObject.forEach(element => {
|
|
289
|
+
desiredOrder.push(element.column);
|
|
290
|
+
});
|
|
291
|
+
const sortedObjects = array.sort(
|
|
292
|
+
(a, b) => desiredOrder.indexOf(a.name) - desiredOrder.indexOf(b.name)
|
|
293
|
+
);
|
|
294
|
+
return sortedObjects;
|
|
295
|
+
}
|
|
296
|
+
|
|
247
297
|
</script>
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { AdminForthPlugin } from "adminforth";
|
|
11
|
+
import { suggestIfTypo } from "adminforth";
|
|
11
12
|
export default class extends AdminForthPlugin {
|
|
12
13
|
constructor(options) {
|
|
13
14
|
super(options, import.meta.url);
|
|
@@ -33,21 +34,17 @@ export default class extends AdminForthPlugin {
|
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
36
|
-
|
|
37
|
+
for (const colOpt of this.options.columns) {
|
|
38
|
+
const column = resourceConfig.columns.find(c => c.name === colOpt.column);
|
|
39
|
+
if (!column) {
|
|
40
|
+
const similar = suggestIfTypo(resourceConfig.columns.map((column) => column.name), colOpt.column);
|
|
41
|
+
throw new Error(`QuickFilters plugin: column '${colOpt.column}' not found in resource '${resourceConfig.resourceId}'. Did you mean '${similar}'?`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
instanceUniqueRepresentation(pluginOptions) {
|
|
39
46
|
// optional method to return unique string representation of plugin instance.
|
|
40
47
|
// Needed if plugin can have multiple instances on one resource
|
|
41
48
|
return `single`;
|
|
42
49
|
}
|
|
43
|
-
setupEndpoints(server) {
|
|
44
|
-
server.endpoint({
|
|
45
|
-
method: 'POST',
|
|
46
|
-
path: `/plugin/${this.pluginInstanceId}/example`,
|
|
47
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
48
|
-
const { name } = body;
|
|
49
|
-
return { hey: `Hello ${name}` };
|
|
50
|
-
})
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
50
|
}
|
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AdminForthPlugin } from "adminforth";
|
|
2
2
|
import type { IAdminForth, IHttpServer, AdminForthResourcePages, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource, AdminForthComponentDeclaration } from "adminforth";
|
|
3
3
|
import type { PluginOptions } from './types.js';
|
|
4
|
-
|
|
4
|
+
import { suggestIfTypo } from "adminforth";
|
|
5
5
|
|
|
6
6
|
export default class extends AdminForthPlugin {
|
|
7
7
|
options: PluginOptions;
|
|
@@ -30,7 +30,13 @@ export default class extends AdminForthPlugin {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
33
|
-
|
|
33
|
+
for ( const colOpt of this.options.columns ) {
|
|
34
|
+
const column = resourceConfig.columns.find(c => c.name === colOpt.column);
|
|
35
|
+
if ( !column ) {
|
|
36
|
+
const similar = suggestIfTypo(resourceConfig.columns.map((column: any) => column.name), colOpt.column);
|
|
37
|
+
throw new Error(`QuickFilters plugin: column '${colOpt.column}' not found in resource '${resourceConfig.resourceId}'. Did you mean '${similar}'?`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
instanceUniqueRepresentation(pluginOptions: any) : string {
|
|
@@ -39,15 +45,4 @@ export default class extends AdminForthPlugin {
|
|
|
39
45
|
return `single`;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
setupEndpoints(server: IHttpServer) {
|
|
43
|
-
server.endpoint({
|
|
44
|
-
method: 'POST',
|
|
45
|
-
path: `/plugin/${this.pluginInstanceId}/example`,
|
|
46
|
-
handler: async ({ body }) => {
|
|
47
|
-
const { name } = body;
|
|
48
|
-
return { hey: `Hello ${name}` };
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
48
|
}
|