From 6895902ddf7d4fbf8bbc8361b90888eb02a9d881 Mon Sep 17 00:00:00 2001
From: Marie <51697796+ijreilly@users.noreply.github.com>
Date: Wed, 10 Sep 2025 18:01:57 +0200
Subject: [PATCH] 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
---
.../constants/DefaultRecordActionsConfig.tsx | 54 ++++++++++---------
.../constants/WorkflowActionsConfig.tsx | 4 +-
...eDeletedRecordsNoSelectionRecordAction.tsx | 6 ++-
.../types/ShouldBeRegisteredFunctionParams.ts | 2 +-
.../useShouldActionBeRegisteredParams.ts | 8 +--
.../components/RecordBoardColumnHeader.tsx | 32 ++++++-----
.../RecordBoardColumnNewRecordButton.tsx | 10 ++++
.../hooks/useCheckIsSoftDeleteFilter.ts | 40 ++++++--------
.../states/hasAnySoftDeleteFilterOnView.ts | 29 ++++++++++
.../utils/getRecordFilterFieldMetadataItem.ts | 28 ++++++++++
.../utils/isRecordFilterAboutSoftDelete.ts | 29 ++++++++++
.../components/RecordTableAddNew.tsx | 9 ++++
.../RecordTableEmptyStateDisplay.tsx | 26 +++++----
.../RecordTableEmptyStateSoftDelete.tsx | 6 ++-
.../RecordTableHeaderLabelIdentifierCell.tsx | 8 ++-
.../views/components/ViewBarDetails.tsx | 36 ++++++-------
16 files changed, 227 insertions(+), 100 deletions(-)
create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/states/hasAnySoftDeleteFilterOnView.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterFieldMetadataItem.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordFilterAboutSoftDelete.ts
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
index 31779f2fa5..28b4c9af68 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
@@ -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: ,
@@ -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: ,
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: ,
},
@@ -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: ,
},
@@ -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: ,
},
@@ -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,
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx
index 7933801486..8058d50705 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx
@@ -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: (
{
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();
diff --git a/packages/twenty-front/src/modules/action-menu/actions/types/ShouldBeRegisteredFunctionParams.ts b/packages/twenty-front/src/modules/action-menu/actions/types/ShouldBeRegisteredFunctionParams.ts
index 0567448a21..4f4aa8fd49 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/types/ShouldBeRegisteredFunctionParams.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/types/ShouldBeRegisteredFunctionParams.ts
@@ -10,7 +10,7 @@ export type ShouldBeRegisteredFunctionParams = {
objectPermissions: ObjectPermissions;
recordFilters?: RecordFilter[];
isShowPage?: boolean;
- isSoftDeleteFilterActive?: boolean;
+ hasAnySoftDeleteFilterOnView?: boolean;
isInRightDrawer?: boolean;
isFavorite?: boolean;
isRemote?: boolean;
diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts b/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts
index c7600e6c00..6ecb29f9d3 100644
--- a/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts
+++ b/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts
@@ -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,
diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx
index b7fd2b506e..7b6301466d 100644
--- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx
@@ -7,10 +7,12 @@ import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/reco
import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { useAggregateRecordsForRecordBoardColumn } from '@/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn';
+import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useToggleDropdown } from '@/ui/layout/dropdown/hooks/useToggleDropdown';
+import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { Tag } from 'twenty-ui/components';
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
@@ -80,6 +82,10 @@ export const RecordBoardColumnHeader = () => {
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
+ const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
+ hasAnySoftDeleteFilterOnViewComponentSelector,
+ );
+
const { createNewIndexRecord } = useCreateNewIndexRecord({
objectMetadataItem: objectMetadataItem,
});
@@ -145,18 +151,20 @@ export const RecordBoardColumnHeader = () => {
});
}}
/>
- {hasObjectUpdatePermissions && (
- {
- createNewIndexRecord({
- position: 'first',
- [selectFieldMetadataItem.name]: columnDefinition.value,
- });
- }}
- />
- )}
+ {hasObjectUpdatePermissions &&
+ !hasAnySoftDeleteFilterOnView && (
+ {
+ createNewIndexRecord({
+ position: 'first',
+ [selectFieldMetadataItem.name]:
+ columnDefinition.value,
+ });
+ }}
+ />
+ )}
)}
diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton.tsx
index 3407909131..ce62e0b46b 100644
--- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton.tsx
@@ -1,7 +1,9 @@
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
+import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
+import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
@@ -32,6 +34,10 @@ export const RecordBoardColumnNewRecordButton = () => {
const { columnDefinition } = useContext(RecordBoardColumnContext);
+ const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
+ hasAnySoftDeleteFilterOnViewComponentSelector,
+ );
+
const objectPermissions = useObjectPermissionsForObject(
objectMetadataItem.id,
);
@@ -46,6 +52,10 @@ export const RecordBoardColumnNewRecordButton = () => {
return null;
}
+ if (hasAnySoftDeleteFilterOnView) {
+ return null;
+ }
+
return (
{
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter.ts
index 4280974cb5..2ab17935a8 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter.ts
@@ -2,39 +2,33 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
import { SOFT_DELETE_FILTER_FIELD_NAME } from '@/object-record/record-filter/constants/SoftDeleteFilterFieldName';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
-import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
-import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
-import { isDefined } from 'twenty-shared/utils';
+import { getRecordFilterFieldMetadataItem } from '@/object-record/record-filter/utils/getRecordFilterFieldMetadataItem';
+import { isRecordFilterAboutSoftDelete as isRecordFilterAboutSoftDeleteUtil } from '@/object-record/record-filter/utils/isRecordFilterAboutSoftDelete';
export const useCheckIsSoftDeleteFilter = () => {
const { objectMetadataItems } = useObjectMetadataItems();
- const isSoftDeleteFilterActive = useRecoilComponentValue(
- isSoftDeleteFilterActiveComponentState,
- );
+ const isRecordFilterAboutSoftDelete = (recordFilter: RecordFilter) => {
+ return isRecordFilterAboutSoftDeleteUtil({
+ recordFilter,
+ objectMetadataItems,
+ });
+ };
- const checkIsSoftDeleteFilter = (recordFilter: RecordFilter) => {
- const allFieldMetadataItems = objectMetadataItems.flatMap(
- (objectMetadataItem) => objectMetadataItem.fields,
- );
+ const isSeeDeletedRecordsFilter = (recordFilter: RecordFilter) => {
+ const foundFieldMetadataItem = getRecordFilterFieldMetadataItem({
+ recordFilter,
+ objectMetadataItems,
+ });
- const foundFieldMetadataItem = allFieldMetadataItems.find(
- (fieldMetadataItem) =>
- fieldMetadataItem.id === recordFilter.fieldMetadataId,
- );
-
- if (!isDefined(foundFieldMetadataItem)) {
- throw new Error(
- `Field metadata item not found for field metadata id: ${recordFilter.fieldMetadataId}`,
- );
- }
+ const isNotEmptyFilter =
+ recordFilter.operand === RecordFilterOperand.IsNotEmpty;
return (
foundFieldMetadataItem.name === SOFT_DELETE_FILTER_FIELD_NAME &&
- isSoftDeleteFilterActive &&
- recordFilter.operand === RecordFilterOperand.IsNotEmpty
+ isNotEmptyFilter
);
};
- return { checkIsSoftDeleteFilter };
+ return { isRecordFilterAboutSoftDelete, isSeeDeletedRecordsFilter };
};
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/states/hasAnySoftDeleteFilterOnView.ts b/packages/twenty-front/src/modules/object-record/record-filter/states/hasAnySoftDeleteFilterOnView.ts
new file mode 100644
index 0000000000..0b223812e8
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-filter/states/hasAnySoftDeleteFilterOnView.ts
@@ -0,0 +1,29 @@
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { isRecordFilterAboutSoftDelete } from '@/object-record/record-filter/utils/isRecordFilterAboutSoftDelete';
+import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
+
+export const hasAnySoftDeleteFilterOnViewComponentSelector =
+ createComponentSelector({
+ key: 'hasAnySoftDeleteFilterOnViewComponentSelector',
+ componentInstanceContext: RecordFiltersComponentInstanceContext,
+ get:
+ ({ instanceId }) =>
+ ({ get }) => {
+ const objectMetadataItems = get(objectMetadataItemsState);
+ const currentRecordFilters = get(
+ currentRecordFiltersComponentState.atomFamily({ instanceId }),
+ );
+
+ const hasAnySoftDeleteFilterOnView = currentRecordFilters.some(
+ (recordFilter) => {
+ return isRecordFilterAboutSoftDelete({
+ recordFilter,
+ objectMetadataItems,
+ });
+ },
+ );
+ return hasAnySoftDeleteFilterOnView;
+ },
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterFieldMetadataItem.ts
new file mode 100644
index 0000000000..4757fb6d9d
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterFieldMetadataItem.ts
@@ -0,0 +1,28 @@
+import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { isDefined } from 'twenty-shared/utils';
+
+export const getRecordFilterFieldMetadataItem = ({
+ recordFilter,
+ objectMetadataItems,
+}: {
+ recordFilter: RecordFilter;
+ objectMetadataItems: ObjectMetadataItem[];
+}) => {
+ const allFieldMetadataItems = objectMetadataItems.flatMap(
+ (objectMetadataItem) => objectMetadataItem.fields,
+ );
+
+ const foundFieldMetadataItem = allFieldMetadataItems.find(
+ (fieldMetadataItem) =>
+ fieldMetadataItem.id === recordFilter.fieldMetadataId,
+ );
+
+ if (!isDefined(foundFieldMetadataItem)) {
+ throw new Error(
+ `Field metadata item not found for field metadata id: ${recordFilter.fieldMetadataId}`,
+ );
+ }
+
+ return foundFieldMetadataItem;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordFilterAboutSoftDelete.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordFilterAboutSoftDelete.ts
new file mode 100644
index 0000000000..cb73cddd43
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordFilterAboutSoftDelete.ts
@@ -0,0 +1,29 @@
+import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { SOFT_DELETE_FILTER_FIELD_NAME } from '@/object-record/record-filter/constants/SoftDeleteFilterFieldName';
+import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
+import { getRecordFilterFieldMetadataItem } from '@/object-record/record-filter/utils/getRecordFilterFieldMetadataItem';
+import isEmpty from 'lodash.isempty';
+
+export const isRecordFilterAboutSoftDelete = ({
+ recordFilter,
+ objectMetadataItems,
+}: {
+ recordFilter: RecordFilter;
+ objectMetadataItems: ObjectMetadataItem[];
+}) => {
+ const foundFieldMetadataItem = getRecordFilterFieldMetadataItem({
+ recordFilter,
+ objectMetadataItems,
+ });
+
+ const valueIsNotEmptyFilter =
+ (recordFilter.operand === RecordFilterOperand.Is &&
+ !isEmpty(recordFilter.value)) ||
+ recordFilter.operand === RecordFilterOperand.IsNotEmpty;
+
+ return (
+ foundFieldMetadataItem.name === SOFT_DELETE_FILTER_FIELD_NAME &&
+ valueIsNotEmptyFilter
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableAddNew.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableAddNew.tsx
index ba4f500f54..2620263854 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableAddNew.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableAddNew.tsx
@@ -1,4 +1,5 @@
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
+import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
@@ -24,6 +25,14 @@ export const RecordTableAddNew = () => {
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
+ const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
+ hasAnySoftDeleteFilterOnViewComponentSelector,
+ );
+
+ if (hasAnySoftDeleteFilterOnView) {
+ return null;
+ }
+
if (!hasObjectUpdatePermissions || !hasRecordTableFetchedAllRecords) {
return null;
}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx
index acd024b12b..d9d5eb3064 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx
@@ -1,6 +1,8 @@
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
+import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
+import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { type IconComponent } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import {
@@ -44,6 +46,10 @@ export const RecordTableEmptyStateDisplay = (
objectMetadataItem,
});
+ const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
+ hasAnySoftDeleteFilterOnViewComponentSelector,
+ );
+
return (
@@ -56,15 +62,17 @@ export const RecordTableEmptyStateDisplay = (
{'buttonComponent' in props && props.buttonComponent}
- {'buttonTitle' in props && !isReadOnly && (
-
- )}
+ {'buttonTitle' in props &&
+ !isReadOnly &&
+ !hasAnySoftDeleteFilterOnView && (
+
+ )}
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx
index 04374f6b87..d727c7ab58 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx
@@ -28,10 +28,12 @@ export const RecordTableEmptyStateSoftDelete = () => {
const { removeRecordFilter } = useRemoveRecordFilter();
- const { checkIsSoftDeleteFilter } = useCheckIsSoftDeleteFilter();
+ const { isRecordFilterAboutSoftDelete } = useCheckIsSoftDeleteFilter();
const handleButtonClick = async () => {
- const deletedFilter = currentRecordFilters.find(checkIsSoftDeleteFilter);
+ const deletedFilter = currentRecordFilters.find(
+ isRecordFilterAboutSoftDelete,
+ );
if (!isDefined(deletedFilter)) {
throw new Error('Deleted filter not found');
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLabelIdentifierCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLabelIdentifierCell.tsx
index 9bc43b03da..6b61250240 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLabelIdentifierCell.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLabelIdentifierCell.tsx
@@ -3,6 +3,7 @@ import { useState } from 'react';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
+import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
@@ -124,6 +125,10 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
resizedFieldMetadataIdComponentState,
);
+ const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
+ hasAnySoftDeleteFilterOnViewComponentSelector,
+ );
+
if (!recordField) {
return <>>;
}
@@ -171,7 +176,8 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
/>
{(isMobile || iconIsVisible) &&
!isReadOnly &&
- hasObjectUpdatePermissions && (
+ hasObjectUpdatePermissions &&
+ !hasAnySoftDeleteFilterOnView && (
theme.spacing(2)};
`;
-const StyledSeperator = styled.div`
+const StyledSeparator = styled.div`
align-self: stretch;
background: ${({ theme }) => theme.background.quaternary};
width: 1px;
@@ -148,19 +148,19 @@ export const ViewBarDetails = ({
const { viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } =
useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter();
- const { checkIsSoftDeleteFilter } = useCheckIsSoftDeleteFilter();
+ const { isSeeDeletedRecordsFilter } = useCheckIsSoftDeleteFilter();
- const softDeleteFilter = currentRecordFilters.find((recordFilter) =>
- checkIsSoftDeleteFilter(recordFilter),
+ const allSoftDeletedRecordsFilter = currentRecordFilters.find(
+ (recordFilter) => isSeeDeletedRecordsFilter(recordFilter),
);
const recordFilters = useMemo(() => {
return currentRecordFilters.filter(
(recordFilter) =>
!recordFilter.recordFilterGroupId &&
- !checkIsSoftDeleteFilter(recordFilter),
+ !isSeeDeletedRecordsFilter(recordFilter),
);
- }, [currentRecordFilters, checkIsSoftDeleteFilter]);
+ }, [currentRecordFilters, isSeeDeletedRecordsFilter]);
const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } =
useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups();
@@ -222,17 +222,17 @@ export const ViewBarDetails = ({
defaultEnableYScroll={false}
>
- {isDefined(softDeleteFilter) && (
+ {isDefined(allSoftDeletedRecordsFilter) && (
)}
- {isDefined(softDeleteFilter) && (
-
-
-
+ {isDefined(allSoftDeletedRecordsFilter) && (
+
+
+
)}
{currentRecordSorts.map((recordSort) => (
-
-
+
+
+
)}
{shouldShowAnyFieldSearchChip && }
{shouldShowAdvancedFilterDropdownButton && (