create hooks and effects

This commit is contained in:
bosiraphael 2024-11-21 12:14:10 +01:00
parent f4a1b4b589
commit cc02517211
12 changed files with 404 additions and 108 deletions

View File

@ -1,24 +1,10 @@
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect';
import { WorkflowRunRecordActionEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionEffect';
import { MultipleRecordsActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect';
import { NoSelectionActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect';
import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
const noSelectionRecordActionEffects = [ExportRecordsActionEffect];
const singleRecordActionEffects = [
ManageFavoritesActionEffect,
DeleteRecordsActionEffect,
];
const multipleRecordActionEffects = [
ExportRecordsActionEffect,
DeleteRecordsActionEffect,
];
export const RecordActionMenuEntriesSetter = () => {
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
@ -33,32 +19,26 @@ export const RecordActionMenuEntriesSetter = () => {
objectId: contextStoreCurrentObjectMetadataId ?? '',
});
const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
if (!objectMetadataItem) {
throw new Error(
`Object metadata item not found for id ${contextStoreCurrentObjectMetadataId}`,
);
}
const actions =
contextStoreNumberOfSelectedRecords === 0
? noSelectionRecordActionEffects
: contextStoreNumberOfSelectedRecords === 1
? singleRecordActionEffects
: multipleRecordActionEffects;
return (
<>
{actions.map((ActionEffect, index) => (
<ActionEffect
key={index}
position={index}
{contextStoreNumberOfSelectedRecords === 0 && (
<NoSelectionActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
))}
{contextStoreNumberOfSelectedRecords === 1 && isWorkflowEnabled && (
<WorkflowRunRecordActionEffect
)}
{contextStoreNumberOfSelectedRecords === 1 && (
<SingleRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
)}
{contextStoreNumberOfSelectedRecords > 1 && (
<MultipleRecordsActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
)}

View File

@ -0,0 +1,24 @@
import { useMultipleRecordsActions } from '@/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useEffect } from 'react';
export const MultipleRecordsActionMenuEntrySetterEffect = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const { registerMultipleRecordsActions, unregisterMultipleRecordsActions } =
useMultipleRecordsActions({
objectMetadataItem,
});
useEffect(() => {
registerMultipleRecordsActions();
return () => {
unregisterMultipleRecordsActions();
};
}, [registerMultipleRecordsActions, unregisterMultipleRecordsActions]);
return null;
};

View File

@ -14,10 +14,10 @@ import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTabl
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useContext, useEffect, useState } from 'react';
import { useCallback, useContext, useState } from 'react';
import { IconTrash, isDefined } from 'twenty-ui';
export const DeleteRecordsActionEffect = ({
export const useDeleteMultipleRecordsAction = ({
position,
objectMetadataItem,
}: {
@ -102,12 +102,12 @@ export const DeleteRecordsActionEffect = ({
const { isInRightDrawer, onActionExecutedCallback } =
useContext(ActionMenuContext);
useEffect(() => {
const registerDeleteMultipleRecordsAction = () => {
if (canDelete) {
addActionMenuEntry({
type: 'standard',
scope: 'record-selection',
key: 'delete',
key: 'delete-multiple-records',
label: 'Delete',
position,
Icon: IconTrash,
@ -120,16 +120,8 @@ export const DeleteRecordsActionEffect = ({
<ConfirmationModal
isOpen={isDeleteRecordsModalOpen}
setIsOpen={setIsDeleteRecordsModalOpen}
title={`Delete ${contextStoreNumberOfSelectedRecords} ${
contextStoreNumberOfSelectedRecords === 1 ? `record` : 'records'
}`}
subtitle={`Are you sure you want to delete ${
contextStoreNumberOfSelectedRecords === 1
? 'this record'
: 'these records'
}? ${
contextStoreNumberOfSelectedRecords === 1 ? 'It' : 'They'
} can be recovered from the Options menu.`}
title={'Delete Records'}
subtitle={`Are you sure you want to delete these records? They can be recovered from the Options menu.`}
onConfirmClick={() => {
handleDeleteClick();
onActionExecutedCallback?.();
@ -137,31 +129,19 @@ export const DeleteRecordsActionEffect = ({
closeRightDrawer();
}
}}
deleteButtonText={`Delete ${
contextStoreNumberOfSelectedRecords > 1 ? 'Records' : 'Record'
}`}
deleteButtonText={'Delete Records'}
/>
),
});
} else {
removeActionMenuEntry('delete');
}
};
return () => {
removeActionMenuEntry('delete');
};
}, [
addActionMenuEntry,
canDelete,
closeRightDrawer,
contextStoreNumberOfSelectedRecords,
handleDeleteClick,
isDeleteRecordsModalOpen,
isInRightDrawer,
onActionExecutedCallback,
position,
removeActionMenuEntry,
]);
const unregisterDeleteMultipleRecordsAction = () => {
removeActionMenuEntry('delete-multiple-records');
};
return null;
return {
registerDeleteMultipleRecordsAction,
unregisterDeleteMultipleRecordsAction,
};
};

View File

@ -1,16 +1,13 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconDatabaseExport } from 'twenty-ui';
import {
displayedExportProgress,
useExportRecords,
} from '@/object-record/record-index/export/hooks/useExportRecords';
import { useEffect } from 'react';
export const ExportRecordsActionEffect = ({
export const useExportMultipleRecordsAction = ({
position,
objectMetadataItem,
}: {
@ -18,9 +15,6 @@ export const ExportRecordsActionEffect = ({
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
);
const { progress, download } = useExportRecords({
delayMs: 100,
@ -29,29 +23,25 @@ export const ExportRecordsActionEffect = ({
filename: `${objectMetadataItem.nameSingular}.csv`,
});
useEffect(() => {
const registerExportMultipleRecordsAction = () => {
addActionMenuEntry({
type: 'standard',
scope: 'record-selection',
key: 'export',
key: 'export-multiple-records',
position,
label: displayedExportProgress(progress),
Icon: IconDatabaseExport,
accent: 'default',
onClick: () => download(),
});
};
return () => {
removeActionMenuEntry('export');
};
}, [
contextStoreNumberOfSelectedRecords,
download,
progress,
addActionMenuEntry,
removeActionMenuEntry,
position,
]);
const unregisterExportMultipleRecordsAction = () => {
removeActionMenuEntry('export-multiple-records');
};
return null;
return {
registerExportMultipleRecordsAction,
unregisterExportMultipleRecordsAction,
};
};

View File

@ -0,0 +1,40 @@
import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction';
import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportMultipleRecordsAction';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const useMultipleRecordsActions = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const {
registerDeleteMultipleRecordsAction,
unregisterDeleteMultipleRecordsAction,
} = useDeleteMultipleRecordsAction({
position: 0,
objectMetadataItem,
});
const {
registerExportViewNoSelectionRecordsAction,
unregisterExportViewNoSelectionRecordsAction,
} = useExportViewNoSelectionRecordAction({
position: 1,
objectMetadataItem,
});
const registerMultipleRecordsActions = () => {
registerDeleteMultipleRecordsAction();
registerExportViewNoSelectionRecordsAction();
};
const unregisterMultipleRecordsActions = () => {
unregisterDeleteMultipleRecordsAction();
unregisterExportViewNoSelectionRecordsAction();
};
return {
registerMultipleRecordsActions,
unregisterMultipleRecordsActions,
};
};

View File

@ -0,0 +1,26 @@
import { useNoSelectionRecordActions } from '@/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useEffect } from 'react';
export const NoSelectionActionMenuEntrySetterEffect = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const {
registerNoSelectionRecordActions,
unregisterNoSelectionRecordActions,
} = useNoSelectionRecordActions({
objectMetadataItem,
});
useEffect(() => {
registerNoSelectionRecordActions();
return () => {
unregisterNoSelectionRecordActions();
};
}, [registerNoSelectionRecordActions, unregisterNoSelectionRecordActions]);
return null;
};

View File

@ -0,0 +1,47 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { IconDatabaseExport } from 'twenty-ui';
import {
displayedExportProgress,
useExportRecords,
} from '@/object-record/record-index/export/hooks/useExportRecords';
export const useExportViewNoSelectionRecordAction = ({
position,
objectMetadataItem,
}: {
position: number;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
const { progress, download } = useExportRecords({
delayMs: 100,
objectMetadataItem,
recordIndexId: objectMetadataItem.namePlural,
filename: `${objectMetadataItem.nameSingular}.csv`,
});
const registerExportViewNoSelectionRecordsAction = () => {
addActionMenuEntry({
type: 'standard',
scope: 'record-selection',
key: 'export-view-no-selection',
position,
label: displayedExportProgress(progress),
Icon: IconDatabaseExport,
accent: 'default',
onClick: () => download(),
});
};
const unregisterExportViewNoSelectionRecordsAction = () => {
removeActionMenuEntry('export-view-no-selection');
};
return {
registerExportViewNoSelectionRecordsAction,
unregisterExportViewNoSelectionRecordsAction,
};
};

View File

@ -0,0 +1,29 @@
import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportMultipleRecordsAction';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const useNoSelectionRecordActions = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const {
registerExportViewNoSelectionRecordsAction,
unregisterExportViewNoSelectionRecordsAction,
} = useExportViewNoSelectionRecordAction({
position: 0,
objectMetadataItem,
});
const registerNoSelectionRecordActions = () => {
registerExportViewNoSelectionRecordsAction();
};
const unregisterNoSelectionRecordActions = () => {
unregisterExportViewNoSelectionRecordsAction();
};
return {
registerNoSelectionRecordActions,
unregisterNoSelectionRecordActions,
};
};

View File

@ -0,0 +1,24 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useEffect } from 'react';
import { useSingleRecordActions } from '../hooks/useSingleRecordActions';
export const SingleRecordActionMenuEntrySetterEffect = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const { registerSingleRecordActions, unregisterSingleRecordActions } =
useSingleRecordActions({
objectMetadataItem,
});
useEffect(() => {
registerSingleRecordActions();
return () => {
unregisterSingleRecordActions();
};
}, [registerSingleRecordActions, unregisterSingleRecordActions]);
return null;
};

View File

@ -0,0 +1,124 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useContext, useState } from 'react';
import { IconTrash, isDefined } from 'twenty-ui';
export const useDeleteSingleRecordAction = ({
position,
objectMetadataItem,
}: {
position: number;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
useState(false);
const { resetTableRowSelection } = useRecordTable({
recordTableId: objectMetadataItem.namePlural,
});
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: objectMetadataItem.nameSingular,
});
const { sortedFavorites: favorites } = useFavorites();
const { deleteFavorite } = useDeleteFavorite();
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
const { closeRightDrawer } = useRightDrawer();
const recordIdToDelete =
contextStoreTargetedRecordsRule.mode === 'selection'
? contextStoreTargetedRecordsRule.selectedRecordIds?.[0]
: undefined;
const handleDeleteClick = useCallback(async () => {
if (!isDefined(recordIdToDelete)) {
return;
}
resetTableRowSelection();
const foundFavorite = favorites?.find(
(favorite) => favorite.recordId === recordIdToDelete,
);
if (isDefined(foundFavorite)) {
deleteFavorite(foundFavorite.id);
}
await deleteOneRecord(recordIdToDelete);
}, [
deleteFavorite,
deleteOneRecord,
favorites,
recordIdToDelete,
resetTableRowSelection,
]);
const isRemoteObject = objectMetadataItem.isRemote;
const { isInRightDrawer, onActionExecutedCallback } =
useContext(ActionMenuContext);
const registerDeleteSingleRecordAction = () => {
if (isRemoteObject || !isDefined(recordIdToDelete)) {
return;
}
addActionMenuEntry({
type: 'standard',
scope: 'record-selection',
key: 'delete-single-record',
label: 'Delete',
position,
Icon: IconTrash,
accent: 'danger',
isPinned: true,
onClick: () => {
setIsDeleteRecordsModalOpen(true);
},
ConfirmationModal: (
<ConfirmationModal
isOpen={isDeleteRecordsModalOpen}
setIsOpen={setIsDeleteRecordsModalOpen}
title={'Delete Record'}
subtitle={
'Are you sure you want to delete this record? It can be recovered from the Options menu.'
}
onConfirmClick={() => {
handleDeleteClick();
onActionExecutedCallback?.();
if (isInRightDrawer) {
closeRightDrawer();
}
}}
deleteButtonText={'Delete Record'}
/>
),
});
};
const unregisterDeleteSingleRecordAction = () => {
removeActionMenuEntry('delete-single-record');
};
return {
registerDeleteSingleRecordAction,
unregisterDeleteSingleRecordAction,
};
};

View File

@ -6,11 +6,10 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { IconHeart, IconHeartOff, isDefined } from 'twenty-ui';
export const ManageFavoritesActionEffect = ({
export const useManageFavoritesSingleRecordAction = ({
position,
objectMetadataItem,
}: {
@ -44,7 +43,7 @@ export const ManageFavoritesActionEffect = ({
const isFavorite = !!selectedRecordId && !!foundFavorite;
useEffect(() => {
const registerManageFavoritesSingleRecordAction = () => {
if (!isDefined(objectMetadataItem) || objectMetadataItem.isRemote) {
return;
}
@ -52,7 +51,7 @@ export const ManageFavoritesActionEffect = ({
addActionMenuEntry({
type: 'standard',
scope: 'record-selection',
key: 'manage-favorites',
key: 'manage-favorites-single-record',
label: isFavorite ? 'Remove from favorites' : 'Add to favorites',
position,
Icon: isFavorite ? IconHeartOff : IconHeart,
@ -64,21 +63,14 @@ export const ManageFavoritesActionEffect = ({
}
},
});
};
return () => {
removeActionMenuEntry('manage-favorites');
};
}, [
addActionMenuEntry,
createFavorite,
deleteFavorite,
foundFavorite?.id,
isFavorite,
objectMetadataItem,
position,
removeActionMenuEntry,
selectedRecord,
]);
const unregisterManageFavoritesSingleRecordAction = () => {
removeActionMenuEntry('manage-favorites-single-record');
};
return null;
return {
registerManageFavoritesSingleRecordAction,
unregisterManageFavoritesSingleRecordAction,
};
};

View File

@ -0,0 +1,40 @@
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useManageFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const useSingleRecordActions = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const {
registerManageFavoritesSingleRecordAction,
unregisterManageFavoritesSingleRecordAction,
} = useManageFavoritesSingleRecordAction({
position: 0,
objectMetadataItem,
});
const {
registerDeleteSingleRecordAction,
unregisterDeleteSingleRecordAction,
} = useDeleteSingleRecordAction({
position: 1,
objectMetadataItem,
});
const registerSingleRecordActions = () => {
registerManageFavoritesSingleRecordAction();
registerDeleteSingleRecordAction();
};
const unregisterSingleRecordActions = () => {
unregisterManageFavoritesSingleRecordAction();
unregisterDeleteSingleRecordAction();
};
return {
registerSingleRecordActions,
unregisterSingleRecordActions,
};
};