adminforth 1.0.31 → 1.0.33

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 (37) hide show
  1. package/dataConnectors/postgres.ts +9 -6
  2. package/dataConnectors/sqlite.ts +1 -0
  3. package/dist/dataConnectors/postgres.js +9 -6
  4. package/dist/dataConnectors/sqlite.js +1 -0
  5. package/dist/index.js +87 -65
  6. package/dist/modules/utils.js +9 -0
  7. package/dist/spa/spa/src/App.vue +0 -7
  8. package/dist/spa/spa/src/components/CustomDatePicker.vue +3 -3
  9. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +3 -3
  10. package/dist/spa/spa/src/components/CustomRangePicker.vue +32 -19
  11. package/dist/spa/spa/src/components/Dropdown.vue +6 -2
  12. package/dist/spa/spa/src/components/ResourceForm.vue +125 -102
  13. package/dist/spa/spa/src/components/ValueRenderer.vue +11 -1
  14. package/dist/spa/spa/src/stores/core.ts +21 -19
  15. package/dist/spa/spa/src/views/CreateView.vue +16 -6
  16. package/dist/spa/spa/src/views/EditView.vue +15 -5
  17. package/dist/spa/spa/src/views/ListView.vue +31 -21
  18. package/dist/spa/spa/src/views/ResourceParent.vue +1 -1
  19. package/dist/spa/spa/src/views/ShowView.vue +21 -6
  20. package/dist/types/AdminForthConfig.js +1 -0
  21. package/index.ts +73 -141
  22. package/modules/utils.ts +14 -0
  23. package/package.json +1 -1
  24. package/spa/src/App.vue +0 -7
  25. package/spa/src/components/CustomDatePicker.vue +3 -3
  26. package/spa/src/components/CustomDateRangePicker.vue +3 -3
  27. package/spa/src/components/CustomRangePicker.vue +32 -19
  28. package/spa/src/components/Dropdown.vue +6 -2
  29. package/spa/src/components/ResourceForm.vue +125 -102
  30. package/spa/src/components/ValueRenderer.vue +11 -1
  31. package/spa/src/stores/core.ts +21 -19
  32. package/spa/src/views/CreateView.vue +16 -6
  33. package/spa/src/views/EditView.vue +15 -5
  34. package/spa/src/views/ListView.vue +31 -21
  35. package/spa/src/views/ResourceParent.vue +1 -1
  36. package/spa/src/views/ShowView.vue +21 -6
  37. 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';
@@ -357,10 +360,6 @@ const totalRows = ref(0);
357
360
  const allCheckedActions = ref([]);
358
361
  const allowedActions = ref({});
359
362
 
360
- onMounted(() => {
361
- console.log('LISTVIEW mounted⌛⌛⌛⌛⌛⌛🔻🔻🔻');
362
- });
363
-
364
363
  const DEFAULT_PAGE_SIZE = 10;
365
364
 
366
365
  const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
@@ -379,6 +378,7 @@ async function selectAll(value) {
379
378
 
380
379
  const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.pageSize || DEFAULT_PAGE_SIZE);
381
380
  const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
381
+ let listComponentsPerColumn = {};
382
382
  const allFromThisPageChecked = computed(() => {
383
383
  if (!rows.value) return false;
384
384
  return rows.value.every((r) => checkboxes.value.includes(r.id));
@@ -422,6 +422,7 @@ async function getList() {
422
422
  path: '/get_resource_data',
423
423
  method: 'POST',
424
424
  body: {
425
+ source: 'list',
425
426
  resourceId: route.params.resourceId,
426
427
  limit: pageSize.value,
427
428
  offset: (page.value - 1) * pageSize.value,
@@ -429,7 +430,16 @@ async function getList() {
429
430
  sort: sort.value,
430
431
  }
431
432
  });
432
- 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;
433
443
  rows.value = data.data?.map(row => {
434
444
  row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
435
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