adminforth 2.17.0-next.6 → 2.17.0-next.61
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.
- package/commands/callTsProxy.js +2 -1
- package/commands/createApp/templates/adminuser.ts.hbs +2 -1
- package/commands/createApp/templates/index.ts.hbs +3 -2
- package/commands/createCustomComponent/main.js +0 -3
- package/commands/createCustomComponent/templates/customCrud/afterBreadcrumbs.vue.hbs +4 -2
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +3 -2
- package/commands/createCustomComponent/templates/customCrud/beforeBreadcrumbs.vue.hbs +4 -2
- package/commands/createCustomComponent/templates/customCrud/bottom.vue.hbs +4 -2
- package/commands/createCustomComponent/templates/customCrud/threeDotsDropdownItems.vue.hbs +4 -2
- package/commands/createPlugin/templates/index.ts.hbs +4 -0
- package/dist/auth.d.ts +2 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +17 -10
- package/dist/auth.js.map +1 -1
- package/dist/basePlugin.d.ts +1 -0
- package/dist/basePlugin.d.ts.map +1 -1
- package/dist/basePlugin.js +4 -2
- package/dist/basePlugin.js.map +1 -1
- package/dist/dataConnectors/baseConnector.d.ts +4 -0
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +103 -14
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts +2 -0
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +15 -4
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts +8 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +72 -28
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts +2 -0
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +22 -23
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts +2 -0
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +23 -26
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts +2 -0
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +19 -19
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +21 -40
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +77 -54
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +45 -63
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +14 -9
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/logger.d.ts +5 -0
- package/dist/modules/logger.d.ts.map +1 -0
- package/dist/modules/logger.js +16 -0
- package/dist/modules/logger.js.map +1 -0
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +31 -24
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/socketBroker.d.ts.map +1 -1
- package/dist/modules/socketBroker.js +6 -5
- package/dist/modules/socketBroker.js.map +1 -1
- package/dist/modules/styles.js +1 -1
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +11 -11
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/package-lock.json +0 -13
- package/dist/spa/package.json +0 -1
- package/dist/spa/src/App.vue +6 -3
- package/dist/spa/src/adminforth.ts +60 -1
- package/dist/spa/src/afcl/DatePicker.vue +0 -1
- package/dist/spa/src/afcl/Dialog.vue +2 -2
- package/dist/spa/src/afcl/Dropzone.vue +6 -4
- package/dist/spa/src/afcl/Select.vue +1 -1
- package/dist/spa/src/afcl/Table.vue +7 -5
- package/dist/spa/src/afcl/Tooltip.vue +38 -4
- package/dist/spa/src/components/ColumnValueInput.vue +14 -1
- package/dist/spa/src/components/CustomDateRangePicker.vue +0 -2
- package/dist/spa/src/components/CustomRangePicker.vue +9 -6
- package/dist/spa/src/components/Filters.vue +4 -4
- package/dist/spa/src/components/ListActionsThreeDots.vue +235 -0
- package/dist/spa/src/components/ResourceForm.vue +4 -4
- package/dist/spa/src/components/ResourceListTable.vue +30 -16
- package/dist/spa/src/components/ResourceListTableVirtual.vue +34 -18
- package/dist/spa/src/components/Sidebar.vue +4 -2
- package/dist/spa/src/components/ThreeDotsMenu.vue +35 -20
- package/dist/spa/src/composables/useFrontendApi.ts +8 -4
- package/dist/spa/src/renderers/CompactField.vue +3 -2
- package/dist/spa/src/renderers/CompactUUID.vue +3 -2
- package/dist/spa/src/renderers/RichText.vue +15 -0
- package/dist/spa/src/stores/core.ts +3 -2
- package/dist/spa/src/stores/filters.ts +1 -1
- package/dist/spa/src/stores/toast.ts +1 -2
- package/dist/spa/src/types/Back.ts +419 -18
- package/dist/spa/src/types/Common.ts +7 -14
- package/dist/spa/src/types/FrontendAPI.ts +27 -11
- package/dist/spa/src/views/CreateView.vue +23 -31
- package/dist/spa/src/views/EditView.vue +27 -31
- package/dist/spa/src/views/ListView.vue +20 -10
- package/dist/spa/src/views/SettingsView.vue +3 -2
- package/dist/spa/src/views/ShowView.vue +7 -6
- package/dist/types/Back.d.ts +371 -44
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +6 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +8 -15
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js +2 -0
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +34 -11
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/package.json +4 -1
- package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +0 -28
|
@@ -10,9 +10,10 @@ export interface FrontendAPIInterface {
|
|
|
10
10
|
* Example:
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
|
-
*
|
|
13
|
+
*import { useAdminforth } from '@/adminforth';
|
|
14
14
|
*
|
|
15
|
-
* const
|
|
15
|
+
* const { confirm } = useAdminforth();
|
|
16
|
+
* const isConfirmed = await confirm({message: 'Are you sure?', yes: 'Yes', no: 'No'})
|
|
16
17
|
* if (isConfirmed) {
|
|
17
18
|
* your code...
|
|
18
19
|
* }
|
|
@@ -31,9 +32,10 @@ export interface FrontendAPIInterface {
|
|
|
31
32
|
* Example:
|
|
32
33
|
*
|
|
33
34
|
* ```ts
|
|
34
|
-
* import
|
|
35
|
+
* import { useAdminforth } from '@/adminforth';
|
|
36
|
+
* const { alert } = useAdminforth();
|
|
35
37
|
*
|
|
36
|
-
*
|
|
38
|
+
* alert({message: 'Hello', variant: 'success'})
|
|
37
39
|
* ```
|
|
38
40
|
*
|
|
39
41
|
* @param params - The parameters of the alert
|
|
@@ -76,13 +78,14 @@ export interface FrontendAPIInterface {
|
|
|
76
78
|
* Example:
|
|
77
79
|
*
|
|
78
80
|
* ```ts
|
|
79
|
-
* import
|
|
81
|
+
* import { useAdminforth } from '@/adminforth';
|
|
80
82
|
*
|
|
83
|
+
* const { list } = useAdminforth();
|
|
81
84
|
* // Regular filter (will show in badge if column.showIn.filter !== false)
|
|
82
|
-
*
|
|
85
|
+
* list.setFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
83
86
|
*
|
|
84
87
|
* // Hidden filter (won't show in badge if column.showIn.filter === false)
|
|
85
|
-
*
|
|
88
|
+
* list.setFilter({field: 'internal_status', operator: 'eq', value: 'active'})
|
|
86
89
|
* ```
|
|
87
90
|
*
|
|
88
91
|
* Please note that you can set/update filter even for fields which have showIn.filter=false in resource configuration.
|
|
@@ -93,7 +96,8 @@ export interface FrontendAPIInterface {
|
|
|
93
96
|
setFilter(filter: FilterParams): void;
|
|
94
97
|
|
|
95
98
|
/**
|
|
96
|
-
*
|
|
99
|
+
* @deprecated does the same as setFilter, kept for backward compatibility, will be removed in 2.0.0
|
|
100
|
+
*
|
|
97
101
|
* Update a filter in the list
|
|
98
102
|
*
|
|
99
103
|
* Filters visibility in badge is automatically determined by column configuration:
|
|
@@ -102,9 +106,9 @@ export interface FrontendAPIInterface {
|
|
|
102
106
|
* Example:
|
|
103
107
|
*
|
|
104
108
|
* ```ts
|
|
105
|
-
* import
|
|
106
|
-
*
|
|
107
|
-
*
|
|
109
|
+
* import { useAdminforth } from '@/adminforth';
|
|
110
|
+
* const { list } = useAdminforth();
|
|
111
|
+
* list.updateFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
108
112
|
* ```
|
|
109
113
|
*
|
|
110
114
|
* @param filter - The filter to update
|
|
@@ -136,6 +140,18 @@ export interface FrontendAPIInterface {
|
|
|
136
140
|
* Close the user menu dropdown
|
|
137
141
|
*/
|
|
138
142
|
closeUserMenuDropdown(): void;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Run save interceptors for a specific resource or all resources if no resourceId is provided
|
|
146
|
+
*/
|
|
147
|
+
runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: object; }>;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Clear save interceptors for a specific resource or all resources if no resourceId is provided
|
|
151
|
+
*
|
|
152
|
+
* @param resourceId - The resource ID to clear interceptors for
|
|
153
|
+
*/
|
|
154
|
+
clearSaveInterceptors(resourceId?: string): void;
|
|
139
155
|
}
|
|
140
156
|
|
|
141
157
|
export type ConfirmParams = {
|
|
@@ -17,25 +17,7 @@
|
|
|
17
17
|
>
|
|
18
18
|
{{ $t('Cancel') }}
|
|
19
19
|
</button>
|
|
20
|
-
|
|
21
|
-
<!-- Custom Save Button injection -->
|
|
22
|
-
<component
|
|
23
|
-
v-if="createSaveButtonInjection"
|
|
24
|
-
:is="getCustomComponent(createSaveButtonInjection)"
|
|
25
|
-
:meta="createSaveButtonInjection.meta"
|
|
26
|
-
:record="record"
|
|
27
|
-
:resource="coreStore.resource"
|
|
28
|
-
:adminUser="coreStore.adminUser"
|
|
29
|
-
:saving="saving"
|
|
30
|
-
:validating="validating"
|
|
31
|
-
:isValid="isValid"
|
|
32
|
-
:disabled="saving || (validating && !isValid)"
|
|
33
|
-
:saveRecord="saveRecord"
|
|
34
|
-
/>
|
|
35
|
-
|
|
36
|
-
<!-- Default Save Button fallback -->
|
|
37
20
|
<button
|
|
38
|
-
v-else
|
|
39
21
|
@click="() => saveRecord()"
|
|
40
22
|
class="af-save-button flex items-center py-1 px-3 text-sm font-medium rounded-default text-lightCreateViewSaveButtonText focus:outline-none bg-lightCreateViewButtonBackground rounded border border-lightCreateViewButtonBorder hover:bg-lightCreateViewButtonBackgroundHover hover:text-lightCreateViewSaveButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightCreateViewButtonFocusRing dark:focus:ring-darkCreateViewButtonFocusRing dark:bg-darkCreateViewButtonBackground dark:text-darkCreateViewSaveButtonText dark:border-darkCreateViewButtonBorder dark:hover:text-darkCreateViewSaveButtonTextHover dark:hover:bg-darkCreateViewButtonBackgroundHover disabled:opacity-50 gap-1"
|
|
41
23
|
:disabled="saving || (validating && !isValid)"
|
|
@@ -99,12 +81,12 @@ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
|
99
81
|
import { useCoreStore } from '@/stores/core';
|
|
100
82
|
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown, checkShowIf } from '@/utils';
|
|
101
83
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
102
|
-
import { onMounted, ref, watch, nextTick } from 'vue';
|
|
84
|
+
import { onMounted, onBeforeMount, ref, watch, nextTick } from 'vue';
|
|
103
85
|
import { useRoute, useRouter } from 'vue-router';
|
|
104
86
|
import { computed } from 'vue';
|
|
105
87
|
import { showErrorTost } from '@/composables/useFrontendApi';
|
|
106
88
|
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
|
|
107
|
-
import
|
|
89
|
+
import { useAdminforth } from '@/adminforth';
|
|
108
90
|
import { useI18n } from 'vue-i18n';
|
|
109
91
|
import { type AdminForthComponentDeclarationFull } from '@/types/Common.js';
|
|
110
92
|
import type { AdminForthResourceColumn } from '@/types/Back';
|
|
@@ -121,18 +103,12 @@ const router = useRouter();
|
|
|
121
103
|
const record = ref({});
|
|
122
104
|
|
|
123
105
|
const coreStore = useCoreStore();
|
|
106
|
+
const { clearSaveInterceptors, runSaveInterceptors, alert } = useAdminforth();
|
|
124
107
|
|
|
125
108
|
const { t } = useI18n();
|
|
126
109
|
|
|
127
110
|
const resourceFormRef = ref<InstanceType<typeof ResourceForm> | null>(null);
|
|
128
111
|
|
|
129
|
-
const createSaveButtonInjection = computed<AdminForthComponentDeclarationFull | null>(() => {
|
|
130
|
-
const raw: any = coreStore.resourceOptions?.pageInjections?.create?.saveButton as any;
|
|
131
|
-
if (!raw) return null;
|
|
132
|
-
const item = Array.isArray(raw) ? raw[0] : raw;
|
|
133
|
-
return item as AdminForthComponentDeclarationFull;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
112
|
const initialValues = ref({});
|
|
137
113
|
|
|
138
114
|
const readonlyColumns = ref([]);
|
|
@@ -142,6 +118,10 @@ async function onUpdateRecord(newRecord: any) {
|
|
|
142
118
|
record.value = newRecord;
|
|
143
119
|
}
|
|
144
120
|
|
|
121
|
+
onBeforeMount(() => {
|
|
122
|
+
clearSaveInterceptors(route.params.resourceId as string);
|
|
123
|
+
});
|
|
124
|
+
|
|
145
125
|
onMounted(async () => {
|
|
146
126
|
loading.value = true;
|
|
147
127
|
await coreStore.fetchResourceFull({
|
|
@@ -182,7 +162,7 @@ onMounted(async () => {
|
|
|
182
162
|
initThreeDotsDropdown();
|
|
183
163
|
});
|
|
184
164
|
|
|
185
|
-
async function saveRecord(
|
|
165
|
+
async function saveRecord() {
|
|
186
166
|
if (!isValid.value) {
|
|
187
167
|
validating.value = true;
|
|
188
168
|
await nextTick();
|
|
@@ -194,6 +174,18 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
194
174
|
const requiredColumns = coreStore.resource?.columns.filter(c => c.required?.create === true) || [];
|
|
195
175
|
const requiredColumnsToSkip = requiredColumns.filter(c => checkShowIf(c, record.value, coreStore.resource?.columns || []) === false);
|
|
196
176
|
saving.value = true;
|
|
177
|
+
const interceptorsResult = await runSaveInterceptors({
|
|
178
|
+
action: 'create',
|
|
179
|
+
values: record.value,
|
|
180
|
+
resource: coreStore.resource,
|
|
181
|
+
resourceId: route.params.resourceId as string,
|
|
182
|
+
});
|
|
183
|
+
if (!interceptorsResult.ok) {
|
|
184
|
+
saving.value = false;
|
|
185
|
+
if (interceptorsResult.error) showErrorTost(interceptorsResult.error);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const interceptorConfirmationResult = (interceptorsResult.extra as Record<string, any>)?.confirmationResult;
|
|
197
189
|
const response = await callAdminForthApi({
|
|
198
190
|
method: 'POST',
|
|
199
191
|
path: `/create_record`,
|
|
@@ -202,7 +194,7 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
202
194
|
record: record.value,
|
|
203
195
|
requiredColumnsToSkip,
|
|
204
196
|
meta: {
|
|
205
|
-
...(
|
|
197
|
+
...(interceptorConfirmationResult ? { confirmationResult: interceptorConfirmationResult } : {}),
|
|
206
198
|
},
|
|
207
199
|
},
|
|
208
200
|
});
|
|
@@ -220,7 +212,7 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
220
212
|
primaryKey: response.newRecordId
|
|
221
213
|
}
|
|
222
214
|
});
|
|
223
|
-
|
|
215
|
+
alert({
|
|
224
216
|
message: t('Record created successfully!'),
|
|
225
217
|
variant: 'success'
|
|
226
218
|
});
|
|
@@ -236,7 +228,7 @@ function scrollToInvalidField() {
|
|
|
236
228
|
}
|
|
237
229
|
}
|
|
238
230
|
const errorMessage = t('Failed to save. Please fix errors for the following fields:') + '<ul class="mt-2 list-disc list-inside">' + columnsWithErrors.map(c => `<li><strong>${c.column.label || c.column.name}</strong>: ${c.error}</li>`).join('') + '</ul>';
|
|
239
|
-
|
|
231
|
+
alert({
|
|
240
232
|
messageHtml: errorMessage,
|
|
241
233
|
variant: 'danger'
|
|
242
234
|
});
|
|
@@ -16,25 +16,7 @@
|
|
|
16
16
|
>
|
|
17
17
|
{{ $t('Cancel') }}
|
|
18
18
|
</button>
|
|
19
|
-
|
|
20
|
-
<!-- Custom Save Button injection -->
|
|
21
|
-
<component
|
|
22
|
-
v-if="editSaveButtonInjection"
|
|
23
|
-
:is="getCustomComponent(editSaveButtonInjection)"
|
|
24
|
-
:meta="editSaveButtonInjection.meta"
|
|
25
|
-
:record="editableRecord"
|
|
26
|
-
:resource="coreStore.resource"
|
|
27
|
-
:adminUser="coreStore.adminUser"
|
|
28
|
-
:saving="saving"
|
|
29
|
-
:validating="validating"
|
|
30
|
-
:isValid="isValid"
|
|
31
|
-
:disabled="saving || (validating && !isValid)"
|
|
32
|
-
:saveRecord="saveRecord"
|
|
33
|
-
/>
|
|
34
|
-
|
|
35
|
-
<!-- Default Save Button fallback -->
|
|
36
19
|
<button
|
|
37
|
-
v-else
|
|
38
20
|
@click="() => saveRecord()"
|
|
39
21
|
class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-lightEditViewSaveButtonText focus:outline-none bg-lightEditViewButtonBackground rounded border border-lightEditViewButtonBorder hover:bg-lightEditViewButtonBackgroundHover hover:text-lightEditViewSaveButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightEditViewButtonFocusRing dark:focus:ring-darkEditViewButtonFocusRing dark:bg-darkEditViewButtonBackground dark:text-darkEditViewSaveButtonText dark:border-darkEditViewButtonBorder dark:hover:text-darkEditViewSaveButtonTextHover dark:hover:bg-darkEditViewButtonBackgroundHover disabled:opacity-50 gap-1"
|
|
40
22
|
:disabled="saving || (validating && !isValid)"
|
|
@@ -94,17 +76,18 @@ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
|
94
76
|
import { useCoreStore } from '@/stores/core';
|
|
95
77
|
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
|
|
96
78
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
97
|
-
import { computed, onMounted, ref, type Ref, nextTick } from 'vue';
|
|
79
|
+
import { computed, onMounted, onBeforeMount, ref, type Ref, nextTick, watch } from 'vue';
|
|
98
80
|
import { useRoute, useRouter } from 'vue-router';
|
|
99
81
|
import { showErrorTost } from '@/composables/useFrontendApi';
|
|
100
82
|
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
|
|
101
|
-
import
|
|
83
|
+
import { useAdminforth } from '@/adminforth';
|
|
102
84
|
import { useI18n } from 'vue-i18n';
|
|
103
85
|
import { type AdminForthComponentDeclarationFull } from '@/types/Common.js';
|
|
104
86
|
import type { AdminForthResourceColumn } from '@/types/Back';
|
|
105
87
|
|
|
106
88
|
const { t } = useI18n();
|
|
107
89
|
const coreStore = useCoreStore();
|
|
90
|
+
const { clearSaveInterceptors, runSaveInterceptors, alert } = useAdminforth();
|
|
108
91
|
|
|
109
92
|
const isValid = ref(false);
|
|
110
93
|
const validating = ref(false);
|
|
@@ -118,14 +101,11 @@ const saving = ref(false);
|
|
|
118
101
|
|
|
119
102
|
const record: Ref<Record<string, any>> = ref({});
|
|
120
103
|
|
|
121
|
-
|
|
104
|
+
watch(record, (newVal) => {
|
|
105
|
+
console.log('Record updated:', newVal);
|
|
106
|
+
}, { deep: true });
|
|
122
107
|
|
|
123
|
-
const
|
|
124
|
-
const raw: any = coreStore.resourceOptions?.pageInjections?.edit?.saveButton as any;
|
|
125
|
-
if (!raw) return null;
|
|
126
|
-
const item = Array.isArray(raw) ? raw[0] : raw;
|
|
127
|
-
return item as AdminForthComponentDeclarationFull;
|
|
128
|
-
});
|
|
108
|
+
const resourceFormRef = ref<InstanceType<typeof ResourceForm> | null>(null);
|
|
129
109
|
|
|
130
110
|
async function onUpdateRecord(newRecord: Record<string, any>) {
|
|
131
111
|
record.value = newRecord;
|
|
@@ -148,6 +128,10 @@ const editableRecord = computed(() => {
|
|
|
148
128
|
return newRecord;
|
|
149
129
|
})
|
|
150
130
|
|
|
131
|
+
onBeforeMount(() => {
|
|
132
|
+
clearSaveInterceptors(route.params.resourceId as string);
|
|
133
|
+
});
|
|
134
|
+
|
|
151
135
|
onMounted(async () => {
|
|
152
136
|
loading.value = true;
|
|
153
137
|
|
|
@@ -169,7 +153,7 @@ onMounted(async () => {
|
|
|
169
153
|
loading.value = false;
|
|
170
154
|
});
|
|
171
155
|
|
|
172
|
-
async function saveRecord(
|
|
156
|
+
async function saveRecord() {
|
|
173
157
|
if (!isValid.value) {
|
|
174
158
|
validating.value = true;
|
|
175
159
|
await nextTick();
|
|
@@ -180,6 +164,18 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
180
164
|
}
|
|
181
165
|
|
|
182
166
|
saving.value = true;
|
|
167
|
+
const interceptorsResult = await runSaveInterceptors({
|
|
168
|
+
action: 'edit',
|
|
169
|
+
values: record.value,
|
|
170
|
+
resource: coreStore.resource,
|
|
171
|
+
resourceId: route.params.resourceId as string,
|
|
172
|
+
});
|
|
173
|
+
if (!interceptorsResult.ok) {
|
|
174
|
+
saving.value = false;
|
|
175
|
+
if (interceptorsResult.error) showErrorTost(interceptorsResult.error);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const interceptorConfirmationResult = interceptorsResult.extra?.confirmationResult;
|
|
183
179
|
const updates: Record<string, any> = {};
|
|
184
180
|
for (const key in record.value) {
|
|
185
181
|
let columnIsUpdated = false;
|
|
@@ -212,14 +208,14 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
212
208
|
recordId: route.params.primaryKey,
|
|
213
209
|
record: updates,
|
|
214
210
|
meta: {
|
|
215
|
-
...(
|
|
211
|
+
...(interceptorConfirmationResult ? { confirmationResult: interceptorConfirmationResult } : {}),
|
|
216
212
|
},
|
|
217
213
|
},
|
|
218
214
|
});
|
|
219
215
|
if (resp.error && resp.error !== 'Operation aborted by hook') {
|
|
220
216
|
showErrorTost(resp.error);
|
|
221
217
|
} else {
|
|
222
|
-
|
|
218
|
+
alert({
|
|
223
219
|
message: t('Record updated successfully'),
|
|
224
220
|
variant: 'success',
|
|
225
221
|
timeout: 400000
|
|
@@ -238,7 +234,7 @@ function scrollToInvalidField() {
|
|
|
238
234
|
}
|
|
239
235
|
}
|
|
240
236
|
const errorMessage = t('Failed to save. Please fix errors for the following fields:') + '<ul class="mt-2 list-disc list-inside">' + columnsWithErrors.map(c => `<li><strong>${c.column.label || c.column.name}</strong>: ${c.error}</li>`).join('') + '</ul>';
|
|
241
|
-
|
|
237
|
+
alert({
|
|
242
238
|
messageHtml: errorMessage,
|
|
243
239
|
variant: 'danger'
|
|
244
240
|
});
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
:adminUser="coreStore.adminUser"
|
|
120
120
|
/>
|
|
121
121
|
<ResourceListTableVirtual
|
|
122
|
-
v-if="isVirtualScrollEnabled"
|
|
122
|
+
v-if="isVirtualScrollEnabled && !coreStore.isResourceFetching"
|
|
123
123
|
:resource="coreStore.resource"
|
|
124
124
|
:rows="rows"
|
|
125
125
|
:page="page"
|
|
@@ -143,6 +143,11 @@
|
|
|
143
143
|
? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
|
|
144
144
|
: []
|
|
145
145
|
"
|
|
146
|
+
:customActionIconsThreeDotsMenuItems="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems)
|
|
147
|
+
? coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems
|
|
148
|
+
: coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems
|
|
149
|
+
? [coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems]
|
|
150
|
+
: []"
|
|
146
151
|
:tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
|
|
147
152
|
? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
|
|
148
153
|
: coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
|
|
@@ -152,7 +157,7 @@
|
|
|
152
157
|
/>
|
|
153
158
|
|
|
154
159
|
<ResourceListTable
|
|
155
|
-
v-else
|
|
160
|
+
v-else-if="!coreStore.isResourceFetching"
|
|
156
161
|
:resource="coreStore.resource"
|
|
157
162
|
:rows="rows"
|
|
158
163
|
:page="page"
|
|
@@ -176,6 +181,11 @@
|
|
|
176
181
|
? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
|
|
177
182
|
: []
|
|
178
183
|
"
|
|
184
|
+
:customActionIconsThreeDotsMenuItems="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems)
|
|
185
|
+
? coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems
|
|
186
|
+
: coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems
|
|
187
|
+
? [coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems]
|
|
188
|
+
: []"
|
|
179
189
|
:tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
|
|
180
190
|
? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
|
|
181
191
|
: coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
|
|
@@ -215,10 +225,10 @@ import {
|
|
|
215
225
|
} from '@iconify-prerendered/vue-flowbite';
|
|
216
226
|
|
|
217
227
|
import Filters from '@/components/Filters.vue';
|
|
218
|
-
import
|
|
228
|
+
import { useAdminforth } from '@/adminforth';
|
|
219
229
|
|
|
220
230
|
const filtersShow = ref(false);
|
|
221
|
-
|
|
231
|
+
const { confirm, alert, list } = useAdminforth();
|
|
222
232
|
const coreStore = useCoreStore();
|
|
223
233
|
const filtersStore = useFiltersStore();
|
|
224
234
|
|
|
@@ -345,7 +355,7 @@ async function refreshExistingList(pk?: any) {
|
|
|
345
355
|
async function startBulkAction(actionId: string) {
|
|
346
356
|
const action = coreStore.resource?.options?.bulkActions?.find(a => a.id === actionId);
|
|
347
357
|
if (action?.confirm) {
|
|
348
|
-
const confirmed = await
|
|
358
|
+
const confirmed = await confirm({
|
|
349
359
|
message: action.confirm,
|
|
350
360
|
});
|
|
351
361
|
if (!confirmed) {
|
|
@@ -370,7 +380,7 @@ async function startBulkAction(actionId: string) {
|
|
|
370
380
|
await getList();
|
|
371
381
|
|
|
372
382
|
if (data.successMessage) {
|
|
373
|
-
|
|
383
|
+
alert({
|
|
374
384
|
message: data.successMessage,
|
|
375
385
|
variant: 'success'
|
|
376
386
|
});
|
|
@@ -454,7 +464,7 @@ async function init() {
|
|
|
454
464
|
clearAutoRefresher();
|
|
455
465
|
if (coreStore.resource!.options?.listRowsAutoRefreshSeconds) {
|
|
456
466
|
listAutorefresher = setInterval(async () => {
|
|
457
|
-
await
|
|
467
|
+
await list.silentRefresh();
|
|
458
468
|
}, coreStore.resource!.options.listRowsAutoRefreshSeconds * 1000);
|
|
459
469
|
}
|
|
460
470
|
}
|
|
@@ -464,7 +474,7 @@ watch([page, sort, () => filtersStore.filters], async () => {
|
|
|
464
474
|
await getList();
|
|
465
475
|
}, { deep: true });
|
|
466
476
|
|
|
467
|
-
|
|
477
|
+
list.refresh = async () => {
|
|
468
478
|
const result = await getList();
|
|
469
479
|
|
|
470
480
|
if (!result) {
|
|
@@ -478,11 +488,11 @@ adminforth.list.refresh = async () => {
|
|
|
478
488
|
return {};
|
|
479
489
|
};
|
|
480
490
|
|
|
481
|
-
|
|
491
|
+
list.silentRefresh = async () => {
|
|
482
492
|
return await refreshExistingList();
|
|
483
493
|
}
|
|
484
494
|
|
|
485
|
-
|
|
495
|
+
list.silentRefreshRow = async (pk: any) => {
|
|
486
496
|
return await refreshExistingList(pk);
|
|
487
497
|
}
|
|
488
498
|
|
|
@@ -28,7 +28,7 @@ import { useRouter } from 'vue-router';
|
|
|
28
28
|
import { useCoreStore } from '@/stores/core';
|
|
29
29
|
import { getCustomComponent, getIcon } from '@/utils';
|
|
30
30
|
import { Dropdown } from 'flowbite';
|
|
31
|
-
import
|
|
31
|
+
import { useAdminforth } from '@/adminforth';
|
|
32
32
|
import { VerticalTabs } from '@/afcl'
|
|
33
33
|
import { useRoute } from 'vue-router'
|
|
34
34
|
|
|
@@ -36,6 +36,7 @@ const route = useRoute()
|
|
|
36
36
|
const coreStore = useCoreStore();
|
|
37
37
|
const router = useRouter();
|
|
38
38
|
|
|
39
|
+
let { closeUserMenuDropdown } = useAdminforth();
|
|
39
40
|
const routerIsReady = ref(false);
|
|
40
41
|
const loginRedirectCheckIsReady = ref(false);
|
|
41
42
|
const dropdownUserButton = ref<HTMLElement | null>(null);
|
|
@@ -58,7 +59,7 @@ watch(dropdownUserButton, (el) => {
|
|
|
58
59
|
document.querySelector('#dropdown-user') as HTMLElement,
|
|
59
60
|
document.querySelector('[data-dropdown-toggle="dropdown-user"]') as HTMLElement,
|
|
60
61
|
);
|
|
61
|
-
|
|
62
|
+
closeUserMenuDropdown = () => dd.hide();
|
|
62
63
|
}
|
|
63
64
|
});
|
|
64
65
|
|
|
@@ -144,16 +144,17 @@ import {callAdminForthApi} from '@/utils';
|
|
|
144
144
|
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
145
145
|
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
|
|
146
146
|
import ShowTable from '@/components/ShowTable.vue';
|
|
147
|
-
import
|
|
147
|
+
import { useAdminforth } from '@/adminforth';
|
|
148
148
|
import { useI18n } from 'vue-i18n';
|
|
149
149
|
import { getIcon } from '@/utils';
|
|
150
|
-
import { type AdminForthComponentDeclarationFull } from '@/types/Common.js';
|
|
150
|
+
import { type AdminForthComponentDeclarationFull, type AdminForthResourceColumnCommon, type FieldGroup } from '@/types/Common.js';
|
|
151
151
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
152
152
|
|
|
153
153
|
const route = useRoute();
|
|
154
154
|
const router = useRouter();
|
|
155
155
|
const loading = ref(true);
|
|
156
156
|
const { t } = useI18n();
|
|
157
|
+
const { confirm, alert, show } = useAdminforth();
|
|
157
158
|
const coreStore = useCoreStore();
|
|
158
159
|
|
|
159
160
|
const actionLoadingStates = ref<Record<string, boolean>>({});
|
|
@@ -206,7 +207,7 @@ const allColumns = computed(() => {
|
|
|
206
207
|
|
|
207
208
|
const otherColumns = computed(() => {
|
|
208
209
|
const groupedColumnNames = new Set(
|
|
209
|
-
groups.value.flatMap(group => group.columns
|
|
210
|
+
groups.value.flatMap(group => group.columns?.map((col: AdminForthResourceColumnCommon) => col.name))
|
|
210
211
|
);
|
|
211
212
|
|
|
212
213
|
return coreStore.resource?.columns.filter(
|
|
@@ -215,7 +216,7 @@ const otherColumns = computed(() => {
|
|
|
215
216
|
});
|
|
216
217
|
|
|
217
218
|
async function deleteRecord() {
|
|
218
|
-
const data = await
|
|
219
|
+
const data = await confirm({
|
|
219
220
|
message: t('Are you sure you want to delete this item?'),
|
|
220
221
|
yes: t('Delete'),
|
|
221
222
|
no: t('Cancel'),
|
|
@@ -282,7 +283,7 @@ async function startCustomAction(actionId: string, extra: any) {
|
|
|
282
283
|
});
|
|
283
284
|
|
|
284
285
|
if (data.successMessage) {
|
|
285
|
-
|
|
286
|
+
alert({
|
|
286
287
|
message: data.successMessage,
|
|
287
288
|
variant: 'success'
|
|
288
289
|
});
|
|
@@ -294,7 +295,7 @@ async function startCustomAction(actionId: string, extra: any) {
|
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
|
|
297
|
-
|
|
298
|
+
show.refresh = () => {
|
|
298
299
|
(async () => {
|
|
299
300
|
try {
|
|
300
301
|
loading.value = true;
|