adminforth 1.1.22 → 1.1.23

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 (38) hide show
  1. package/dist/plugins/AccessControl/index.js +56 -3
  2. package/dist/spa/index.html +2 -2
  3. package/dist/spa/package-lock.json +189 -1
  4. package/dist/spa/package.json +6 -1
  5. package/dist/spa/spa/src/composables/useFrontendApi.ts +12 -0
  6. package/dist/spa/spa/src/views/ShowView.vue +22 -27
  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 +0 -1
  28. package/package.json +1 -1
  29. package/spa/src/composables/useFrontendApi.ts +12 -0
  30. package/spa/src/views/ShowView.vue +22 -27
  31. package/dist/plugins/AuditLog/index.js +0 -66
  32. package/dist/plugins/AuditLog/types.js +0 -1
  33. package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +0 -248
  34. package/dist/spa/spa/src/components/ShowTableItem.vue +0 -14
  35. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  36. package/dist/spa/src/views/HomeView.vue +0 -8
  37. package/plugins/AccessControl/index.ts +0 -29
  38. package/plugins/AccessControl/types.ts +0 -7
@@ -2,11 +2,11 @@
2
2
  <div>
3
3
 
4
4
  <div
5
- class="relative shadow-md sm:rounded-lg dark:shadow-2xl"
5
+ class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black"
6
6
  >
7
7
  <form autocomplete="off" @submit.prevent>
8
8
  <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
9
- <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
9
+ <thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
10
10
  <tr>
11
11
  <th scope="col" class="px-6 py-3">
12
12
  Field
@@ -18,97 +18,116 @@
18
18
  </thead>
19
19
  <tbody>
20
20
  <tr v-for="column, i in editableColumns" :key="column.name"
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"
21
+ class="bg-form-view-bg 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.enum"
40
- :options="column.enum"
41
- :modelValue="currentValues[column.name]"
42
- @update:modelValue="setCurrentValue(column.name, $event)"
43
- />
44
- <Dropdown
45
- single
46
- v-else-if="column.type === 'boolean'"
47
- :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
48
- :modelValue="currentValues[column.name]"
49
- @update:modelValue="setCurrentValue(column.name, $event)"
50
- />
51
- <input
52
- v-else-if="['integer'].includes(column.type)"
53
- type="number"
54
- step="1"
55
- 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"
56
- placeholder="0"
57
- :value="currentValues[column.name]"
58
- @input="setCurrentValue(column.name, $event.target.value)"
59
- >
60
- <CustomDatePicker
61
- v-else-if="['datetime'].includes(column.type)"
62
- :column="column"
63
- :valueStart="currentValues[column.name]"
64
- @update:valueStart="setCurrentValue(column.name, $event)"
65
- />
66
- <input
67
- v-else-if="['decimal', 'float'].includes(column.type)"
68
- type="number"
69
- step="0.1"
70
- 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"
71
- placeholder="0.0"
72
- :value="currentValues[column.name]"
73
- @input="setCurrentValue(column.name, $event.target.value)"
74
- />
75
- <textarea
76
- v-else-if="['text'].includes(column.type)"
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-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"
78
- placeholder="Text"
79
- :value="currentValues[column.name]"
80
- @input="setCurrentValue(column.name, $event.target.value)"
81
- >
82
- </textarea>
83
- <input
84
- v-else
85
- :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
86
- 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"
87
- placeholder="Text"
88
- :value="currentValues[column.name]"
89
- @input="setCurrentValue(column.name, $event.target.value)"
90
- autocomplete="false"
91
- data-lpignore="true"
92
- readonly
93
- onfocus="this.removeAttribute('readonly');"
94
- >
95
-
96
- <button
97
- v-if="column.masked"
98
- type="button"
99
- @click="unmasked[column.name] = !unmasked[column.name]"
100
- class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
101
- >
102
- <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
103
- <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
104
- </button>
105
-
106
-
107
-
108
- <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
109
-
110
- <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
111
- </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
+
48
+ <Dropdown
49
+ single
50
+ v-if="column.foreignResource"
51
+ :options="columnOptions[column.name] || []"
52
+ :modelValue="currentValues[column.name]"
53
+ @update:modelValue="setCurrentValue(column.name, $event)"
54
+ />
55
+ <Dropdown
56
+ single
57
+ v-else-if="column.enum"
58
+ :options="column.enum"
59
+ :modelValue="currentValues[column.name]"
60
+ @update:modelValue="setCurrentValue(column.name, $event)"
61
+ />
62
+ <Dropdown
63
+ single
64
+ v-else-if="column.type === 'boolean'"
65
+ :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
66
+ :modelValue="currentValues[column.name]"
67
+ @update:modelValue="setCurrentValue(column.name, $event)"
68
+ />
69
+ <input
70
+ v-else-if="['integer'].includes(column.type)"
71
+ type="number"
72
+ step="1"
73
+ 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"
74
+ placeholder="0"
75
+ :value="currentValues[column.name]"
76
+ @input="setCurrentValue(column.name, $event.target.value)"
77
+ >
78
+ <CustomDatePicker
79
+ v-else-if="['datetime'].includes(column.type)"
80
+ :column="column"
81
+ :valueStart="currentValues[column.name]"
82
+ @update:valueStart="setCurrentValue(column.name, $event)"
83
+ />
84
+ <input
85
+ v-else-if="['decimal', 'float'].includes(column.type)"
86
+ type="number"
87
+ step="0.1"
88
+ 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"
89
+ placeholder="0.0"
90
+ :value="currentValues[column.name]"
91
+ @input="setCurrentValue(column.name, $event.target.value)"
92
+ />
93
+ <textarea
94
+ v-else-if="['text'].includes(column.type)"
95
+ 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"
96
+ placeholder="Text"
97
+ :value="currentValues[column.name]"
98
+ @input="setCurrentValue(column.name, $event.target.value)"
99
+ >
100
+ </textarea>
101
+ <input
102
+ v-else
103
+ :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
104
+ 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"
105
+ placeholder="Text"
106
+ :value="currentValues[column.name]"
107
+ @input="setCurrentValue(column.name, $event.target.value)"
108
+ autocomplete="false"
109
+ data-lpignore="true"
110
+ readonly
111
+ onfocus="this.removeAttribute('readonly');"
112
+ >
113
+
114
+ <button
115
+ v-if="column.masked"
116
+ type="button"
117
+ @click="unmasked[column.name] = !unmasked[column.name]"
118
+ class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
119
+ >
120
+ <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
121
+ <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
122
+ </button>
123
+
124
+
125
+
126
+ <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
127
+
128
+ <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
129
+ </td>
130
+ </template>
112
131
  </tr>
113
132
 
114
133
  </tbody>
@@ -121,19 +140,24 @@
121
140
 
122
141
  <script setup>
123
142
 
124
- import { ref, computed, onMounted, watch } from 'vue';
125
- import { useCoreStore } from '@/stores/core';
126
- import Dropdown from '@/components/Dropdown.vue';
127
- import { IconExclamationCircleSolid } from '@iconify-prerendered/vue-flowbite';
128
- import { initFlowbite } from 'flowbite'
129
- import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
130
143
  import CustomDatePicker from "@/components/CustomDatePicker.vue";
144
+ import Dropdown from '@/components/Dropdown.vue';
145
+ import { useCoreStore } from '@/stores/core';
146
+ import { callAdminForthApi } from '@/utils';
147
+ import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
148
+ import { computedAsync } from '@vueuse/core';
149
+ import { initFlowbite } from 'flowbite';
150
+ import { computed, onMounted, ref, watch } from 'vue';
151
+ import { useRouter } from 'vue-router';
152
+
153
+ const router = useRouter();
131
154
 
132
155
  const props = defineProps({
133
156
  loading: Boolean,
134
157
  resourceColumns: Object,
135
158
  record: Object,
136
159
  validating: Boolean,
160
+ customComponentsPerColumn: Object,
137
161
  });
138
162
 
139
163
 
@@ -145,6 +169,7 @@ const emit = defineEmits(['update:record', 'update:isValid']);
145
169
 
146
170
  const currentValues = ref({});
147
171
 
172
+
148
173
  const columnError = (column) => {
149
174
  const val = computed(() => {
150
175
  if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
@@ -166,14 +191,24 @@ const columnError = (column) => {
166
191
  return `This field must be less than ${column.maxValue}`;
167
192
  }
168
193
  }
194
+ if ( column.validation && column.validation.length ){
195
+ const validationArray = column.validation;
196
+ for (let i = 0; i < validationArray.length; i++) {
197
+ if (validationArray[i].regExp) {
198
+ const regExp = new RegExp(validationArray[i].regExp);
199
+ if (!regExp.test(currentValues.value[column.name])) {
200
+ return validationArray[i].message;
201
+ }
202
+ }
203
+ }
204
+
205
+ }
169
206
  return null;
170
207
  });
171
208
  return val.value;
172
209
  };
173
210
 
174
211
 
175
-
176
-
177
212
  const setCurrentValue = (key, value) => {
178
213
  currentValues.value[key] = value;
179
214
 
@@ -185,8 +220,31 @@ onMounted(() => {
185
220
  currentValues.value[key] = props.record[key];
186
221
  });
187
222
  initFlowbite();
223
+
224
+
188
225
  });
189
226
 
227
+ const columnOptions = computedAsync(async () => {
228
+ return (await Promise.all(
229
+ Object.values(props.resourceColumns).map(async (column) => {
230
+ if (column.foreignResource) {
231
+ const list = await callAdminForthApi({
232
+ method: 'POST',
233
+ path: `/get_resource_foreign_data`,
234
+ body: {
235
+ resourceId: router.currentRoute.value.params.resourceId,
236
+ column: column.name,
237
+ limit: 1000,
238
+ offset: 0,
239
+ },
240
+ });
241
+ return { [column.name]: list.items };
242
+ }
243
+ })
244
+ )).reduce((acc, val) => Object.assign(acc, val), {})
245
+
246
+ }, {});
247
+
190
248
  const coreStore = useCoreStore();
191
249
 
192
250
  const editableColumns = computed(() => {
@@ -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;