adminforth 1.3.54-next.5 → 1.3.54-next.6
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.
|
@@ -266,7 +266,6 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
|
|
|
266
266
|
const where = this.whereClause(resource, filters);
|
|
267
267
|
const d = this.whereParams(filters);
|
|
268
268
|
|
|
269
|
-
|
|
270
269
|
const countQ = await this.client.query({
|
|
271
270
|
query: `SELECT COUNT(*) as count FROM ${tableName} ${where}`,
|
|
272
271
|
format: 'JSONEachRow',
|
package/package.json
CHANGED
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
<script setup>
|
|
251
251
|
|
|
252
252
|
|
|
253
|
-
import { computed, ref, watch } from 'vue';
|
|
253
|
+
import { computed, onMounted, ref, watch } from 'vue';
|
|
254
254
|
import { callAdminForthApi } from '@/utils';
|
|
255
255
|
|
|
256
256
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
@@ -260,19 +260,20 @@ import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
|
260
260
|
import SkeleteLoader from '@/components/SkeleteLoader.vue';
|
|
261
261
|
|
|
262
262
|
import {
|
|
263
|
-
IconInboxOutline,
|
|
263
|
+
IconInboxOutline,
|
|
264
264
|
} from '@iconify-prerendered/vue-flowbite';
|
|
265
265
|
|
|
266
266
|
import {
|
|
267
|
-
IconEyeSolid,
|
|
268
|
-
IconPenSolid,
|
|
269
|
-
IconTrashBinSolid
|
|
267
|
+
IconEyeSolid,
|
|
268
|
+
IconPenSolid,
|
|
269
|
+
IconTrashBinSolid
|
|
270
270
|
} from '@iconify-prerendered/vue-flowbite';
|
|
271
271
|
import router from '@/router';
|
|
272
272
|
|
|
273
273
|
const coreStore = useCoreStore();
|
|
274
274
|
|
|
275
275
|
const props = defineProps([
|
|
276
|
+
'page',
|
|
276
277
|
'resource',
|
|
277
278
|
'rows',
|
|
278
279
|
'totalRows',
|
|
@@ -296,6 +297,7 @@ const page = ref(1);
|
|
|
296
297
|
const sort = ref([]);
|
|
297
298
|
|
|
298
299
|
|
|
300
|
+
|
|
299
301
|
watch(() => page.value, (newPage) => {
|
|
300
302
|
emits('update:page', newPage);
|
|
301
303
|
});
|
|
@@ -319,6 +321,10 @@ watch(() => props.sort, (newSort) => {
|
|
|
319
321
|
sort.value = newSort;
|
|
320
322
|
});
|
|
321
323
|
|
|
324
|
+
watch(() => props.page, (newPage) => {
|
|
325
|
+
page.value = newPage;
|
|
326
|
+
});
|
|
327
|
+
|
|
322
328
|
function addToCheckedValues(id) {
|
|
323
329
|
console.log('checking', checkboxesInternal.value, 'id', id)
|
|
324
330
|
if (checkboxesInternal.value.includes(id)) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
{{ visualValue }}
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
|
|
7
|
+
import { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
|
|
8
|
+
|
|
9
|
+
const visualValue = computed(() => {
|
|
10
|
+
return record[column.name];
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const props = defineProps(['column', 'record', 'meta']);
|
|
14
|
+
|
|
15
|
+
</script>
|
package/spa/src/utils.ts
CHANGED
|
@@ -146,4 +146,15 @@ export function applyRegexValidation(value: any, validation: ValidationObject[]
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function currentQuery() {
|
|
152
|
+
return router.currentRoute.value.query;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function setQuery(query: any) {
|
|
156
|
+
const currentQuery = { ...router.currentRoute.value.query, ...query };
|
|
157
|
+
router.replace({
|
|
158
|
+
query: currentQuery,
|
|
159
|
+
});
|
|
149
160
|
}
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
<ResourceListTable
|
|
90
90
|
:resource="coreStore.resource"
|
|
91
91
|
:rows="rows"
|
|
92
|
+
:page="page"
|
|
92
93
|
@update:page="page = $event"
|
|
93
94
|
@update:sort="sort = $event"
|
|
94
95
|
@update:checkboxes="checkboxes = $event"
|
|
@@ -115,7 +116,7 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
115
116
|
import ResourceListTable from '@/components/ResourceListTable.vue';
|
|
116
117
|
import { useCoreStore } from '@/stores/core';
|
|
117
118
|
import { useFiltersStore } from '@/stores/filters';
|
|
118
|
-
import { callAdminForthApi, getIcon } from '@/utils';
|
|
119
|
+
import { callAdminForthApi, currentQuery, getIcon, setQuery } from '@/utils';
|
|
119
120
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
120
121
|
import { useRoute } from 'vue-router';
|
|
121
122
|
import { showErrorTost } from '@/composables/useFrontendApi'
|
|
@@ -227,6 +228,18 @@ async function startBulkAction(actionId) {
|
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
|
|
231
|
+
class SortQuerySerializer {
|
|
232
|
+
static serialize(sort) {
|
|
233
|
+
return sort.map(s => `${s.field}__${s.direction}`).join(',');
|
|
234
|
+
}
|
|
235
|
+
static deserialize(str) {
|
|
236
|
+
return str.split(',').map(s => {
|
|
237
|
+
const [field, direction] = s.split('__');
|
|
238
|
+
return { field, direction };
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
230
243
|
|
|
231
244
|
async function init() {
|
|
232
245
|
|
|
@@ -236,9 +249,26 @@ async function init() {
|
|
|
236
249
|
|
|
237
250
|
initFlowbite();
|
|
238
251
|
|
|
239
|
-
// !!! clear filters should be in same tick with sort assignment so that watch can catch it
|
|
240
|
-
|
|
241
|
-
|
|
252
|
+
// !!! clear filters should be in same tick with sort assignment so that watch can catch it as one change
|
|
253
|
+
|
|
254
|
+
// try to init filters from query params
|
|
255
|
+
const filters = Object.keys(route.query).filter(k => k.startsWith('filter__')).map(k => {
|
|
256
|
+
const [_, field, operator] = k.split('__');
|
|
257
|
+
return {
|
|
258
|
+
field,
|
|
259
|
+
operator,
|
|
260
|
+
value: decodeURIComponent(route.query[k])
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
if (filters.length) {
|
|
264
|
+
filtersStore.setFilters(filters);
|
|
265
|
+
} else {
|
|
266
|
+
filtersStore.clearFilters();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (route.query.sort) {
|
|
270
|
+
sort.value = SortQuerySerializer.deserialize(route.query.sort);
|
|
271
|
+
} else if (coreStore.resource.options?.defaultSort) {
|
|
242
272
|
sort.value = [{
|
|
243
273
|
field: coreStore.resource.options.defaultSort.columnName,
|
|
244
274
|
direction: coreStore.resource.options.defaultSort.direction
|
|
@@ -246,6 +276,11 @@ async function init() {
|
|
|
246
276
|
} else {
|
|
247
277
|
sort.value = [];
|
|
248
278
|
}
|
|
279
|
+
// page init should be also in same tick
|
|
280
|
+
if (route.query.page) {
|
|
281
|
+
page.value = parseInt(route.query.page);
|
|
282
|
+
}
|
|
283
|
+
|
|
249
284
|
// await getList(); - Not needed here, watch will trigger it
|
|
250
285
|
columnsMinMax.value = await callAdminForthApi({
|
|
251
286
|
path: '/get_min_max_for_columns',
|
|
@@ -257,6 +292,8 @@ async function init() {
|
|
|
257
292
|
}
|
|
258
293
|
|
|
259
294
|
watch([page, sort, () => filtersStore.filters], async () => {
|
|
295
|
+
console.log('🔄️ page/sort/filter change fired, page:', page.value);
|
|
296
|
+
|
|
260
297
|
await getList();
|
|
261
298
|
}, { deep: true });
|
|
262
299
|
|
|
@@ -264,16 +301,51 @@ window.adminforth.list.refresh = async () => {
|
|
|
264
301
|
await getList();
|
|
265
302
|
}
|
|
266
303
|
|
|
304
|
+
let initInProcess = false;
|
|
305
|
+
|
|
267
306
|
watch(() => filtersStore.filters, async (to, from) => {
|
|
307
|
+
if (initInProcess) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
console.log('🔄️ filters changed', JSON.stringify(to))
|
|
268
311
|
page.value = 1;
|
|
269
312
|
checkboxes.value = [];
|
|
313
|
+
// update query param for each filter as filter_<column_name>=value
|
|
314
|
+
const query = {};
|
|
315
|
+
const currentQ = currentQuery();
|
|
316
|
+
filtersStore.filters.forEach(f => {
|
|
317
|
+
query[`filter__${f.field}__${f.operator}`] = encodeURIComponent(f.value);
|
|
318
|
+
});
|
|
319
|
+
// set every key in currentQ which starts with filter_ to undefined if it is not in query
|
|
320
|
+
Object.keys(currentQ).forEach(k => {
|
|
321
|
+
if (k.startsWith('filter_') && !query[k]) {
|
|
322
|
+
query[k] = undefined;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
setQuery(query);
|
|
270
326
|
}, {deep: true});
|
|
271
327
|
|
|
272
328
|
onMounted(async () => {
|
|
329
|
+
initInProcess = true;
|
|
273
330
|
await init();
|
|
274
331
|
initThreeDotsDropdown();
|
|
332
|
+
initInProcess = false;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
watch([page], async () => {
|
|
336
|
+
setQuery({ page: page.value });
|
|
275
337
|
});
|
|
276
338
|
|
|
277
339
|
|
|
278
340
|
|
|
341
|
+
|
|
342
|
+
watch([sort], async () => {
|
|
343
|
+
if (!sort.value.length) {
|
|
344
|
+
setQuery({ sort: undefined });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
setQuery({ sort: SortQuerySerializer.serialize(sort.value) });
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
|
|
279
351
|
</script>
|