adminforth 1.0.32 → 1.0.34

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/dataConnectors/sqlite.ts +1 -0
  2. package/dist/dataConnectors/sqlite.js +1 -0
  3. package/dist/index.js +87 -74
  4. package/dist/modules/utils.js +11 -0
  5. package/dist/spa/spa/src/App.vue +0 -3
  6. package/dist/spa/spa/src/components/CustomDatePicker.vue +3 -3
  7. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +3 -3
  8. package/dist/spa/spa/src/components/CustomRangePicker.vue +32 -19
  9. package/dist/spa/spa/src/components/Dropdown.vue +6 -1
  10. package/dist/spa/spa/src/components/ResourceForm.vue +125 -102
  11. package/dist/spa/spa/src/components/ValueRenderer.vue +11 -1
  12. package/dist/spa/spa/src/stores/core.ts +21 -19
  13. package/dist/spa/spa/src/views/CreateView.vue +16 -6
  14. package/dist/spa/spa/src/views/EditView.vue +15 -5
  15. package/dist/spa/spa/src/views/ListView.vue +31 -17
  16. package/dist/spa/spa/src/views/ResourceParent.vue +1 -1
  17. package/dist/spa/spa/src/views/ShowView.vue +21 -6
  18. package/dist/types/AdminForthConfig.js +1 -0
  19. package/index.ts +73 -149
  20. package/modules/utils.ts +16 -0
  21. package/package.json +1 -1
  22. package/spa/src/App.vue +0 -3
  23. package/spa/src/components/CustomDatePicker.vue +3 -3
  24. package/spa/src/components/CustomDateRangePicker.vue +3 -3
  25. package/spa/src/components/CustomRangePicker.vue +32 -19
  26. package/spa/src/components/Dropdown.vue +6 -1
  27. package/spa/src/components/ResourceForm.vue +125 -102
  28. package/spa/src/components/ValueRenderer.vue +11 -1
  29. package/spa/src/stores/core.ts +21 -19
  30. package/spa/src/views/CreateView.vue +16 -6
  31. package/spa/src/views/EditView.vue +15 -5
  32. package/spa/src/views/ListView.vue +31 -17
  33. package/spa/src/views/ResourceParent.vue +1 -1
  34. package/spa/src/views/ShowView.vue +21 -6
  35. package/types/AdminForthConfig.ts +159 -0
@@ -20,102 +20,113 @@
20
20
  <tr v-for="column, i in editableColumns" :key="column.name"
21
21
  class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
22
22
  >
23
- <td class="px-6 py-4 whitespace-nowrap">
24
- {{ column.label }}
25
- <span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
26
- <IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
27
- :class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
23
+ <!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
24
+ <template v-if="customComponentsPerColumn[column.name]">
25
+ <component
26
+ :is="customComponentsPerColumn[column.name]"
27
+ :column="column"
28
+ :value="currentValues[column.name]"
29
+ @update:value="setCurrentValue(column.name, $event)"
28
30
  />
29
- </span>
30
- <div :id="`tooltip-show-${i}`"
31
- role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
32
- Required field
33
- <div class="tooltip-arrow" data-popper-arrow></div>
34
- </div>
35
- </td>
36
- <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
37
- <Dropdown
38
- single
39
- v-if="column.foreignResource"
40
- :options="columnOptions[column.name] || []"
41
- :modelValue="currentValues[column.name]"
42
- @update:modelValue="setCurrentValue(column.name, $event)"
43
- />
44
- <Dropdown
45
- single
46
- v-else-if="column.enum"
47
- :options="column.enum"
48
- :modelValue="currentValues[column.name]"
49
- @update:modelValue="setCurrentValue(column.name, $event)"
50
- />
51
- <Dropdown
52
- single
53
- v-else-if="column.type === 'boolean'"
54
- :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
55
- :modelValue="currentValues[column.name]"
56
- @update:modelValue="setCurrentValue(column.name, $event)"
57
- />
58
- <input
59
- v-else-if="['integer'].includes(column.type)"
60
- type="number"
61
- step="1"
62
- class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-40 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
63
- placeholder="0"
64
- :value="currentValues[column.name]"
65
- @input="setCurrentValue(column.name, $event.target.value)"
66
- >
67
- <CustomDatePicker
68
- v-else-if="['datetime'].includes(column.type)"
69
- :column="column"
70
- :valueStart="currentValues[column.name]"
71
- @update:valueStart="setCurrentValue(column.name, $event)"
72
- />
73
- <input
74
- v-else-if="['decimal', 'float'].includes(column.type)"
75
- type="number"
76
- step="0.1"
77
- class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-40 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
78
- placeholder="0.0"
79
- :value="currentValues[column.name]"
80
- @input="setCurrentValue(column.name, $event.target.value)"
81
- />
82
- <textarea
83
- v-else-if="['text'].includes(column.type)"
84
- class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
85
- placeholder="Text"
86
- :value="currentValues[column.name]"
87
- @input="setCurrentValue(column.name, $event.target.value)"
88
- >
89
- </textarea>
90
- <input
91
- v-else
92
- :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
93
- class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
94
- placeholder="Text"
95
- :value="currentValues[column.name]"
96
- @input="setCurrentValue(column.name, $event.target.value)"
97
- autocomplete="false"
98
- data-lpignore="true"
99
- readonly
100
- onfocus="this.removeAttribute('readonly');"
101
- >
102
-
103
- <button
104
- v-if="column.masked"
105
- type="button"
106
- @click="unmasked[column.name] = !unmasked[column.name]"
107
- class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
108
- >
109
- <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
110
- <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
111
- </button>
112
-
113
-
114
-
115
- <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
116
-
117
- <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
118
- </td>
31
+ </template>
32
+ <template v-else>
33
+ <td class="px-6 py-4 whitespace-nowrap">
34
+ {{ column.label }}
35
+ <span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
36
+ <IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
37
+ :class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
38
+ />
39
+ </span>
40
+ <div :id="`tooltip-show-${i}`"
41
+ role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
42
+ Required field
43
+ <div class="tooltip-arrow" data-popper-arrow></div>
44
+ </div>
45
+ </td>
46
+ <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
47
+ <Dropdown
48
+ single
49
+ v-if="column.foreignResource"
50
+ :options="columnOptions[column.name] || []"
51
+ :modelValue="currentValues[column.name]"
52
+ @update:modelValue="setCurrentValue(column.name, $event)"
53
+ />
54
+ <Dropdown
55
+ single
56
+ v-else-if="column.enum"
57
+ :options="column.enum"
58
+ :modelValue="currentValues[column.name]"
59
+ @update:modelValue="setCurrentValue(column.name, $event)"
60
+ />
61
+ <Dropdown
62
+ single
63
+ v-else-if="column.type === 'boolean'"
64
+ :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
65
+ :modelValue="currentValues[column.name]"
66
+ @update:modelValue="setCurrentValue(column.name, $event)"
67
+ />
68
+ <input
69
+ v-else-if="['integer'].includes(column.type)"
70
+ type="number"
71
+ step="1"
72
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-40 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
73
+ placeholder="0"
74
+ :value="currentValues[column.name]"
75
+ @input="setCurrentValue(column.name, $event.target.value)"
76
+ >
77
+ <CustomDatePicker
78
+ v-else-if="['datetime'].includes(column.type)"
79
+ :column="column"
80
+ :valueStart="currentValues[column.name]"
81
+ @update:valueStart="setCurrentValue(column.name, $event)"
82
+ />
83
+ <input
84
+ v-else-if="['decimal', 'float'].includes(column.type)"
85
+ type="number"
86
+ step="0.1"
87
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-40 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
88
+ placeholder="0.0"
89
+ :value="currentValues[column.name]"
90
+ @input="setCurrentValue(column.name, $event.target.value)"
91
+ />
92
+ <textarea
93
+ v-else-if="['text'].includes(column.type)"
94
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
95
+ placeholder="Text"
96
+ :value="currentValues[column.name]"
97
+ @input="setCurrentValue(column.name, $event.target.value)"
98
+ >
99
+ </textarea>
100
+ <input
101
+ v-else
102
+ :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
103
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
104
+ placeholder="Text"
105
+ :value="currentValues[column.name]"
106
+ @input="setCurrentValue(column.name, $event.target.value)"
107
+ autocomplete="false"
108
+ data-lpignore="true"
109
+ readonly
110
+ onfocus="this.removeAttribute('readonly');"
111
+ >
112
+
113
+ <button
114
+ v-if="column.masked"
115
+ type="button"
116
+ @click="unmasked[column.name] = !unmasked[column.name]"
117
+ class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
118
+ >
119
+ <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
120
+ <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
121
+ </button>
122
+
123
+
124
+
125
+ <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
126
+
127
+ <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
128
+ </td>
129
+ </template>
119
130
  </tr>
120
131
 
121
132
  </tbody>
@@ -128,16 +139,15 @@
128
139
 
129
140
  <script setup>
130
141
 
131
- import { ref, computed, onMounted, watch } from 'vue';
132
- import { useCoreStore } from '@/stores/core';
133
- import Dropdown from '@/components/Dropdown.vue';
134
- import { IconExclamationCircleSolid } from '@iconify-prerendered/vue-flowbite';
135
- import { initFlowbite } from 'flowbite'
136
- import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
137
142
  import CustomDatePicker from "@/components/CustomDatePicker.vue";
143
+ import Dropdown from '@/components/Dropdown.vue';
144
+ import { useCoreStore } from '@/stores/core';
138
145
  import { callAdminForthApi } from '@/utils';
139
- import { useRouter } from 'vue-router';
146
+ import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
140
147
  import { computedAsync } from '@vueuse/core';
148
+ import { initFlowbite } from 'flowbite';
149
+ import { computed, onMounted, ref, watch } from 'vue';
150
+ import { useRouter } from 'vue-router';
141
151
 
142
152
  const router = useRouter();
143
153
 
@@ -146,6 +156,7 @@ const props = defineProps({
146
156
  resourceColumns: Object,
147
157
  record: Object,
148
158
  validating: Boolean,
159
+ customComponentsPerColumn: Object,
149
160
  });
150
161
 
151
162
 
@@ -179,6 +190,18 @@ const columnError = (column) => {
179
190
  return `This field must be less than ${column.maxValue}`;
180
191
  }
181
192
  }
193
+ if ( column.validation && column.validation.length ){
194
+ const validationArray = column.validation;
195
+ for (let i = 0; i < validationArray.length; i++) {
196
+ if (validationArray[i].regExp) {
197
+ const regExp = new RegExp(validationArray[i].regExp);
198
+ if (!regExp.test(currentValues.value[column.name])) {
199
+ return validationArray[i].message;
200
+ }
201
+ }
202
+ }
203
+
204
+ }
182
205
  return null;
183
206
  });
184
207
  return val.value;
@@ -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,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,6 +24,7 @@ export const useCoreStore = defineStore('core', () => {
40
24
  }, {});
41
25
  config.value = resp.config;
42
26
  user.value = resp.user;
27
+ console.log('🌍 AdminForth version', resp.version);
43
28
 
44
29
  // find homepage:true in menu recuresively
45
30
 
@@ -47,15 +32,32 @@ export const useCoreStore = defineStore('core', () => {
47
32
 
48
33
  async function fetchRecord({ resourceId, primaryKey }) {
49
34
  record.value = null;
35
+
36
+ if (!resourceColumns.value) {
37
+ throw new Error('Columns not fetched yet');
38
+ }
50
39
 
51
- record.value = await callAdminForthApi({
52
- path: '/get_record',
40
+ const respData = await callAdminForthApi({
41
+ path: '/get_resource_data',
53
42
  method: 'POST',
54
43
  body: {
44
+ source: 'show',
55
45
  resourceId: resourceId,
56
- 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
57
56
  }
58
57
  });
58
+
59
+ console.log('📦 record', respData);
60
+ record.value = respData.data[0];
59
61
  }
60
62
 
61
63
  async function fetchColumns({ resourceId }) {
@@ -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';
44
+ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
45
+ import ResourceForm from '@/components/ResourceForm.vue';
46
+ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
45
47
  import { useCoreStore } from '@/stores/core';
46
48
  import { callAdminForthApi } from '@/utils';
47
- import ResourceForm from '@/components/ResourceForm.vue';
48
- import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
49
49
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
50
- import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
50
+ import { defineAsyncComponent, 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,17 @@ 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
+ const path = column.component.create.replace('@@', '../custom');
80
+ let component = defineAsyncComponent(() => import(`${path}`))
81
+ acc[column.name] = component;
82
+ }
83
+ return acc;
84
+ }, {});
75
85
  loading.value = false;
76
86
  });
77
87
 
@@ -28,6 +28,7 @@
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';
43
+ import { useCoreStore } from '@/stores/core';
46
44
  import { callAdminForthApi } from '@/utils';
45
+ import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
46
+ import { defineAsyncComponent, onMounted, ref } from 'vue';
47
+ import { useRoute, useRouter } from 'vue-router';
47
48
 
48
49
  const coreStore = useCoreStore();
49
50
 
@@ -58,6 +59,7 @@ 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;
@@ -75,6 +77,14 @@ onMounted(async () => {
75
77
  primaryKey: route.params.primaryKey,
76
78
  });
77
79
 
80
+ editComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
81
+ if (column.component?.edit) {
82
+ const path = column.component.edit.replace('@@', '../custom');
83
+ let component = defineAsyncComponent(() => import(`${path}`))
84
+ acc[column.name] = component;
85
+ }
86
+ return acc;
87
+ }, {});
78
88
  loading.value = false;
79
89
  });
80
90
 
@@ -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,27 @@
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, defineAsyncComponent, onMounted, ref, watch } from 'vue';
324
+ import { useRoute } from 'vue-router';
320
325
 
321
326
  import ValueRenderer from '@/components/ValueRenderer.vue';
322
327
 
323
328
  import {
324
- IconChevronDoubleLeftOutline,
325
- IconChevronDoubleRightOutline,
326
- IconInboxFullSolid,
327
- IconInboxOutline,
328
- IconPlusOutline
329
+ IconInboxOutline,
330
+ IconPlusOutline
329
331
  } from '@iconify-prerendered/vue-flowbite';
330
332
 
331
333
  import {
332
- IconEyeSolid,
333
- IconTrashBinSolid,
334
- IconPenSolid,
335
- IconFilterOutline, IconBanOutline
334
+ IconBanOutline,
335
+ IconEyeSolid,
336
+ IconFilterOutline,
337
+ IconPenSolid,
338
+ IconTrashBinSolid
336
339
  } from '@iconify-prerendered/vue-flowbite';
337
340
 
338
341
  import Filters from '@/components/Filters.vue';
@@ -375,6 +378,7 @@ async function selectAll(value) {
375
378
 
376
379
  const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.pageSize || DEFAULT_PAGE_SIZE);
377
380
  const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
381
+ let listComponentsPerColumn = {};
378
382
  const allFromThisPageChecked = computed(() => {
379
383
  if (!rows.value) return false;
380
384
  return rows.value.every((r) => checkboxes.value.includes(r.id));
@@ -418,6 +422,7 @@ async function getList() {
418
422
  path: '/get_resource_data',
419
423
  method: 'POST',
420
424
  body: {
425
+ source: 'list',
421
426
  resourceId: route.params.resourceId,
422
427
  limit: pageSize.value,
423
428
  offset: (page.value - 1) * pageSize.value,
@@ -425,7 +430,16 @@ async function getList() {
425
430
  sort: sort.value,
426
431
  }
427
432
  });
428
- fetchStatus.value.pending = false;
433
+ listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
434
+ if (column.component?.list) {
435
+ const path = column.component.list.replace('@@', '../custom');
436
+ let component = defineAsyncComponent(() => import(`${path}`))
437
+ acc[column.name] = component;
438
+ }
439
+ return acc;
440
+ }, {});
441
+
442
+ fetchStatus.value.pending = false;
429
443
  rows.value = data.data?.map(row => {
430
444
  row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
431
445
  return row;
@@ -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>
@@ -48,7 +48,12 @@
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
+ <!-- if column.name in showComponentsPerColumn, render it. If not, render ValueRenderer -->
52
+ <component
53
+ :is="showComponentsPerColumn[column.name] || ValueRenderer"
54
+ :column="column"
55
+ :row="coreStore.record"
56
+ />
52
57
  </td>
53
58
  </tr>
54
59
 
@@ -63,18 +68,18 @@
63
68
 
64
69
  <script setup>
65
70
 
66
- import { ref, computed, onMounted } from 'vue';
67
- import { useRoute } from 'vue-router';
68
- import { callAdminForthApi } from '@/utils';
69
71
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
70
- import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
71
- import { useCoreStore } from '@/stores/core';
72
72
  import ValueRenderer from '@/components/ValueRenderer.vue';
73
+ import { useCoreStore } from '@/stores/core';
74
+ import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
75
+ import { defineAsyncComponent, 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,16 @@ 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
+ const path = column.component.show.replace('@@', '../custom');
98
+ console.log('path', path);
99
+ let component = defineAsyncComponent(() => import(`${path}`))
100
+ acc[column.name] = component;
101
+ }
102
+ return acc;
103
+ }, {});
104
+ console.log('showComponentsPerColumn', showComponentsPerColumn);
90
105
  loading.value = false;
91
106
  });
92
107
 
@@ -0,0 +1 @@
1
+ export {};