Command to deduplicate command menu items (#19202)
- Deduplicate standard record command menu items by merging single-record and multi-record delete, restore, destroy, and export actions into shared record commands. - Update frontend command registrations and engine component mappings to use the new unified keys, while keeping deprecated engine keys temporarily mapped for backward compatibility. - Add a 1-21 upgrade command that removes legacy command menu items from workspaces and creates the new unified standard items.
This commit is contained in:
+20
-28
@@ -1,25 +1,21 @@
|
||||
import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand';
|
||||
import { HeadlessOpenSidePanelPageEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessOpenSidePanelPageEngineCommand';
|
||||
import { DeleteMultipleRecordsCommand } from '@/command-menu-item/engine-command/record/multiple-records/components/DeleteMultipleRecordsCommand';
|
||||
import { DestroyMultipleRecordsCommand } from '@/command-menu-item/engine-command/record/multiple-records/components/DestroyMultipleRecordsCommand';
|
||||
import { ExportMultipleRecordsCommand } from '@/command-menu-item/engine-command/record/multiple-records/components/ExportMultipleRecordsCommand';
|
||||
import { MergeMultipleRecordsCommand } from '@/command-menu-item/engine-command/record/multiple-records/components/MergeMultipleRecordsCommand';
|
||||
import { RestoreMultipleRecordsCommand } from '@/command-menu-item/engine-command/record/multiple-records/components/RestoreMultipleRecordsCommand';
|
||||
import { UpdateMultipleRecordsCommand } from '@/command-menu-item/engine-command/record/multiple-records/components/UpdateMultipleRecordsCommand';
|
||||
import { CreateNewIndexRecordNoSelectionRecordCommand } from '@/command-menu-item/engine-command/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand';
|
||||
import { CreateNewViewNoSelectionRecordCommand } from '@/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand';
|
||||
import { HideDeletedRecordsNoSelectionRecordCommand } from '@/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand';
|
||||
import { ImportRecordsNoSelectionRecordCommand } from '@/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand';
|
||||
import { SeeDeletedRecordsNoSelectionRecordCommand } from '@/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand';
|
||||
import { DeleteRecordsCommand } from '@/command-menu-item/engine-command/record/components/DeleteRecordsCommand';
|
||||
import { DestroyRecordsCommand } from '@/command-menu-item/engine-command/record/components/DestroyRecordsCommand';
|
||||
import { ExportRecordsCommand } from '@/command-menu-item/engine-command/record/components/ExportRecordsCommand';
|
||||
import { RestoreRecordsCommand } from '@/command-menu-item/engine-command/record/components/RestoreRecordsCommand';
|
||||
import { AddToFavoritesSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/AddToFavoritesSingleRecordCommand';
|
||||
import { DeleteSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/DeleteSingleRecordCommand';
|
||||
import { DestroySingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/DestroySingleRecordCommand';
|
||||
import { ExportNoteSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/ExportNoteSingleRecordCommand';
|
||||
import { ExportSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/ExportSingleRecordCommand';
|
||||
import { NavigateToNextRecordSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/NavigateToNextRecordSingleRecordCommand';
|
||||
import { NavigateToPreviousRecordSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand';
|
||||
import { RemoveFromFavoritesSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/RemoveFromFavoritesSingleRecordCommand';
|
||||
import { RestoreSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/components/RestoreSingleRecordCommand';
|
||||
import { CancelDashboardSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand';
|
||||
import { DuplicateDashboardSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand';
|
||||
import { EditDashboardSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/dashboard/components/EditDashboardSingleRecordCommand';
|
||||
@@ -57,18 +53,9 @@ export const ENGINE_COMPONENT_KEY_COMPONENT_MAP: Record<
|
||||
[EngineComponentKey.CREATE_NEW_RECORD]: (
|
||||
<CreateNewIndexRecordNoSelectionRecordCommand />
|
||||
),
|
||||
[EngineComponentKey.DELETE_SINGLE_RECORD]: <DeleteSingleRecordCommand />,
|
||||
[EngineComponentKey.DELETE_MULTIPLE_RECORDS]: (
|
||||
<DeleteMultipleRecordsCommand />
|
||||
),
|
||||
[EngineComponentKey.RESTORE_SINGLE_RECORD]: <RestoreSingleRecordCommand />,
|
||||
[EngineComponentKey.RESTORE_MULTIPLE_RECORDS]: (
|
||||
<RestoreMultipleRecordsCommand />
|
||||
),
|
||||
[EngineComponentKey.DESTROY_SINGLE_RECORD]: <DestroySingleRecordCommand />,
|
||||
[EngineComponentKey.DESTROY_MULTIPLE_RECORDS]: (
|
||||
<DestroyMultipleRecordsCommand />
|
||||
),
|
||||
[EngineComponentKey.DELETE_RECORDS]: <DeleteRecordsCommand />,
|
||||
[EngineComponentKey.RESTORE_RECORDS]: <RestoreRecordsCommand />,
|
||||
[EngineComponentKey.DESTROY_RECORDS]: <DestroyRecordsCommand />,
|
||||
[EngineComponentKey.ADD_TO_FAVORITES]: <AddToFavoritesSingleRecordCommand />,
|
||||
[EngineComponentKey.REMOVE_FROM_FAVORITES]: (
|
||||
<RemoveFromFavoritesSingleRecordCommand />
|
||||
@@ -107,20 +94,14 @@ export const ENGINE_COMPONENT_KEY_COMPONENT_MAP: Record<
|
||||
<NavigateToPreviousRecordSingleRecordCommand />
|
||||
),
|
||||
[EngineComponentKey.EXPORT_NOTE_TO_PDF]: <ExportNoteSingleRecordCommand />,
|
||||
[EngineComponentKey.EXPORT_FROM_RECORD_INDEX]: (
|
||||
<ExportMultipleRecordsCommand />
|
||||
),
|
||||
[EngineComponentKey.EXPORT_FROM_RECORD_SHOW]: <ExportSingleRecordCommand />,
|
||||
[EngineComponentKey.EXPORT_MULTIPLE_RECORDS]: (
|
||||
<ExportMultipleRecordsCommand />
|
||||
),
|
||||
[EngineComponentKey.EXPORT_RECORDS]: <ExportRecordsCommand />,
|
||||
[EngineComponentKey.UPDATE_MULTIPLE_RECORDS]: (
|
||||
<UpdateMultipleRecordsCommand />
|
||||
),
|
||||
[EngineComponentKey.IMPORT_RECORDS]: (
|
||||
<ImportRecordsNoSelectionRecordCommand />
|
||||
),
|
||||
[EngineComponentKey.EXPORT_VIEW]: <ExportMultipleRecordsCommand />,
|
||||
[EngineComponentKey.EXPORT_VIEW]: <ExportRecordsCommand />,
|
||||
[EngineComponentKey.SEE_DELETED_RECORDS]: (
|
||||
<SeeDeletedRecordsNoSelectionRecordCommand />
|
||||
),
|
||||
@@ -259,4 +240,15 @@ export const ENGINE_COMPONENT_KEY_COMPONENT_MAP: Record<
|
||||
[EngineComponentKey.FRONT_COMPONENT_RENDERER]: (
|
||||
<HeadlessFrontComponentRendererEngineCommand />
|
||||
),
|
||||
|
||||
// Deprecated keys kept for backward compatibility until migration runs
|
||||
[EngineComponentKey.DELETE_SINGLE_RECORD]: <DeleteRecordsCommand />,
|
||||
[EngineComponentKey.DELETE_MULTIPLE_RECORDS]: <DeleteRecordsCommand />,
|
||||
[EngineComponentKey.RESTORE_SINGLE_RECORD]: <RestoreRecordsCommand />,
|
||||
[EngineComponentKey.RESTORE_MULTIPLE_RECORDS]: <RestoreRecordsCommand />,
|
||||
[EngineComponentKey.DESTROY_SINGLE_RECORD]: <DestroyRecordsCommand />,
|
||||
[EngineComponentKey.DESTROY_MULTIPLE_RECORDS]: <DestroyRecordsCommand />,
|
||||
[EngineComponentKey.EXPORT_FROM_RECORD_INDEX]: <ExportRecordsCommand />,
|
||||
[EngineComponentKey.EXPORT_FROM_RECORD_SHOW]: <ExportRecordsCommand />,
|
||||
[EngineComponentKey.EXPORT_MULTIPLE_RECORDS]: <ExportRecordsCommand />,
|
||||
};
|
||||
|
||||
+28
-20
@@ -2,34 +2,37 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-c
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
import { useRemoveNavigationMenuItemByTargetRecordId } from '@/navigation-menu-item/common/hooks/useRemoveNavigationMenuItemByTargetRecordId';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
|
||||
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 { type RecordGqlOperationFilter } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const DeleteSingleRecordCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, selectedRecords } =
|
||||
export const DeleteRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, selectedRecords, graphqlFilter } =
|
||||
useHeadlessCommandContextApi();
|
||||
|
||||
const recordId = selectedRecords[0]?.id;
|
||||
|
||||
if (
|
||||
!isDefined(recordId) ||
|
||||
!isDefined(recordIndexId) ||
|
||||
!isDefined(objectMetadataItem)
|
||||
) {
|
||||
if (!isDefined(recordIndexId) || !isDefined(objectMetadataItem)) {
|
||||
throw new Error(
|
||||
'Record ID, record index ID, and object metadata are required to delete single record',
|
||||
'Record index ID and object metadata are required to delete records',
|
||||
);
|
||||
}
|
||||
|
||||
const recordId = selectedRecords[0]?.id;
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { deleteOneRecord } = useDeleteOneRecord({
|
||||
const noMatchFilter: RecordGqlOperationFilter = { id: { in: [] } };
|
||||
|
||||
const { incrementalDeleteManyRecords } = useIncrementalDeleteManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
filter: graphqlFilter ?? noMatchFilter,
|
||||
pageSize: DEFAULT_QUERY_PAGE_SIZE,
|
||||
delayInMsBetweenMutations: 50,
|
||||
});
|
||||
|
||||
const { navigationMenuItems, workspaceNavigationMenuItems } =
|
||||
@@ -40,19 +43,24 @@ export const DeleteSingleRecordCommand = () => {
|
||||
|
||||
const handleExecute = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
const foundNavigationMenuItem = [
|
||||
...navigationMenuItems,
|
||||
...workspaceNavigationMenuItems,
|
||||
].find((item) => item.targetRecordId === recordId);
|
||||
if (isDefined(recordId)) {
|
||||
const foundNavigationMenuItem = [
|
||||
...navigationMenuItems,
|
||||
...workspaceNavigationMenuItems,
|
||||
].find((item) => item.targetRecordId === recordId);
|
||||
|
||||
if (isDefined(foundNavigationMenuItem)) {
|
||||
removeNavigationMenuItemsByTargetRecordIds([recordId]);
|
||||
if (isDefined(foundNavigationMenuItem)) {
|
||||
removeNavigationMenuItemsByTargetRecordIds([recordId]);
|
||||
}
|
||||
}
|
||||
|
||||
await deleteOneRecord(recordId);
|
||||
if (!isDefined(graphqlFilter)) {
|
||||
throw new Error('Cannot delete records without a valid filter');
|
||||
}
|
||||
|
||||
await incrementalDeleteManyRecords();
|
||||
};
|
||||
|
||||
return <HeadlessEngineCommandWrapperEffect execute={handleExecute} />;
|
||||
+37
-15
@@ -5,33 +5,36 @@ import { useIncrementalDestroyManyRecords } from '@/object-record/hooks/useIncre
|
||||
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
|
||||
import { AppPath, type RecordGqlOperationFilter } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
export const DestroyMultipleRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, graphqlFilter } =
|
||||
export const DestroyRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, selectedRecords, graphqlFilter } =
|
||||
useHeadlessCommandContextApi();
|
||||
|
||||
if (
|
||||
!isDefined(recordIndexId) ||
|
||||
!isDefined(objectMetadataItem) ||
|
||||
!isDefined(graphqlFilter)
|
||||
) {
|
||||
if (!isDefined(recordIndexId) || !isDefined(objectMetadataItem)) {
|
||||
throw new Error(
|
||||
'Record index ID, object metadata item, and graphql filter are required to destroy multiple records',
|
||||
'Record index ID and object metadata are required to destroy records',
|
||||
);
|
||||
}
|
||||
|
||||
const isSingleRecord = selectedRecords.length === 1;
|
||||
|
||||
const navigateApp = useNavigateApp();
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const noMatchFilter: RecordGqlOperationFilter = { id: { in: [] } };
|
||||
|
||||
const deletedAtFilter: RecordGqlOperationFilter = {
|
||||
deletedAt: { is: 'NOT_NULL' },
|
||||
};
|
||||
|
||||
const combinedFilter = {
|
||||
...graphqlFilter,
|
||||
const combinedFilter: RecordGqlOperationFilter = {
|
||||
...(graphqlFilter ?? noMatchFilter),
|
||||
...deletedAtFilter,
|
||||
};
|
||||
|
||||
@@ -44,17 +47,36 @@ export const DestroyMultipleRecordsCommand = () => {
|
||||
|
||||
const handleExecute = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
if (!isDefined(graphqlFilter)) {
|
||||
throw new Error('Cannot destroy records without a valid filter');
|
||||
}
|
||||
|
||||
await incrementalDestroyManyRecords();
|
||||
|
||||
if (isSingleRecord) {
|
||||
navigateApp(AppPath.RecordIndexPage, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const objectLabel = isSingleRecord
|
||||
? objectMetadataItem.labelSingular
|
||||
: objectMetadataItem.labelPlural;
|
||||
|
||||
const title = t`Permanently Destroy ${objectLabel}`;
|
||||
const subtitle = isSingleRecord
|
||||
? t`Are you sure you want to destroy this ${objectMetadataItem.labelSingular}? It cannot be recovered anymore.`
|
||||
: t`Are you sure you want to destroy these ${objectMetadataItem.labelPlural}? They won't be recoverable anymore.`;
|
||||
const confirmButtonText = `${t`Permanently Destroy`} ${objectLabel}`;
|
||||
|
||||
return (
|
||||
<HeadlessConfirmationModalEngineCommandEffect
|
||||
title={t`Permanently Destroy Records`}
|
||||
subtitle={t`Are you sure you want to destroy these records? They won't be recoverable anymore.`}
|
||||
confirmButtonText={t`Destroy Records`}
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
confirmButtonText={confirmButtonText}
|
||||
execute={handleExecute}
|
||||
/>
|
||||
);
|
||||
+41
-6
@@ -4,13 +4,14 @@ import { CommandComponentInstanceContext } from '@/command-menu-item/engine-comm
|
||||
import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useRecordIndexExportRecords } from '@/object-record/record-index/export/hooks/useRecordIndexExportRecords';
|
||||
import { useExportSingleRecord } from '@/object-record/record-show/hooks/useExportSingleRecord';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const ExportMultipleRecordsCommandContent = ({
|
||||
const ExportIndexRecordsContent = ({
|
||||
objectMetadataItem,
|
||||
recordIndexId,
|
||||
setCommandMenuItemProgress,
|
||||
@@ -42,8 +43,26 @@ const ExportMultipleRecordsCommandContent = ({
|
||||
return <HeadlessEngineCommandWrapperEffect execute={download} />;
|
||||
};
|
||||
|
||||
export const ExportMultipleRecordsCommand = () => {
|
||||
const { objectMetadataItem, recordIndexId } = useHeadlessCommandContextApi();
|
||||
const ExportShowRecordContent = ({
|
||||
objectMetadataItem,
|
||||
recordId,
|
||||
}: {
|
||||
objectMetadataItem: EnrichedObjectMetadataItem;
|
||||
recordId: string;
|
||||
}) => {
|
||||
const filename = `${objectMetadataItem.nameSingular}.csv`;
|
||||
const { download } = useExportSingleRecord({
|
||||
filename,
|
||||
objectMetadataItem,
|
||||
recordId,
|
||||
});
|
||||
|
||||
return <HeadlessEngineCommandWrapperEffect execute={download} />;
|
||||
};
|
||||
|
||||
export const ExportRecordsCommand = () => {
|
||||
const { objectMetadataItem, recordIndexId, selectedRecords } =
|
||||
useHeadlessCommandContextApi();
|
||||
|
||||
const engineCommandId = useAvailableComponentInstanceIdOrThrow(
|
||||
CommandComponentInstanceContext,
|
||||
@@ -54,9 +73,25 @@ export const ExportMultipleRecordsCommand = () => {
|
||||
engineCommandId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem) || !isDefined(recordIndexId)) {
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error('Object metadata item is required to export records');
|
||||
}
|
||||
|
||||
const recordId = selectedRecords[0]?.id;
|
||||
const isShowPageExport = !isDefined(recordIndexId) && isDefined(recordId);
|
||||
|
||||
if (isShowPageExport) {
|
||||
return (
|
||||
<ExportShowRecordContent
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(recordIndexId)) {
|
||||
throw new Error(
|
||||
'Object metadata item and record index ID are required to export multiple records',
|
||||
'Record index ID is required to export records from index page',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,7 +99,7 @@ export const ExportMultipleRecordsCommand = () => {
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
>
|
||||
<ExportMultipleRecordsCommandContent
|
||||
<ExportIndexRecordsContent
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordIndexId={recordIndexId}
|
||||
setCommandMenuItemProgress={setCommandMenuItemProgress}
|
||||
+27
-13
@@ -9,20 +9,18 @@ import { t } from '@lingui/core/macro';
|
||||
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const RestoreMultipleRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, graphqlFilter } =
|
||||
export const RestoreRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, selectedRecords, graphqlFilter } =
|
||||
useHeadlessCommandContextApi();
|
||||
|
||||
if (
|
||||
!isDefined(recordIndexId) ||
|
||||
!isDefined(objectMetadataItem) ||
|
||||
!isDefined(graphqlFilter)
|
||||
) {
|
||||
if (!isDefined(recordIndexId) || !isDefined(objectMetadataItem)) {
|
||||
throw new Error(
|
||||
'Record index ID, object metadata item, and graphql filter are required to restore multiple records',
|
||||
'Record index ID and object metadata are required to restore records',
|
||||
);
|
||||
}
|
||||
|
||||
const isSingleRecord = selectedRecords.length === 1;
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
@@ -31,12 +29,14 @@ export const RestoreMultipleRecordsCommand = () => {
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const noMatchFilter: RecordGqlOperationFilter = { id: { in: [] } };
|
||||
|
||||
const deletedAtFilter: RecordGqlOperationFilter = {
|
||||
deletedAt: { is: 'NOT_NULL' },
|
||||
};
|
||||
|
||||
const combinedFilter = {
|
||||
...graphqlFilter,
|
||||
const combinedFilter: RecordGqlOperationFilter = {
|
||||
...(graphqlFilter ?? noMatchFilter),
|
||||
...deletedAtFilter,
|
||||
};
|
||||
|
||||
@@ -49,6 +49,11 @@ export const RestoreMultipleRecordsCommand = () => {
|
||||
|
||||
const handleExecute = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
|
||||
if (!isDefined(graphqlFilter)) {
|
||||
throw new Error('Cannot restore records without a valid filter');
|
||||
}
|
||||
|
||||
const recordsToRestore = await fetchAllRecordIds();
|
||||
const recordIdsToRestore = recordsToRestore.map((record) => record.id);
|
||||
|
||||
@@ -59,11 +64,20 @@ export const RestoreMultipleRecordsCommand = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const objectLabel = isSingleRecord
|
||||
? objectMetadataItem.labelSingular
|
||||
: objectMetadataItem.labelPlural;
|
||||
|
||||
const title = t`Restore ${objectLabel}`;
|
||||
const subtitle = isSingleRecord
|
||||
? t`Are you sure you want to restore this ${objectMetadataItem.labelSingular}?`
|
||||
: t`Are you sure you want to restore these ${objectMetadataItem.labelPlural}?`;
|
||||
|
||||
return (
|
||||
<HeadlessConfirmationModalEngineCommandEffect
|
||||
title={t`Restore Records`}
|
||||
subtitle={t`Are you sure you want to restore these records?`}
|
||||
confirmButtonText={t`Restore Records`}
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
confirmButtonText={title}
|
||||
confirmButtonAccent="default"
|
||||
execute={handleExecute}
|
||||
/>
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
|
||||
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 { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const DeleteMultipleRecordsCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, graphqlFilter } =
|
||||
useHeadlessCommandContextApi();
|
||||
|
||||
if (
|
||||
!isDefined(recordIndexId) ||
|
||||
!isDefined(objectMetadataItem) ||
|
||||
!isDefined(graphqlFilter)
|
||||
) {
|
||||
throw new Error(
|
||||
'Record index ID, object metadata item, and graphql filter are required to delete multiple records',
|
||||
);
|
||||
}
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { incrementalDeleteManyRecords } = useIncrementalDeleteManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
filter: graphqlFilter,
|
||||
pageSize: DEFAULT_QUERY_PAGE_SIZE,
|
||||
delayInMsBetweenMutations: 50,
|
||||
});
|
||||
|
||||
const handleExecute = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
resetTableRowSelection();
|
||||
await incrementalDeleteManyRecords();
|
||||
};
|
||||
|
||||
return <HeadlessEngineCommandWrapperEffect execute={handleExecute} />;
|
||||
};
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
import { HeadlessConfirmationModalEngineCommandEffect } from '@/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect';
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
|
||||
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
export const DestroySingleRecordCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, selectedRecords } =
|
||||
useHeadlessCommandContextApi();
|
||||
|
||||
const recordId = selectedRecords[0]?.id;
|
||||
|
||||
if (
|
||||
!isDefined(recordId) ||
|
||||
!isDefined(recordIndexId) ||
|
||||
!isDefined(objectMetadataItem)
|
||||
) {
|
||||
throw new Error(
|
||||
'Record ID, record index ID, and object metadata are required to destroy single record',
|
||||
);
|
||||
}
|
||||
|
||||
const navigateApp = useNavigateApp();
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { destroyOneRecord } = useDestroyOneRecord({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const handleExecute = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
resetTableRowSelection();
|
||||
|
||||
await destroyOneRecord(recordId);
|
||||
navigateApp(AppPath.RecordIndexPage, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<HeadlessConfirmationModalEngineCommandEffect
|
||||
title={t`Permanently Destroy Record`}
|
||||
subtitle={t`Are you sure you want to destroy this record? It cannot be recovered anymore.`}
|
||||
confirmButtonText={t`Permanently Destroy Record`}
|
||||
execute={handleExecute}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
import { useExportSingleRecord } from '@/object-record/record-show/hooks/useExportSingleRecord';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const ExportSingleRecordCommand = () => {
|
||||
const { objectMetadataItem, currentViewId, selectedRecords } =
|
||||
useHeadlessCommandContextApi();
|
||||
|
||||
const recordId = selectedRecords[0]?.id;
|
||||
|
||||
if (
|
||||
!isDefined(currentViewId) ||
|
||||
!isDefined(recordId) ||
|
||||
!isDefined(objectMetadataItem)
|
||||
) {
|
||||
throw new Error(
|
||||
'Current view ID, record ID, and object metadata are required to export single record',
|
||||
);
|
||||
}
|
||||
|
||||
const filename = `${objectMetadataItem.nameSingular}.csv`;
|
||||
const { download } = useExportSingleRecord({
|
||||
filename,
|
||||
objectMetadataItem,
|
||||
recordId,
|
||||
});
|
||||
|
||||
return <HeadlessEngineCommandWrapperEffect execute={download} />;
|
||||
};
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
import { HeadlessConfirmationModalEngineCommandEffect } from '@/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect';
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const RestoreSingleRecordCommand = () => {
|
||||
const { recordIndexId, objectMetadataItem, selectedRecords } =
|
||||
useHeadlessCommandContextApi();
|
||||
|
||||
const recordId = selectedRecords[0]?.id;
|
||||
|
||||
if (
|
||||
!isDefined(recordId) ||
|
||||
!isDefined(recordIndexId) ||
|
||||
!isDefined(objectMetadataItem)
|
||||
) {
|
||||
throw new Error(
|
||||
'Record ID, record index ID, and object metadata are required to restore single record',
|
||||
);
|
||||
}
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
|
||||
const { removeSelectedRecordsFromRecordBoard } =
|
||||
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
|
||||
|
||||
const { restoreManyRecords } = useRestoreManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const handleExecute = async () => {
|
||||
removeSelectedRecordsFromRecordBoard();
|
||||
resetTableRowSelection();
|
||||
|
||||
await restoreManyRecords({
|
||||
idsToRestore: [recordId],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<HeadlessConfirmationModalEngineCommandEffect
|
||||
title={t`Restore Record`}
|
||||
subtitle={t`Are you sure you want to restore this record?`}
|
||||
confirmButtonText={t`Restore Record`}
|
||||
confirmButtonAccent="default"
|
||||
execute={handleExecute}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user