adminforth 1.3.24 → 1.3.25

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.
@@ -32,8 +32,15 @@ export default class OperationalResource {
32
32
  })).data[0] || null;
33
33
  });
34
34
  }
35
- list(filter, limit, offset, sort) {
36
- return __awaiter(this, void 0, void 0, function* () {
35
+ list(filter_1) {
36
+ return __awaiter(this, arguments, void 0, function* (filter, limit = null, offset = null, sort = []) {
37
+ // check if type of limit and offset is number
38
+ if (limit !== null && typeof limit !== 'number') {
39
+ throw new Error('Limit must be a number');
40
+ }
41
+ if (offset !== null && typeof offset !== 'number') {
42
+ throw new Error('Offset must be a number');
43
+ }
37
44
  let appliedLimit = limit;
38
45
  if (limit === null) {
39
46
  appliedLimit = 1000000000;
@@ -36,10 +36,18 @@ export default class OperationalResource implements IOperationalResource {
36
36
 
37
37
  async list(
38
38
  filter: IAdminForthFilter | IAdminForthFilter[],
39
- limit: number | null,
40
- offset: number | null,
41
- sort: IAdminForthSort | IAdminForthSort[]
39
+ limit: number | null = null,
40
+ offset: number | null = null,
41
+ sort: IAdminForthSort | IAdminForthSort[] = []
42
42
  ): Promise<any[]> {
43
+ // check if type of limit and offset is number
44
+ if (limit !== null && typeof limit !== 'number') {
45
+ throw new Error('Limit must be a number');
46
+ }
47
+ if (offset !== null && typeof offset !== 'number') {
48
+ throw new Error('Offset must be a number');
49
+ }
50
+
43
51
  let appliedLimit = limit;
44
52
  if (limit === null) {
45
53
  appliedLimit = 1000000000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.3.24",
3
+ "version": "1.3.25",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -230,10 +230,8 @@
230
230
  <script setup>
231
231
 
232
232
 
233
- import { initFlowbite } from 'flowbite';
234
- import { computed, onMounted, ref, watch } from 'vue';
235
- import { useRoute } from 'vue-router';
236
- import { callAdminForthApi, getIcon } from '@/utils';
233
+ import { computed, ref, watch } from 'vue';
234
+ import { callAdminForthApi } from '@/utils';
237
235
 
238
236
  import ValueRenderer from '@/components/ValueRenderer.vue';
239
237
  import { getCustomComponent } from '@/utils';
@@ -0,0 +1,43 @@
1
+ <template >
2
+ <template v-if="threeDotsDropdownItems?.length">
3
+ <button
4
+ data-dropdown-toggle="listThreeDotsDropdown"
5
+ class="flex items-center py-2 px-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
6
+ >
7
+ <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 4 15">
8
+ <path d="M3.5 1.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 6.041a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 5.959a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"/>
9
+ </svg>
10
+ </button>
11
+
12
+ <!-- Dropdown menu -->
13
+ <div
14
+ id="listThreeDotsDropdown"
15
+ class="z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700 dark:divide-gray-600">
16
+ <ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownMenuIconButton">
17
+ <li v-for="item in threeDotsDropdownItems" :key="`dropdown-item-${item.label}`">
18
+ <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
19
+ <component :is="getCustomComponent(item)"
20
+ :meta="item.meta"
21
+ :resource="coreStore.resource"
22
+ :adminUser="coreStore.adminUser"
23
+ />
24
+ </a>
25
+ </li>
26
+ </ul>
27
+ </div>
28
+ </template>
29
+ </template>
30
+
31
+
32
+ <script setup lang="ts">
33
+
34
+ import { getCustomComponent } from '@/utils';
35
+ import { useCoreStore } from '@/stores/core'
36
+
37
+ const coreStore = useCoreStore()
38
+
39
+ const props = defineProps<{
40
+ threeDotsDropdownItems: any[]
41
+ }>()
42
+
43
+ </script>
@@ -24,17 +24,11 @@ import type { AdminForthResourceColumn } from '@/types/AdminForthConfig';
24
24
 
25
25
  declare global {
26
26
  interface Window {
27
- adminforth: {
28
- confirm: (params: ConfirmParams) => Promise<void>;
29
- alert: (params: AlertParams) => void;
30
- setListFilter: (filter: any) => void;
31
- updateListFilter: (filter: any) => void;
32
- clearListFilters: () => void;
33
- };
27
+ adminforth: FrontendAPIInterface;
34
28
  }
35
29
  }
36
30
 
37
- export class FrontendAPI implements FrontendAPIInterface {
31
+ export class FrontendAPI {
38
32
  private toastStore:any
39
33
  private modalStore:any
40
34
  private filtersStore:any
@@ -45,14 +39,19 @@ export class FrontendAPI implements FrontendAPIInterface {
45
39
  }
46
40
  this.toastStore = useToastStore();
47
41
  this.modalStore = useModalStore();
48
- console.log(this.toastStore, this.modalStore,'init of adminforth frontend api')
42
+
49
43
  window.adminforth = {
50
44
  confirm: this.confirm.bind(this),
51
45
  alert: this.alert.bind(this),
52
- setListFilter: this.setListFilter.bind(this),
53
- updateListFilter: this.updateListFilter.bind(this),
54
- clearListFilters: this.clearListFilters.bind(this),
55
- }
46
+
47
+ list: {
48
+ refresh: () => {/* will be redefined in list*/},
49
+ closeThreeDotsDropdown: () => {/* will be redefined in list*/},
50
+ setFilter: () => this.setListFilter.bind(this),
51
+ updateFilter: () => this.updateListFilter.bind(this),
52
+ clearFilters: () => this.clearListFilters.bind(this),
53
+ }
54
+ };
56
55
  }
57
56
 
58
57
  confirm(params: ConfirmParams): Promise<void> {
package/spa/src/utils.ts CHANGED
@@ -2,9 +2,10 @@ import { onMounted, ref, resolveComponent } from 'vue';
2
2
  import type { CoreConfig } from './spa_types/core';
3
3
 
4
4
  import router from "./router";
5
- import { useRouter } from 'vue-router';
6
5
  import { useCoreStore } from './stores/core';
7
6
  import { useUserStore } from './stores/user';
7
+ import { Dropdown } from 'flowbite';
8
+
8
9
 
9
10
  export async function callApi({path, method, body=undefined}: {
10
11
  path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
@@ -100,4 +101,16 @@ export function checkAcessByAllowedActions(allowedActions:any, action:any ) {
100
101
  }
101
102
  }
102
103
 
103
-
104
+ export function initThreeDotsDropdown() {
105
+ const threeDotsDropdown: HTMLElement | null = document.querySelector('#listThreeDotsDropdown');
106
+ if (threeDotsDropdown) {
107
+ // this resource has three dots dropdown
108
+ const dd = new Dropdown(
109
+ threeDotsDropdown,
110
+ document.querySelector('[data-dropdown-toggle="listThreeDotsDropdown"]') as HTMLElement,
111
+ );
112
+ window.adminforth.list.closeThreeDotsDropdown = () => {
113
+ dd.hide();
114
+ }
115
+ }
116
+ }
@@ -29,6 +29,10 @@
29
29
  Save
30
30
  </button>
31
31
 
32
+ <ThreeDotsMenu
33
+ :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.create?.threeDotsDropdownItems"
34
+ ></ThreeDotsMenu>
35
+
32
36
  </BreadcrumbsWithButtons>
33
37
 
34
38
  <component
@@ -73,12 +77,13 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
73
77
  import ResourceForm from '@/components/ResourceForm.vue';
74
78
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
75
79
  import { useCoreStore } from '@/stores/core';
76
- import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from '@/utils';
80
+ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
77
81
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
78
82
  import { onMounted, ref, watch } from 'vue';
79
83
  import { useRoute, useRouter } from 'vue-router';
80
84
  import { computed } from 'vue';
81
85
  import { showErrorTost } from '@/composables/useFrontendApi';
86
+ import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
82
87
 
83
88
 
84
89
  const isValid = ref(false);
@@ -116,6 +121,7 @@ onMounted(async () => {
116
121
  });
117
122
  loading.value = false;
118
123
  checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'create');
124
+ initThreeDotsDropdown();
119
125
  });
120
126
 
121
127
  async function saveRecord() {
@@ -25,6 +25,10 @@
25
25
  Save
26
26
  </button>
27
27
 
28
+ <ThreeDotsMenu
29
+ :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.edit?.threeDotsDropdownItems"
30
+ ></ThreeDotsMenu>
31
+
28
32
  </BreadcrumbsWithButtons>
29
33
 
30
34
  <component
@@ -69,11 +73,13 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
69
73
  import ResourceForm from '@/components/ResourceForm.vue';
70
74
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
71
75
  import { useCoreStore } from '@/stores/core';
72
- import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from '@/utils';
76
+ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
73
77
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
74
78
  import { computed, onMounted, ref } from 'vue';
75
79
  import { useRoute, useRouter } from 'vue-router';
76
80
  import { showErrorTost } from '@/composables/useFrontendApi';
81
+ import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
82
+
77
83
 
78
84
  const coreStore = useCoreStore();
79
85
 
@@ -107,12 +113,13 @@ const editableRecord = computed(() => {
107
113
  })
108
114
 
109
115
  onMounted(async () => {
110
-
111
116
  loading.value = true;
112
117
 
113
118
  await coreStore.fetchResourceFull({
114
119
  resourceId: route.params.resourceId
115
120
  });
121
+ initThreeDotsDropdown();
122
+
116
123
  await coreStore.fetchRecord({
117
124
  resourceId: route.params.resourceId,
118
125
  primaryKey: route.params.primaryKey,
@@ -73,32 +73,9 @@
73
73
  </span>
74
74
  </button>
75
75
 
76
- <template v-if="coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems">
77
- <button id="dropdownMenuIconButton"
78
- data-dropdown-toggle="dropdownDots"
79
- class="flex items-center py-2 px-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
80
- >
81
- <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 4 15">
82
- <path d="M3.5 1.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 6.041a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 5.959a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"/>
83
- </svg>
84
- </button>
85
-
86
- <!-- Dropdown menu -->
87
- <div id="dropdownDots" class="z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700 dark:divide-gray-600">
88
- <ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownMenuIconButton">
89
- <li v-for="item in coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems" :key="`dropdown-item-${item.label}`">
90
- <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
91
- <component :is="getCustomComponent(item)"
92
- :meta="item.meta"
93
- :resource="coreStore.resource"
94
- :adminUser="coreStore.adminUser"
95
- />
96
- </a>
97
- </li>
98
- </ul>
99
- </div>
100
- </template>
101
-
76
+ <ThreeDotsMenu
77
+ :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems"
78
+ ></ThreeDotsMenu>
102
79
  </BreadcrumbsWithButtons>
103
80
 
104
81
  <component
@@ -139,11 +116,12 @@ import ResourceListTable from '@/components/ResourceListTable.vue';
139
116
  import { useCoreStore } from '@/stores/core';
140
117
  import { useFiltersStore } from '@/stores/filters';
141
118
  import { callAdminForthApi, getIcon } from '@/utils';
142
- import { initFlowbite } from 'flowbite';
143
119
  import { computed, onMounted, ref, watch } from 'vue';
144
120
  import { useRoute } from 'vue-router';
145
121
  import { showErrorTost } from '@/composables/useFrontendApi'
146
- import { getCustomComponent } from '@/utils';
122
+ import { getCustomComponent, initThreeDotsDropdown } from '@/utils';
123
+ import { initFlowbite } from 'flowbite';
124
+ import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
147
125
 
148
126
 
149
127
  import {
@@ -180,9 +158,6 @@ const DEFAULT_PAGE_SIZE = 10;
180
158
  const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
181
159
 
182
160
 
183
-
184
-
185
-
186
161
  async function getList() {
187
162
  rows.value = null;
188
163
  const data = await callAdminForthApi({
@@ -258,7 +233,6 @@ async function init() {
258
233
 
259
234
  initFlowbite();
260
235
 
261
-
262
236
  // !!! clear filters should be in same tick with sort assignment so that watch can catch it
263
237
  filtersStore.clearFilters();
264
238
  if (coreStore.resource.options?.defaultSort) {
@@ -269,8 +243,7 @@ async function init() {
269
243
  } else {
270
244
  sort.value = [];
271
245
  }
272
- console.log('↘️init fired');
273
- // await getList();
246
+ // await getList(); - Not needed here, watch will trigger it
274
247
  columnsMinMax.value = await callAdminForthApi({
275
248
  path: '/get_min_max_for_columns',
276
249
  method: 'POST',
@@ -281,19 +254,21 @@ async function init() {
281
254
  }
282
255
 
283
256
  watch([page, sort, () => filtersStore.filters], async () => {
284
- console.log('↘️watch fired getList');
285
257
  await getList();
286
258
  }, { deep: true });
287
259
 
260
+ window.adminforth.list.refresh = async () => {
261
+ await getList();
262
+ }
263
+
288
264
  watch(() => filtersStore.filters, async (to, from) => {
289
265
  page.value = 1;
290
266
  checkboxes.value = [];
291
267
  }, {deep: true});
292
268
 
293
269
  onMounted(async () => {
294
- console.log('🧱onMounted fired');
295
- initFlowbite();
296
270
  await init();
271
+ initThreeDotsDropdown();
297
272
  });
298
273
 
299
274
 
@@ -174,7 +174,7 @@ async function login() {
174
174
  body: {
175
175
  username,
176
176
  password,
177
- rememberMe: rememberInput.value.checked,
177
+ rememberMe: rememberInput.value?.checked,
178
178
  }
179
179
  });
180
180
  inProgress.value = false;
@@ -22,6 +22,10 @@
22
22
  <IconTrashBinSolid class="w-4 h-4" />
23
23
  Delete
24
24
  </button>
25
+
26
+ <ThreeDotsMenu
27
+ :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.show?.threeDotsDropdownItems"
28
+ ></ThreeDotsMenu>
25
29
  </BreadcrumbsWithButtons>
26
30
 
27
31
  <component
@@ -119,31 +123,27 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
119
123
 
120
124
  import ValueRenderer from '@/components/ValueRenderer.vue';
121
125
  import { useCoreStore } from '@/stores/core';
122
- import { useModalStore } from '@/stores/modal';
123
- import { getCustomComponent, checkAcessByAllowedActions } from '@/utils';
126
+ import { getCustomComponent, checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
124
127
  import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
125
128
  import { onMounted, ref } from 'vue';
126
129
  import { useRoute,useRouter } from 'vue-router';
127
130
  import {callAdminForthApi} from '@/utils';
128
131
  import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
132
+ import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
129
133
 
130
134
 
131
- const item = ref(null);
132
135
  const route = useRoute();
133
136
  const router = useRouter();
134
137
  const loading = ref(true);
135
138
 
136
- console.log(route.params,'showWiev');
137
-
138
-
139
139
  const coreStore = useCoreStore();
140
- const modalStore = useModalStore();
141
140
 
142
141
  onMounted(async () => {
143
142
  loading.value = true;
144
143
  await coreStore.fetchResourceFull({
145
144
  resourceId: route.params.resourceId
146
145
  });
146
+ initThreeDotsDropdown();
147
147
  await coreStore.fetchRecord({
148
148
  resourceId: route.params.resourceId,
149
149
  primaryKey: route.params.primaryKey,
@@ -176,9 +176,8 @@ async function deleteRecord(row) {
176
176
 
177
177
  } catch (e) {
178
178
  console.error(e);
179
- };
180
- }
181
-
179
+ };
180
+ }
182
181
 
183
182
  }
184
183
 
@@ -19,6 +19,7 @@ export interface FrontendAPIInterface {
19
19
  * @returns A promise that resolves when the user confirms the dialog
20
20
  */
21
21
  confirm(params:ConfirmParams ): Promise<void>;
22
+
22
23
  /**
23
24
  * Show an alert
24
25
  *
@@ -33,37 +34,53 @@ export interface FrontendAPIInterface {
33
34
  * @param params - The parameters of the alert
34
35
  */
35
36
  alert(params:AlertParams): void;
36
- /**
37
- * Add a filter to the list of filters.
38
- * Works only when user located on the list page.
39
- * Can be used to set filter from charts or other components in pageInjections.
40
- *
41
- * Example:
42
- *
43
- * ```ts
44
- * window.adminforth.updateListFilter({field: 'name', operator: 'ilike', value: 'john'})
45
- * ```
46
- *
47
- * @param filter - The filter to add
48
- */
49
- setListFilter(filter: any): void;
50
- /**
51
- * Update a filter in the list of filters
52
- *
53
- * Example:
54
- *
55
- * ```ts
56
- * window.adminforth.updateListFilter({field: 'name', operator: 'ilike', value: 'john'})
57
- * ```
58
- *
59
- * @param filter - The filter to update
60
- */
61
- updateListFilter(filter: any): void;
62
- /**
63
- * Clear all filters from the list
64
- */
65
- clearListFilters(): void;
66
37
 
38
+
39
+ list: {
40
+
41
+ /**
42
+ * Refresh the list
43
+ */
44
+ refresh(): void;
45
+
46
+ /**
47
+ * Close the three dots dropdown
48
+ */
49
+ closeThreeDotsDropdown(): void;
50
+
51
+ /**
52
+ * Set a filter in the list
53
+ * Works only when user located on the list page.
54
+ * Can be used to set filter from charts or other components in pageInjections.
55
+ *
56
+ * Example:
57
+ *
58
+ * ```ts
59
+ * window.adminforth.list.setFilter({field: 'name', operator: 'ilike', value: 'john'})
60
+ * ```
61
+ *
62
+ * @param filter - The filter to set
63
+ */
64
+ setFilter(filter: any): void;
65
+
66
+ /**
67
+ * Update a filter in the list
68
+ *
69
+ * Example:
70
+ *
71
+ * ```ts
72
+ * window.adminforth.list.updateFilter({field: 'name', operator: 'ilike', value: 'john'})
73
+ * ```
74
+ *
75
+ * @param filter - The filter to update
76
+ */
77
+ updateFilter(filter: any): void;
78
+
79
+ /**
80
+ * Clear all filters from the list
81
+ */
82
+ clearFilters(): void;
83
+ }
67
84
  }
68
85
 
69
86
  export type ConfirmParams = {