From b1ad5303a6fb034eb724d0de79d47c1f23e9ef82 Mon Sep 17 00:00:00 2001 From: Norbert Csaba Herczeg Date: Wed, 22 Nov 2023 17:54:49 +0100 Subject: [PATCH] JNG-4838 wire in action hooks and operation call post action hooks --- docs/pages/01_ui_react.adoc | 125 +----------------- .../judo/ui/generator/react/UiPageHelper.java | 4 + .../ui/generator/react/UiPandinoHelper.java | 53 +++++++- .../actor/src/containers/container.tsx.hbs | 5 + .../widget-fragments/binarytypeinput.hbs | 2 +- .../containers/widget-fragments/button.hbs | 2 +- .../widget-fragments/buttongroup.hbs | 2 +- .../containers/widget-fragments/dateinput.hbs | 2 +- .../widget-fragments/datetimeinput.hbs | 2 +- .../containers/widget-fragments/divider.hbs | 2 +- .../widget-fragments/enumerationcombo.hbs | 2 +- .../widget-fragments/enumerationradio.hbs | 2 +- .../widget-fragments/flex/column.hbs | 2 +- .../containers/widget-fragments/flex/row.hbs | 2 +- .../containers/widget-fragments/formatted.hbs | 2 +- .../src/containers/widget-fragments/label.hbs | 2 +- .../src/containers/widget-fragments/link.hbs | 2 +- .../widget-fragments/numericinput.hbs | 2 +- .../containers/widget-fragments/spacer.hbs | 2 +- .../containers/widget-fragments/switch.hbs | 2 +- .../widget-fragments/tabcontroller.hbs | 2 +- .../src/containers/widget-fragments/table.hbs | 2 +- .../src/containers/widget-fragments/text.hbs | 2 +- .../containers/widget-fragments/textarea.hbs | 2 +- .../containers/widget-fragments/textinput.hbs | 2 +- .../containers/widget-fragments/timeinput.hbs | 2 +- .../widget-fragments/trinarylogiccombo.hbs | 2 +- .../src/custom/custom-element-types.ts.hbs | 6 - .../resources/actor/src/dialogs/index.tsx.hbs | 28 +++- .../container/common-imports.fragment.hbs | 1 + .../actions/CallOperationAction.fragment.hbs | 64 +++++---- .../resources/actor/src/pages/index.tsx.hbs | 21 +++ .../actor/src/utilities/error-handling.ts.hbs | 15 --- 33 files changed, 168 insertions(+), 200 deletions(-) diff --git a/docs/pages/01_ui_react.adoc b/docs/pages/01_ui_react.adoc index b472f11d..cc0a864d 100644 --- a/docs/pages/01_ui_react.adoc +++ b/docs/pages/01_ui_react.adoc @@ -333,87 +333,6 @@ class CustomL10NProvider implements L10NTranslationProvider { } ---- -=== Implementing a custom error processor - -Errors which may be triggered by the application can be customized. The level of customization only applies to: - -- response toast triggering -- response toast message -- validation error feedbacks - -Whether and what errors are triggered cannot be modified! - -The pattern with regards to how can this be achieved is similar to the previous. - -You need to register a service for the `ERROR_PROCESSOR_HOOK_INTERFACE_KEY` with variable service parameters depending -on the error handler in question. - -> This is due to the fact that different types of errors may be configured in a more general or specific way, and service - properties help target these services. - -In the following example we will customize the validation error message for the `MISSING_REQUIRED_ATTRIBUTE` error code -only for a certain `Create` operation, and everything else will behave as per default. - -[source,typescriptjsx] ----- -import { useTranslation } from 'react-i18next'; -import type { BundleContext } from '@pandino/pandino-api'; -import type { ApplicationCustomizer } from './interfaces'; -import type { ErrorHandlingOption, ErrorProcessorHook, ErrorProcessResult, ServerError } from '../utilities/error-handling'; -import { ERROR_PROCESSOR_HOOK_INTERFACE_KEY } from '../utilities/error-handling'; -import { useSnackbar } from '../components'; -import { ViewGalaxy } from '../generated/data-api'; - -export class DefaultApplicationCustomizer implements ApplicationCustomizer { - async customize(context: BundleContext): Promise { - // Mind the service parameters! Without these, our registration wouldn't match. - context.registerService>(ERROR_PROCESSOR_HOOK_INTERFACE_KEY, galaxiesCreateFormErrorHook, { - operation: 'Create', - component: 'PageCreateGalaxiesForm', - }); - } -} - -const galaxiesCreateFormErrorHook: ErrorProcessorHook = () => { - const { t } = useTranslation(); - const [enqueueSnackbar] = useSnackbar(); - - /** - * @param {ErrorProcessResult} defaultResults Contains the pre-filled results, the usage is optional - * @param {any} [payload] Is present depending on the use-case, usually contains the data sent to the backend - */ - return (error: any, defaultResults: ErrorProcessResult, options?: ErrorHandlingOption, payload?: ViewGalaxy) => { - // only modify validation results - if (error?.response?.status === 400) { - const errorList = error.response.data as ServerError[]; - // if the host page has validation errors turned on - if (typeof options?.setValidation === 'function' && defaultResults.validation) { - // filter errors where we know the affected field's name - errorList.filter((e) => e.location).forEach((error) => { - // only modify prepared results for required errors - if (error.code === 'MISSING_REQUIRED_ATTRIBUTE') { - defaultResults.validation.set(error.location, t('you forgot to fill this') as string); - } - }); - - options.setValidation(defaultResults.validation); - } - } - - // if by default we have a toast message, display it, but we can enforce the same by calling - // `enqueueSnackbar()` without any condition. - if (defaultResults.toastMessage) { - enqueueSnackbar(defaultResults.toastMessage, defaultResults.errorToastConfig); - } - }; -}; ----- - -As explained in the comments, **the provisioning of service parameters is mandatory!** - -The best way to find out what services requires what parameters, you only need to search for the `useErrorHandler` hook's -usage, and you should be able to see how does the corresponding `filter` look like. - === Implementing a custom visual element Every Visual element implementation can be replaced by a custom one, given in the model the `customImplementation` @@ -601,7 +520,7 @@ the action type, and return parameter (or lack thereof). * pop a success toast and * refresh the current page -*Overriding the above logic can ge done by:* +*Overriding the above logic can be done by:* - implementing the `PostHandlerHook` interface for an operation - registering this implementation in the `application-customizer.tsx` file @@ -739,48 +658,6 @@ const customGodCreateNameOnBlurHook: GodCreateNameOnBlurHook = () => { }; ---- -=== Implementing filter initializers for components representing single relations - -There are cases when we need to provide additional filtering for Link components besides the modeled range expressions. -Usually this occurs when we need to rely on what state our current forms/pages are in. - -We can achieve this by providing a FilterInitializer service for the Link component which we would like to fine tune. - -Just like every other hook, these have their corresponding `INTERFACE_KEY` and the interfaces for the keys. - -*src/custom/application-customizer.tsx:* -[source,typescriptjsx] ----- -import type { BundleContext } from '@pandino/pandino-api'; -import { ApplicationCustomizer } from './interfaces'; -import { - CITY_LINK_FILTER_INITIALIZER_INTERFACE_KEY, CityLinkFilterInitializerHook -} from "~/pages/admin/admin/dashboardhome/view/actions/adminDashboard/AdminDashboardCreateIssueForm/components/CityLink"; -import { _StringOperation, AdminCityStored, AdminCreateIssueInput } from "~/generated/data-api"; -import { Filter, FilterType } from "~/components-api"; -import { buildFilter } from "~/utilities"; - -export class DefaultApplicationCustomizer implements ApplicationCustomizer { - async customize(context: BundleContext): Promise { - // register your implementations here - context.registerService(CITY_LINK_FILTER_INITIALIZER_INTERFACE_KEY, cityLinkFilterInitializer); - } -} - -// We are building an initializer for the "City" component on the AdminDashboardCreateIssueForm -const cityLinkFilterInitializer: CityLinkFilterInitializerHook = () => { - return (ownerData: AdminCreateIssueInput): Filter[] | undefined => { - // we are interested in another single's value on the form (county) - if (ownerData.county) { - // we are utilizing the `buildFilter` helper to create a basic `Filter` instance - return [ - buildFilter(FilterType.string, _StringOperation.equal, 'county', ownerData.county.name), - ]; - } - }; -}; ----- - === (Global) Hotkey support Currently you can wire in hotkeys for access-based actions, such as triggering create dialogs. diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageHelper.java index fd60857b..6bc3b91b 100644 --- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageHelper.java +++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageHelper.java @@ -172,6 +172,10 @@ public static List getApiImportsForPage(PageDefinition pageDefinition) { res.add(classDataName(actionDefinition.getTargetType(), "Stored")); res.add(classDataName(actionDefinition.getTargetType(), "QueryCustomizer")); } + if (actionDefinition instanceof CallOperationActionDefinition callOperationActionDefinition && callOperationActionDefinition.getOperation().getOutput() != null) { + res.add(classDataName(callOperationActionDefinition.getOperation().getOutput().getTarget(), "")); + res.add(classDataName(callOperationActionDefinition.getOperation().getOutput().getTarget(), "Stored")); + } } if (pageDefinition.getContainer().isIsSelector()) { diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPandinoHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPandinoHelper.java index 088c0768..6cd594e0 100644 --- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPandinoHelper.java +++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPandinoHelper.java @@ -21,9 +21,17 @@ */ import hu.blackbelt.judo.generator.commons.annotations.TemplateHelper; -import hu.blackbelt.judo.meta.ui.VisualElement; +import hu.blackbelt.judo.meta.ui.*; import lombok.extern.java.Log; -import org.springframework.util.StringUtils; + +import java.util.*; + +import static hu.blackbelt.judo.ui.generator.react.UiPageContainerHelper.containerComponentName; +import static hu.blackbelt.judo.ui.generator.react.UiPageContainerHelper.simpleActionDefinitionName; +import static hu.blackbelt.judo.ui.generator.react.UiWidgetHelper.collectVisualElementsMatchingCondition; +import static hu.blackbelt.judo.ui.generator.react.UiWidgetHelper.componentName; +import static hu.blackbelt.judo.ui.generator.typescript.rest.commons.UiCommonsHelper.firstToLower; +import static hu.blackbelt.judo.ui.generator.typescript.rest.commons.UiCommonsHelper.firstToUpper; @Log @TemplateHelper @@ -33,10 +41,49 @@ public static String camelCaseNameToInterfaceKey(String name) { } public static String getCustomizationComponentInterface(VisualElement element) { - return /*pageName(element.getPageDefinition()) + */StringUtils.capitalize(element.getName()); + return /*pageName(element.getPageDefinition()) + */componentName(element); } public static String getCustomizationComponentInterfaceKey(VisualElement element) { return camelCaseNameToInterfaceKey(getCustomizationComponentInterface(element)); } + + public static SortedSet getVisualElementsWithCustomImplementation(PageContainer container) { + SortedSet result = new TreeSet<>(Comparator.comparing((VisualElement v) -> v.getFQName().trim())); + + collectVisualElementsMatchingCondition(container, VisualElement::isCustomImplementation, result); + + return result; + } + + public static String pageActionFQName(Action action) { + String adn = simpleActionDefinitionName(action.getActionDefinition()); + PageDefinition pageDefinition = (PageDefinition) action.eContainer(); + + return firstToLower(containerComponentName(pageDefinition.getContainer())) + firstToUpper(adn); + } + + public static String pageActionTypeName(Action action) { + String adn = simpleActionDefinitionName(action.getActionDefinition()); + PageDefinition pageDefinition = (PageDefinition) action.eContainer(); + + return containerComponentName(pageDefinition.getContainer()) + firstToUpper(adn); + } + + public static String pageActionHookInterfaceKey(Action action) { + String asd = pageActionFQName(action); + return camelCaseNameToInterfaceKey(firstToUpper(asd)) + "_HOOK_INTERFACE_KEY"; + } + + public static String pageActionInterfaceKey(Action action) { + String asd = pageActionFQName(action); + return camelCaseNameToInterfaceKey(firstToUpper(asd)) + "_INTERFACE_KEY"; + } + + public static List getAllCallOperationActions(PageDefinition pageDefinition) { + return pageDefinition.getActions().stream() + .filter(Action::getIsCallOperationAction) + .sorted(Comparator.comparing(NamedElement::getFQName)) + .toList(); + } } diff --git a/judo-ui-react/src/main/resources/actor/src/containers/container.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/container.tsx.hbs index fa35b744..e46c7dfe 100644 --- a/judo-ui-react/src/main/resources/actor/src/containers/container.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/containers/container.tsx.hbs @@ -21,6 +21,11 @@ {{/ each }} {{# unless (containerIsEmptyDashboard container) }} +{{# each (getVisualElementsWithCustomImplementation container) as |ve| }} + export const {{ getCustomizationComponentInterfaceKey ve }} = '{{ getCustomizationComponentInterface ve }}'; + export interface {{ getCustomizationComponentInterface ve }} extends FC> {} +{{/ each }} + export interface {{ pageContainerActionDefinitionTypeName container }}{{# if (containerHasRelationComponents container) }} extends {{# each (getContainerActionsExtends container) as |ext| }}{{ ext }}{{# unless @last}},{{/ unless}}{{/ each }}{{/ if }} { {{# each (getContainerOwnActionDefinitions container) as |actionDefinition| }} {{ simpleActionDefinitionName actionDefinition }}?: ({{{ getContainerOwnActionParameters actionDefinition container }}}) => Promise<{{ getContainerOwnActionReturnType actionDefinition container }}>; diff --git a/judo-ui-react/src/main/resources/actor/src/containers/widget-fragments/binarytypeinput.hbs b/judo-ui-react/src/main/resources/actor/src/containers/widget-fragments/binarytypeinput.hbs index b3e73f08..af275ae0 100644 --- a/judo-ui-react/src/main/resources/actor/src/containers/widget-fragments/binarytypeinput.hbs +++ b/judo-ui-react/src/main/resources/actor/src/containers/widget-fragments/binarytypeinput.hbs @@ -2,7 +2,7 @@ {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if this.customImplementation }} {{# if this.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} {{# if child.customImplementation }} { */ readonly editMode: boolean; - /** - * The "payloadDiff" is a `Record` where we only store changes applied to the state, and not the state as a whole. - * We are usually only sending this "diffed" payload on CRUD Update operations. - */ - readonly payloadDiff: Record; - /** * State modifier method which implicitly updates the `data` and adds an entry to the `payloadDiff` as well. * diff --git a/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs index 13014a64..2a0b58ef 100644 --- a/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs @@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef, useState, lazy, Suspense } from 'react'; {{# unless (containerIsEmptyDashboard page.container) }} import { OBJECTCLASS } from '@pandino/pandino-api'; + import { useTrackService } from '@pandino/react-hooks'; import type { JudoIdentifiable } from '@judo/data-api-common'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; @@ -41,6 +42,26 @@ import { useCallback, useEffect, useRef, useState, lazy, Suspense } from 'react' {{/ unless }} {{# unless (containerIsEmptyDashboard page.container) }} + export const {{ camelCaseNameToInterfaceKey (pageName page) }}_ACTIONS_HOOK_INTERFACE_KEY = '{{ containerComponentName page.container }}ActionsHook'; + export type {{ containerComponentName page.container }}DialogActionsExtended = {{ containerComponentName page.container }}DialogActions & { + {{# each (getAllCallOperationActions page) as |action| }} + post{{ firstToUpper (simpleActionDefinitionName action.actionDefinition) }}?: ( + {{# if action.actionDefinition.targetType }}target: {{ classDataName action.actionDefinition.targetType 'Stored' }},{{/ if }} + {{# if action.actionDefinition.operation.output }}output: {{ classDataName action.actionDefinition.operation.output.target '' }},{{/ if }} + {{# if page.container.form }}onSubmit: (result?: {{# if (pageHasOutputTarget page) }}{{ classDataName (getPageOutputTarget page) 'Stored' }}{{ else }}{{ dialogDataType page }}{{# if page.container.table }}[]{{/ if }}{{/ if }}) => void{{/ if }} + ) => Promise; + {{/ each }} + }; + export type {{ containerComponentName page.container }}ActionsHook = ( + ownerData: any, + data: {{ dialogDataType page }}{{# if page.container.table }}[]{{/ if }}, + editMode: boolean, + {{# unless page.container.table }} + storeDiff: (attributeName: keyof {{ classDataName (getReferenceClassType page) '' }}, value: any) => void, + {{/ unless }} + {{# if page.container.isSelector }}selectionDiff: {{ dialogDataType page }}[],{{/ if }} + ) => {{ containerComponentName page.container }}DialogActionsExtended; + export const use{{ pageName page }} = (): ({{{ getDialogOpenParameters page }}}) => Promise void; {{# if (pageHasOutputTarget page) }} - onSubmit: ({{# if (pageHasOutputTarget page) }}result?: {{ classDataName (getPageOutputTarget page) 'Stored' }}{{/ if }}) => void; + onSubmit: (result?: {{ classDataName (getPageOutputTarget page) 'Stored' }}) => void; {{ else }} onSubmit: (result?: {{ dialogDataType page }}{{# if page.container.table }}[]{{/ if }}) => void; {{/ if }} @@ -167,6 +188,10 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) { {{/ if }} {{/ unless }} + // Pandino Action overrides + const { service: customActionsHook } = useTrackService<{{ containerComponentName page.container }}ActionsHook>(`(${OBJECTCLASS}=${ {{~ camelCaseNameToInterfaceKey (pageName page) }}_ACTIONS_HOOK_INTERFACE_KEY})`); + const customActions: {{ containerComponentName page.container }}DialogActionsExtended | undefined = customActionsHook && customActionsHook(ownerData, data, editMode{{# unless page.container.table }}, storeDiff{{/ unless }}{{# if page.container.isSelector }}, selectionDiff{{/ if }}); + // Dialog hooks {{# each (getRelatedDialogs page false) as |relatedDialog| }} const open{{ pageName relatedDialog }} = use{{ pageName relatedDialog }}(); @@ -188,6 +213,7 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) { {{# each page.actions as |action| }} {{ simpleActionDefinitionName action.actionDefinition }}, {{/ each }} + ...(customActions ?? {}), }; // Effect section diff --git a/judo-ui-react/src/main/resources/actor/src/fragments/container/common-imports.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/fragments/container/common-imports.fragment.hbs index 02065746..c4da0e83 100644 --- a/judo-ui-react/src/main/resources/actor/src/fragments/container/common-imports.fragment.hbs +++ b/judo-ui-react/src/main/resources/actor/src/fragments/container/common-imports.fragment.hbs @@ -5,6 +5,7 @@ import { NumericFormat } from 'react-number-format'; import { LoadingButton } from '@mui/lab'; import { OBJECTCLASS } from '@pandino/pandino-api'; import type { JudoIdentifiable } from '@judo/data-api-common'; +import type { CustomFormVisualElementProps } from '~/custom'; import { ComponentProxy } from '@pandino/react-hooks'; import { clsx } from 'clsx'; import Box from '@mui/material/Box'; diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/CallOperationAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/CallOperationAction.fragment.hbs index f8dd89a8..429554cb 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/actions/CallOperationAction.fragment.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/CallOperationAction.fragment.hbs @@ -30,31 +30,19 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if a {{/ if }} ); - enqueueSnackbar(t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string, { - variant: 'success', - ...toastConfig.success, - }); + if (customActions?.post{{ firstToUpper (simpleActionDefinitionName action.actionDefinition) }}) { + await customActions.post{{ firstToUpper (simpleActionDefinitionName action.actionDefinition) }}( + {{# if action.actionDefinition.targetType }}target!,{{/ if }} + {{# if operation.output }}result,{{/ if }} + {{# if page.container.form }}onSubmit{{/ if }} + ); + } else { + enqueueSnackbar(t('judo.action.operation.success', { defaultValue: 'Operation executed successfully' }) as string, { + variant: 'success', + ...toastConfig.success, + }); - {{# if page.container.form }} - {{# if operation.output }} - if (result) { - onSubmit(result); - } else { - onSubmit(); - } - {{ else }} - onSubmit(); - {{/ if }} - {{/ if }} - {{# if page.container.view }} - if (!editMode) { - {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }} - await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(pageQueryCustomizer)); - {{/ with }} - } - {{/ if }} - {{# if page.container.table }} - {{# if page.container.isSelector }} + {{# if page.container.form }} {{# if operation.output }} if (result) { onSubmit(result); @@ -62,12 +50,32 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if a onSubmit(); } {{ else }} - onSubmit(selectionDiff); + onSubmit(); + {{/ if }} + {{/ if }} + {{# if page.container.view }} + if (!editMode) { + {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }} + await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(pageQueryCustomizer)); + {{/ with }} + } + {{/ if }} + {{# if page.container.table }} + {{# if page.container.isSelector }} + {{# if operation.output }} + if (result) { + onSubmit(result); + } else { + onSubmit(); + } + {{ else }} + onSubmit(selectionDiff); + {{/ if }} + {{ else }} + setRefreshCounter((prev) => prev + 1); {{/ if }} - {{ else }} - setRefreshCounter((prev) => prev + 1); {{/ if }} - {{/ if }} + } } catch (error) { {{# unless page.container.table }} handleError<{{ classDataName (getReferenceClassType page) '' }}>(error, { setValidation }, data); diff --git a/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs index 724f788b..c5d08fe2 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs @@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef, useState, lazy, Suspense } from 'react'; {{# unless (containerIsEmptyDashboard page.container) }} import { OBJECTCLASS } from '@pandino/pandino-api'; + import { useTrackService } from '@pandino/react-hooks'; import type { JudoIdentifiable } from '@judo/data-api-common'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; @@ -42,6 +43,21 @@ import { useCallback, useEffect, useRef, useState, lazy, Suspense } from 'react' {{/ unless }} {{# unless (containerIsEmptyDashboard page.container) }} + export const {{ camelCaseNameToInterfaceKey (pageName page) }}_ACTIONS_HOOK_INTERFACE_KEY = '{{ containerComponentName page.container }}ActionsHook'; + export type {{ containerComponentName page.container }}PageActionsExtended = {{ containerComponentName page.container }}PageActions & { + {{# each (getAllCallOperationActions page) as |action| }} + post{{ firstToUpper (simpleActionDefinitionName action.actionDefinition) }}?: ({{# if action.actionDefinition.targetType }}target: {{ classDataName action.actionDefinition.targetType 'Stored' }}{{/ if }}{{# if action.actionDefinition.operation.output }}{{# if action.actionDefinition.targetType }},{{/ if }}output: {{ classDataName action.actionDefinition.operation.output.target '' }}{{/ if }}) => Promise; + {{/ each }} + }; + export type {{ containerComponentName page.container }}ActionsHook = ( + data: {{ classDataName (getReferenceClassType page) 'Stored' }}{{# if page.container.table }}[]{{/ if }}, + editMode: boolean, + {{# unless page.container.table }} + storeDiff: (attributeName: keyof {{ classDataName (getReferenceClassType page) '' }}, value: any) => void, + {{/ unless }} + {{# if page.container.isSelector }}selectionDiff: {{ classDataName (getReferenceClassType page) 'Stored' }}[],{{/ if }} + ) => {{ containerComponentName page.container }}PageActionsExtended; + {{# unless page.container.table }} {{# if isDebugPrint }}// include: actor/src/fragments/page/payload-converter.fragment.hbs{{/ if }} {{> actor/src/fragments/page/payload-converter.fragment.hbs classType=page.dataElement.target page=page }} @@ -113,6 +129,10 @@ export default function {{ pageName page }}() { {{/ if }} {{/ unless }} + // Pandino Action overrides + const { service: customActionsHook } = useTrackService<{{ containerComponentName page.container }}ActionsHook>(`(${OBJECTCLASS}=${ {{~ camelCaseNameToInterfaceKey (pageName page) }}_ACTIONS_HOOK_INTERFACE_KEY})`); + const customActions: {{ containerComponentName page.container }}PageActionsExtended | undefined = customActionsHook && customActionsHook(data, editMode{{# unless page.container.table }}, storeDiff{{/ unless }}{{# if page.container.isSelector }}, selectionDiff{{/ if }}); + // Dialog hooks {{# each (getRelatedDialogs page false) as |relatedDialog| }} const open{{ pageName relatedDialog }} = use{{ pageName relatedDialog }}(); @@ -141,6 +161,7 @@ export default function {{ pageName page }}() { {{# each page.actions as |action| }} {{ simpleActionDefinitionName action.actionDefinition }}, {{/ each }} + ...(customActions ?? {}), }; // Effect section diff --git a/judo-ui-react/src/main/resources/actor/src/utilities/error-handling.ts.hbs b/judo-ui-react/src/main/resources/actor/src/utilities/error-handling.ts.hbs index 7727dcbc..139d19a5 100644 --- a/judo-ui-react/src/main/resources/actor/src/utilities/error-handling.ts.hbs +++ b/judo-ui-react/src/main/resources/actor/src/utilities/error-handling.ts.hbs @@ -35,14 +35,6 @@ export interface ValidationError { location: string; } -export type ErrorProcessor = (error: any, defaultResults: ErrorProcessResult, options?: ErrorHandlingOption, payload?: T) => void; - -export const ERROR_PROCESSOR_HOOK_INTERFACE_KEY = 'ErrorProcessorHook'; - -export type ErrorProcessorHook = () => ErrorProcessor; - -// export type ErrorHandler = (error: any, options?: ErrorHandlingOption, payload?: T) => void; - export interface ErrorProcessResult { errorToastConfig: OptionsObject, toastMessage?: string | undefined; @@ -52,8 +44,6 @@ export interface ErrorProcessResult { export const useErrorHandler = () => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); - // const { service: processorHook } = useTrackService>(filter); - // const customErrorProcessor = processorHook && processorHook(); return (error: any, options?: ErrorHandlingOption, payload?: T) => { console.error(error); @@ -111,11 +101,6 @@ export const useErrorHandler = () => { } } - // if (typeof customErrorProcessor === 'function') { - // customErrorProcessor(error, errorResults, options, payload); - // return; - // } - if (errorResults.toastMessage) { enqueueSnackbar(errorResults.toastMessage, errorResults.errorToastConfig); }