diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiGeneralHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiGeneralHelper.java index 2278e7dd..25146504 100644 --- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiGeneralHelper.java +++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiGeneralHelper.java @@ -199,6 +199,15 @@ public static List getWritableTimeAttributesForClass(ClassType classType .collect(Collectors.toList()); } + public static List getWritableTablesForClass(ClassType classType) { + return classType.getRelations().stream() + .filter(ReferenceType::isIsCollection) + .filter(r -> !r.isIsReadOnly()) + .map(NamedElement::getName) + .sorted() + .collect(Collectors.toList()); + } + public static boolean hasRelationRange(ReferenceType ref) { return ref.getIsRangeable() || ref.getIsSetable() || ref.getIsAddable(); } diff --git a/judo-ui-react/src/main/resources/actor/src/fragments/page/store-diff-body.hbs b/judo-ui-react/src/main/resources/actor/src/fragments/page/store-diff-body.hbs index 66542e6b..230763b1 100644 --- a/judo-ui-react/src/main/resources/actor/src/fragments/page/store-diff-body.hbs +++ b/judo-ui-react/src/main/resources/actor/src/fragments/page/store-diff-body.hbs @@ -8,12 +8,26 @@ const timeTypes: string[] = [{{# each (getWritableTimeAttributesForClass classType) as |aName| }} '{{ aName }}', {{/ each }}]; + const tableTypes: string[] = [{{# each (getWritableTablesForClass classType) as |aName| }} + '{{ aName }}', + {{/ each }}]; if (dateTypes.includes(attributeName as string)) { payloadDiff[attributeName] = uiDateToServiceDate(value); } else if (dateTimeTypes.includes(attributeName as string)) { payloadDiff[attributeName] = value; } else if (timeTypes.includes(attributeName as string)) { payloadDiff[attributeName] = uiTimeToServiceTime(value); + } else if (tableTypes.includes(attributeName as string)) { + const filteredList = []; + for (const filtered of value) { + const shallow = {...filtered}; + if (shallow.__identifier && shallow.__identifier.startsWith('JUDO_TMP_')) { + // remove temp identifier which has been put in place to mitigate mui key errors in tables. + delete shallow.__identifier; + } + filteredList.push(shallow); + } + payloadDiff[attributeName] = filteredList; } else { payloadDiff[attributeName] = value; } diff --git a/judo-ui-react/src/main/resources/actor/src/fragments/table/row-actions.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/fragments/table/row-actions.fragment.hbs index 5d297cc2..717b74a0 100644 --- a/judo-ui-react/src/main/resources/actor/src/fragments/table/row-actions.fragment.hbs +++ b/judo-ui-react/src/main/resources/actor/src/fragments/table/row-actions.fragment.hbs @@ -12,7 +12,7 @@ const {{ table.dataElement.name }}RowActions: TableRowAction<{{ classDataName ta }, {{ else }} action: async (row: {{ classDataName table.dataElement.target 'Stored' }}) => { - storeDiff('{{ table.dataElement.name }}', [...(ownerData.{{ table.dataElement.name }} || []).filter((e: {{ classDataName table.dataElement.target 'Stored' }}) => e.__signedIdentifier !== row.__signedIdentifier)]); + storeDiff('{{ table.dataElement.name }}', [...(ownerData.{{ table.dataElement.name }} || []).filter((e: {{ classDataName table.dataElement.target 'Stored' }}) => e.__identifier !== row.__identifier)]); }, {{/ if }} }, @@ -22,8 +22,19 @@ const {{ table.dataElement.name }}RowActions: TableRowAction<{{ classDataName ta id: '{{ createId action }}', label: t('judo.pages.table.delete', { defaultValue: 'Delete' }) as string, icon: , - action: async (row: {{ classDataName table.dataElement.target 'Stored' }}) => {{ actionFunctionName action }}({{# if (hasDataElementOwner action.dataElement) }}ownerData, {{/ if }}row, () => fetchOwnerData()), - disabled: (row: {{ classDataName table.dataElement.target 'Stored' }}) => {{# if table.enabledBy }}!ownerData.{{ table.enabledBy.name }} ||{{/ if }} editMode || !row.__deleteable, + {{# if table.dataElement.isRelationKindComposition }} + action: async (row: {{ classDataName table.dataElement.target 'Stored' }}) => { + if (!editMode) { + {{ actionFunctionName action }}({{# if (hasDataElementOwner action.dataElement) }}ownerData, {{/ if }}row, () => fetchOwnerData()); + } else { + storeDiff('{{ table.dataElement.name }}', [...(ownerData.{{ table.dataElement.name }} || []).filter((e: {{ classDataName table.dataElement.target 'Stored' }}) => e.__identifier !== row.__identifier)]); + } + }, + disabled: (row: {{ classDataName table.dataElement.target 'Stored' }}) => {{# if table.enabledBy }}!ownerData.{{ table.enabledBy.name }} ||{{/ if }} (row.__deleteable !== undefined && !row.__deleteable), + {{ else }} + action: async (row: {{ classDataName table.dataElement.target 'Stored' }}) => {{ actionFunctionName action }}({{# if (hasDataElementOwner action.dataElement) }}ownerData, {{/ if }}row, () => fetchOwnerData()), + disabled: (row: {{ classDataName table.dataElement.target 'Stored' }}) => {{# if table.enabledBy }}!ownerData.{{ table.enabledBy.name }} ||{{/ if }} editMode || !row.__deleteable, + {{/ if }} }, {{/ if }} {{# if action.isCallOperationAction }} diff --git a/judo-ui-react/src/main/resources/actor/src/pages/access/table/page-actions.hbs b/judo-ui-react/src/main/resources/actor/src/pages/access/table/page-actions.hbs index 02d02747..a9f2d6c1 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/access/table/page-actions.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/access/table/page-actions.hbs @@ -18,6 +18,7 @@ export interface PageActionsProps { export function PageActions (props: PageActionsProps) { const { isLoading, fetchData } = props; const { t } = useTranslation(); + const editMode = false; {{# each (getOnlyPageActions page) as |action| }} {{# unless action.isFilterAction }} @@ -30,7 +31,7 @@ export function PageActions (props: PageActionsProps) { {{# each (getUniquePageActions page) as |action| }} {{# if action.isCreateAction }} - diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/action/create-action.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/action/create-action.tsx.hbs index f1a6de15..83624b98 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/actions/action/create-action.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/action/create-action.tsx.hbs @@ -38,6 +38,7 @@ import { {{ pageActionFormComponentName action }} } from './{{ pageActionFormCom export type {{ actionFunctionTypeName action }} = () => ( {{# unless (isActionAccess action) }}owner: JudoIdentifiable<{{ classDataName action.dataElement.target '' }}>,{{/ unless }} + callerEditMode: boolean, successCallback: (result: {{ classDataName action.dataElement.target 'Stored' }}) => void, closedCallback?: () => void, ) => void; @@ -51,7 +52,7 @@ export const {{ actionFunctionHookName action }}: {{ actionFunctionTypeName acti {{/ unless }} const { enqueueSnackbar } = useSnackbar(); - return function {{ actionFunctionName action }} ({{# unless (isActionAccess action) }}owner: JudoIdentifiable<{{ classDataName action.dataElement.target '' }}>,{{/ unless }}successCallback: (result: {{ classDataName action.dataElement.target 'Stored' }}) => void, closedCallback?: () => void) { + return function {{ actionFunctionName action }} ({{# unless (isActionAccess action) }}owner: JudoIdentifiable<{{ classDataName action.dataElement.target '' }}>,{{/ unless }}callerEditMode: boolean, successCallback: (result: {{ classDataName action.dataElement.target 'Stored' }}) => void, closedCallback?: () => void) { {{# if (actionHasVisualElements action) }} createDialog({ {{# if action.target.dialogSize }} @@ -66,13 +67,16 @@ export const {{ actionFunctionHookName action }}: {{ actionFunctionTypeName acti }, children: ( <{{ pageActionFormComponentName action }} + callerEditMode={callerEditMode} successCallback={(result: {{ classDataName action.dataElement.target 'Stored' }}, open?: boolean) => { if(!open) { - closeDialog(); - enqueueSnackbar(t('judo.action.create.success', { defaultValue: 'Create successful' }), { - variant: 'success', - ...toastConfig.success, - }); + closeDialog(); + if (!callerEditMode) { + enqueueSnackbar(t('judo.action.create.success', { defaultValue: 'Create successful' }), { + variant: 'success', + ...toastConfig.success, + }); + } } successCallback(result); diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/actionForm/components/link.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/actionForm/components/link.tsx.hbs index 2be49ac7..fd378b20 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/actions/actionForm/components/link.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/actionForm/components/link.tsx.hbs @@ -73,6 +73,11 @@ export function {{ linkComponentName link }}(props: {{ linkComponentName link }} storeDiff('{{ link.dataElement.name }}', {{ link.dataElement.name }}); } } {{# each link.actions as |action| }} + {{# if action.isCreateAction }} + onCreate={ async () => {{ actionFunctionName action }}({{# unless (isActionAccess action) }}ownerData, {{/ unless }}editMode, (result: {{ classDataName action.dataElement.target 'Stored' }}) => { + storeDiff('{{ link.dataElement.name }}', result); + }) } + {{/ if }} {{# if action.isRemoveAction }} onRemove={ async () => { storeDiff('{{ link.dataElement.name }}', null); diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/actionForm/createActionForm.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/actionForm/createActionForm.tsx.hbs index 369aedee..4d2c62d4 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/actions/actionForm/createActionForm.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/actionForm/createActionForm.tsx.hbs @@ -32,6 +32,7 @@ import { {{/ each }} export interface {{ pageActionFormComponentName action }}Props { + callerEditMode?: boolean; successCallback: (result: {{ classDataName page.dataElement.target 'Stored' }}, open?: boolean) => void; cancel: () => void; {{# if (hasDataElementOwner action.dataElement) }} @@ -40,6 +41,7 @@ export interface {{ pageActionFormComponentName action }}Props { } export function {{ pageActionFormComponentName action }}({ + callerEditMode, successCallback, cancel, {{# if (hasDataElementOwner action.dataElement) }} @@ -104,9 +106,16 @@ export function {{ pageActionFormComponentName action }}({ setIsLoading(true); try { - const res = await {{ dataElementRelationName page.dataElement }}Impl.create{{ ucFirst page.dataElement.name }}({{# if (hasDataElementOwner action.dataElement) }}owner, {{/ if }}payloadDiff); - - return res; + if (!callerEditMode) { + // only call backend if the create dialog has been triggered on a View Page + return await {{ dataElementRelationName page.dataElement }}Impl.create{{ ucFirst page.dataElement.name }}({{# if (hasDataElementOwner action.dataElement) }}owner, {{/ if }}payloadDiff); + } else { + // add temp identifier in order to counter mui key errors in tables, the `storeDiff` function should take care of it. + return { + ...payloadDiff, + __identifier: `JUDO_TMP_${new Date()}`, + } as {{ classDataName page.dataElement.target 'Stored' }}; + } } catch (error) { handleCreateError(error, { setValidation }, data); } finally { @@ -181,30 +190,33 @@ export function {{ pageActionFormComponentName action }}({ if (result) { successCallback(result); - {{# unless (pageShouldOpenInDialog (getViewPageForCreatePage page application)) }} - navigate(routeTo{{ pageName (getViewPageForCreatePage page application) }}(result.__signedIdentifier)); - {{ else }} - createDialog({ - {{# if (adjustDialogSize (getViewPageForCreatePage page application)) }} - fullWidth: true, - maxWidth: '{{ getDialogSizeForViewPageOfCreatePage page application }}', - {{/ if }} - onClose: (event: object, reason: string) => { - if (reason !== 'backdropClick') { - closeDialog(); - } - }, - children: ( - <{{ pageName (getViewPageForCreatePage page application) }} - successCallback={ () => { - successCallback(result, true); - } } - cancel={closeDialog} - entry={result} - /> - ), - }); - {{/ unless }} + if (!callerEditMode) { + // only trigger followup behaviors if action was initiated on a View Page and not in editMode + {{# unless (pageShouldOpenInDialog (getViewPageForCreatePage page application)) }} + navigate(routeTo{{ pageName (getViewPageForCreatePage page application) }}(result.__signedIdentifier)); + {{ else }} + createDialog({ + {{# if (adjustDialogSize (getViewPageForCreatePage page application)) }} + fullWidth: true, + maxWidth: '{{ getDialogSizeForViewPageOfCreatePage page application }}', + {{/ if }} + onClose: (event: object, reason: string) => { + if (reason !== 'backdropClick') { + closeDialog(); + } + }, + children: ( + <{{ pageName (getViewPageForCreatePage page application) }} + successCallback={ () => { + successCallback(result, true); + } } + cancel={closeDialog} + entry={result} + /> + ), + }); + {{/ unless }} + } } } } disabled={isLoading}> {t('judo.pages.create-and-navigate', { defaultValue: 'Create and navigate' })} diff --git a/judo-ui-react/src/main/resources/actor/src/pages/components/link.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/pages/components/link.tsx.hbs index 9fc677ba..e71f68d7 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/components/link.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/components/link.tsx.hbs @@ -84,8 +84,12 @@ export function {{ linkComponentName link }}(props: {{ linkComponentName link }} onView={ async () => {{ actionFunctionName action }}(ownerData, ownerData?.{{ link.dataElement.name }}!, () => fetchOwnerData()) } {{/ if }} {{# if action.isCreateAction }} - onCreate={ async () => {{ actionFunctionName action }}({{# unless (isActionAccess action) }}ownerData, {{/ unless }}() => { - fetchOwnerData(); + onCreate={ async () => {{ actionFunctionName action }}({{# unless (isActionAccess action) }}ownerData, {{/ unless }}editMode, (result: {{ classDataName action.dataElement.target 'Stored' }}) => { + if (!editMode) { + fetchOwnerData(); + } else { + storeDiff('{{ link.dataElement.name }}', result); + } }) } {{/ if }} {{# if action.isDeleteAction }} @@ -117,7 +121,7 @@ export function {{ linkComponentName link }}(props: {{ linkComponentName link }} // we do not allow creation in such cases. createTrigger: !ownerData.{{ link.dataElement.name }} ? async () => { return new Promise((resolve) => { - {{ actionFunctionName (getCreateActionForLink link) }}({{# unless (isActionAccess action) }}ownerData, {{/ unless }}(result: {{ classDataName link.dataElement.target 'Stored' }}) => { + {{ actionFunctionName (getCreateActionForLink link) }}({{# unless (isActionAccess action) }}ownerData, {{/ unless }}editMode, (result: {{ classDataName link.dataElement.target 'Stored' }}) => { resolve(result); }, () => { resolve(undefined); diff --git a/judo-ui-react/src/main/resources/actor/src/pages/components/table/for-aggregation.hbs b/judo-ui-react/src/main/resources/actor/src/pages/components/table/for-aggregation.hbs index 350cc3ba..d8049bc5 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/components/table/for-aggregation.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/components/table/for-aggregation.hbs @@ -188,11 +188,15 @@ export const {{ tableComponentName table }} = (props: {{ tableComponentName tabl id="{{ createId action }}" startIcon={} variant="text" - onClick={() => {{ actionFunctionName action }}({{# unless (isActionAccess action) }}ownerData, {{/ unless }}() => { - fetchOwnerData(); + onClick={() => {{ actionFunctionName action }}({{# unless (isActionAccess action) }}ownerData, {{/ unless }}editMode, (result: {{ classDataName action.dataElement.target 'Stored' }}) => { + if (!editMode) { + fetchOwnerData(); + } else { + storeDiff('{{ table.dataElement.name }}', [...(ownerData.{{ table.dataElement.name }} || []), result]); + } })} disabled={ - editMode + {{# if table.dataElement.isRelationKindComposition }}false{{ else }}editMode{{/ if }} || isOwnerLoading || {{# if table.enabledBy }}!ownerData.{{ table.enabledBy.name }} ||{{/ if }} {{ boolValue table.dataElement.isReadOnly }} || !isFormUpdateable() diff --git a/judo-ui-react/src/main/resources/actor/src/pages/relation/table/page-actions.hbs b/judo-ui-react/src/main/resources/actor/src/pages/relation/table/page-actions.hbs index 2c413da5..dc34fd55 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/relation/table/page-actions.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/relation/table/page-actions.hbs @@ -24,6 +24,7 @@ export interface PageActionsProps { export function PageActions (props: PageActionsProps) { const { isLoading, fetchData, signedIdentifier } = props; const { t } = useTranslation(); + const editMode = false; {{# each (getOnlyPageActions page) as |action| }} {{# unless action.isFilterAction }} @@ -58,7 +59,7 @@ export function PageActions (props: PageActionsProps) { {{/ if }} {{# if action.isCreateAction }} -