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:
+28
-26
@@ -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,
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+4
-2
@@ -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();
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ export type ShouldBeRegisteredFunctionParams = {
|
||||
objectPermissions: ObjectPermissions;
|
||||
recordFilters?: RecordFilter[];
|
||||
isShowPage?: boolean;
|
||||
isSoftDeleteFilterActive?: boolean;
|
||||
hasAnySoftDeleteFilterOnView?: boolean;
|
||||
isInRightDrawer?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isRemote?: boolean;
|
||||
|
||||
+4
-4
@@ -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,
|
||||
|
||||
+20
-12
@@ -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 && (
|
||||
<LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconPlus}
|
||||
onClick={() => {
|
||||
createNewIndexRecord({
|
||||
position: 'first',
|
||||
[selectFieldMetadataItem.name]: columnDefinition.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{hasObjectUpdatePermissions &&
|
||||
!hasAnySoftDeleteFilterOnView && (
|
||||
<LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconPlus}
|
||||
onClick={() => {
|
||||
createNewIndexRecord({
|
||||
position: 'first',
|
||||
[selectFieldMetadataItem.name]:
|
||||
columnDefinition.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledHeaderActions>
|
||||
)}
|
||||
</StyledRightContainer>
|
||||
|
||||
+10
@@ -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 (
|
||||
<StyledNewButton
|
||||
onClick={() => {
|
||||
|
||||
+17
-23
@@ -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 };
|
||||
};
|
||||
|
||||
+29
@@ -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<boolean>({
|
||||
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;
|
||||
},
|
||||
});
|
||||
+28
@@ -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;
|
||||
};
|
||||
+29
@@ -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
|
||||
);
|
||||
};
|
||||
+9
@@ -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;
|
||||
}
|
||||
|
||||
+17
-9
@@ -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 (
|
||||
<AnimatedPlaceholderEmptyContainer>
|
||||
<AnimatedPlaceholder type={props.animatedPlaceholderType} />
|
||||
@@ -56,15 +62,17 @@ export const RecordTableEmptyStateDisplay = (
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
{'buttonComponent' in props && props.buttonComponent}
|
||||
{'buttonTitle' in props && !isReadOnly && (
|
||||
<Button
|
||||
Icon={props.ButtonIcon}
|
||||
title={props.buttonTitle}
|
||||
variant="secondary"
|
||||
onClick={props.onClick}
|
||||
disabled={props.buttonIsDisabled}
|
||||
/>
|
||||
)}
|
||||
{'buttonTitle' in props &&
|
||||
!isReadOnly &&
|
||||
!hasAnySoftDeleteFilterOnView && (
|
||||
<Button
|
||||
Icon={props.ButtonIcon}
|
||||
title={props.buttonTitle}
|
||||
variant="secondary"
|
||||
onClick={props.onClick}
|
||||
disabled={props.buttonIsDisabled}
|
||||
/>
|
||||
)}
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-2
@@ -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');
|
||||
|
||||
+7
-1
@@ -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 && (
|
||||
<StyledHeaderIcon>
|
||||
<LightIconButton
|
||||
Icon={IconPlus}
|
||||
|
||||
@@ -11,7 +11,6 @@ import { EditableSortChip } from '@/views/components/EditableSortChip';
|
||||
import { ViewBarDetailsAddFilterButton } from '@/views/components/ViewBarDetailsAddFilterButton';
|
||||
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
|
||||
import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { SoftDeleteFilterChip } from '@/views/components/SoftDeleteFilterChip';
|
||||
@@ -21,6 +20,7 @@ import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAr
|
||||
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
||||
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
@@ -80,7 +80,7 @@ const StyledFilterContainer = styled.div`
|
||||
overflow-x: hidden;
|
||||
`;
|
||||
|
||||
const StyledSeperatorContainer = styled.div`
|
||||
const StyledSeparatorContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
@@ -90,7 +90,7 @@ const StyledSeperatorContainer = styled.div`
|
||||
padding-top: ${({ theme }) => 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}
|
||||
>
|
||||
<StyledChipContainer>
|
||||
{isDefined(softDeleteFilter) && (
|
||||
{isDefined(allSoftDeletedRecordsFilter) && (
|
||||
<SoftDeleteFilterChip
|
||||
key={softDeleteFilter.fieldMetadataId}
|
||||
recordFilter={softDeleteFilter}
|
||||
key={allSoftDeletedRecordsFilter.fieldMetadataId}
|
||||
recordFilter={allSoftDeletedRecordsFilter}
|
||||
viewBarId={viewBarId}
|
||||
/>
|
||||
)}
|
||||
{isDefined(softDeleteFilter) && (
|
||||
<StyledSeperatorContainer>
|
||||
<StyledSeperator />
|
||||
</StyledSeperatorContainer>
|
||||
{isDefined(allSoftDeletedRecordsFilter) && (
|
||||
<StyledSeparatorContainer>
|
||||
<StyledSeparator />
|
||||
</StyledSeparatorContainer>
|
||||
)}
|
||||
{currentRecordSorts.map((recordSort) => (
|
||||
<EditableSortChip
|
||||
@@ -242,9 +242,9 @@ export const ViewBarDetails = ({
|
||||
))}
|
||||
{isNonEmptyArray(recordFilters) &&
|
||||
isNonEmptyArray(currentRecordSorts) && (
|
||||
<StyledSeperatorContainer>
|
||||
<StyledSeperator />
|
||||
</StyledSeperatorContainer>
|
||||
<StyledSeparatorContainer>
|
||||
<StyledSeparator />
|
||||
</StyledSeparatorContainer>
|
||||
)}
|
||||
{shouldShowAnyFieldSearchChip && <AnyFieldSearchDropdownButton />}
|
||||
{shouldShowAdvancedFilterDropdownButton && (
|
||||
|
||||
Reference in New Issue
Block a user