From e3f7a0572e398d5aac6f70dfd690e18b993459b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:15:27 +0100 Subject: [PATCH] 9260 refactor multiple record actions and no selection actions (#9314) Closes #9260 - Refactored multiple record actions and no selection record actions to use config file - Simplified actions registration logic - Updated tests --- .../RecordActionMenuEntriesSetter.tsx | 104 ++++----- .../components/RegisterRecordActionEffect.tsx | 55 +++++ .../DefaultActionsConfigV1.ts} | 56 ++++- .../DefaultActionsConfigV2.ts} | 56 ++++- .../WorkflowActionsConfig.ts} | 52 ++++- .../WorkflowRunsActionsConfig.ts} | 36 +++- .../WorkflowVersionsActionsConfig.ts} | 36 +++- ...ipleRecordsActionMenuEntrySetterEffect.tsx | 24 --- .../useDeleteMultipleRecordsAction.test.tsx | 153 +++++-------- .../useExportMultipleRecordsAction.test.tsx | 124 ++++------- .../hooks/useDeleteMultipleRecordsAction.tsx | 201 +++++++----------- .../hooks/useExportMultipleRecordsAction.tsx | 55 +---- .../hooks/useMultipleRecordsActions.tsx | 38 ---- ...NoSelectionActionMenuEntrySetterEffect.tsx | 26 --- ...ExportViewNoSelectionRecordAction.test.tsx | 108 ---------- .../useExportViewNoSelectionRecordAction.tsx | 54 ----- .../hooks/useNoSelectionRecordActions.tsx | 28 --- ...ingleRecordActionMenuEntrySetterEffect.tsx | 34 --- ...eAddToFavoritesSingleRecordAction.test.tsx | 3 - .../useDeleteSingleRecordAction.test.tsx | 1 - ...veFromFavoritesSingleRecordAction.test.tsx | 3 - .../useAddToFavoritesSingleRecordAction.ts | 9 +- .../hooks/useDeleteSingleRecordAction.tsx | 128 +++++------ .../hooks/useDestroySingleRecordAction.tsx | 100 ++++----- .../hooks/useExportNoteAction.ts | 68 +++--- ...eNavigateToNextRecordSingleRecordAction.ts | 9 +- ...igateToPreviousRecordSingleRecordAction.ts | 10 +- ...seRemoveFromFavoritesSingleRecordAction.ts | 9 +- .../hooks/useSelectedRecordIdOrThrow.tsx | 18 ++ .../single-record/utils/getActionConfig.ts | 27 --- ...ateDraftWorkflowSingleRecordAction.test.ts | 10 +- ...dVersionWorkflowSingleRecordAction.test.ts | 10 +- ...activateWorkflowSingleRecordAction.test.ts | 15 +- ...ardDraftWorkflowSingleRecordAction.test.ts | 20 +- ...ActivateDraftWorkflowSingleRecordAction.ts | 9 +- ...lishedVersionWorkflowSingleRecordAction.ts | 9 +- ...useDeactivateWorkflowSingleRecordAction.ts | 9 +- ...eDiscardDraftWorkflowSingleRecordAction.ts | 9 +- ...ActiveVersionWorkflowSingleRecordAction.ts | 9 +- .../useSeeRunsWorkflowSingleRecordAction.ts | 9 +- ...seSeeVersionsWorkflowSingleRecordAction.ts | 9 +- .../useTestWorkflowSingleRecordAction.ts | 9 +- ...eeRunsWorkflowVersionSingleRecordAction.ts | 9 +- ...rsionsWorkflowVersionSingleRecordAction.ts | 14 +- ...DraftWorkflowVersionSingleRecordAction.tsx | 9 +- .../action-menu/actions/types/ActionHook.ts | 14 ++ .../actions/types/SingleRecordActionHook.ts | 20 -- .../actions/utils/getActionConfig.ts | 25 +++ .../actions/utils/getActionViewType.ts | 32 +++ .../useActionMenuEntriesWithCallbacks.ts | 70 ------ .../testing/jest/JestContextStoreSetter.tsx | 10 + ...taAndApolloMocksAndContextStoreWrapper.tsx | 5 + 52 files changed, 854 insertions(+), 1106 deletions(-) create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RegisterRecordActionEffect.tsx rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/constants/DefaultSingleRecordActionsConfigV1.ts => constants/DefaultActionsConfigV1.ts} (50%) rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/constants/DefaultSingleRecordActionsConfigV2.ts => constants/DefaultActionsConfigV2.ts} (70%) rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts => constants/WorkflowActionsConfig.ts} (83%) rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts => constants/WorkflowRunsActionsConfig.ts} (67%) rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts => constants/WorkflowVersionsActionsConfig.ts} (78%) delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/types/ActionHook.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/types/SingleRecordActionHook.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/utils/getActionViewType.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx index 02dfb9cc0..6927fe94c 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx @@ -1,13 +1,10 @@ -import { MultipleRecordsActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect'; -import { NoSelectionActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect'; -import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect'; +import { RegisterRecordActionEffect } from '@/action-menu/actions/record-actions/components/RegisterRecordActionEffect'; import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter'; -import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; +import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig'; +import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType'; import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; -import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; @@ -19,33 +16,13 @@ export const RecordActionMenuEntriesSetter = () => { const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( contextStoreCurrentObjectMetadataIdComponentState, ); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (item) => item.id === contextStoreCurrentObjectMetadataId, ); - if ( - !isDefined(contextStoreCurrentObjectMetadataId) || - !isDefined(objectMetadataItem) - ) { - return null; - } - - return ( - - ); -}; - -const ActionEffects = ({ - objectMetadataItemId, -}: { - objectMetadataItemId: string; -}) => { - const { objectMetadataItem } = useObjectMetadataItemById({ - objectId: objectMetadataItemId, - }); - const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( contextStoreTargetedRecordsRuleComponentState, ); @@ -58,43 +35,52 @@ const ActionEffects = ({ FeatureFlagKey.IsWorkflowEnabled, ); + const isPageHeaderV2Enabled = useIsFeatureEnabled( + FeatureFlagKey.IsPageHeaderV2Enabled, + ); + + if ( + !isDefined(contextStoreCurrentObjectMetadataId) || + !isDefined(objectMetadataItem) + ) { + return null; + } + + const viewType = getActionViewType( + contextStoreCurrentViewType, + contextStoreTargetedRecordsRule, + ); + + const actionConfig = getActionConfig( + objectMetadataItem, + isPageHeaderV2Enabled, + ); + + const actionsToRegister = isDefined(viewType) + ? Object.values(actionConfig ?? {}).filter((action) => + action.availableOn?.includes(viewType), + ) + : []; + return ( <> - {contextStoreTargetedRecordsRule.mode === 'selection' && - contextStoreTargetedRecordsRule.selectedRecordIds.length === 0 && ( - ( + + ))} + + {isWorkflowEnabled && + !( + contextStoreTargetedRecordsRule?.mode === 'selection' && + contextStoreTargetedRecordsRule?.selectedRecordIds.length === 0 + ) && ( + )} - {contextStoreTargetedRecordsRule.mode === 'selection' && - contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 && ( - <> - {contextStoreCurrentViewType === ContextStoreViewType.ShowPage && ( - - )} - {(contextStoreCurrentViewType === ContextStoreViewType.Table || - contextStoreCurrentViewType === ContextStoreViewType.Kanban) && ( - - )} - {isWorkflowEnabled && ( - - )} - - )} - {(contextStoreTargetedRecordsRule.mode === 'exclusion' || - contextStoreTargetedRecordsRule.selectedRecordIds.length > 1) && ( - - )} ); }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RegisterRecordActionEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RegisterRecordActionEffect.tsx new file mode 100644 index 000000000..25159234c --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RegisterRecordActionEffect.tsx @@ -0,0 +1,55 @@ +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; +import { wrapActionInCallbacks } from '@/action-menu/actions/utils/wrapActionInCallbacks'; +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; +import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { useContext, useEffect } from 'react'; + +type RegisterRecordActionEffectProps = { + action: ActionMenuEntry & { + actionHook: ActionHook; + }; + objectMetadataItem: ObjectMetadataItem; +}; + +export const RegisterRecordActionEffect = ({ + action, + objectMetadataItem, +}: RegisterRecordActionEffectProps) => { + const { shouldBeRegistered, onClick, ConfirmationModal } = action.actionHook({ + objectMetadataItem, + }); + + const { onActionStartedCallback, onActionExecutedCallback } = + useContext(ActionMenuContext); + + const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); + + const wrappedAction = wrapActionInCallbacks({ + action: { + ...action, + onClick, + ConfirmationModal, + }, + onActionStartedCallback, + onActionExecutedCallback, + }); + + useEffect(() => { + if (shouldBeRegistered) { + addActionMenuEntry(wrappedAction); + } + + return () => { + removeActionMenuEntry(wrappedAction.key); + }; + }, [ + addActionMenuEntry, + removeActionMenuEntry, + shouldBeRegistered, + wrappedAction, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts similarity index 50% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts index ba6584225..12da07f3f 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts @@ -1,20 +1,29 @@ +import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction'; +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction'; import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction'; import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, ActionMenuEntryType, } from '@/action-menu/types/ActionMenuEntry'; -import { IconHeart, IconHeartOff, IconTrash } from 'twenty-ui'; +import { + IconDatabaseExport, + IconHeart, + IconHeartOff, + IconTrash, +} from 'twenty-ui'; -export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record< +export const DEFAULT_ACTIONS_CONFIG_V1: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { addToFavoritesSingleRecord: { @@ -58,4 +67,43 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record< ], actionHook: useDeleteSingleRecordAction, }, + deleteMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.DELETE, + label: 'Delete records', + shortLabel: 'Delete', + position: 3, + Icon: IconTrash, + accent: 'danger', + isPinned: true, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useDeleteMultipleRecordsAction, + }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 4, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 5, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts similarity index 70% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts index 6e41b853e..f7dd5b223 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts @@ -1,3 +1,7 @@ +import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction'; +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction'; import { useDestroySingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction'; @@ -6,8 +10,8 @@ import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction'; import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction'; import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, @@ -16,6 +20,7 @@ import { import { IconChevronDown, IconChevronUp, + IconDatabaseExport, IconFileExport, IconHeart, IconHeartOff, @@ -23,10 +28,10 @@ import { IconTrashX, } from 'twenty-ui'; -export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< +export const DEFAULT_ACTIONS_CONFIG_V2: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { exportNoteToPdf: { @@ -87,13 +92,52 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< ], actionHook: useDeleteSingleRecordAction, }, + deleteMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.DELETE, + label: 'Delete records', + shortLabel: 'Delete', + position: 4, + Icon: IconTrash, + accent: 'danger', + isPinned: true, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useDeleteMultipleRecordsAction, + }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 5, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 6, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, destroySingleRecord: { type: ActionMenuEntryType.Standard, scope: ActionMenuEntryScope.RecordSelection, key: SingleRecordActionKeys.DESTROY, label: 'Permanently destroy record', shortLabel: 'Destroy', - position: 4, + position: 7, Icon: IconTrashX, accent: 'danger', isPinned: true, @@ -109,7 +153,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< key: SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD, label: 'Navigate to previous record', shortLabel: '', - position: 5, + position: 8, isPinned: true, Icon: IconChevronUp, availableOn: [ActionViewType.SHOW_PAGE], @@ -121,7 +165,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< key: SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD, label: 'Navigate to next record', shortLabel: '', - position: 6, + position: 9, isPinned: true, Icon: IconChevronDown, availableOn: [ActionViewType.SHOW_PAGE], diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts similarity index 83% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts index f6d85ae3b..773ff4ce6 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts @@ -1,3 +1,7 @@ +import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction'; +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction'; import { useDestroySingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction'; @@ -14,8 +18,8 @@ import { useSeeRunsWorkflowSingleRecordAction } from '@/action-menu/actions/reco import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction'; import { useTestWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction'; import { WorkflowSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/workflow-actions/types/WorkflowSingleRecordActionsKeys'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, @@ -24,6 +28,7 @@ import { import { IconChevronDown, IconChevronUp, + IconDatabaseExport, IconHeart, IconHeartOff, IconHistory, @@ -35,10 +40,10 @@ import { IconTrashX, } from 'twenty-ui'; -export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record< +export const WORKFLOW_ACTIONS_CONFIG: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { activateWorkflowDraftSingleRecord: { @@ -229,13 +234,26 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record< ], actionHook: useDeleteSingleRecordAction, }, + deleteMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.DELETE, + label: 'Delete records', + shortLabel: 'Delete', + position: 14, + Icon: IconTrash, + accent: 'danger', + isPinned: true, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useDeleteMultipleRecordsAction, + }, destroySingleRecord: { type: ActionMenuEntryType.Standard, scope: ActionMenuEntryScope.RecordSelection, key: SingleRecordActionKeys.DESTROY, label: 'Permanently destroy record', shortLabel: 'Destroy', - position: 14, + position: 15, Icon: IconTrashX, accent: 'danger', isPinned: false, @@ -245,4 +263,30 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record< ], actionHook: useDestroySingleRecordAction, }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 16, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 17, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.ts similarity index 67% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.ts index 7f6f24633..ee0f56f97 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.ts @@ -1,10 +1,13 @@ +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction'; import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction'; import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction'; import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, @@ -13,14 +16,15 @@ import { import { IconChevronDown, IconChevronUp, + IconDatabaseExport, IconHeart, IconHeartOff, } from 'twenty-ui'; -export const WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG: Record< +export const WORKFLOW_RUNS_ACTIONS_CONFIG: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { addToFavoritesSingleRecord: { @@ -77,4 +81,30 @@ export const WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG: Record< availableOn: [ActionViewType.SHOW_PAGE], actionHook: useNavigateToNextRecordSingleRecordAction, }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 4, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 5, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.ts similarity index 78% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.ts index 95f531329..2fbff23aa 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.ts @@ -1,3 +1,6 @@ +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction'; import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction'; @@ -7,8 +10,8 @@ import { useSeeRunsWorkflowVersionSingleRecordAction } from '@/action-menu/actio import { useSeeVersionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction'; import { useUseAsDraftWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction'; import { WorkflowVersionSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/types/WorkflowVersionSingleRecordActionsKeys'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, @@ -17,6 +20,7 @@ import { import { IconChevronDown, IconChevronUp, + IconDatabaseExport, IconHeart, IconHeartOff, IconHistory, @@ -24,10 +28,10 @@ import { IconPencil, } from 'twenty-ui'; -export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record< +export const WORKFLOW_VERSIONS_ACTIONS_CONFIG: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { useAsDraftWorkflowVersionSingleRecord: { @@ -125,4 +129,30 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record< ], actionHook: useRemoveFromFavoritesSingleRecordAction, }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 8, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 9, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect.tsx deleted file mode 100644 index 67b96f1b7..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useMultipleRecordsActions } from '@/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useEffect } from 'react'; - -export const MultipleRecordsActionMenuEntrySetterEffect = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { registerMultipleRecordsActions, unregisterMultipleRecordsActions } = - useMultipleRecordsActions({ - objectMetadataItem, - }); - - useEffect(() => { - registerMultipleRecordsActions(); - - return () => { - unregisterMultipleRecordsActions(); - }; - }, [registerMultipleRecordsActions, unregisterMultipleRecordsActions]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx index 447a08447..9eba7ca23 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx @@ -1,132 +1,87 @@ -import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; -import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; -import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { expect } from '@storybook/test'; -import { renderHook } from '@testing-library/react'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; -import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getPeopleMock } from '~/testing/mock-data/people'; import { useDeleteMultipleRecordsAction } from '../useDeleteMultipleRecordsAction'; +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +)!; + +const peopleMock = getPeopleMock(); + +const deleteManyRecordsMock = jest.fn(); +const resetTableRowSelectionMock = jest.fn(); + jest.mock('@/object-record/hooks/useDeleteManyRecords', () => ({ useDeleteManyRecords: () => ({ - deleteManyRecords: jest.fn(), + deleteManyRecords: deleteManyRecordsMock, }), })); -jest.mock('@/favorites/hooks/useDeleteFavorite', () => ({ - useDeleteFavorite: () => ({ - deleteFavorite: jest.fn(), - }), -})); -jest.mock('@/favorites/hooks/useFavorites', () => ({ - useFavorites: () => ({ - sortedFavorites: [], - }), + +jest.mock('@/object-record/hooks/useLazyFetchAllRecords', () => ({ + useLazyFetchAllRecords: () => { + return { + fetchAllRecords: () => [peopleMock[0], peopleMock[1]], + }; + }, })); + jest.mock('@/object-record/record-table/hooks/useRecordTable', () => ({ useRecordTable: () => ({ - resetTableRowSelection: jest.fn(), + resetTableRowSelection: resetTableRowSelectionMock, }), })); -const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', -)!; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ +const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ apolloMocks: [], - onInitializeRecoilSnapshot: ({ set }) => { - set( - contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ - instanceId: '1', - }), - 3, - ); + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + personMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[0].id, peopleMock[1].id], + }, + contextStoreNumberOfSelectedRecords: 2, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]); + snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]); }, }); describe('useDeleteMultipleRecordsAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - {children} - - - - ); - - it('should register delete action', () => { + it('should call deleteManyRecords on click', async () => { const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDeleteMultipleRecordsAction: useDeleteMultipleRecordsAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; + () => + useDeleteMultipleRecordsAction({ + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper, }, - { wrapper }, ); - act(() => { - result.current.useDeleteMultipleRecordsAction.registerDeleteMultipleRecordsAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('delete-multiple-records'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('delete-multiple-records')?.position, - ).toBe(1); - }); - - it('should unregister delete action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDeleteMultipleRecordsAction: useDeleteMultipleRecordsAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); + expect(result.current.ConfirmationModal?.props?.isOpen).toBe(false); act(() => { - result.current.useDeleteMultipleRecordsAction.registerDeleteMultipleRecordsAction( - { position: 1 }, - ); + result.current.onClick(); }); - expect(result.current.actionMenuEntries.size).toBe(1); + expect(result.current.ConfirmationModal?.props?.isOpen).toBe(true); act(() => { - result.current.useDeleteMultipleRecordsAction.unregisterDeleteMultipleRecordsAction(); + result.current.ConfirmationModal?.props?.onConfirmClick(); }); - expect(result.current.actionMenuEntries.size).toBe(0); + await waitFor(() => { + expect(resetTableRowSelectionMock).toHaveBeenCalled(); + + expect(deleteManyRecordsMock).toHaveBeenCalledWith([ + peopleMock[0].id, + peopleMock[1].id, + ]); + }); }); }); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx index 7d9dbff6e..46e645080 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx @@ -1,105 +1,59 @@ -import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; -import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { expect } from '@storybook/test'; -import { renderHook } from '@testing-library/react'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; -import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getPeopleMock } from '~/testing/mock-data/people'; import { useExportMultipleRecordsAction } from '../useExportMultipleRecordsAction'; -const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', )!; -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ +const peopleMock = getPeopleMock(); + +const downloadMock = jest.fn(); + +jest.mock('@/object-record/record-index/export/hooks/useExportRecords', () => ({ + useExportRecords: () => ({ + download: downloadMock, + }), +})); + +const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + personMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[0].id, peopleMock[1].id], + }, + contextStoreNumberOfSelectedRecords: 2, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]); + snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]); + }, }); describe('useExportMultipleRecordsAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register export multiple records action', () => { + it('should call exportManyRecords on click', async () => { const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useExportMultipleRecordsAction: useExportMultipleRecordsAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; + () => + useExportMultipleRecordsAction({ + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper, }, - { wrapper }, ); act(() => { - result.current.useExportMultipleRecordsAction.registerExportMultipleRecordsAction( - { position: 1 }, - ); + result.current.onClick(); }); - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('export-multiple-records'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('export-multiple-records')?.position, - ).toBe(1); - }); - - it('should unregister export multiple records action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useExportMultipleRecordsAction: useExportMultipleRecordsAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useExportMultipleRecordsAction.registerExportMultipleRecordsAction( - { position: 1 }, - ); + await waitFor(() => { + expect(downloadMock).toHaveBeenCalled(); }); - - expect(result.current.actionMenuEntries.size).toBe(1); - - act(() => { - result.current.useExportMultipleRecordsAction.unregisterExportMultipleRecordsAction(); - }); - - expect(result.current.actionMenuEntries.size).toBe(0); }); }); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx index 7c48a63a0..dfcbb5350 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx @@ -1,15 +1,9 @@ -import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; -import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; + import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize'; import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; @@ -18,140 +12,101 @@ import { FilterOperand } from '@/object-record/object-filter-dropdown/types/Filt import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; -import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useCallback, useContext, useState } from 'react'; -import { IconTrash, isDefined } from 'twenty-ui'; +import { useCallback, useState } from 'react'; +import { isDefined } from 'twenty-ui'; -export const useDeleteMultipleRecordsAction = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); +export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = + useState(false); - const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = - useState(false); + const { resetTableRowSelection } = useRecordTable({ + recordTableId: objectMetadataItem.namePlural, + }); - const { resetTableRowSelection } = useRecordTable({ - recordTableId: objectMetadataItem.namePlural, - }); + const { deleteManyRecords } = useDeleteManyRecords({ + objectNameSingular: objectMetadataItem.nameSingular, + }); - const { deleteManyRecords } = useDeleteManyRecords({ - objectNameSingular: objectMetadataItem.nameSingular, - }); + const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2( + contextStoreNumberOfSelectedRecordsComponentState, + ); - const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2( - contextStoreNumberOfSelectedRecordsComponentState, - ); + const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( + contextStoreTargetedRecordsRuleComponentState, + ); - const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( - contextStoreTargetedRecordsRuleComponentState, - ); + const contextStoreFilters = useRecoilComponentValueV2( + contextStoreFiltersComponentState, + ); - const contextStoreFilters = useRecoilComponentValueV2( - contextStoreFiltersComponentState, - ); + const { filterValueDependencies } = useFilterValueDependencies(); - const { filterValueDependencies } = useFilterValueDependencies(); + const graphqlFilter = computeContextStoreFilters( + contextStoreTargetedRecordsRule, + contextStoreFilters, + objectMetadataItem, + filterValueDependencies, + ); - const graphqlFilter = computeContextStoreFilters( - contextStoreTargetedRecordsRule, - contextStoreFilters, - objectMetadataItem, - filterValueDependencies, - ); + const deletedAtFieldMetadata = objectMetadataItem.fields.find( + (field) => field.name === 'deletedAt', + ); - const deletedAtFieldMetadata = objectMetadataItem.fields.find( - (field) => field.name === 'deletedAt', - ); + const isDeletedFilterActive = contextStoreFilters.some( + (filter) => + filter.fieldMetadataId === deletedAtFieldMetadata?.id && + filter.operand === FilterOperand.IsNotEmpty, + ); - const isDeletedFilterActive = contextStoreFilters.some( - (filter) => - filter.fieldMetadataId === deletedAtFieldMetadata?.id && - filter.operand === FilterOperand.IsNotEmpty, - ); + const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({ + objectNameSingular: objectMetadataItem.nameSingular, + filter: graphqlFilter, + limit: DEFAULT_QUERY_PAGE_SIZE, + recordGqlFields: { id: true }, + }); - const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({ - objectNameSingular: objectMetadataItem.nameSingular, - filter: graphqlFilter, - limit: DEFAULT_QUERY_PAGE_SIZE, - recordGqlFields: { id: true }, - }); + const handleDeleteClick = useCallback(async () => { + const recordsToDelete = await fetchAllRecordIds(); + const recordIdsToDelete = recordsToDelete.map((record) => record.id); - const { closeRightDrawer } = useRightDrawer(); + resetTableRowSelection(); - const handleDeleteClick = useCallback(async () => { - const recordsToDelete = await fetchAllRecordIds(); - const recordIdsToDelete = recordsToDelete.map((record) => record.id); + await deleteManyRecords(recordIdsToDelete); + }, [deleteManyRecords, fetchAllRecordIds, resetTableRowSelection]); - resetTableRowSelection(); + const isRemoteObject = objectMetadataItem.isRemote; - await deleteManyRecords(recordIdsToDelete); - }, [deleteManyRecords, fetchAllRecordIds, resetTableRowSelection]); + const shouldBeRegistered = + !isRemoteObject && + !isDeletedFilterActive && + isDefined(contextStoreNumberOfSelectedRecords) && + contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT && + contextStoreNumberOfSelectedRecords > 0; - const isRemoteObject = objectMetadataItem.isRemote; + const onClick = () => { + if (!shouldBeRegistered) { + return; + } - const canDelete = - !isRemoteObject && - !isDeletedFilterActive && - isDefined(contextStoreNumberOfSelectedRecords) && - contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT && - contextStoreNumberOfSelectedRecords > 0; + setIsDeleteRecordsModalOpen(true); + }; - const { isInRightDrawer, onActionStartedCallback, onActionExecutedCallback } = - useContext(ActionMenuContext); + const confirmationModal = ( + + ); - const registerDeleteMultipleRecordsAction = ({ - position, - }: { - position: number; - }) => { - if (canDelete) { - addActionMenuEntry({ - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - key: MultipleRecordsActionKeys.DELETE, - label: 'Delete records', - shortLabel: 'Delete', - position, - Icon: IconTrash, - accent: 'danger', - isPinned: true, - onClick: () => { - setIsDeleteRecordsModalOpen(true); - }, - ConfirmationModal: ( - { - onActionStartedCallback?.({ - key: 'delete-multiple-records', - }); - await handleDeleteClick(); - onActionExecutedCallback?.({ - key: 'delete-multiple-records', - }); - if (isInRightDrawer) { - closeRightDrawer(); - } - }} - deleteButtonText={'Delete Records'} - /> - ), - }); - } + return { + shouldBeRegistered, + onClick, + ConfirmationModal: confirmationModal, + }; }; - - const unregisterDeleteMultipleRecordsAction = () => { - removeActionMenuEntry('delete-multiple-records'); - }; - - return { - registerDeleteMultipleRecordsAction, - unregisterDeleteMultipleRecordsAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx index 3b4d10bec..9d07a41c1 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx @@ -1,68 +1,25 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { IconDatabaseExport } from 'twenty-ui'; -import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; -import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { - displayedExportProgress, - useExportRecords, -} from '@/object-record/record-index/export/hooks/useExportRecords'; -import { useContext } from 'react'; +import { useExportRecords } from '@/object-record/record-index/export/hooks/useExportRecords'; export const useExportMultipleRecordsAction = ({ objectMetadataItem, }: { objectMetadataItem: ObjectMetadataItem; }) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { progress, download } = useExportRecords({ + const { download } = useExportRecords({ delayMs: 100, objectMetadataItem, recordIndexId: objectMetadataItem.namePlural, filename: `${objectMetadataItem.nameSingular}.csv`, }); - const { onActionStartedCallback, onActionExecutedCallback } = - useContext(ActionMenuContext); - - const registerExportMultipleRecordsAction = ({ - position, - }: { - position: number; - }) => { - addActionMenuEntry({ - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - key: MultipleRecordsActionKeys.EXPORT, - position, - label: displayedExportProgress(progress), - shortLabel: 'Export', - Icon: IconDatabaseExport, - accent: 'default', - onClick: async () => { - await onActionStartedCallback?.({ - key: MultipleRecordsActionKeys.EXPORT, - }); - await download(); - await onActionExecutedCallback?.({ - key: MultipleRecordsActionKeys.EXPORT, - }); - }, - }); - }; - - const unregisterExportMultipleRecordsAction = () => { - removeActionMenuEntry('export-multiple-records'); + const onClick = async () => { + await download(); }; return { - registerExportMultipleRecordsAction, - unregisterExportMultipleRecordsAction, + shouldBeRegistered: true, + onClick, }; }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx deleted file mode 100644 index 2b135aaa0..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction'; -import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const useMultipleRecordsActions = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { - registerDeleteMultipleRecordsAction, - unregisterDeleteMultipleRecordsAction, - } = useDeleteMultipleRecordsAction({ - objectMetadataItem, - }); - - const { - registerExportMultipleRecordsAction, - unregisterExportMultipleRecordsAction, - } = useExportMultipleRecordsAction({ - objectMetadataItem, - }); - - const registerMultipleRecordsActions = () => { - registerDeleteMultipleRecordsAction({ position: 1 }); - registerExportMultipleRecordsAction({ position: 2 }); - }; - - const unregisterMultipleRecordsActions = () => { - unregisterDeleteMultipleRecordsAction(); - unregisterExportMultipleRecordsAction(); - }; - - return { - registerMultipleRecordsActions, - unregisterMultipleRecordsActions, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect.tsx deleted file mode 100644 index 2cba15829..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useNoSelectionRecordActions } from '@/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useEffect } from 'react'; - -export const NoSelectionActionMenuEntrySetterEffect = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { - registerNoSelectionRecordActions, - unregisterNoSelectionRecordActions, - } = useNoSelectionRecordActions({ - objectMetadataItem, - }); - - useEffect(() => { - registerNoSelectionRecordActions(); - - return () => { - unregisterNoSelectionRecordActions(); - }; - }, [registerNoSelectionRecordActions, unregisterNoSelectionRecordActions]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx deleted file mode 100644 index 7b00c1865..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; -import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { expect } from '@storybook/test'; -import { renderHook } from '@testing-library/react'; -import { act } from 'react'; -import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; - -import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction'; -const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', -)!; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: [], -}); - -describe('useExportViewNoSelectionRecordAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register export view action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useExportViewNoSelectionRecordAction: - useExportViewNoSelectionRecordAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useExportViewNoSelectionRecordAction.registerExportViewNoSelectionRecordsAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('export-view-no-selection'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('export-view-no-selection') - ?.position, - ).toBe(1); - }); - - it('should unregister export view action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useExportViewNoSelectionRecordAction: - useExportViewNoSelectionRecordAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useExportViewNoSelectionRecordAction.registerExportViewNoSelectionRecordsAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - - act(() => { - result.current.useExportViewNoSelectionRecordAction.unregisterExportViewNoSelectionRecordsAction(); - }); - - expect(result.current.actionMenuEntries.size).toBe(0); - }); -}); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx deleted file mode 100644 index e3ace2ee6..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { IconDatabaseExport } from 'twenty-ui'; - -import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { - displayedExportProgress, - useExportRecords, -} from '@/object-record/record-index/export/hooks/useExportRecords'; - -export const useExportViewNoSelectionRecordAction = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { progress, download } = useExportRecords({ - delayMs: 100, - objectMetadataItem, - recordIndexId: objectMetadataItem.namePlural, - filename: `${objectMetadataItem.nameSingular}.csv`, - }); - - const registerExportViewNoSelectionRecordsAction = ({ - position, - }: { - position: number; - }) => { - addActionMenuEntry({ - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.Global, - key: NoSelectionRecordActionKeys.EXPORT_VIEW, - position, - label: displayedExportProgress(progress), - Icon: IconDatabaseExport, - accent: 'default', - onClick: () => download(), - }); - }; - - const unregisterExportViewNoSelectionRecordsAction = () => { - removeActionMenuEntry('export-view-no-selection'); - }; - - return { - registerExportViewNoSelectionRecordsAction, - unregisterExportViewNoSelectionRecordsAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx deleted file mode 100644 index af08e721b..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const useNoSelectionRecordActions = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { - registerExportViewNoSelectionRecordsAction, - unregisterExportViewNoSelectionRecordsAction, - } = useExportViewNoSelectionRecordAction({ - objectMetadataItem, - }); - - const registerNoSelectionRecordActions = () => { - registerExportViewNoSelectionRecordsAction({ position: 1 }); - }; - - const unregisterNoSelectionRecordActions = () => { - unregisterExportViewNoSelectionRecordsAction(); - }; - - return { - registerNoSelectionRecordActions, - unregisterNoSelectionRecordActions, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx deleted file mode 100644 index 3280eb62c..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { useActionMenuEntriesWithCallbacks } from '@/action-menu/hooks/useActionMenuEntriesWithCallbacks'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useEffect } from 'react'; - -export const SingleRecordActionMenuEntrySetterEffect = ({ - objectMetadataItem, - viewType, -}: { - objectMetadataItem: ObjectMetadataItem; - viewType: ActionViewType; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { actionMenuEntries } = useActionMenuEntriesWithCallbacks( - objectMetadataItem, - viewType, - ); - - useEffect(() => { - for (const action of actionMenuEntries) { - addActionMenuEntry(action); - } - - return () => { - for (const action of actionMenuEntries) { - removeActionMenuEntry(action.key); - } - }; - }, [actionMenuEntries, addActionMenuEntry, removeActionMenuEntry]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx index 9137cb853..4e7f6a162 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx @@ -85,7 +85,6 @@ describe('useAddToFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useAddToFavoritesSingleRecordAction({ - recordId: peopleMock[1].id, objectMetadataItem: personMockObjectMetadataItem, }), { @@ -100,7 +99,6 @@ describe('useAddToFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useAddToFavoritesSingleRecordAction({ - recordId: peopleMock[0].id, objectMetadataItem: personMockObjectMetadataItem, }), { @@ -115,7 +113,6 @@ describe('useAddToFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useAddToFavoritesSingleRecordAction({ - recordId: peopleMock[1].id, objectMetadataItem: personMockObjectMetadataItem, }), { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx index a650d86e4..9c9f39520 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx @@ -39,7 +39,6 @@ describe('useDeleteSingleRecordAction', () => { const { result } = renderHook( () => useDeleteSingleRecordAction({ - recordId: peopleMock[0].id, objectMetadataItem: personMockObjectMetadataItem, }), { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx index 8cf965dd5..6da41f206 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx @@ -85,7 +85,6 @@ describe('useRemoveFromFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useRemoveFromFavoritesSingleRecordAction({ - recordId: peopleMock[0].id, objectMetadataItem: personMockObjectMetadataItem, }), { @@ -100,7 +99,6 @@ describe('useRemoveFromFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useRemoveFromFavoritesSingleRecordAction({ - recordId: peopleMock[1].id, objectMetadataItem: personMockObjectMetadataItem, }), { @@ -115,7 +113,6 @@ describe('useRemoveFromFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useRemoveFromFavoritesSingleRecordAction({ - recordId: peopleMock[0].id, objectMetadataItem: personMockObjectMetadataItem, }), { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts index ec3f6ddf0..eace0a761 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -6,8 +7,10 @@ import { isNull } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useAddToFavoritesSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { +export const useAddToFavoritesSingleRecordAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const recordId = useSelectedRecordIdOrThrow(); + const { sortedFavorites: favorites } = useFavorites(); const { createFavorite } = useCreateFavorite(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx index e499c72d0..0fbc2ad36 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { useFavorites } from '@/favorites/hooks/useFavorites'; @@ -12,80 +13,83 @@ import { useCallback, useContext, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useDeleteSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { - const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = - useState(false); +export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({ + objectMetadataItem, +}) => { + const recordId = useSelectedRecordIdOrThrow(); - const { resetTableRowSelection } = useRecordTable({ - recordTableId: objectMetadataItem.namePlural, - }); + const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = + useState(false); - const { deleteOneRecord } = useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, - }); + const { resetTableRowSelection } = useRecordTable({ + recordTableId: objectMetadataItem.namePlural, + }); - const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); + const { deleteOneRecord } = useDeleteOneRecord({ + objectNameSingular: objectMetadataItem.nameSingular, + }); - const { sortedFavorites: favorites } = useFavorites(); - const { deleteFavorite } = useDeleteFavorite(); + const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); - const { closeRightDrawer } = useRightDrawer(); + const { sortedFavorites: favorites } = useFavorites(); + const { deleteFavorite } = useDeleteFavorite(); - const handleDeleteClick = useCallback(async () => { - resetTableRowSelection(); + const { closeRightDrawer } = useRightDrawer(); - const foundFavorite = favorites?.find( - (favorite) => favorite.recordId === recordId, - ); + const handleDeleteClick = useCallback(async () => { + resetTableRowSelection(); - if (isDefined(foundFavorite)) { - deleteFavorite(foundFavorite.id); - } + const foundFavorite = favorites?.find( + (favorite) => favorite.recordId === recordId, + ); - await deleteOneRecord(recordId); - }, [ - deleteFavorite, - deleteOneRecord, - favorites, - resetTableRowSelection, - recordId, - ]); + if (isDefined(foundFavorite)) { + deleteFavorite(foundFavorite.id); + } - const isRemoteObject = objectMetadataItem.isRemote; + await deleteOneRecord(recordId); + }, [ + deleteFavorite, + deleteOneRecord, + favorites, + resetTableRowSelection, + recordId, + ]); - const { isInRightDrawer } = useContext(ActionMenuContext); + const isRemoteObject = objectMetadataItem.isRemote; - const shouldBeRegistered = - !isRemoteObject && isNull(selectedRecord?.deletedAt); + const { isInRightDrawer } = useContext(ActionMenuContext); - const onClick = () => { - if (!shouldBeRegistered) { - return; - } + const shouldBeRegistered = + !isRemoteObject && isNull(selectedRecord?.deletedAt); - setIsDeleteRecordsModalOpen(true); - }; + const onClick = () => { + if (!shouldBeRegistered) { + return; + } - return { - shouldBeRegistered, - onClick, - ConfirmationModal: ( - { - handleDeleteClick(); - if (isInRightDrawer) { - closeRightDrawer(); - } - }} - deleteButtonText={'Delete Record'} - /> - ), - }; + setIsDeleteRecordsModalOpen(true); }; + + return { + shouldBeRegistered, + onClick, + ConfirmationModal: ( + { + handleDeleteClick(); + if (isInRightDrawer) { + closeRightDrawer(); + } + }} + deleteButtonText={'Delete Record'} + /> + ), + }; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx index 85bcf89e8..107d68aab 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -9,63 +10,66 @@ import { useCallback, useContext, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useDestroySingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { - const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] = - useState(false); +export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({ + objectMetadataItem, +}) => { + const recordId = useSelectedRecordIdOrThrow(); - const { resetTableRowSelection } = useRecordTable({ - recordTableId: objectMetadataItem.namePlural, - }); + const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] = + useState(false); - const { destroyOneRecord } = useDestroyOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, - }); + const { resetTableRowSelection } = useRecordTable({ + recordTableId: objectMetadataItem.namePlural, + }); - const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); + const { destroyOneRecord } = useDestroyOneRecord({ + objectNameSingular: objectMetadataItem.nameSingular, + }); - const { closeRightDrawer } = useRightDrawer(); + const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); - const handleDeleteClick = useCallback(async () => { - resetTableRowSelection(); + const { closeRightDrawer } = useRightDrawer(); - await destroyOneRecord(recordId); - }, [resetTableRowSelection, destroyOneRecord, recordId]); + const handleDeleteClick = useCallback(async () => { + resetTableRowSelection(); - const isRemoteObject = objectMetadataItem.isRemote; + await destroyOneRecord(recordId); + }, [resetTableRowSelection, destroyOneRecord, recordId]); - const { isInRightDrawer } = useContext(ActionMenuContext); + const isRemoteObject = objectMetadataItem.isRemote; - const shouldBeRegistered = - !isRemoteObject && isDefined(selectedRecord?.deletedAt); + const { isInRightDrawer } = useContext(ActionMenuContext); - const onClick = () => { - if (!shouldBeRegistered) { - return; - } + const shouldBeRegistered = + !isRemoteObject && isDefined(selectedRecord?.deletedAt); - setIsDestroyRecordsModalOpen(true); - }; + const onClick = () => { + if (!shouldBeRegistered) { + return; + } - return { - shouldBeRegistered, - onClick, - ConfirmationModal: ( - { - await handleDeleteClick(); - if (isInRightDrawer) { - closeRightDrawer(); - } - }} - deleteButtonText={'Permanently Destroy Record'} - /> - ), - }; + setIsDestroyRecordsModalOpen(true); }; + + return { + shouldBeRegistered, + onClick, + ConfirmationModal: ( + { + await handleDeleteClick(); + if (isInRightDrawer) { + closeRightDrawer(); + } + }} + deleteButtonText={'Permanently Destroy Record'} + /> + ), + }; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useExportNoteAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useExportNoteAction.ts index bce0cfdf0..80f3b1960 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useExportNoteAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useExportNoteAction.ts @@ -1,49 +1,51 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { BlockNoteEditor } from '@blocknote/core'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useExportNoteAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { - const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); +export const useExportNoteAction: ActionHookWithObjectMetadataItem = ({ + objectMetadataItem, +}) => { + const recordId = useSelectedRecordIdOrThrow(); - const filename = `${(selectedRecord?.title || 'Untitled Note').replace(/[<>:"/\\|?*]/g, '-')}`; + const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); - const isNoteOrTask = - objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note || - objectMetadataItem?.nameSingular === CoreObjectNameSingular.Task; + const filename = `${(selectedRecord?.title || 'Untitled Note').replace(/[<>:"/\\|?*]/g, '-')}`; - const shouldBeRegistered = - isDefined(objectMetadataItem) && - isDefined(selectedRecord) && - isNoteOrTask; + const isNoteOrTask = + objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note || + objectMetadataItem?.nameSingular === CoreObjectNameSingular.Task; - const onClick = async () => { - if (!shouldBeRegistered || !selectedRecord?.body) { - return; - } + const shouldBeRegistered = + isDefined(objectMetadataItem) && isDefined(selectedRecord) && isNoteOrTask; - const editor = await BlockNoteEditor.create({ - initialContent: JSON.parse(selectedRecord.body), - }); + const onClick = async () => { + if (!shouldBeRegistered || !selectedRecord?.body) { + return; + } - const { exportBlockNoteEditorToPdf } = await import( - '@/action-menu/actions/record-actions/single-record/utils/exportBlockNoteEditorToPdf' - ); + const editor = await BlockNoteEditor.create({ + initialContent: JSON.parse(selectedRecord.body), + }); - await exportBlockNoteEditorToPdf(editor, filename); + const { exportBlockNoteEditorToPdf } = await import( + '@/action-menu/actions/record-actions/single-record/utils/exportBlockNoteEditorToPdf' + ); - // TODO later: implement DOCX export - // const { exportBlockNoteEditorToDocx } = await import( - // '@/action-menu/actions/record-actions/single-record/utils/exportBlockNoteEditorToDocx' - // ); - // await exportBlockNoteEditorToDocx(editor, filename); - }; + await exportBlockNoteEditorToPdf(editor, filename); - return { - shouldBeRegistered, - onClick, - }; + // TODO later: implement DOCX export + // const { exportBlockNoteEditorToDocx } = await import( + // '@/action-menu/actions/record-actions/single-record/utils/exportBlockNoteEditorToDocx' + // ); + // await exportBlockNoteEditorToDocx(editor, filename); }; + + return { + shouldBeRegistered, + onClick, + }; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction.ts index 4e55d6315..35757df3b 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination'; import { useContext } from 'react'; -export const useNavigateToNextRecordSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { +export const useNavigateToNextRecordSingleRecordAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const recordId = useSelectedRecordIdOrThrow(); + const { isInRightDrawer } = useContext(ActionMenuContext); const { navigateToNextRecord } = useRecordShowPagePagination( diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction.ts index 127cfa97a..36cec0c5f 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction.ts @@ -1,11 +1,15 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination'; import { useContext } from 'react'; -export const useNavigateToPreviousRecordSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { +export const useNavigateToPreviousRecordSingleRecordAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const recordId = useSelectedRecordIdOrThrow(); + const { isInRightDrawer } = useContext(ActionMenuContext); + const { navigateToPreviousRecord } = useRecordShowPagePagination( objectMetadataItem.nameSingular, recordId, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts index 57802d539..00d924365 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { isDefined } from 'twenty-ui'; -export const useRemoveFromFavoritesSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { +export const useRemoveFromFavoritesSingleRecordAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const recordId = useSelectedRecordIdOrThrow(); + const { sortedFavorites: favorites } = useFavorites(); const { deleteFavorite } = useDeleteFavorite(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow.tsx new file mode 100644 index 000000000..17f5aab14 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow.tsx @@ -0,0 +1,18 @@ +import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +export const useSelectedRecordIdOrThrow = () => { + const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( + contextStoreTargetedRecordsRuleComponentState, + ); + + if ( + contextStoreTargetedRecordsRule.mode === 'exclusion' || + (contextStoreTargetedRecordsRule.mode === 'selection' && + contextStoreTargetedRecordsRule.selectedRecordIds.length === 0) + ) { + throw new Error('Selected record ID is required'); + } + + return contextStoreTargetedRecordsRule.selectedRecordIds[0]; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts deleted file mode 100644 index 6c567e0c5..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1 } from '@/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1'; -import { DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2 } from '@/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2'; -import { WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig'; -import { WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig'; -import { WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const getActionConfig = ( - objectMetadataItem: ObjectMetadataItem, - isPageHeaderV2Enabled: boolean, -) => { - if (objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow) { - return WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG; - } - if ( - objectMetadataItem.nameSingular === CoreObjectNameSingular.WorkflowVersion - ) { - return WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG; - } - if (objectMetadataItem.nameSingular === CoreObjectNameSingular.WorkflowRun) { - return WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG; - } - return isPageHeaderV2Enabled - ? DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2 - : DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts index 19e5e64b1..279c1f079 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts @@ -55,10 +55,7 @@ const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ describe('useActivateDraftWorkflowSingleRecordAction', () => { it('should be registered', () => { const { result } = renderHook( - () => - useActivateDraftWorkflowSingleRecordAction({ - recordId: workflowMock.id, - }), + () => useActivateDraftWorkflowSingleRecordAction(), { wrapper, }, @@ -69,10 +66,7 @@ describe('useActivateDraftWorkflowSingleRecordAction', () => { it('should call activateWorkflowVersion on click', () => { const { result } = renderHook( - () => - useActivateDraftWorkflowSingleRecordAction({ - recordId: workflowMock.id, - }), + () => useActivateDraftWorkflowSingleRecordAction(), { wrapper, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts index 9cac8dfb5..e7d3c536d 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts @@ -56,10 +56,7 @@ const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ describe('useActivateLastPublishedVersionWorkflowSingleRecordAction', () => { it('should be registered', () => { const { result } = renderHook( - () => - useActivateLastPublishedVersionWorkflowSingleRecordAction({ - recordId: workflowMock.id, - }), + () => useActivateLastPublishedVersionWorkflowSingleRecordAction(), { wrapper, }, @@ -70,10 +67,7 @@ describe('useActivateLastPublishedVersionWorkflowSingleRecordAction', () => { it('should call activateWorkflowVersion on click', () => { const { result } = renderHook( - () => - useActivateLastPublishedVersionWorkflowSingleRecordAction({ - recordId: workflowMock.id, - }), + () => useActivateLastPublishedVersionWorkflowSingleRecordAction(), { wrapper, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts index 3dd091f46..77bc31ff8 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts @@ -104,10 +104,7 @@ describe('useDeactivateWorkflowSingleRecordAction', () => { () => deactivatedWorkflowMock, ); const { result } = renderHook( - () => - useDeactivateWorkflowSingleRecordAction({ - recordId: deactivatedWorkflowMock.id, - }), + () => useDeactivateWorkflowSingleRecordAction(), { wrapper: deactivatedWorkflowWrapper, }, @@ -121,10 +118,7 @@ describe('useDeactivateWorkflowSingleRecordAction', () => { () => activeWorkflowMock, ); const { result } = renderHook( - () => - useDeactivateWorkflowSingleRecordAction({ - recordId: activeWorkflowMock.id, - }), + () => useDeactivateWorkflowSingleRecordAction(), { wrapper: activeWorkflowWrapper, }, @@ -138,10 +132,7 @@ describe('useDeactivateWorkflowSingleRecordAction', () => { () => activeWorkflowMock, ); const { result } = renderHook( - () => - useDeactivateWorkflowSingleRecordAction({ - recordId: activeWorkflowMock.id, - }), + () => useDeactivateWorkflowSingleRecordAction(), { wrapper: activeWorkflowWrapper, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts index cf37a4a0f..430954ddd 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts @@ -178,10 +178,7 @@ describe('useDiscardDraftWorkflowSingleRecordAction', () => { () => noDraftWorkflowMock, ); const { result } = renderHook( - () => - useDiscardDraftWorkflowSingleRecordAction({ - recordId: noDraftWorkflowMock.id, - }), + () => useDiscardDraftWorkflowSingleRecordAction(), { wrapper: noDraftWorkflowWrapper, }, @@ -196,10 +193,7 @@ describe('useDiscardDraftWorkflowSingleRecordAction', () => { ); const { result } = renderHook( - () => - useDiscardDraftWorkflowSingleRecordAction({ - recordId: draftWorkflowMockWithOneVersion.id, - }), + () => useDiscardDraftWorkflowSingleRecordAction(), { wrapper: draftWorkflowWithOneVersionWrapper, }, @@ -213,10 +207,7 @@ describe('useDiscardDraftWorkflowSingleRecordAction', () => { () => draftWorkflowMock, ); const { result } = renderHook( - () => - useDiscardDraftWorkflowSingleRecordAction({ - recordId: draftWorkflowMock.id, - }), + () => useDiscardDraftWorkflowSingleRecordAction(), { wrapper: draftWorkflowWrapper, }, @@ -230,10 +221,7 @@ describe('useDiscardDraftWorkflowSingleRecordAction', () => { () => draftWorkflowMock, ); const { result } = renderHook( - () => - useDiscardDraftWorkflowSingleRecordAction({ - recordId: draftWorkflowMock.id, - }), + () => useDiscardDraftWorkflowSingleRecordAction(), { wrapper: draftWorkflowWrapper, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts index 280aa8019..99f72c0f8 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useActivateDraftWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useActivateDraftWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const { activateWorkflowVersion } = useActivateWorkflowVersion(); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts index 0c68e1a62..061569ac1 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useActivateLastPublishedVersionWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useActivateLastPublishedVersionWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const { activateWorkflowVersion } = useActivateWorkflowVersion(); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts index dd61f50de..2a51f735b 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useDeactivateWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useDeactivateWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion(); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts index a1fe61b51..d5db87b19 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useDiscardDraftWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useDiscardDraftWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion(); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts index 678a197f8..527df28b4 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts @@ -1,12 +1,15 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { useNavigate } from 'react-router-dom'; import { isDefined } from 'twenty-ui'; -export const useSeeActiveVersionWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeActiveVersionWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflow = useWorkflowWithCurrentVersion(recordId); const isDraft = workflow?.statuses?.includes('DRAFT') || false; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts index 08e427e61..0b245f7f3 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; @@ -6,8 +7,10 @@ import qs from 'qs'; import { useNavigate } from 'react-router-dom'; import { isDefined } from 'twenty-ui'; -export const useSeeRunsWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeRunsWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); const navigate = useNavigate(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts index 452424804..6abcc3993 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; @@ -6,8 +7,10 @@ import qs from 'qs'; import { useNavigate } from 'react-router-dom'; import { isDefined } from 'twenty-ui'; -export const useSeeVersionsWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeVersionsWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); const navigate = useNavigate(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts index dd1daeba2..5a74e7fd4 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useTestWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useTestWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); const { runWorkflowVersion } = useRunWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeRunsWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeRunsWorkflowVersionSingleRecordAction.ts index e8c397554..ab5e2795d 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeRunsWorkflowVersionSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeRunsWorkflowVersionSingleRecordAction.ts @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; @@ -8,8 +9,10 @@ import { useNavigate } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useSeeRunsWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeRunsWorkflowVersionSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId)); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion( diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts index 738b11fb7..e3d21342d 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts @@ -1,21 +1,23 @@ +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction'; -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useSeeVersionsWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeVersionsWorkflowVersionSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId)); if (!isDefined(workflowVersion)) { throw new Error('Workflow version not found'); } + // TODO: Add recordIds to the hook const { shouldBeRegistered, onClick } = - useSeeVersionsWorkflowSingleRecordAction({ - recordId: workflowVersion.workflow.id, - }); + useSeeVersionsWorkflowSingleRecordAction(); return { shouldBeRegistered, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx index 24e098306..437d16e12 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL'; import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal'; @@ -10,8 +11,10 @@ import { useNavigate } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useUseAsDraftWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useUseAsDraftWorkflowVersionSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowVersion = useWorkflowVersion(recordId); const workflow = useWorkflowWithCurrentVersion( diff --git a/packages/twenty-front/src/modules/action-menu/actions/types/ActionHook.ts b/packages/twenty-front/src/modules/action-menu/actions/types/ActionHook.ts new file mode 100644 index 000000000..2add89e61 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/types/ActionHook.ts @@ -0,0 +1,14 @@ +import { ActionHookResult } from '@/action-menu/actions/types/ActionHookResult'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export type ActionHook = + | ActionHookWithoutObjectMetadataItem + | ActionHookWithObjectMetadataItem; + +export type ActionHookWithoutObjectMetadataItem = () => ActionHookResult; + +export type ActionHookWithObjectMetadataItem = ({ + objectMetadataItem, +}: { + objectMetadataItem: ObjectMetadataItem; +}) => ActionHookResult; diff --git a/packages/twenty-front/src/modules/action-menu/actions/types/SingleRecordActionHook.ts b/packages/twenty-front/src/modules/action-menu/actions/types/SingleRecordActionHook.ts deleted file mode 100644 index 4bf981332..000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/types/SingleRecordActionHook.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ActionHookResult } from '@/action-menu/actions/types/ActionHookResult'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export type SingleRecordActionHook = - | SingleRecordActionHookWithoutObjectMetadataItem - | SingleRecordActionHookWithObjectMetadataItem; - -export type SingleRecordActionHookWithoutObjectMetadataItem = ({ - recordId, -}: { - recordId: string; -}) => ActionHookResult; - -export type SingleRecordActionHookWithObjectMetadataItem = ({ - recordId, - objectMetadataItem, -}: { - recordId: string; - objectMetadataItem: ObjectMetadataItem; -}) => ActionHookResult; diff --git a/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts new file mode 100644 index 000000000..9d2cd4b21 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts @@ -0,0 +1,25 @@ +import { DEFAULT_ACTIONS_CONFIG_V1 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV1'; +import { DEFAULT_ACTIONS_CONFIG_V2 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV2'; +import { WORKFLOW_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowActionsConfig'; +import { WORKFLOW_RUNS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig'; +import { WORKFLOW_VERSIONS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export const getActionConfig = ( + objectMetadataItem: ObjectMetadataItem, + isPageHeaderV2Enabled: boolean, +) => { + switch (objectMetadataItem.nameSingular) { + case CoreObjectNameSingular.Workflow: + return WORKFLOW_ACTIONS_CONFIG; + case CoreObjectNameSingular.WorkflowVersion: + return WORKFLOW_VERSIONS_ACTIONS_CONFIG; + case CoreObjectNameSingular.WorkflowRun: + return WORKFLOW_RUNS_ACTIONS_CONFIG; + default: + return isPageHeaderV2Enabled + ? DEFAULT_ACTIONS_CONFIG_V2 + : DEFAULT_ACTIONS_CONFIG_V1; + } +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/utils/getActionViewType.ts b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionViewType.ts new file mode 100644 index 000000000..435bebe05 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionViewType.ts @@ -0,0 +1,32 @@ +import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; +import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; + +export const getActionViewType = ( + contextStoreCurrentViewType: ContextStoreViewType | null, + contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule, +) => { + if (contextStoreCurrentViewType === null) { + return null; + } + + if (contextStoreCurrentViewType === ContextStoreViewType.ShowPage) { + return ActionViewType.SHOW_PAGE; + } + + if ( + contextStoreTargetedRecordsRule.mode === 'selection' && + contextStoreTargetedRecordsRule.selectedRecordIds.length === 0 + ) { + return ActionViewType.INDEX_PAGE_NO_SELECTION; + } + + if ( + contextStoreTargetedRecordsRule.mode === 'selection' && + contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 + ) { + return ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION; + } + + return ActionViewType.INDEX_PAGE_BULK_SELECTION; +}; diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts deleted file mode 100644 index 3bdb784cf..000000000 --- a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { getActionConfig } from '@/action-menu/actions/record-actions/single-record/utils/getActionConfig'; -import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { wrapActionInCallbacks } from '@/action-menu/actions/utils/wrapActionInCallbacks'; -import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { useContext } from 'react'; -import { isDefined } from 'twenty-ui'; -import { FeatureFlagKey } from '~/generated/graphql'; - -export const useActionMenuEntriesWithCallbacks = ( - objectMetadataItem: ObjectMetadataItem, - viewType: ActionViewType, -) => { - const isPageHeaderV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IsPageHeaderV2Enabled, - ); - - const actionConfig = getActionConfig( - objectMetadataItem, - isPageHeaderV2Enabled, - ); - - const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( - contextStoreTargetedRecordsRuleComponentState, - ); - - const selectedRecordId = - contextStoreTargetedRecordsRule.mode === 'selection' - ? contextStoreTargetedRecordsRule.selectedRecordIds[0] - : undefined; - - if (!isDefined(selectedRecordId)) { - throw new Error('Selected record ID is required'); - } - - const { onActionStartedCallback, onActionExecutedCallback } = - useContext(ActionMenuContext); - - const actionMenuEntries = Object.values(actionConfig ?? {}) - .filter((action) => action.availableOn?.includes(viewType)) - .map((action) => { - const { shouldBeRegistered, onClick, ConfirmationModal } = - action.actionHook({ - recordId: selectedRecordId, - objectMetadataItem, - }); - - if (!shouldBeRegistered) { - return undefined; - } - - const wrappedAction = wrapActionInCallbacks({ - action: { - ...action, - onClick, - ConfirmationModal, - }, - onActionStartedCallback, - onActionExecutedCallback, - }); - - return wrappedAction; - }) - .filter(isDefined); - - return { actionMenuEntries }; -}; diff --git a/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx b/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx index 47ac02bcc..f08377a19 100644 --- a/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx @@ -1,6 +1,7 @@ import { ReactNode, useEffect, useState } from 'react'; import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; +import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { ContextStoreTargetedRecordsRule, contextStoreTargetedRecordsRuleComponentState, @@ -13,10 +14,12 @@ export const JestContextStoreSetter = ({ mode: 'selection', selectedRecordIds: [], }, + contextStoreNumberOfSelectedRecords = 0, contextStoreCurrentObjectMetadataNameSingular = '', children, }: { contextStoreTargetedRecordsRule?: ContextStoreTargetedRecordsRule; + contextStoreNumberOfSelectedRecords?: number; contextStoreCurrentObjectMetadataNameSingular?: string; children: ReactNode; }) => { @@ -28,6 +31,10 @@ export const JestContextStoreSetter = ({ contextStoreCurrentObjectMetadataIdComponentState, ); + const setContextStoreNumberOfSelectedRecords = useSetRecoilComponentStateV2( + contextStoreNumberOfSelectedRecordsComponentState, + ); + const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular: contextStoreCurrentObjectMetadataNameSingular, }); @@ -38,12 +45,15 @@ export const JestContextStoreSetter = ({ useEffect(() => { setContextStoreTargetedRecordsRule(contextStoreTargetedRecordsRule); setContextStoreCurrentObjectMetadataId(contextStoreCurrentObjectMetadataId); + setContextStoreNumberOfSelectedRecords(contextStoreNumberOfSelectedRecords); setIsLoaded(true); }, [ setContextStoreTargetedRecordsRule, setContextStoreCurrentObjectMetadataId, contextStoreTargetedRecordsRule, contextStoreCurrentObjectMetadataId, + setContextStoreNumberOfSelectedRecords, + contextStoreNumberOfSelectedRecords, ]); return isLoaded ? <>{children} : null; diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx index 882675cd4..194601009 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx @@ -13,6 +13,7 @@ export type GetJestMetadataAndApolloMocksAndActionMenuWrapperProps = { | undefined; onInitializeRecoilSnapshot?: (snapshot: MutableSnapshot) => void; contextStoreTargetedRecordsRule?: ContextStoreTargetedRecordsRule; + contextStoreNumberOfSelectedRecords?: number; contextStoreCurrentObjectMetadataNameSingular?: string; componentInstanceId: string; }; @@ -21,6 +22,7 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({ apolloMocks, onInitializeRecoilSnapshot, contextStoreTargetedRecordsRule, + contextStoreNumberOfSelectedRecords, contextStoreCurrentObjectMetadataNameSingular, componentInstanceId, }: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps) => { @@ -43,6 +45,9 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({ >