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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.3.54-next.5",
3
+ "version": "1.3.54-next.6",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
- filtersStore.clearFilters();
241
- if (coreStore.resource.options?.defaultSort) {
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>