diff --git a/adminforth/modules/configValidator.ts b/adminforth/modules/configValidator.ts index 43a7c8e3f..6c86d0688 100644 --- a/adminforth/modules/configValidator.ts +++ b/adminforth/modules/configValidator.ts @@ -330,6 +330,31 @@ export default class ConfigValidator implements IConfigValidator { return showInTransformedToObject as ShowIn; } + validateAndNormalizeCustomActions(resInput: AdminForthResourceInput, res: Partial, errors: string[]): any[] { + if (!resInput.options?.actions) { + return []; + } + + const actions = [...resInput.options.actions]; + + actions.forEach((action) => { + if (!action.name) { + errors.push(`Resource "${res.resourceId}" has action without name`); + } + + if (!action.action) { + errors.push(`Resource "${res.resourceId}" action "${action.name}" must have action function`); + } + + // Generate ID if not present + if (!action.id) { + action.id = md5hash(action.name); + } + }); + + return actions; + } + validateAndNormalizeResources(errors: string[], warnings: string[]): AdminForthResource[] { if (!this.inputConfig.resources) { errors.push('No resources defined, at least one resource must be defined'); @@ -590,6 +615,7 @@ export default class ConfigValidator implements IConfigValidator { } options.bulkActions = this.validateAndNormalizeBulkActions(resInput, res, errors); + options.actions = this.validateAndNormalizeCustomActions(resInput, res, errors); // if pageInjection is a string, make array with one element. Also check file exists const possibleInjections = ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom', 'threeDotsDropdownItems', 'customActionIcons']; diff --git a/adminforth/modules/restApi.ts b/adminforth/modules/restApi.ts index 003a0b45c..fdcdd364f 100644 --- a/adminforth/modules/restApi.ts +++ b/adminforth/modules/restApi.ts @@ -1048,5 +1048,31 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { this.adminforth.activatedPlugins.forEach((plugin) => { plugin.setupEndpoints(server); }); + + server.endpoint({ + method: 'POST', + path: '/start_custom_action', + handler: async ({ body, adminUser, tr }) => { + const { resourceId, actionId, recordId } = body; + const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId); + if (!resource) { + return { error: await tr(`Resource {resourceId} not found`, 'errors', { resourceId }) }; + } + console.log("resource", actionId); + const action = resource.options.actions.find((act) => act.id == actionId); + if (!action) { + return { error: await tr(`Action {actionId} not found`, 'errors', { actionId }) }; + } + + const response = await action.action({ recordId, adminUser, resource, tr }); + + return { + actionId, + recordId, + resourceId, + ...response + } + } + }); } } \ No newline at end of file diff --git a/adminforth/spa/src/components/ResourceListTable.vue b/adminforth/spa/src/components/ResourceListTable.vue index 8c48f4ff4..e4d1669c4 100644 --- a/adminforth/spa/src/components/ResourceListTable.vue +++ b/adminforth/spa/src/components/ResourceListTable.vue @@ -172,6 +172,19 @@ :record="row" /> + + @@ -280,7 +293,7 @@ import { getCustomComponent } from '@/utils'; import { useCoreStore } from '@/stores/core'; import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi'; import SkeleteLoader from '@/components/SkeleteLoader.vue'; - +import { getIcon } from '@/utils'; import { IconInboxOutline, } from '@iconify-prerendered/vue-flowbite'; @@ -505,4 +518,39 @@ async function deleteRecord(row) { }; } } + +const actionLoadingStates = ref({}); + +async function startCustomAction(actionId, row) { + actionLoadingStates.value[actionId] = true; + + const data = await callAdminForthApi({ + path: '/start_custom_action', + method: 'POST', + body: { + resourceId: props.resource.resourceId, + actionId: actionId, + recordId: row._primaryKeyValue + } + }); + + actionLoadingStates.value[actionId] = false; + + if (data?.ok) { + emits('update:records', true); + + if (data.successMessage) { + adminforth.alert({ + message: data.successMessage, + variant: 'success' + }); + } + } + + if (data?.error) { + showErrorTost(data.error); + } +} + + \ No newline at end of file diff --git a/adminforth/spa/src/components/ThreeDotsMenu.vue b/adminforth/spa/src/components/ThreeDotsMenu.vue index 7ad4d9915..73851f40d 100644 --- a/adminforth/spa/src/components/ThreeDotsMenu.vue +++ b/adminforth/spa/src/components/ThreeDotsMenu.vue @@ -1,5 +1,5 @@