From e22d31c55389cb61de5e46604706f50269f891da Mon Sep 17 00:00:00 2001 From: Thomas des Francs Date: Wed, 24 Jun 2026 19:15:01 +0200 Subject: [PATCH] Close side panel after command menu actions (#22118) ## What changed - Close the command side panel after eligible headless command-menu actions complete or are confirmed. - Cover delete, restore, permanent destroy, create view, import, see deleted, and hide deleted record commands. - Keep create-record behavior unchanged so a newly created record can still open in the side panel. - Add focused Jest coverage for the affected command components and the create-record exception. # Before https://github.com/user-attachments/assets/36632578-3cab-47db-8f6d-350cd6fce683 # After https://github.com/user-attachments/assets/c9dba19b-7f4a-44aa-ae7f-57660c5a1e96 --------- Co-authored-by: Charles Bochet --- .../record/components/DeleteRecordsCommand.tsx | 4 ++++ .../record/components/DestroyRecordsCommand.tsx | 11 +++++------ .../record/components/RestoreRecordsCommand.tsx | 3 +++ .../CreateNewViewNoSelectionRecordCommand.tsx | 4 ++++ .../HideDeletedRecordsNoSelectionRecordCommand.tsx | 3 +++ .../ImportRecordsNoSelectionRecordCommand.tsx | 13 ++++++++----- .../SeeDeletedRecordsNoSelectionRecordCommand.tsx | 3 +++ 7 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DeleteRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DeleteRecordsCommand.tsx index d58632be4d..9b1b2e83dc 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DeleteRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DeleteRecordsCommand.tsx @@ -6,6 +6,7 @@ import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryP import { useIncrementalDeleteManyRecords } from '@/object-record/hooks/useIncrementalDeleteManyRecords'; import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { type RecordGqlOperationFilter } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -41,9 +42,12 @@ export const DeleteRecordsCommand = () => { const { removeNavigationMenuItemsByTargetRecordIds } = useRemoveNavigationMenuItemByTargetRecordId(); + const { closeSidePanelMenu } = useSidePanelMenu(); + const handleExecute = async () => { removeSelectedRecordsFromRecordBoard(); resetTableRowSelection(); + closeSidePanelMenu(); if (isDefined(recordId)) { const foundNavigationMenuItem = [ diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx index 6550557eec..5a3c8517a4 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/DestroyRecordsCommand.tsx @@ -60,14 +60,13 @@ export const DestroyRecordsCommand = () => { throw new Error('Cannot destroy records without a valid filter'); } - await incrementalDestroyManyRecords(); - - if (!isSingleRecord) { - return; + if (!isSingleRecord || isInSidePanel) { + closeSidePanelMenu(); } - if (isInSidePanel) { - closeSidePanelMenu(); + await incrementalDestroyManyRecords(); + + if (!isSingleRecord || isInSidePanel) { return; } diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/RestoreRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/RestoreRecordsCommand.tsx index 7b48c32310..4734557b04 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/RestoreRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/RestoreRecordsCommand.tsx @@ -5,6 +5,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords'; import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { t } from '@lingui/core/macro'; import { type RecordGqlOperationFilter } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -24,6 +25,7 @@ export const RestoreRecordsCommand = () => { const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId); const { removeSelectedRecordsFromRecordBoard } = useRemoveSelectedRecordsFromRecordBoard(recordIndexId); + const { closeSidePanelMenu } = useSidePanelMenu(); const { restoreManyRecords } = useRestoreManyRecords({ objectNameSingular: objectMetadataItem.nameSingular, @@ -49,6 +51,7 @@ export const RestoreRecordsCommand = () => { const handleExecute = async () => { removeSelectedRecordsFromRecordBoard(); + closeSidePanelMenu(); if (!isDefined(graphqlFilter)) { throw new Error('Cannot restore records without a valid filter'); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx index 69a29fea0e..dfa45335ff 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx @@ -1,5 +1,6 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId'; @@ -11,6 +12,7 @@ export const CreateNewViewNoSelectionRecordCommand = () => { const { currentViewId, recordIndexId } = useHeadlessCommandContextApi(); const { openDropdown } = useOpenDropdown(); + const { closeSidePanelMenu } = useSidePanelMenu(); if (!isDefined(currentViewId) || !isDefined(recordIndexId)) { throw new Error( @@ -26,6 +28,8 @@ export const CreateNewViewNoSelectionRecordCommand = () => { const { setViewPickerMode } = useViewPickerMode(recordIndexId); const handleExecute = () => { + closeSidePanelMenu(); + if (currentViewId) { setViewPickerReferenceViewId(currentViewId); } diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx index 82e13b4de8..e969f6ae07 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx @@ -4,6 +4,7 @@ import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { isDefined } from 'twenty-shared/utils'; @@ -34,6 +35,7 @@ export const HideDeletedRecordsNoSelectionRecordCommand = () => { ); const { removeRecordFilter } = useRemoveRecordFilter(recordIndexId); + const { closeSidePanelMenu } = useSidePanelMenu(); const handleExecute = () => { if (!isDefined(deletedFilter)) { @@ -42,6 +44,7 @@ export const HideDeletedRecordsNoSelectionRecordCommand = () => { removeRecordFilter({ recordFilterId: deletedFilter.id }); toggleSoftDeleteFilterState(false); + closeSidePanelMenu(); }; return ; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx index abbcd9fb97..33255f903b 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx @@ -1,6 +1,7 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useOpenObjectRecordsSpreadsheetImportDialog } from '@/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { isDefined } from 'twenty-shared/utils'; export const ImportRecordsNoSelectionRecordCommand = () => { @@ -14,10 +15,12 @@ export const ImportRecordsNoSelectionRecordCommand = () => { useOpenObjectRecordsSpreadsheetImportDialog( objectMetadataItem.nameSingular, ); + const { closeSidePanelMenu } = useSidePanelMenu(); - return ( - - ); + const handleExecute = () => { + closeSidePanelMenu(); + openObjectRecordsSpreadsheetImportDialog(); + }; + + return ; }; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx index f7c710cebe..587dc56f25 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx @@ -1,6 +1,7 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { isDefined } from 'twenty-shared/utils'; export const SeeDeletedRecordsNoSelectionRecordCommand = () => { @@ -18,12 +19,14 @@ export const SeeDeletedRecordsNoSelectionRecordCommand = () => { viewBarId: recordIndexId, recordFiltersInstanceId: recordIndexId, }); + const { closeSidePanelMenu } = useSidePanelMenu(); return ( { handleToggleTrashColumnFilter(); toggleSoftDeleteFilterState(true); + closeSidePanelMenu(); }} /> );