Remove Add New button when softDelete filter is present (#14385)

Closes https://github.com/twentyhq/twenty/issues/13854

In this PR
- When evaluating whether there was a soft delete filter enabled, we
were only taking into account the filter that shows all deleted records,
that can be enabled from the side panel ("See deleted records"). Now we
are also taking into account any filter on deletedAt.
- We lacked some places where we should not offer to add a new record if
a soft delete filter is on, ex on the empty page + in the kanban headers
- I decided not to add a constraint on api-side because I think there
could be use cases when importing data where we would want to be able to
create soft deleted records
This commit is contained in:
Marie
2025-09-10 18:01:57 +02:00
committed by GitHub
parent 4239fe93b9
commit 6895902ddf
16 changed files with 227 additions and 100 deletions
@@ -74,8 +74,9 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
position: 0,
isPinned: true,
Icon: IconPlus,
shouldBeRegistered: ({ objectPermissions, isSoftDeleteFilterActive }) =>
(objectPermissions.canUpdateObjectRecords && !isSoftDeleteFilterActive) ??
shouldBeRegistered: ({ objectPermissions, hasAnySoftDeleteFilterOnView }) =>
(objectPermissions.canUpdateObjectRecords &&
!hasAnySoftDeleteFilterOnView) ??
false,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
component: <CreateNewTableRecordNoSelectionRecordAction />,
@@ -108,12 +109,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
shouldBeRegistered: ({
selectedRecord,
isFavorite,
isSoftDeleteFilterActive,
hasAnySoftDeleteFilterOnView,
}) =>
!selectedRecord?.isRemote &&
!isFavorite &&
!isDefined(selectedRecord?.deletedAt) &&
!isSoftDeleteFilterActive,
!hasAnySoftDeleteFilterOnView,
availableOn: [
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
ActionViewType.SHOW_PAGE,
@@ -132,14 +133,14 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
shouldBeRegistered: ({
selectedRecord,
isFavorite,
isSoftDeleteFilterActive,
hasAnySoftDeleteFilterOnView,
}) =>
isDefined(selectedRecord) &&
!selectedRecord?.isRemote &&
isDefined(isFavorite) &&
isFavorite &&
!isDefined(selectedRecord?.deletedAt) &&
!isSoftDeleteFilterActive,
!hasAnySoftDeleteFilterOnView,
availableOn: [
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
ActionViewType.SHOW_PAGE,
@@ -231,8 +232,8 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
Icon: IconFileImport,
accent: 'default',
isPinned: false,
shouldBeRegistered: ({ isSoftDeleteFilterActive }) =>
!isSoftDeleteFilterActive,
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
!hasAnySoftDeleteFilterOnView,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
component: <ImportRecordsNoSelectionRecordAction />,
requiredPermissionFlag: PermissionFlagType.IMPORT_CSV,
@@ -264,12 +265,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
isPinned: true,
shouldBeRegistered: ({
selectedRecord,
isSoftDeleteFilterActive,
hasAnySoftDeleteFilterOnView,
objectPermissions,
}) =>
(isDefined(selectedRecord) &&
!selectedRecord.isRemote &&
!isSoftDeleteFilterActive &&
!hasAnySoftDeleteFilterOnView &&
objectPermissions.canSoftDeleteObjectRecords &&
!isDefined(selectedRecord?.deletedAt)) ??
false,
@@ -292,12 +293,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
shouldBeRegistered: ({
objectPermissions,
isRemote,
isSoftDeleteFilterActive,
hasAnySoftDeleteFilterOnView,
numberOfSelectedRecords,
}) =>
(objectPermissions.canSoftDeleteObjectRecords &&
!isRemote &&
!isSoftDeleteFilterActive &&
!hasAnySoftDeleteFilterOnView &&
isDefined(numberOfSelectedRecords) &&
numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
false,
@@ -314,8 +315,8 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
Icon: IconRotate2,
accent: 'default',
isPinned: false,
shouldBeRegistered: ({ isSoftDeleteFilterActive }) =>
!isSoftDeleteFilterActive,
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
!hasAnySoftDeleteFilterOnView,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
component: <SeeDeletedRecordsNoSelectionRecordAction />,
},
@@ -329,8 +330,8 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
Icon: IconLayout,
accent: 'default',
isPinned: false,
shouldBeRegistered: ({ isSoftDeleteFilterActive }) =>
!isSoftDeleteFilterActive,
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
!hasAnySoftDeleteFilterOnView,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
component: <CreateNewViewNoSelectionRecord />,
},
@@ -344,8 +345,8 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
Icon: IconEyeOff,
accent: 'default',
isPinned: false,
shouldBeRegistered: ({ isSoftDeleteFilterActive }) =>
isDefined(isSoftDeleteFilterActive) && isSoftDeleteFilterActive,
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
isDefined(hasAnySoftDeleteFilterOnView) && hasAnySoftDeleteFilterOnView,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
component: <HideDeletedRecordsNoSelectionRecordAction />,
},
@@ -407,13 +408,13 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
shouldBeRegistered: ({
objectPermissions,
isRemote,
isSoftDeleteFilterActive,
hasAnySoftDeleteFilterOnView,
numberOfSelectedRecords,
}) =>
(objectPermissions.canDestroyObjectRecords &&
!isRemote &&
isDefined(isSoftDeleteFilterActive) &&
isSoftDeleteFilterActive &&
isDefined(hasAnySoftDeleteFilterOnView) &&
hasAnySoftDeleteFilterOnView &&
isDefined(numberOfSelectedRecords) &&
numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
false,
@@ -435,13 +436,14 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
objectPermissions,
isRemote,
isShowPage,
isSoftDeleteFilterActive,
hasAnySoftDeleteFilterOnView,
}) =>
(!isRemote &&
isDefined(selectedRecord?.deletedAt) &&
objectPermissions.canSoftDeleteObjectRecords &&
((isDefined(isShowPage) && isShowPage) ||
(isDefined(isSoftDeleteFilterActive) && isSoftDeleteFilterActive))) ??
(isDefined(hasAnySoftDeleteFilterOnView) &&
hasAnySoftDeleteFilterOnView))) ??
false,
availableOn: [
ActionViewType.SHOW_PAGE,
@@ -462,13 +464,13 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
shouldBeRegistered: ({
objectPermissions,
isRemote,
isSoftDeleteFilterActive,
hasAnySoftDeleteFilterOnView,
numberOfSelectedRecords,
}) =>
(objectPermissions.canSoftDeleteObjectRecords &&
!isRemote &&
isDefined(isSoftDeleteFilterActive) &&
isSoftDeleteFilterActive &&
isDefined(hasAnySoftDeleteFilterOnView) &&
hasAnySoftDeleteFilterOnView &&
isDefined(numberOfSelectedRecords) &&
numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
false,
@@ -202,8 +202,8 @@ export const WORKFLOW_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
Icon: IconHistoryToggle,
accent: 'default',
isPinned: true,
shouldBeRegistered: ({ isSoftDeleteFilterActive }) =>
!isSoftDeleteFilterActive,
shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
!hasAnySoftDeleteFilterOnView,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
component: (
<ActionLink
@@ -30,14 +30,16 @@ export const HideDeletedRecordsNoSelectionRecordAction = () => {
viewBarId: recordIndexId,
});
const { checkIsSoftDeleteFilter } = useCheckIsSoftDeleteFilter();
const { isRecordFilterAboutSoftDelete } = useCheckIsSoftDeleteFilter();
const currentRecordFilters = useRecoilComponentValue(
currentRecordFiltersComponentState,
recordIndexId,
);
const deletedFilter = currentRecordFilters.find(checkIsSoftDeleteFilter);
const deletedFilter = currentRecordFilters.find(
isRecordFilterAboutSoftDelete,
);
const { removeRecordFilter } = useRemoveRecordFilter();
@@ -10,7 +10,7 @@ export type ShouldBeRegisteredFunctionParams = {
objectPermissions: ObjectPermissions;
recordFilters?: RecordFilter[];
isShowPage?: boolean;
isSoftDeleteFilterActive?: boolean;
hasAnySoftDeleteFilterOnView?: boolean;
isInRightDrawer?: boolean;
isFavorite?: boolean;
isRemote?: boolean;
@@ -10,8 +10,8 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useContext } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
@@ -51,8 +51,8 @@ export const useShouldActionBeRegisteredParams = ({
const { isInRightDrawer } = useContext(ActionMenuContext);
const isSoftDeleteFilterActive = useRecoilComponentValue(
isSoftDeleteFilterActiveComponentState,
const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
hasAnySoftDeleteFilterOnViewComponentSelector,
);
const isShowPage =
@@ -106,7 +106,7 @@ export const useShouldActionBeRegisteredParams = ({
objectPermissions,
isNoteOrTask,
isInRightDrawer,
isSoftDeleteFilterActive,
hasAnySoftDeleteFilterOnView,
isShowPage,
selectedRecord,
numberOfSelectedRecords,