adminforth 1.0.46 → 1.0.48

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.
@@ -41,9 +41,9 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
41
41
  import ResourceForm from '@/components/ResourceForm.vue';
42
42
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
43
43
  import { useCoreStore } from '@/stores/core';
44
- import { callAdminForthApi } from '@/utils';
44
+ import { callAdminForthApi, getCustomComponent } from '@/utils';
45
45
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
46
- import { defineAsyncComponent, onMounted, ref, computed } from 'vue';
46
+ import { computed, onMounted, ref } from 'vue';
47
47
  import { useRoute, useRouter } from 'vue-router';
48
48
 
49
49
  const coreStore = useCoreStore();
@@ -90,13 +90,11 @@ onMounted(async () => {
90
90
  });
91
91
 
92
92
  editComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
93
- if (column.component?.edit) {
94
- const path = column.component.edit.replace('@@', '../custom');
95
- let component = defineAsyncComponent(() => import(`${path}`))
96
- acc[column.name] = component;
97
- }
98
- return acc;
99
- }, {});
93
+ if (column.component?.show) {
94
+ acc[column.name] = getCustomComponent(column.component.edit.replace('@@', '').replace('./custom/', '')).split('.')[0];
95
+ }
96
+ return acc;
97
+ }, {});
100
98
  loading.value = false;
101
99
  });
102
100
 
@@ -109,13 +107,21 @@ async function saveRecord() {
109
107
  }
110
108
 
111
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
+
112
118
  await callAdminForthApi({
113
119
  method: 'POST',
114
120
  path: `/update_record`,
115
121
  body: {
116
122
  resourceId: route.params.resourceId,
117
123
  recordId: route.params.primaryKey,
118
- record: record.value,
124
+ record: updates,
119
125
  },
120
126
  });
121
127
  saving.value = false;
@@ -320,10 +320,11 @@ import { useCoreStore } from '@/stores/core';
320
320
  import { useModalStore } from '@/stores/modal';
321
321
  import { callAdminForthApi, getIcon } from '@/utils';
322
322
  import { initFlowbite } from 'flowbite';
323
- import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
323
+ import { computed, onMounted, ref, watch } from 'vue';
324
324
  import { useRoute } from 'vue-router';
325
325
 
326
326
  import ValueRenderer from '@/components/ValueRenderer.vue';
327
+ import { getCustomComponent } from '@/utils';
327
328
 
328
329
  import {
329
330
  IconInboxOutline,
@@ -431,13 +432,11 @@ async function getList() {
431
432
  }
432
433
  });
433
434
  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
- }, {});
435
+ if (column.component?.show) {
436
+ acc[column.name] = getCustomComponent(column.component.list.replace('@@', '').replace('./custom/', '')).split('.')[0];
437
+ }
438
+ return acc;
439
+ }, {});
441
440
 
442
441
  fetchStatus.value.pending = false;
443
442
  rows.value = data.data?.map(row => {
@@ -48,7 +48,6 @@
48
48
  {{ column.label }}
49
49
  </td>
50
50
  <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
51
- <!-- if column.name in showComponentsPerColumn, render it. If not, render ValueRenderer -->
52
51
  <component
53
52
  :is="showComponentsPerColumn[column.name] || ValueRenderer"
54
53
  :column="column"
@@ -71,8 +70,9 @@
71
70
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
72
71
  import ValueRenderer from '@/components/ValueRenderer.vue';
73
72
  import { useCoreStore } from '@/stores/core';
73
+ import { getCustomComponent } from '@/utils';
74
74
  import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
75
- import { defineAsyncComponent, onMounted, ref } from 'vue';
75
+ import { onMounted, ref } from 'vue';
76
76
  import { useRoute } from 'vue-router';
77
77
 
78
78
 
@@ -94,14 +94,10 @@ onMounted(async () => {
94
94
  });
95
95
  showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
96
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;
97
+ acc[column.name] = getCustomComponent(column.component.show.replace('@@', '').replace('./custom/', '')).split('.')[0];
98
+ }
99
+ return acc;
103
100
  }, {});
104
- console.log('showComponentsPerColumn', showComponentsPerColumn);
105
101
  loading.value = false;
106
102
  });
107
103
 
package/index.ts CHANGED
@@ -230,6 +230,19 @@ class AdminForth {
230
230
  if (item.component && !item.path) {
231
231
  errors.push(`Menu item with component must have path : ${JSON.stringify(item)}`);
232
232
  }
233
+
234
+ if (item.type === 'resource' && !item.resourceId) {
235
+ errors.push(`Menu item with type 'resource' must have resourceId : ${JSON.stringify(item)}`);
236
+ }
237
+
238
+ if (item.resourceId && !this.config.resources.find((res) => res.resourceId === item.resourceId)) {
239
+ errors.push(`Menu item with type 'resourceId' has resourceId which is not in resources: ${JSON.stringify(item)}`);
240
+ }
241
+
242
+ if (item.type === 'component' && !item.component) {
243
+ errors.push(`Menu item with type 'component' must have component : ${JSON.stringify(item)}`);
244
+ }
245
+
233
246
  // make sure component starts with @@
234
247
  if (item.component) {
235
248
  if (!item.component.startsWith('@@')) {
@@ -486,6 +499,7 @@ class AdminForth {
486
499
  deleteConfirmation: this.config.deleteConfirmation,
487
500
  auth: this.config.auth,
488
501
  usernameField: this.config.auth.usernameField,
502
+ title: this.config?.title,
489
503
  },
490
504
  adminUser,
491
505
  version: ADMINFORTH_VERSION,
@@ -603,11 +617,20 @@ class AdminForth {
603
617
  data.data.forEach((item) => {
604
618
  item[col.name] = targetDataMap[item[col.name]];
605
619
  });
620
+
621
+ data.data.forEach((item) => {
622
+ Object.keys(item).forEach((key) => {
623
+ if (!targetResource.columns.find((col) => col.name === key) || targetResource.columns.find((col) => col.name === key && col.backendOnly)) {
624
+ delete item[key];
625
+ }
626
+ })
627
+ });
628
+
606
629
  })
607
630
  );
608
631
 
609
632
  data.data.forEach((item) => {
610
- item._label = resource.itemLabel(item)
633
+ item._label = resource.itemLabel(item);
611
634
  })
612
635
 
613
636
 
@@ -622,6 +645,15 @@ class AdminForth {
622
645
  }
623
646
  }
624
647
 
648
+ // remove all columns which are not defined in resources, or defined but backendOnly
649
+ data.data.forEach((item) => {
650
+ Object.keys(item).forEach((key) => {
651
+ if (!resource.columns.find((col) => col.name === key) || resource.columns.find((col) => col.name === key && col.backendOnly)) {
652
+ delete item[key];
653
+ }
654
+ })
655
+ });
656
+
625
657
  return {...data, options: resource?.options };
626
658
  },
627
659
  });
@@ -725,7 +757,6 @@ class AdminForth {
725
757
  });
726
758
 
727
759
  server.endpoint({
728
- noAuth: true, // TODO
729
760
  method: 'POST',
730
761
  path: '/create_record',
731
762
  handler: async ({ body, adminUser }) => {
@@ -800,7 +831,6 @@ class AdminForth {
800
831
  }
801
832
  });
802
833
  server.endpoint({
803
- noAuth: true, // TODO
804
834
  method: 'POST',
805
835
  path: '/update_record',
806
836
  handler: async ({ body, adminUser }) => {
@@ -831,11 +861,19 @@ class AdminForth {
831
861
  }
832
862
 
833
863
  const newValues = {};
834
- for (const col of resource.columns.filter((col) => !col.virtual)) {
835
- if (record[col.name] !== oldRecord[col.name]) {
836
- newValues[col.name] = connector.setFieldValue(col, record[col.name]);
864
+
865
+ for (const recordField in record) {
866
+ if (record[recordField] !== oldRecord[recordField]) {
867
+ const column = resource.columns.find((col) => col.name === recordField);
868
+ if (column) {
869
+ newValues[recordField] = connector.setFieldValue(column, record[recordField]);
870
+ } else {
871
+ newValues[recordField] = record[recordField];
837
872
  }
838
- }
873
+ }
874
+ }
875
+
876
+ console.log('✅ newValues', newValues)
839
877
  if (Object.keys(newValues).length > 0) {
840
878
  await connector.updateRecord({ resource, recordId, record, newValues});
841
879
  }
@@ -858,7 +896,6 @@ class AdminForth {
858
896
  }
859
897
  });
860
898
  server.endpoint({
861
- noAuth: true, // TODO
862
899
  method: 'POST',
863
900
  path: '/delete_record',
864
901
  handler: async ({ body, adminUser }) => {
@@ -900,7 +937,6 @@ class AdminForth {
900
937
  }
901
938
  });
902
939
  server.endpoint({
903
- noAuth: true, // TODO
904
940
  method: 'POST',
905
941
  path: '/start_bulk_action',
906
942
  handler: async ({ body }) => {
@@ -1,12 +1,11 @@
1
- import fs from 'fs';
2
- import fsExtra from 'fs-extra';
3
- import filewatcher from 'filewatcher';
4
1
  import { exec, spawn } from 'child_process';
5
- import { promisify } from 'util';
6
- import path from 'path';
7
- import { fileURLToPath } from 'url';
8
2
  import crypto from 'crypto';
3
+ import filewatcher from 'filewatcher';
4
+ import fs from 'fs';
5
+ import fsExtra from 'fs-extra';
9
6
  import os from 'os';
7
+ import path from 'path';
8
+ import { promisify } from 'util';
10
9
  import AdminForth from '../index.js';
11
10
  import { ADMIN_FORTH_ABSOLUTE_PATH } from './utils.js';
12
11
 
@@ -173,6 +172,26 @@ class CodeInjector {
173
172
  return `import { ${PascalIconName} } from '@iconify-prerendered/vue-${collection}';`;
174
173
  }).join('\n');
175
174
 
175
+ // for each custom component generate import statement
176
+ const customComponentsDir = this.adminforth.config.customization?.customComponentsDir;
177
+ let customComponentsImports = '';
178
+ if (customComponentsDir) {
179
+ // if file - return, if dir - go recursively
180
+ const customComponents = await fs.promises.readdir(customComponentsDir);
181
+ for (const filePath of customComponents) {
182
+ fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
183
+ if (err) {
184
+ console.log(err);
185
+ return;
186
+ }
187
+ if (data.isFile()) {
188
+ const componentName = filePath.split('.')[0].replace('/', '');
189
+ customComponentsImports += `import ${componentName} from '@/custom/${filePath}';\n`;
190
+ }
191
+ })
192
+ }
193
+ }
194
+
176
195
  // Generate Vue.component statements for each icon
177
196
  const iconComponents = uniqueIcons.map((icon) => {
178
197
  const [ collection, iconName ] = icon.split(':');
@@ -182,7 +201,28 @@ class CodeInjector {
182
201
  return `app.component('${PascalIconName}', ${PascalIconName});`;
183
202
  }).join('\n');
184
203
 
204
+ // Generate Vue.component statements for each custom component
205
+ let customComponentsComponents = '';
206
+ if (customComponentsDir) {
207
+ const customComponents = await fs.promises.readdir(customComponentsDir);
208
+ for (const filePath of customComponents) {
209
+ fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
210
+ if (err) {
211
+ console.log('Custom components importing error: ', err);
212
+ return;
213
+ }
214
+ if (data.isFile()) {
215
+ const componentName = filePath.split('.')[0].replace('/', '');
216
+ customComponentsComponents += `app.component('${componentName}', ${componentName});\n`;
217
+ }
218
+ })
219
+ }
220
+ }
221
+
222
+
185
223
  let imports = iconImports + '\n';
224
+ imports += customComponentsImports + '\n';
225
+
186
226
 
187
227
  if (this.adminforth.config.customization?.vueUsesFile) {
188
228
  imports += `import addCustomUses from '${this.adminforth.config.customization.vueUsesFile}';\n`;
@@ -192,7 +232,7 @@ class CodeInjector {
192
232
  const appVuePath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'main.ts');
193
233
  let appVueContent = await fs.promises.readFile(appVuePath, 'utf-8');
194
234
  appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH IMPORTS */', imports);
195
- appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */', iconComponents + '\n' );
235
+ appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */', iconComponents + '\n' + customComponentsComponents + '\n');
196
236
  if (this.adminforth.config.customization?.vueUsesFile) {
197
237
  appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH CUSTOM USES */', 'addCustomUses(app);');
198
238
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,6 +15,7 @@
15
15
  "flowbite": "^2.3.0",
16
16
  "flowbite-datepicker": "^1.2.6",
17
17
  "pinia": "^2.1.7",
18
+ "unhead": "^1.9.12",
18
19
  "vue": "^3.4.21",
19
20
  "vue-router": "^4.3.0",
20
21
  "vue-slider-component": "^4.1.0-beta.7"
@@ -1149,6 +1150,41 @@
1149
1150
  "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
1150
1151
  "dev": true
1151
1152
  },
1153
+ "node_modules/@unhead/dom": {
1154
+ "version": "1.9.12",
1155
+ "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.9.12.tgz",
1156
+ "integrity": "sha512-3MY1TbZmEjGNZapi3wvJW0vWNS2CLKHt7/m57sScDHCNvNBe1mTwrIOhtZFDgAndhml2EVQ68RMa0Vhum/M+cw==",
1157
+ "dependencies": {
1158
+ "@unhead/schema": "1.9.12",
1159
+ "@unhead/shared": "1.9.12"
1160
+ },
1161
+ "funding": {
1162
+ "url": "https://github.com/sponsors/harlan-zw"
1163
+ }
1164
+ },
1165
+ "node_modules/@unhead/schema": {
1166
+ "version": "1.9.12",
1167
+ "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.9.12.tgz",
1168
+ "integrity": "sha512-ue2FKyIZKsuZDpWJBMlBGwMm4s+vFeU3NUWsNt8Z+2JkOUIqO/VG43LxNgY1M595bOS71Gdxk+G9VtzfKJ5uEA==",
1169
+ "dependencies": {
1170
+ "hookable": "^5.5.3",
1171
+ "zhead": "^2.2.4"
1172
+ },
1173
+ "funding": {
1174
+ "url": "https://github.com/sponsors/harlan-zw"
1175
+ }
1176
+ },
1177
+ "node_modules/@unhead/shared": {
1178
+ "version": "1.9.12",
1179
+ "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.9.12.tgz",
1180
+ "integrity": "sha512-72wlLXG3FP3sXUrwd42Uv8jYpHSg4R6IFJcsl+QisRjKM89JnjOFSw1DqWO4IOftW5xOxS4J5v7SQyJ4NJo7Bw==",
1181
+ "dependencies": {
1182
+ "@unhead/schema": "1.9.12"
1183
+ },
1184
+ "funding": {
1185
+ "url": "https://github.com/sponsors/harlan-zw"
1186
+ }
1187
+ },
1152
1188
  "node_modules/@vitejs/plugin-vue": {
1153
1189
  "version": "5.0.4",
1154
1190
  "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
@@ -2471,6 +2507,11 @@
2471
2507
  "he": "bin/he"
2472
2508
  }
2473
2509
  },
2510
+ "node_modules/hookable": {
2511
+ "version": "5.5.3",
2512
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
2513
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
2514
+ },
2474
2515
  "node_modules/ignore": {
2475
2516
  "version": "5.3.1",
2476
2517
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@@ -3914,6 +3955,20 @@
3914
3955
  "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
3915
3956
  "dev": true
3916
3957
  },
3958
+ "node_modules/unhead": {
3959
+ "version": "1.9.12",
3960
+ "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.9.12.tgz",
3961
+ "integrity": "sha512-s6VxcTV45hy8c/IioKQOonFnAO+kBOSpgDfqEHhnU0YVSQYaRPEp9pzW1qSPf0lx+bg9RKeOQyNNbSGGUP26aQ==",
3962
+ "dependencies": {
3963
+ "@unhead/dom": "1.9.12",
3964
+ "@unhead/schema": "1.9.12",
3965
+ "@unhead/shared": "1.9.12",
3966
+ "hookable": "^5.5.3"
3967
+ },
3968
+ "funding": {
3969
+ "url": "https://github.com/sponsors/harlan-zw"
3970
+ }
3971
+ },
3917
3972
  "node_modules/update-browserslist-db": {
3918
3973
  "version": "1.0.16",
3919
3974
  "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
@@ -4260,6 +4315,14 @@
4260
4315
  "funding": {
4261
4316
  "url": "https://github.com/sponsors/sindresorhus"
4262
4317
  }
4318
+ },
4319
+ "node_modules/zhead": {
4320
+ "version": "2.2.4",
4321
+ "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz",
4322
+ "integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==",
4323
+ "funding": {
4324
+ "url": "https://github.com/sponsors/harlan-zw"
4325
+ }
4263
4326
  }
4264
4327
  }
4265
4328
  }
package/spa/package.json CHANGED
@@ -19,6 +19,7 @@
19
19
  "flowbite": "^2.3.0",
20
20
  "flowbite-datepicker": "^1.2.6",
21
21
  "pinia": "^2.1.7",
22
+ "unhead": "^1.9.12",
22
23
  "vue": "^3.4.21",
23
24
  "vue-router": "^4.3.0",
24
25
  "vue-slider-component": "^4.1.0-beta.7"
package/spa/src/App.vue CHANGED
@@ -64,6 +64,7 @@
64
64
  </div>
65
65
  </nav>
66
66
 
67
+
67
68
  <aside
68
69
  v-if="loggedIn"
69
70
 
@@ -132,10 +133,22 @@ import AcceptModal from './components/AcceptModal.vue';
132
133
  import MenuLink from './components/MenuLink.vue';
133
134
  import { useRoute, useRouter } from 'vue-router';
134
135
  import { getIcon } from '@/utils';
136
+ import { useHead } from 'unhead'
137
+ import { createHead } from 'unhead'
138
+ const coreStore = useCoreStore();
139
+
140
+
141
+ createHead()
142
+
143
+
144
+
145
+
135
146
 
136
147
 
137
148
  const route = useRoute();
138
149
  const router = useRouter();
150
+ const title = ref('');
151
+
139
152
 
140
153
  const routerIsReady = ref(false);
141
154
 
@@ -153,7 +166,6 @@ async function logout() {
153
166
  router.push({ name: 'login' });
154
167
  }
155
168
 
156
- const coreStore = useCoreStore();
157
169
 
158
170
 
159
171
 
@@ -167,6 +179,11 @@ onMounted(async () => {
167
179
  initRouter();
168
180
  initFlowbite();
169
181
  await coreStore.fetchMenuAndResource();
182
+ title.value = coreStore.config.title || 'Admin Forth';
183
+ useHead({
184
+ title: title.value,
185
+
186
+ })
170
187
  })
171
188
 
172
189
  </script>
package/spa/src/utils.ts CHANGED
@@ -29,9 +29,9 @@ export async function callAdminForthApi({ path, method, body=undefined }) {
29
29
  }
30
30
  }
31
31
 
32
-
33
-
34
-
32
+ export function getCustomComponent(name) {
33
+ return resolveComponent(name);
34
+ }
35
35
 
36
36
  export function getIcon(icon: string) {
37
37
  // icon format is "feather:icon-name". We need to get IconName in pascal case
@@ -45,9 +45,9 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
45
45
  import ResourceForm from '@/components/ResourceForm.vue';
46
46
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
47
47
  import { useCoreStore } from '@/stores/core';
48
- import { callAdminForthApi } from '@/utils';
48
+ import { callAdminForthApi, getCustomComponent } from '@/utils';
49
49
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
50
- import { defineAsyncComponent, onMounted, ref } from 'vue';
50
+ import { onMounted, ref } from 'vue';
51
51
  import { useRoute, useRouter } from 'vue-router';
52
52
 
53
53
  const isValid = ref(false);
@@ -75,12 +75,10 @@ onMounted(async () => {
75
75
  resourceId: route.params.resourceId
76
76
  });
77
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;
78
+ if (column.component?.show) {
79
+ acc[column.name] = getCustomComponent(column.component.create.replace('@@', '').replace('./custom/', '')).split('.')[0];
80
+ }
81
+ return acc;
84
82
  }, {});
85
83
  loading.value = false;
86
84
  });
@@ -41,9 +41,9 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
41
41
  import ResourceForm from '@/components/ResourceForm.vue';
42
42
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
43
43
  import { useCoreStore } from '@/stores/core';
44
- import { callAdminForthApi } from '@/utils';
44
+ import { callAdminForthApi, getCustomComponent } from '@/utils';
45
45
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
46
- import { defineAsyncComponent, onMounted, ref, computed } from 'vue';
46
+ import { computed, onMounted, ref } from 'vue';
47
47
  import { useRoute, useRouter } from 'vue-router';
48
48
 
49
49
  const coreStore = useCoreStore();
@@ -90,13 +90,11 @@ onMounted(async () => {
90
90
  });
91
91
 
92
92
  editComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
93
- if (column.component?.edit) {
94
- const path = column.component.edit.replace('@@', '../custom');
95
- let component = defineAsyncComponent(() => import(`${path}`))
96
- acc[column.name] = component;
97
- }
98
- return acc;
99
- }, {});
93
+ if (column.component?.show) {
94
+ acc[column.name] = getCustomComponent(column.component.edit.replace('@@', '').replace('./custom/', '')).split('.')[0];
95
+ }
96
+ return acc;
97
+ }, {});
100
98
  loading.value = false;
101
99
  });
102
100
 
@@ -109,13 +107,21 @@ async function saveRecord() {
109
107
  }
110
108
 
111
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
+
112
118
  await callAdminForthApi({
113
119
  method: 'POST',
114
120
  path: `/update_record`,
115
121
  body: {
116
122
  resourceId: route.params.resourceId,
117
123
  recordId: route.params.primaryKey,
118
- record: record.value,
124
+ record: updates,
119
125
  },
120
126
  });
121
127
  saving.value = false;
@@ -320,10 +320,11 @@ import { useCoreStore } from '@/stores/core';
320
320
  import { useModalStore } from '@/stores/modal';
321
321
  import { callAdminForthApi, getIcon } from '@/utils';
322
322
  import { initFlowbite } from 'flowbite';
323
- import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
323
+ import { computed, onMounted, ref, watch } from 'vue';
324
324
  import { useRoute } from 'vue-router';
325
325
 
326
326
  import ValueRenderer from '@/components/ValueRenderer.vue';
327
+ import { getCustomComponent } from '@/utils';
327
328
 
328
329
  import {
329
330
  IconInboxOutline,
@@ -431,13 +432,11 @@ async function getList() {
431
432
  }
432
433
  });
433
434
  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
- }, {});
435
+ if (column.component?.show) {
436
+ acc[column.name] = getCustomComponent(column.component.list.replace('@@', '').replace('./custom/', '')).split('.')[0];
437
+ }
438
+ return acc;
439
+ }, {});
441
440
 
442
441
  fetchStatus.value.pending = false;
443
442
  rows.value = data.data?.map(row => {
@@ -48,7 +48,6 @@
48
48
  {{ column.label }}
49
49
  </td>
50
50
  <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
51
- <!-- if column.name in showComponentsPerColumn, render it. If not, render ValueRenderer -->
52
51
  <component
53
52
  :is="showComponentsPerColumn[column.name] || ValueRenderer"
54
53
  :column="column"
@@ -71,8 +70,9 @@
71
70
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
72
71
  import ValueRenderer from '@/components/ValueRenderer.vue';
73
72
  import { useCoreStore } from '@/stores/core';
73
+ import { getCustomComponent } from '@/utils';
74
74
  import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
75
- import { defineAsyncComponent, onMounted, ref } from 'vue';
75
+ import { onMounted, ref } from 'vue';
76
76
  import { useRoute } from 'vue-router';
77
77
 
78
78
 
@@ -94,14 +94,10 @@ onMounted(async () => {
94
94
  });
95
95
  showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
96
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;
97
+ acc[column.name] = getCustomComponent(column.component.show.replace('@@', '').replace('./custom/', '')).split('.')[0];
98
+ }
99
+ return acc;
103
100
  }, {});
104
- console.log('showComponentsPerColumn', showComponentsPerColumn);
105
101
  loading.value = false;
106
102
  });
107
103