62 create see deleted records action (#10525)

Closes https://github.com/twentyhq/core-team-issues/issues/62

- Created action
- Removed action from the option menu
- Fixed the filters and sorts providers for the command menu



https://github.com/user-attachments/assets/b42de3ea-536c-458c-a0e7-abd6f929d234
This commit is contained in:
Raphaël Bosi
2025-02-26 18:51:17 +01:00
committed by GitHub
parent 5327febcbe
commit b349d3f8c2
9 changed files with 119 additions and 33 deletions

View File

@ -1,6 +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 { useSeeDeletedRecordsNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useSeeDeletedRecordsNoSelectionRecordAction';
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';
@ -18,6 +19,7 @@ import {
IconDatabaseExport,
IconHeart,
IconHeartOff,
IconRotate2,
IconTrash,
} from 'twenty-ui';
@ -107,4 +109,17 @@ export const DEFAULT_ACTIONS_CONFIG_V1: Record<
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useExportMultipleRecordsAction,
},
seeDeletedRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.Object,
key: NoSelectionRecordActionKeys.SEE_DELETED_RECORDS,
label: msg`See deleted records`,
shortLabel: msg`Deleted records`,
position: 6,
Icon: IconRotate2,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useSeeDeletedRecordsNoSelectionRecordAction,
},
};

View File

@ -3,6 +3,7 @@ import { useDestroyMultipleRecordsAction } from '@/action-menu/actions/record-ac
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 { useCreateNewTableRecordNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction';
import { useSeeDeletedRecordsNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useSeeDeletedRecordsNoSelectionRecordAction';
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';
@ -28,6 +29,7 @@ import {
IconHeart,
IconHeartOff,
IconPlus,
IconRotate2,
IconTrash,
IconTrashX,
} from 'twenty-ui';
@ -147,13 +149,26 @@ export const DEFAULT_ACTIONS_CONFIG_V2: Record<
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useExportMultipleRecordsAction,
},
seeDeletedRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.Object,
key: NoSelectionRecordActionKeys.SEE_DELETED_RECORDS,
label: msg`See deleted records`,
shortLabel: msg`Deleted records`,
position: 8,
Icon: IconRotate2,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
useAction: useSeeDeletedRecordsNoSelectionRecordAction,
},
destroySingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.DESTROY,
label: msg`Permanently destroy record`,
shortLabel: msg`Destroy`,
position: 8,
position: 9,
Icon: IconTrashX,
accent: 'danger',
isPinned: true,
@ -168,7 +183,7 @@ export const DEFAULT_ACTIONS_CONFIG_V2: Record<
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD,
label: msg`Navigate to previous record`,
position: 9,
position: 10,
isPinned: true,
Icon: IconChevronUp,
availableOn: [ActionViewType.SHOW_PAGE],
@ -179,7 +194,7 @@ export const DEFAULT_ACTIONS_CONFIG_V2: Record<
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD,
label: msg`Navigate to next record`,
position: 10,
position: 11,
isPinned: true,
Icon: IconChevronDown,
availableOn: [ActionViewType.SHOW_PAGE],
@ -191,7 +206,7 @@ export const DEFAULT_ACTIONS_CONFIG_V2: Record<
key: MultipleRecordsActionKeys.DESTROY,
label: msg`Permanently destroy records`,
shortLabel: msg`Destroy`,
position: 11,
position: 12,
Icon: IconTrashX,
accent: 'danger',
isPinned: true,

View File

@ -1,12 +1,23 @@
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { getRecordIndexIdFromObjectNamePlural } from '@/object-record/utils/getRecordIndexIdFromObjectNamePlural';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjectMetadataItem =
({ objectMetadataItem }) => {
const recordTableId = getRecordIndexIdFromObjectNamePlural(
const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
if (!currentViewId) {
throw new Error('Current view ID is not defined');
}
const recordTableId = getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
currentViewId,
);
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();

View File

@ -0,0 +1,39 @@
import { useCallback } from 'react';
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const useSeeDeletedRecordsNoSelectionRecordAction: ActionHookWithObjectMetadataItem =
({ objectMetadataItem }) => {
const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
if (!currentViewId) {
throw new Error('Current view ID is not defined');
}
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
currentViewId,
);
const { handleToggleTrashColumnFilter, toggleSoftDeleteFilterState } =
useHandleToggleTrashColumnFilter({
objectNameSingular: objectMetadataItem.nameSingular,
viewBarId: recordIndexId,
});
const onClick = useCallback(() => {
handleToggleTrashColumnFilter();
toggleSoftDeleteFilterState(true);
}, [handleToggleTrashColumnFilter, toggleSoftDeleteFilterState]);
return {
shouldBeRegistered: true,
onClick,
};
};

View File

@ -1,4 +1,5 @@
export enum NoSelectionRecordActionKeys {
EXPORT_VIEW = 'export-view-no-selection',
CREATE_NEW_RECORD = 'create-new-record-no-selection',
SEE_DELETED_RECORDS = 'see-deleted-records-no-selection',
}

View File

@ -13,12 +13,16 @@ import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKey
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
@ -89,15 +93,29 @@ export const CommandMenuContainer = ({
const setCommandMenuSearch = useSetRecoilState(commandMenuSearchState);
const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
contextStoreCurrentObjectMetadataItem?.namePlural ?? '',
currentViewId ?? '',
);
return (
<RecordFilterGroupsComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
value={{ instanceId: recordIndexId }}
>
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
value={{ instanceId: recordIndexId }}
>
<RecordSortsComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
value={{ instanceId: recordIndexId }}
>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}

View File

@ -5,15 +5,11 @@ import {
IconLayout,
IconLayoutList,
IconList,
IconRotate2,
IconTag,
MenuItem,
useIcons,
} from 'twenty-ui';
import { useObjectNamePluralFromSingular } from '@/object-metadata/hooks/useObjectNamePluralFromSingular';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
@ -48,10 +44,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
const CurrentViewIcon = currentView?.icon ? getIcon(currentView.icon) : null;
const { objectNamePlural } = useObjectNamePluralFromSingular({
objectNameSingular: objectMetadataItem.nameSingular,
});
const recordGroupFieldMetadata = useRecoilComponentValueV2(
recordGroupFieldMetadataComponentState,
);
@ -68,12 +60,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
TableOptionsHotkeyScope.Dropdown,
);
const { handleToggleTrashColumnFilter, toggleSoftDeleteFilterState } =
useHandleToggleTrashColumnFilter({
objectNameSingular: objectMetadataItem.nameSingular,
viewBarId: recordIndexId,
});
const { visibleBoardFields } = useObjectOptionsForBoard({
objectNameSingular: objectMetadataItem.nameSingular,
recordBoardId: recordIndexId,
@ -163,15 +149,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
text={t`Import`}
/>
)}
<MenuItem
onClick={() => {
handleToggleTrashColumnFilter();
toggleSoftDeleteFilterState(true);
closeDropdown();
}}
LeftIcon={IconRotate2}
text={t`Deleted ${objectNamePlural}`}
/>
</DropdownMenuItemsContainer>
</>
);

View File

@ -14,6 +14,7 @@ import { RecordIndexLoadBaseOnContextStoreEffect } from '@/object-record/record-
import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader';
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -36,7 +37,10 @@ export const RecordIndexContainerGater = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const recordIndexId = `${objectMetadataItem.namePlural}-${contextStoreCurrentViewId}`;
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
contextStoreCurrentViewId || '',
);
const handleIndexRecordsLoaded = useRecoilCallback(
({ set }) =>

View File

@ -0,0 +1,6 @@
export const getRecordIndexIdFromObjectNamePluralAndViewId = (
objectNamePlural: string,
viewId: string,
): string => {
return `${objectNamePlural}-${viewId}`;
};