adminforth 1.1.20 → 1.1.22

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 (50) hide show
  1. package/dist/index.js +68 -17
  2. package/dist/plugins/AccessControl/index.js +3 -56
  3. package/dist/plugins/AuditLog/index.js +66 -0
  4. package/dist/plugins/AuditLog/types.js +1 -0
  5. package/dist/plugins/ForeignInlineListPlugin/custom/InlineList.vue +3 -9
  6. package/dist/plugins/plugins/ForeignInlineListPlugin/custom/InlineList.vue +248 -0
  7. package/dist/spa/index.html +2 -2
  8. package/dist/spa/package-lock.json +1 -189
  9. package/dist/spa/package.json +1 -6
  10. package/dist/spa/spa/src/components/ResourceListTable.vue +35 -1
  11. package/dist/spa/spa/src/components/ShowTableItem.vue +14 -0
  12. package/dist/spa/spa/src/composables/useFrontendApi.ts +14 -0
  13. package/dist/spa/spa/src/utils.ts +2 -0
  14. package/dist/spa/spa/src/views/CreateView.vue +3 -5
  15. package/dist/spa/spa/src/views/EditView.vue +2 -5
  16. package/dist/spa/spa/src/views/HomeView.vue +8 -0
  17. package/dist/spa/spa/src/views/ListView.vue +4 -33
  18. package/dist/spa/src/App.vue +57 -76
  19. package/dist/spa/src/components/AcceptModal.vue +1 -1
  20. package/dist/spa/src/components/CustomDatePicker.vue +3 -3
  21. package/dist/spa/src/components/CustomDateRangePicker.vue +3 -3
  22. package/dist/spa/src/components/Dropdown.vue +4 -13
  23. package/dist/spa/src/components/Filters.vue +22 -55
  24. package/dist/spa/src/components/MenuLink.vue +2 -2
  25. package/dist/spa/src/components/ResourceForm.vue +99 -157
  26. package/dist/spa/src/components/ValueRenderer.vue +1 -11
  27. package/dist/spa/src/router/index.ts +2 -3
  28. package/dist/spa/src/stores/core.ts +33 -22
  29. package/dist/spa/src/utils.ts +3 -5
  30. package/dist/spa/src/views/CreateView.vue +7 -15
  31. package/dist/spa/src/views/EditView.vue +8 -36
  32. package/dist/spa/src/views/HomeView.vue +8 -0
  33. package/dist/spa/src/views/ListView.vue +23 -35
  34. package/dist/spa/src/views/LoginView.vue +1 -1
  35. package/dist/spa/src/views/ResourceParent.vue +1 -1
  36. package/dist/spa/src/views/ShowView.vue +9 -20
  37. package/dist/spa/tailwind.config.js +2 -5
  38. package/index.ts +87 -17
  39. package/package.json +1 -1
  40. package/plugins/AccessControl/index.ts +1 -55
  41. package/plugins/AccessControl/types.ts +1 -9
  42. package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +3 -9
  43. package/spa/src/components/ResourceListTable.vue +35 -1
  44. package/spa/src/composables/useFrontendApi.ts +14 -0
  45. package/spa/src/utils.ts +2 -0
  46. package/spa/src/views/CreateView.vue +3 -5
  47. package/spa/src/views/EditView.vue +2 -5
  48. package/spa/src/views/ListView.vue +4 -33
  49. package/types/AdminForthConfig.ts +12 -1
  50. package/dist/spa/src/components/CustomRangePicker.vue +0 -148
@@ -2,11 +2,11 @@
2
2
  <div>
3
3
 
4
4
  <div
5
- class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black"
5
+ class="relative shadow-md sm:rounded-lg dark:shadow-2xl"
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-form-view-heading dark:bg-gray-700 dark:text-gray-400">
9
+ <thead class="text-xs text-gray-700 uppercase bg-gray-50 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,116 +18,97 @@
18
18
  </thead>
19
19
  <tbody>
20
20
  <tr v-for="column, i in editableColumns" :key="column.name"
21
- class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
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
- <!-- 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)"
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'"
30
28
  />
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>
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>
131
112
  </tr>
132
113
 
133
114
  </tbody>
@@ -140,24 +121,19 @@
140
121
 
141
122
  <script setup>
142
123
 
143
- import CustomDatePicker from "@/components/CustomDatePicker.vue";
144
- import Dropdown from '@/components/Dropdown.vue';
124
+ import { ref, computed, onMounted, watch } from 'vue';
145
125
  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();
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
+ import CustomDatePicker from "@/components/CustomDatePicker.vue";
154
131
 
155
132
  const props = defineProps({
156
133
  loading: Boolean,
157
134
  resourceColumns: Object,
158
135
  record: Object,
159
136
  validating: Boolean,
160
- customComponentsPerColumn: Object,
161
137
  });
162
138
 
163
139
 
@@ -169,7 +145,6 @@ const emit = defineEmits(['update:record', 'update:isValid']);
169
145
 
170
146
  const currentValues = ref({});
171
147
 
172
-
173
148
  const columnError = (column) => {
174
149
  const val = computed(() => {
175
150
  if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
@@ -191,24 +166,14 @@ const columnError = (column) => {
191
166
  return `This field must be less than ${column.maxValue}`;
192
167
  }
193
168
  }
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
- }
206
169
  return null;
207
170
  });
208
171
  return val.value;
209
172
  };
210
173
 
211
174
 
175
+
176
+
212
177
  const setCurrentValue = (key, value) => {
213
178
  currentValues.value[key] = value;
214
179
 
@@ -220,31 +185,8 @@ onMounted(() => {
220
185
  currentValues.value[key] = props.record[key];
221
186
  });
222
187
  initFlowbite();
223
-
224
-
225
188
  });
226
189
 
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
-
248
190
  const coreStore = useCoreStore();
249
191
 
250
192
  const editableColumns = computed(() => {
@@ -1,16 +1,6 @@
1
1
  <template>
2
2
  <div>
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'">
3
+ <span v-if="column.type === 'boolean'">
14
4
  <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>
15
5
  <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>
16
6
  </span>
@@ -1,4 +1,5 @@
1
1
  import { createRouter, createWebHistory } from 'vue-router'
2
+ import HomeView from '../views/HomeView.vue'
2
3
  import ResourceParent from '@/views/ResourceParent.vue'
3
4
  import ListView from '@/views/ListView.vue'
4
5
  import ShowView from '@/views/ShowView.vue'
@@ -11,9 +12,7 @@ const router = createRouter({
11
12
  {
12
13
  path: '/',
13
14
  name: 'home',
14
- //redirect to login
15
- /* IMPORTANT:ADMINFORTH REDIRECT */
16
-
15
+ component: HomeView
17
16
  },
18
17
  {
19
18
  path: '/login',
@@ -1,6 +1,22 @@
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
+ }
4
20
 
5
21
  export const useCoreStore = defineStore('core', () => {
6
22
  const resourceById = ref([]);
@@ -24,40 +40,35 @@ export const useCoreStore = defineStore('core', () => {
24
40
  }, {});
25
41
  config.value = resp.config;
26
42
  user.value = resp.user;
27
- console.log('🌍 AdminForth v', resp.version);
28
43
 
29
44
  // find homepage:true in menu recuresively
30
-
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
+ }
31
59
  }
32
60
 
33
61
  async function fetchRecord({ resourceId, primaryKey }) {
34
62
  record.value = null;
35
-
36
- if (!resourceColumns.value) {
37
- throw new Error('Columns not fetched yet');
38
- }
39
63
 
40
- const respData = await callAdminForthApi({
41
- path: '/get_resource_data',
64
+ record.value = await callAdminForthApi({
65
+ path: '/get_record',
42
66
  method: 'POST',
43
67
  body: {
44
- source: 'show',
45
68
  resourceId: resourceId,
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
69
+ primaryKey: primaryKey,
56
70
  }
57
71
  });
58
-
59
- console.log('📦 record', respData);
60
- record.value = respData.data[0];
61
72
  }
62
73
 
63
74
  async function fetchColumns({ resourceId }) {
@@ -29,11 +29,9 @@ export async function callAdminForthApi({ path, method, body=undefined }) {
29
29
  }
30
30
  }
31
31
 
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
- }
32
+
33
+
34
+
37
35
 
38
36
  export function getIcon(icon: string) {
39
37
  // icon format is "feather:icon-name". We need to get IconName in pascal case
@@ -31,7 +31,6 @@
31
31
  @update:record="onUpdateRecord"
32
32
  @update:isValid="isValid = $event"
33
33
  :validating="validating"
34
- :customComponentsPerColumn="createComponentsPerColumn"
35
34
  >
36
35
  </ResourceForm>
37
36
 
@@ -41,14 +40,14 @@
41
40
 
42
41
  <script setup>
43
42
 
44
- import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
45
- import ResourceForm from '@/components/ResourceForm.vue';
46
- import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
43
+ import { ref, computed, onMounted } from 'vue';
44
+ import { useRoute, useRouter } from 'vue-router';
47
45
  import { useCoreStore } from '@/stores/core';
48
- import { callAdminForthApi, getCustomComponent } from '@/utils';
46
+ 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 { onMounted, ref } from 'vue';
51
- import { useRoute, useRouter } from 'vue-router';
50
+ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
52
51
 
53
52
  const isValid = ref(false);
54
53
  const validating = ref(false);
@@ -60,7 +59,6 @@ const route = useRoute();
60
59
  const router = useRouter();
61
60
 
62
61
  const record = ref({});
63
- let createComponentsPerColumn = {};
64
62
 
65
63
  const coreStore = useCoreStore();
66
64
 
@@ -71,15 +69,9 @@ async function onUpdateRecord(newRecord) {
71
69
 
72
70
  onMounted(async () => {
73
71
  loading.value = true;
74
- await coreStore.fetchColumns({
72
+ coreStore.fetchColumns({
75
73
  resourceId: route.params.resourceId
76
74
  });
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
- }, {});
83
75
  loading.value = false;
84
76
  });
85
77
 
@@ -23,12 +23,11 @@
23
23
 
24
24
  <ResourceForm
25
25
  v-else
26
- :record="editableRecord"
26
+ :record="coreStore.record"
27
27
  :resourceColumns="coreStore.resourceColumns"
28
28
  @update:record="onUpdateRecord"
29
29
  @update:isValid="isValid = $event"
30
30
  :validating="validating"
31
- :customComponentsPerColumn="editComponentsPerColumn"
32
31
  >
33
32
  </ResourceForm>
34
33
  </div>
@@ -37,14 +36,14 @@
37
36
 
38
37
  <script setup>
39
38
 
40
- import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
41
- import ResourceForm from '@/components/ResourceForm.vue';
42
- import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
39
+ import { ref, computed, onMounted } from 'vue';
40
+ import { useRoute, useRouter } from 'vue-router';
43
41
  import { useCoreStore } from '@/stores/core';
44
- import { callAdminForthApi, getCustomComponent } from '@/utils';
42
+ import ResourceForm from '@/components/ResourceForm.vue';
43
+ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
45
44
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
46
- import { computed, onMounted, ref } from 'vue';
47
- import { useRoute, useRouter } from 'vue-router';
45
+ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
46
+ import { callAdminForthApi } from '@/utils';
48
47
 
49
48
  const coreStore = useCoreStore();
50
49
 
@@ -59,24 +58,11 @@ const loading = ref(false);
59
58
  const saving = ref(false);
60
59
 
61
60
  const record = ref({});
62
- let editComponentsPerColumn = {};
63
61
 
64
62
  async function onUpdateRecord(newRecord) {
65
63
  record.value = newRecord;
66
64
  }
67
65
 
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
- })
80
66
 
81
67
  onMounted(async () => {
82
68
  loading.value = true;
@@ -89,12 +75,6 @@ onMounted(async () => {
89
75
  primaryKey: route.params.primaryKey,
90
76
  });
91
77
 
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
- }, {});
98
78
  loading.value = false;
99
79
  });
100
80
 
@@ -107,21 +87,13 @@ async function saveRecord() {
107
87
  }
108
88
 
109
89
  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
-
118
90
  await callAdminForthApi({
119
91
  method: 'POST',
120
92
  path: `/update_record`,
121
93
  body: {
122
94
  resourceId: route.params.resourceId,
123
95
  recordId: route.params.primaryKey,
124
- record: updates,
96
+ record: record.value,
125
97
  },
126
98
  });
127
99
  saving.value = false;
@@ -0,0 +1,8 @@
1
+ <script setup lang="ts">
2
+ </script>
3
+
4
+ <template>
5
+ <main>
6
+ 123
7
+ </main>
8
+ </template>