adminforth 1.1.19 → 1.1.20

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.
Files changed (35) hide show
  1. package/dist/index.js +33 -1
  2. package/dist/modules/codeInjector.js +1 -1
  3. package/dist/spa/index.html +2 -2
  4. package/dist/spa/package-lock.json +189 -1
  5. package/dist/spa/package.json +6 -1
  6. package/dist/spa/spa/src/components/ResourceListTable.vue +4 -2
  7. package/dist/spa/src/App.vue +76 -57
  8. package/dist/spa/src/components/AcceptModal.vue +1 -1
  9. package/dist/spa/src/components/CustomDatePicker.vue +3 -3
  10. package/dist/spa/src/components/CustomDateRangePicker.vue +3 -3
  11. package/dist/spa/src/components/CustomRangePicker.vue +148 -0
  12. package/dist/spa/src/components/Dropdown.vue +13 -4
  13. package/dist/spa/src/components/Filters.vue +55 -22
  14. package/dist/spa/src/components/MenuLink.vue +2 -2
  15. package/dist/spa/src/components/ResourceForm.vue +157 -99
  16. package/dist/spa/src/components/ValueRenderer.vue +11 -1
  17. package/dist/spa/src/router/index.ts +3 -2
  18. package/dist/spa/src/stores/core.ts +22 -33
  19. package/dist/spa/src/utils.ts +5 -3
  20. package/dist/spa/src/views/CreateView.vue +15 -7
  21. package/dist/spa/src/views/EditView.vue +36 -8
  22. package/dist/spa/src/views/ListView.vue +35 -23
  23. package/dist/spa/src/views/LoginView.vue +1 -1
  24. package/dist/spa/src/views/ResourceParent.vue +1 -1
  25. package/dist/spa/src/views/ShowView.vue +20 -9
  26. package/dist/spa/tailwind.config.js +5 -2
  27. package/index.ts +37 -1
  28. package/modules/codeInjector.ts +1 -1
  29. package/package.json +1 -1
  30. package/plugins/AccessControl/types.ts +2 -2
  31. package/spa/src/components/ResourceListTable.vue +4 -2
  32. package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +0 -248
  33. package/dist/spa/spa/src/components/ShowTableItem.vue +0 -14
  34. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  35. package/dist/spa/src/views/HomeView.vue +0 -8
@@ -1,6 +1,16 @@
1
1
  <template>
2
2
  <div>
3
- <span v-if="column.type === 'boolean'">
3
+ <span v-if="column.foreignResource">
4
+ <RouterLink v-if="row[column.name]" class="font-medium text-blue-600 dark:text-blue-500 hover:brightness-110"
5
+ :to="{ name: 'resource-show', params: { resourceId: column.foreignResource.resourceId, primaryKey: row[column.name].pk } }">
6
+ {{ row[column.name].label }}
7
+ </RouterLink>
8
+ <div v-else>
9
+ <span class="text-gray-400">-</span>
10
+ </div>
11
+ </span>
12
+
13
+ <span v-else-if="column.type === 'boolean'">
4
14
  <span v-if="row[column.name]" class="bg-green-100 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">Yes</span>
5
15
  <span v-else class="bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">No</span>
6
16
  </span>
@@ -1,5 +1,4 @@
1
1
  import { createRouter, createWebHistory } from 'vue-router'
2
- import HomeView from '../views/HomeView.vue'
3
2
  import ResourceParent from '@/views/ResourceParent.vue'
4
3
  import ListView from '@/views/ListView.vue'
5
4
  import ShowView from '@/views/ShowView.vue'
@@ -12,7 +11,9 @@ const router = createRouter({
12
11
  {
13
12
  path: '/',
14
13
  name: 'home',
15
- component: HomeView
14
+ //redirect to login
15
+ /* IMPORTANT:ADMINFORTH REDIRECT */
16
+
16
17
  },
17
18
  {
18
19
  path: '/login',
@@ -1,22 +1,6 @@
1
1
  import { ref, computed } from 'vue'
2
2
  import { defineStore } from 'pinia'
3
3
  import { callAdminForthApi } from '@/utils';
4
- import router from '@/router';
5
-
6
- async function findHomepage(menu) {
7
- for (const item of menu) {
8
- if (item.homepage) {
9
- return item;
10
- }
11
- if (item.children) {
12
- const res = findHomepage(item.children);
13
- if (res) {
14
- return res;
15
- }
16
- }
17
- }
18
- return null;
19
- }
20
4
 
21
5
  export const useCoreStore = defineStore('core', () => {
22
6
  const resourceById = ref([]);
@@ -40,35 +24,40 @@ export const useCoreStore = defineStore('core', () => {
40
24
  }, {});
41
25
  config.value = resp.config;
42
26
  user.value = resp.user;
27
+ console.log('🌍 AdminForth v', resp.version);
43
28
 
44
29
  // find homepage:true in menu recuresively
45
- if (import.meta.env.DEV){
46
- return
47
- } else {
48
- const homepage = await findHomepage(menu.value);
49
- if (homepage) {
50
- if (homepage.resourceId) {
51
- // redirect to homepage
52
- router.push({ name: 'resource-list', params: { resourceId: homepage.resourceId } });
53
- } else {
54
- // redirect to path
55
- router.push(homepage.path);
56
- }
57
- }
58
- }
30
+
59
31
  }
60
32
 
61
33
  async function fetchRecord({ resourceId, primaryKey }) {
62
34
  record.value = null;
35
+
36
+ if (!resourceColumns.value) {
37
+ throw new Error('Columns not fetched yet');
38
+ }
63
39
 
64
- record.value = await callAdminForthApi({
65
- path: '/get_record',
40
+ const respData = await callAdminForthApi({
41
+ path: '/get_resource_data',
66
42
  method: 'POST',
67
43
  body: {
44
+ source: 'show',
68
45
  resourceId: resourceId,
69
- primaryKey: primaryKey,
46
+ filters: [
47
+ {
48
+ field: resourceColumns.value.find((col) => col.primaryKey).name,
49
+ operator: 'eq',
50
+ value: primaryKey
51
+ }
52
+ ],
53
+ sort: [],
54
+ limit: 1,
55
+ offset: 0
70
56
  }
71
57
  });
58
+
59
+ console.log('📦 record', respData);
60
+ record.value = respData.data[0];
72
61
  }
73
62
 
74
63
  async function fetchColumns({ resourceId }) {
@@ -29,9 +29,11 @@ export async function callAdminForthApi({ path, method, body=undefined }) {
29
29
  }
30
30
  }
31
31
 
32
-
33
-
34
-
32
+ export function getCustomComponent(filePath: string) {
33
+ const name = filePath.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
34
+ console.log('resolving name', name);
35
+ return resolveComponent(name);
36
+ }
35
37
 
36
38
  export function getIcon(icon: string) {
37
39
  // icon format is "feather:icon-name". We need to get IconName in pascal case
@@ -31,6 +31,7 @@
31
31
  @update:record="onUpdateRecord"
32
32
  @update:isValid="isValid = $event"
33
33
  :validating="validating"
34
+ :customComponentsPerColumn="createComponentsPerColumn"
34
35
  >
35
36
  </ResourceForm>
36
37
 
@@ -40,14 +41,14 @@
40
41
 
41
42
  <script setup>
42
43
 
43
- import { ref, computed, onMounted } from 'vue';
44
- import { useRoute, useRouter } from 'vue-router';
45
- import { useCoreStore } from '@/stores/core';
46
- import { callAdminForthApi } from '@/utils';
47
- import ResourceForm from '@/components/ResourceForm.vue';
48
44
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
49
- import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
45
+ import ResourceForm from '@/components/ResourceForm.vue';
50
46
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
47
+ import { useCoreStore } from '@/stores/core';
48
+ import { callAdminForthApi, getCustomComponent } from '@/utils';
49
+ import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
50
+ import { onMounted, ref } from 'vue';
51
+ import { useRoute, useRouter } from 'vue-router';
51
52
 
52
53
  const isValid = ref(false);
53
54
  const validating = ref(false);
@@ -59,6 +60,7 @@ const route = useRoute();
59
60
  const router = useRouter();
60
61
 
61
62
  const record = ref({});
63
+ let createComponentsPerColumn = {};
62
64
 
63
65
  const coreStore = useCoreStore();
64
66
 
@@ -69,9 +71,15 @@ async function onUpdateRecord(newRecord) {
69
71
 
70
72
  onMounted(async () => {
71
73
  loading.value = true;
72
- coreStore.fetchColumns({
74
+ await coreStore.fetchColumns({
73
75
  resourceId: route.params.resourceId
74
76
  });
77
+ createComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
78
+ if (column.component?.create) {
79
+ acc[column.name] = getCustomComponent(column.component.create);
80
+ }
81
+ return acc;
82
+ }, {});
75
83
  loading.value = false;
76
84
  });
77
85
 
@@ -23,11 +23,12 @@
23
23
 
24
24
  <ResourceForm
25
25
  v-else
26
- :record="coreStore.record"
26
+ :record="editableRecord"
27
27
  :resourceColumns="coreStore.resourceColumns"
28
28
  @update:record="onUpdateRecord"
29
29
  @update:isValid="isValid = $event"
30
30
  :validating="validating"
31
+ :customComponentsPerColumn="editComponentsPerColumn"
31
32
  >
32
33
  </ResourceForm>
33
34
  </div>
@@ -36,14 +37,14 @@
36
37
 
37
38
  <script setup>
38
39
 
39
- import { ref, computed, onMounted } from 'vue';
40
- import { useRoute, useRouter } from 'vue-router';
41
- import { useCoreStore } from '@/stores/core';
42
- import ResourceForm from '@/components/ResourceForm.vue';
43
40
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
44
- import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
41
+ import ResourceForm from '@/components/ResourceForm.vue';
45
42
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
46
- import { callAdminForthApi } from '@/utils';
43
+ import { useCoreStore } from '@/stores/core';
44
+ import { callAdminForthApi, getCustomComponent } from '@/utils';
45
+ import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
46
+ import { computed, onMounted, ref } from 'vue';
47
+ import { useRoute, useRouter } from 'vue-router';
47
48
 
48
49
  const coreStore = useCoreStore();
49
50
 
@@ -58,11 +59,24 @@ const loading = ref(false);
58
59
  const saving = ref(false);
59
60
 
60
61
  const record = ref({});
62
+ let editComponentsPerColumn = {};
61
63
 
62
64
  async function onUpdateRecord(newRecord) {
63
65
  record.value = newRecord;
64
66
  }
65
67
 
68
+ const editableRecord = computed(() => {
69
+ const newRecord = { ...coreStore.record };
70
+ if (!coreStore.resourceColumns) {
71
+ return {};
72
+ }
73
+ coreStore.resourceColumns.forEach(column => {
74
+ if (column.foreignResource) {
75
+ newRecord[column.name] = newRecord[column.name]?.pk
76
+ }
77
+ });
78
+ return newRecord;
79
+ })
66
80
 
67
81
  onMounted(async () => {
68
82
  loading.value = true;
@@ -75,6 +89,12 @@ onMounted(async () => {
75
89
  primaryKey: route.params.primaryKey,
76
90
  });
77
91
 
92
+ editComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
93
+ if (column.component?.edit) {
94
+ acc[column.name] = getCustomComponent(column.component.edit);
95
+ }
96
+ return acc;
97
+ }, {});
78
98
  loading.value = false;
79
99
  });
80
100
 
@@ -87,13 +107,21 @@ async function saveRecord() {
87
107
  }
88
108
 
89
109
  saving.value = true;
110
+
111
+ const updates = {};
112
+ for (const key in record.value) {
113
+ if (record.value[key] !== coreStore.record[key]) {
114
+ updates[key] = record.value[key];
115
+ }
116
+ }
117
+
90
118
  await callAdminForthApi({
91
119
  method: 'POST',
92
120
  path: `/update_record`,
93
121
  body: {
94
122
  resourceId: route.params.resourceId,
95
123
  recordId: route.params.primaryKey,
96
- record: record.value,
124
+ record: updates,
97
125
  },
98
126
  });
99
127
  saving.value = false;
@@ -65,7 +65,7 @@
65
65
  </BreadcrumbsWithButtons>
66
66
 
67
67
  <!-- table -->
68
- <div class="relative overflow-x-auto shadow-md sm:rounded-lg dark:shadow-2xl">
68
+ <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black dark:s">
69
69
 
70
70
 
71
71
  <!-- skelet loader -->
@@ -90,7 +90,7 @@
90
90
  </div>
91
91
 
92
92
  <table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
93
- <thead class="text-xs text-gray-700 bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
93
+ <thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
94
94
  <tr>
95
95
  <th scope="col" class="p-4">
96
96
  <div v-if="rows && rows.length" class="flex items-center">
@@ -127,7 +127,7 @@
127
127
  </tr>
128
128
  </thead>
129
129
  <tbody>
130
- <tr v-if="!rows" class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
130
+ <tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
131
131
  <td :colspan="coreStore.resourceColumns.length + 2">
132
132
 
133
133
  <div role="status"
@@ -171,7 +171,7 @@
171
171
  </div>
172
172
  </td>
173
173
  </tr>
174
- <tr v-else-if="rows.length === 0" class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
174
+ <tr v-else-if="rows.length === 0" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
175
175
  <td :colspan="coreStore.resourceColumns.length + 2">
176
176
 
177
177
  <div id="toast-simple"
@@ -185,7 +185,7 @@
185
185
  </tr>
186
186
 
187
187
  <tr v-else v-for="(row, rowI) in rows" :key="row.id"
188
- class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
188
+ class="bg-list-view-table-bg border-b dark:bg-gray-800 border-list-view-border-color dark:border-gray-700 hover:bg-list-view-table-row-hover dark:hover:bg-gray-600">
189
189
  <td class="w-4 p-4">
190
190
  <div class="flex items center">
191
191
  <input
@@ -198,7 +198,12 @@
198
198
  </div>
199
199
  </td>
200
200
  <td v-for="c in columnsListed" class="px-6 py-4">
201
- <ValueRenderer :column="c" :row="row"/>
201
+ <!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
202
+ <component
203
+ :is="listComponentsPerColumn[c.name] || ValueRenderer"
204
+ :column="c"
205
+ :row="row"
206
+ />
202
207
  </td>
203
208
  <td class="flex items-center px-6 py-4">
204
209
 
@@ -310,29 +315,28 @@
310
315
  </template>
311
316
 
312
317
  <script setup>
313
- import {ref, onMounted, watch, computed} from 'vue';
314
- import {callAdminForthApi, getIcon} from '@/utils';
315
- import {useRoute} from 'vue-router';
316
- import {useCoreStore} from '@/stores/core';
317
- import {useModalStore} from '@/stores/modal';
318
318
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
319
- import {initFlowbite} from 'flowbite'
319
+ import { useCoreStore } from '@/stores/core';
320
+ import { useModalStore } from '@/stores/modal';
321
+ import { callAdminForthApi, getIcon } from '@/utils';
322
+ import { initFlowbite } from 'flowbite';
323
+ import { computed, onMounted, ref, watch } from 'vue';
324
+ import { useRoute } from 'vue-router';
320
325
 
321
326
  import ValueRenderer from '@/components/ValueRenderer.vue';
327
+ import { getCustomComponent } from '@/utils';
322
328
 
323
329
  import {
324
- IconChevronDoubleLeftOutline,
325
- IconChevronDoubleRightOutline,
326
- IconInboxFullSolid,
327
- IconInboxOutline,
328
- IconPlusOutline
330
+ IconInboxOutline,
331
+ IconPlusOutline
329
332
  } from '@iconify-prerendered/vue-flowbite';
330
333
 
331
334
  import {
332
- IconEyeSolid,
333
- IconTrashBinSolid,
334
- IconPenSolid,
335
- IconFilterOutline, IconBanOutline
335
+ IconBanOutline,
336
+ IconEyeSolid,
337
+ IconFilterOutline,
338
+ IconPenSolid,
339
+ IconTrashBinSolid
336
340
  } from '@iconify-prerendered/vue-flowbite';
337
341
 
338
342
  import Filters from '@/components/Filters.vue';
@@ -357,7 +361,6 @@ const totalRows = ref(0);
357
361
  const allCheckedActions = ref([]);
358
362
  const allowedActions = ref({});
359
363
 
360
-
361
364
  const DEFAULT_PAGE_SIZE = 10;
362
365
 
363
366
  const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
@@ -376,6 +379,7 @@ async function selectAll(value) {
376
379
 
377
380
  const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.pageSize || DEFAULT_PAGE_SIZE);
378
381
  const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
382
+ let listComponentsPerColumn = {};
379
383
  const allFromThisPageChecked = computed(() => {
380
384
  if (!rows.value) return false;
381
385
  return rows.value.every((r) => checkboxes.value.includes(r.id));
@@ -419,6 +423,7 @@ async function getList() {
419
423
  path: '/get_resource_data',
420
424
  method: 'POST',
421
425
  body: {
426
+ source: 'list',
422
427
  resourceId: route.params.resourceId,
423
428
  limit: pageSize.value,
424
429
  offset: (page.value - 1) * pageSize.value,
@@ -426,7 +431,14 @@ async function getList() {
426
431
  sort: sort.value,
427
432
  }
428
433
  });
429
- fetchStatus.value.pending = false;
434
+ listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
435
+ if (column.component?.list) {
436
+ acc[column.name] = getCustomComponent(column.component.list);
437
+ }
438
+ return acc;
439
+ }, {});
440
+
441
+ fetchStatus.value.pending = false;
430
442
  rows.value = data.data?.map(row => {
431
443
  row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
432
444
  return row;
@@ -11,7 +11,7 @@
11
11
  <div id="authentication-modal" tabindex="-1" class=" overflow-y-auto overflow-x-hidden z-50 min-w-[400px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
12
12
  <div class="relative p-4 w-full max-w-md max-h-full">
13
13
  <!-- Modal content -->
14
- <div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
14
+ <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
15
15
  <!-- Modal header -->
16
16
  <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
17
17
  <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`">
3
3
  <RouterView/>
4
4
  </div>
5
5
  </template>
@@ -27,10 +27,10 @@
27
27
  </div>
28
28
  <div
29
29
  v-else
30
- class="relative overflow-x-auto shadow-md sm:rounded-lg"
30
+ class="relative overflow-x-auto shadow-resourse-form-shadow "
31
31
  >
32
32
  <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
33
- <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
33
+ <thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
34
34
  <tr>
35
35
  <th scope="col" class="px-6 py-3">
36
36
  Field
@@ -42,13 +42,17 @@
42
42
  </thead>
43
43
  <tbody>
44
44
  <tr v-for="column in coreStore.resourceColumns?.filter(c => c.showIn.includes('show'))" :key="column.name"
45
- class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
45
+ class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
46
46
  >
47
47
  <td class="px-6 py-4 whitespace-nowrap">
48
48
  {{ column.label }}
49
49
  </td>
50
50
  <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
51
- <ValueRenderer :column="column" :row="coreStore.record" />
51
+ <component
52
+ :is="showComponentsPerColumn[column.name] || ValueRenderer"
53
+ :column="column"
54
+ :row="coreStore.record"
55
+ />
52
56
  </td>
53
57
  </tr>
54
58
 
@@ -63,18 +67,19 @@
63
67
 
64
68
  <script setup>
65
69
 
66
- import { ref, computed, onMounted } from 'vue';
67
- import { useRoute } from 'vue-router';
68
- import { callAdminForthApi } from '@/utils';
69
70
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
70
- import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
71
- import { useCoreStore } from '@/stores/core';
72
71
  import ValueRenderer from '@/components/ValueRenderer.vue';
72
+ import { useCoreStore } from '@/stores/core';
73
+ import { getCustomComponent } from '@/utils';
74
+ import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
75
+ import { onMounted, ref } from 'vue';
76
+ import { useRoute } from 'vue-router';
73
77
 
74
78
 
75
79
  const item = ref(null);
76
80
  const route = useRoute();
77
81
  const loading = ref(false);
82
+ let showComponentsPerColumn = {};
78
83
 
79
84
  const coreStore = useCoreStore();
80
85
 
@@ -87,6 +92,12 @@ onMounted(async () => {
87
92
  resourceId: route.params.resourceId,
88
93
  primaryKey: route.params.primaryKey,
89
94
  });
95
+ showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
96
+ if (column.component?.show) {
97
+ acc[column.name] = getCustomComponent(column.component.show);
98
+ }
99
+ return acc;
100
+ }, {});
90
101
  loading.value = false;
91
102
  });
92
103
 
@@ -1,9 +1,12 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  export default {
3
- content: ["./src/**/*.{vue, js}", "./node_modules/flowbite/**/*.js"],
3
+ content: ["./src/**/*.{vue, js}","./src/*.{vue, js}", "./index.html", "./node_modules/flowbite/**/*.js"],
4
4
  theme: {
5
- extend: {},
5
+ extend: {
6
+ /* IMPORTANT:ADMINFORTH TAILWIND STYLES */
7
+ }
6
8
  },
9
+
7
10
  darkMode: 'class',
8
11
  plugins: [
9
12
  require('flowbite/plugin'),
package/index.ts CHANGED
@@ -648,13 +648,49 @@ class AdminForth implements AdminForthClass {
648
648
  [this.config.auth.usernameField]: username,
649
649
  [this.config.auth.userFullNameField]: userFullName
650
650
  };
651
+ const checkIsMenuItemVisible = (menuItem) => {
652
+ if (typeof menuItem.visible === 'function') {
653
+ const toReturn = menuItem.visible( adminUser );
654
+ if (typeof toReturn !== 'boolean') {
655
+ throw new Error(`'visible' function of ${menuItem.label || menuItem.type } must return boolean value`);
656
+ }
657
+ return toReturn;
658
+ }}
659
+ let newMenu = []
660
+ for (let menuItem of this.config.menu) {
661
+ let newMenuItem = {...menuItem,}
662
+ if (menuItem.visible){
663
+ if (!checkIsMenuItemVisible(menuItem)){
664
+ continue
665
+ }
666
+ }
667
+ if (menuItem.children){
668
+ let newChildren = []
669
+ for (let child of menuItem.children){
670
+ let newChild = {...child,}
671
+ if (child.visible){
672
+ if (!checkIsMenuItemVisible(child)){
673
+ continue
674
+ }
675
+ }
676
+ newChildren.push(newChild)
677
+ }
678
+ newMenuItem = {...newMenuItem, children: newChildren}
679
+ }
680
+ newMenu.push(newMenuItem)
681
+ }
682
+
683
+
684
+
685
+
686
+
651
687
  return {
652
688
  user: userData,
653
689
  resources: this.config.resources.map((res) => ({
654
690
  resourceId: res.resourceId,
655
691
  label: res.label,
656
692
  })),
657
- menu: this.config.menu,
693
+ menu: newMenu,
658
694
  config: {
659
695
  brandName: this.config.customization.brandName,
660
696
  brandLogo: this.config.customization.brandLogo,
@@ -149,7 +149,7 @@ class CodeInjector implements CodeInjectorType {
149
149
  routes += `{
150
150
  path: '${item.path}',
151
151
  name: '${item.path}',
152
- component: () => import('${item.component}'),}
152
+ component: () => import('${item.component}'),
153
153
  meta: { title: '${item?.meta?.title}'
154
154
  },\n`
155
155
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.1.19",
3
+ "version": "1.1.20",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -7,9 +7,9 @@ export type PluginOptions = {
7
7
  * Called to check if user has access to a page
8
8
  * Should return `true` if user has access. If user has no access, return `false` or a string with an error message
9
9
  * @param user - The user object
10
- * @param action - The {@link AllowedActionsEnum} action
10
+ * @param page - The page to check access for
11
11
  * @param meta - The meta object containing query for list/show or record for save/edit
12
12
  * @returns {boolean | string} -
13
13
  */
14
- hasAccess: (user: AdminUser, action: AllowedActionsEnum, meta: any) => Promise<boolean | string>;
14
+ hasAccess: (user: AdminUser, page: AllowedActionsEnum, meta: any) => Promise<boolean | string>;
15
15
  }
@@ -358,9 +358,11 @@ async function selectAll(value) {
358
358
  const totalPages = computed(() => Math.ceil(props.totalRows / props.pageSize));
359
359
 
360
360
 
361
+
362
+
361
363
  const allFromThisPageChecked = computed(() => {
362
- if (!props.rows.value) return false;
363
- return props.rows.value.every((r) => checkboxes.value.includes(r.id));
364
+ if (!props.rows) return false;
365
+ return props.rows.every((r) => props.checkboxes.includes(r.id));
364
366
  });
365
367
  const ascArr = computed(() => sort.value.filter((s) => s.direction === 'asc').map((s) => s.field));
366
368
  const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').map((s) => s.field));