diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java index a4c4ead7..1cca93e6 100644 --- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java +++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java @@ -316,7 +316,7 @@ public static String tableToolbarButtonDisabledConditions(Button button, Table t String result = ""; if (!container.isTable()) { - if (button.getActionDefinition().getIsOpenFormAction()) { + if (button.getActionDefinition().getIsOpenFormAction() || button.getActionDefinition().getIsBulkDeleteAction()) { result += "editMode || "; } else if (button.getActionDefinition().getIsOpenSelectorAction() || button.getActionDefinition().getIsClearAction()) { if (container.isView()) { @@ -325,7 +325,10 @@ public static String tableToolbarButtonDisabledConditions(Button button, Table t } } if (button.getActionDefinition().isIsBulk() && button.getHiddenBy() != null) { - result += "!selectedRows.current.every(s => !s." + button.getHiddenBy().getName() + ") ||"; + result += "!selectedRows.current.every(s => !s." + button.getHiddenBy().getName() + ") || "; + } + if (button.getActionDefinition().getIsBulkDeleteAction()) { + result += "selectedRows.current.some(s => !s.__deleteable) || "; } return result + "isLoading"; diff --git a/judo-ui-react/src/main/resources/actor/src/containers/components/table.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/components/table.tsx.hbs index 29255c3f..917b2d13 100644 --- a/judo-ui-react/src/main/resources/actor/src/containers/components/table.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/containers/components/table.tsx.hbs @@ -42,7 +42,9 @@ import { {{ else }} getUpdatedRowsSelected, {{/ if }} + {{# if table.isEager }} applyInMemoryFilters, + {{/ if }} fileHandling, serviceDateToUiDate, serviceTimeToUiTime, @@ -50,8 +52,8 @@ import { processQueryCustomizer, useErrorHandler, } from '~/utilities'; -import type { TableRowAction } from '~/utilities'; -import { {{# if (tableHasBulkOperations table) }}useCRUDDialog, {{/ if }}useDataStore } from '~/hooks'; +import type { DialogResult, TableRowAction } from '~/utilities'; +import { useDataStore } from '~/hooks'; import { OBJECTCLASS } from '@pandino/pandino-api'; {{# if (stringValueIsTrue useTableRowHighlighting) }} import { useTrackService } from '@pandino/react-hooks'; @@ -71,7 +73,11 @@ export interface {{ tableComponentName table }}ActionDefinitions { {{# if actionDefinition.isSelectorRangeAction }} {{ simpleActionDefinitionName actionDefinition }}?: (queryCustomizer: {{ classDataName (getReferenceClassType table) 'QueryCustomizer' }}) => Promise<{{ classDataName (getReferenceClassType table) 'Stored' }}[]>; {{ else }} - {{ simpleActionDefinitionName actionDefinition }}?: ({{# if actionDefinition.targetType }}target: {{ classDataName actionDefinition.targetType 'Stored' }}{{/ if }}) => Promise; + {{# if actionDefinition.isBulk }} + {{ simpleActionDefinitionName actionDefinition }}?: (selectedRows: {{ classDataName (getReferenceClassType table) 'Stored' }}[]) => Promise>; + {{ else }} + {{ simpleActionDefinitionName actionDefinition }}?: ({{# if actionDefinition.targetType }}target: {{ classDataName actionDefinition.targetType 'Stored' }}{{/ if }}) => Promise; + {{/ if }} {{/ if }} {{/ if }} {{/ if }} @@ -128,9 +134,6 @@ export function {{ tableComponentName table }}(props: {{ tableComponentName tabl const { downloadFile, extractFileNameFromToken } = fileHandling(); const { t } = useTranslation(); const handleError = useErrorHandler(); - {{# if (tableHasBulkOperations table) }} - const openCRUDDialog = useCRUDDialog(); - {{/ if }} {{# if (stringValueIsTrue useTableRowHighlighting) }} const { service: rowHighlightingHook } = useTrackService>(`(&(${OBJECTCLASS}=${TABLE_ROW_HIGHLIGHTING_HOOK_INTERFACE_KEY})(component={{~ tableComponentName table ~}}))`); @@ -531,31 +534,10 @@ export function {{ tableComponentName table }}(props: {{ tableComponentName tabl await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(queryCustomizer)); {{ else }} {{# if actionDefinition.isBulk }} - openCRUDDialog<{{ classDataName (getReferenceClassType table) 'Stored' }}>({ - dialogTitle: t('TMP', { defaultValue: '{{ button.label }}' }), - {{# with (getFirstTitleColumnForTable table) as |column| }} - itemTitleFn: (item) => item.{{ column.attributeType.name }}!, - {{ else }} - itemTitleFn: (item) => t('judo.placeholder', { defaultValue: 'placeholder' }) as string, - {{/ with }} - selectedItems: selectedRows.current, - action: async (item, successHandler: () => void, errorHandler: (error: any) => void) => { - try { - await actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}!(item, true); - successHandler(); - } catch (error) { - errorHandler(error); - } - }, - onClose: async (needsRefresh) => { - if (needsRefresh) { - {{# with (getRefreshActionDefinitionForTable table) as |refreshActionDefinition| }} - await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(queryCustomizer)); - {{/ with }} - setSelectionModel([]); // not resetting on refreshes because refreshes would always remove selections... - } - }, - }); + const { result: bulkResult } = await actions.{{ simpleActionDefinitionName actionDefinition }}!(selectedRows.current); + if (bulkResult === 'submit') { + setSelectionModel([]); // not resetting on refreshes because refreshes would always remove selections... + } {{ else }} await actions.{{ simpleActionDefinitionName actionDefinition }}!(); {{/ if }} 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 b8eb29b8..ea4c56a0 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 @@ -12,6 +12,7 @@ import { useCallback, useEffect, useRef, useState, lazy, Suspense } from 'react' import { useJudoNavigation } from '~/components'; import { useConfirmDialog, useDialog, useFilterDialog } from '~/components/dialog'; import { toastConfig } from '~/config'; + import { useCRUDDialog } from '~/hooks'; import { passesLocalValidation, processQueryCustomizer, @@ -109,6 +110,7 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) { const { openFilterDialog } = useFilterDialog(); const { openConfirmDialog } = useConfirmDialog(); const handleError = useErrorHandler(); + const openCRUDDialog = useCRUDDialog(); const [createDialog, closeDialog] = useDialog(); // State section diff --git a/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/BulkDeleteAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/BulkDeleteAction.fragment.hbs index 007ab0f3..fae6b009 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/BulkDeleteAction.fragment.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/BulkDeleteAction.fragment.hbs @@ -1,3 +1,48 @@ -const {{ simpleActionDefinitionName action.actionDefinition }} = async () => { - // alert('BulkDeleteAction'); +const {{ simpleActionDefinitionName action.actionDefinition }} = async (selectedRows: {{ classDataName action.actionDefinition.bulkOf.targetType 'Stored' }}[]): Promise>> => { + {{# with (getTableParentForActionDefinition action.actionDefinition) as |table| }} + return new Promise((resolve) => { + openCRUDDialog<{{ classDataName (getReferenceClassType table) 'Stored' }}>({ + dialogTitle: t('TMP', { defaultValue: 'Delete' }), + {{# with (getFirstTitleColumnForTable table) as |column| }} + itemTitleFn: (item) => item.{{ column.attributeType.name }}!, + {{ else }} + itemTitleFn: (item) => t('judo.placeholder', { defaultValue: 'placeholder' }) as string, + {{/ with }} + selectedItems: selectedRows, + action: async (item, successHandler: () => void, errorHandler: (error: any) => void) => { + try { + if (actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}) { + await actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}!(item, true); + } + successHandler(); + } catch (error) { + errorHandler(error); + } + }, + onClose: async (needsRefresh) => { + if (needsRefresh) { + {{# if page.container.table }} + setRefreshCounter((prev) => prev + 1); + {{/ if }} + {{# if page.container.view }} + {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }} + if (actions.{{ simpleActionDefinitionName refreshActionDefinition }}) { + await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(pageQueryCustomizer)); + } + {{/ with }} + {{/ if }} + resolve({ + result: 'submit', + data: [], + }); + } else { + resolve({ + result: 'close', + data: [], + }); + } + }, + }); + }); + {{/ with }} }; diff --git a/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/BulkRemoveAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/BulkRemoveAction.fragment.hbs index 4c5420b3..502807c6 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/BulkRemoveAction.fragment.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/BulkRemoveAction.fragment.hbs @@ -1,3 +1,57 @@ -const {{ simpleActionDefinitionName action.actionDefinition }} = async () => { - // alert('BulkRemoveAction'); +const {{ simpleActionDefinitionName action.actionDefinition }} = async (selectedRows: {{ classDataName action.actionDefinition.bulkOf.targetType 'Stored' }}[]): Promise>> => { + {{# with (getTableParentForActionDefinition action.actionDefinition) as |table| }} + return new Promise((resolve) => { + {{# if table.isEager }} + const selectedIds = selectedRows.map(r => r.__identifier); + const newList = (data?.{{ table.dataElement.name }} ?? []).filter(c => !selectedIds.includes(c.__identifier)); + storeDiff('{{ table.dataElement.name }}', newList); + resolve({ + result: 'submit', + data: [], + }); + {{ else }} + openCRUDDialog<{{ classDataName (getReferenceClassType table) 'Stored' }}>({ + dialogTitle: t('TMP', { defaultValue: 'Remove' }), + {{# with (getFirstTitleColumnForTable table) as |column| }} + itemTitleFn: (item) => item.{{ column.attributeType.name }}!, + {{ else }} + itemTitleFn: (item) => t('judo.placeholder', { defaultValue: 'placeholder' }) as string, + {{/ with }} + selectedItems: selectedRows, + action: async (item, successHandler: () => void, errorHandler: (error: any) => void) => { + try { + if (actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}) { + await actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}!(item, true); + } + successHandler(); + } catch (error) { + errorHandler(error); + } + }, + onClose: async (needsRefresh) => { + if (needsRefresh) { + {{# if page.container.table }} + setRefreshCounter((prev) => prev + 1); + {{/ if }} + {{# if page.container.view }} + {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }} + if (actions.{{ simpleActionDefinitionName refreshActionDefinition }}) { + await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(pageQueryCustomizer)); + } + {{/ with }} + {{/ if }} + resolve({ + result: 'submit', + data: [], + }); + } else { + resolve({ + result: 'close', + }); + } + }, + }); + {{/ if }} + }); + {{/ with }} }; diff --git a/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/RemoveAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/RemoveAction.fragment.hbs index 216eb03e..b1e0ffbc 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/RemoveAction.fragment.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/v2/actions/RemoveAction.fragment.hbs @@ -6,13 +6,11 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (target?: storeDiff('{{ table.dataElement.name }}', newList); {{ else }} try { - {{# if action.actionDefinition.isBulkCapable }} - if (!silentMode) { - await {{ getServiceImplForPage page }}.remove{{ firstToUpper action.ownerDataElement.name }}({{# if page.container.table }}{ __signedIdentifier: signedIdentifier } as JudoIdentifiable{{ else }}data{{/ if }}, [target!]); - } - {{ else }} + if (!silentMode) { setIsLoading(true); - await {{ getServiceImplForPage page }}.remove{{ firstToUpper action.ownerDataElement.name }}({{# if page.container.table }}{ __signedIdentifier: signedIdentifier } as JudoIdentifiable{{ else }}data{{/ if }}, [target!]); + } + await {{ getServiceImplForPage page }}.remove{{ firstToUpper action.ownerDataElement.name }}({{# if page.container.table }}{ __signedIdentifier: signedIdentifier } as JudoIdentifiable{{ else }}data{{/ if }}, [target!]); + if (!silentMode) { {{# if page.container.table }} setRefreshCounter((prev) => prev + 1); {{/ if }} @@ -23,27 +21,19 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (target?: {{/ with }} } {{/ if }} - {{/ if }} + } } catch(error) { - {{# if action.actionDefinition.isBulkCapable }} if (!silentMode) { - {{/ if }} {{# if action.actionDefinition.targetType }} handleError<{{ classDataName action.actionDefinition.targetType '' }}>(error, undefined, target); {{ else }} handleError(error, undefined, data); {{/ if }} - {{# if action.actionDefinition.isBulkCapable }} } - {{/ if }} } finally { - {{# if action.actionDefinition.isBulkCapable }} if (!silentMode) { - {{/ if }} setIsLoading(false); - {{# if action.actionDefinition.isBulkCapable }} } - {{/ if }} } {{/ if }} } diff --git a/judo-ui-react/src/main/resources/actor/src/pages/v2/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/pages/v2/index.tsx.hbs index 9a421a32..973c233e 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/v2/index.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/v2/index.tsx.hbs @@ -12,6 +12,7 @@ import { useCallback, useEffect, useRef, useState, lazy, Suspense } from 'react' import { useJudoNavigation } from '~/components'; import { useConfirmDialog, useDialog, useFilterDialog } from '~/components/dialog'; import { toastConfig } from '~/config'; + import { useCRUDDialog } from '~/hooks'; import { passesLocalValidation, processQueryCustomizer, @@ -19,6 +20,9 @@ import { useCallback, useEffect, useRef, useState, lazy, Suspense } from 'react' uiTimeToServiceTime, useErrorHandler, } from '~/utilities'; + import type { + DialogResult, + } from '~/utilities'; import { PageContainerTransition } from '~/theme/animations'; {{# each (getRelatedPages page) as |relatedPage| }} import { routeTo{{ pageName relatedPage }} } from '~/routes'; @@ -61,6 +65,7 @@ export default function {{ pageName page }}() { const { openFilterDialog } = useFilterDialog(); const { openConfirmDialog } = useConfirmDialog(); const handleError = useErrorHandler(); + const openCRUDDialog = useCRUDDialog(); const [createDialog, closeDialog] = useDialog(); // State section