Skip to content

Commit

Permalink
8978 add navigation inside the command menu for showpage (#9103)
Browse files Browse the repository at this point in the history
Closes #8978

- Added new options in the actions config files: `shortLabel`,
`availableOn`
- Added two actions: Navigate to previous records and Navigate to next
records
- Modified `useRecordShowPagePagination` to loop on records when we are
on first record and we hit previous or when we are on last record and we
hit next
- Introduced a new component state
`contextStoreCurrentViewTypeComponentState`
  • Loading branch information
bosiraphael authored Dec 17, 2024
1 parent bb8c763 commit b033a50
Show file tree
Hide file tree
Showing 22 changed files with 528 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
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 { ShowPageSingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/ShowPageSingleRecordActionMenuEntrySetterEffect';
import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect';
import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
Expand Down Expand Up @@ -46,6 +49,10 @@ const ActionEffects = ({
contextStoreTargetedRecordsRuleComponentState,
);

const contextStoreCurrentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
);

const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');

return (
Expand All @@ -59,9 +66,17 @@ const ActionEffects = ({
{contextStoreTargetedRecordsRule.mode === 'selection' &&
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 && (
<>
<SingleRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
{contextStoreCurrentViewType === ContextStoreViewType.ShowPage && (
<ShowPageSingleRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
)}
{(contextStoreCurrentViewType === ContextStoreViewType.Table ||
contextStoreCurrentViewType === ContextStoreViewType.Kanban) && (
<SingleRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
)}
{isWorkflowEnabled && (
<WorkflowRunRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { getActionConfig } from '@/action-menu/actions/record-actions/single-record/utils/getActionConfig';
import { ActionAvailableOn } from '@/action-menu/actions/types/actionAvailableOn';
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useEffect } from 'react';
import { isDefined } from 'twenty-ui';

export const ShowPageSingleRecordActionMenuEntrySetterEffect = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const isPageHeaderV2Enabled = useIsFeatureEnabled(
'IS_PAGE_HEADER_V2_ENABLED',
);

const actionConfig = getActionConfig(
objectMetadataItem,
isPageHeaderV2Enabled,
);

const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();

const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);

const selectedRecordId =
contextStoreTargetedRecordsRule.mode === 'selection'
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
: undefined;

if (!isDefined(selectedRecordId)) {
throw new Error('Selected record ID is required');
}

const actionMenuEntries = Object.values(actionConfig ?? {})
.filter((action) =>
action.availableOn?.includes(ActionAvailableOn.SHOW_PAGE),
)
.map((action) => {
const { shouldBeRegistered, onClick, ConfirmationModal } =
action.actionHook({
recordId: selectedRecordId,
objectMetadataItem,
});

if (shouldBeRegistered) {
return {
...action,
onClick,
ConfirmationModal,
};
}

return undefined;
})
.filter(isDefined);

useEffect(() => {
for (const action of actionMenuEntries) {
addActionMenuEntry(action);
}

return () => {
for (const action of actionMenuEntries) {
removeActionMenuEntry(action.key);
}
};
}, [actionMenuEntries, addActionMenuEntry, removeActionMenuEntry]);

return null;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getActionConfig } from '@/action-menu/actions/record-actions/single-record/utils/getActionConfig';
import { ActionAvailableOn } from '@/action-menu/actions/types/actionAvailableOn';
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
Expand Down Expand Up @@ -37,6 +38,11 @@ export const SingleRecordActionMenuEntrySetterEffect = ({
}

const actionMenuEntries = Object.values(actionConfig ?? {})
.filter((action) =>
action.availableOn?.includes(
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
),
)
.map((action) => {
const { shouldBeRegistered, onClick, ConfirmationModal } =
action.actionHook({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
import { ActionAvailableOn } from '@/action-menu/actions/types/actionAvailableOn';
import { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook';
import {
ActionMenuEntry,
Expand All @@ -22,6 +23,10 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
label: 'Add to favorites',
position: 0,
Icon: IconHeart,
availableOn: [
ActionAvailableOn.SHOW_PAGE,
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useAddToFavoritesSingleRecordAction,
},
removeFromFavoritesSingleRecord: {
Expand All @@ -31,6 +36,10 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
label: 'Remove from favorites',
position: 1,
Icon: IconHeartOff,
availableOn: [
ActionAvailableOn.SHOW_PAGE,
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useRemoveFromFavoritesSingleRecordAction,
},
deleteSingleRecord: {
Expand All @@ -42,6 +51,10 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
Icon: IconTrash,
accent: 'danger',
isPinned: true,
availableOn: [
ActionAvailableOn.SHOW_PAGE,
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useDeleteSingleRecordAction,
},
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction';
import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
import { ActionAvailableOn } from '@/action-menu/actions/types/actionAvailableOn';
import { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook';
import {
ActionMenuEntry,
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { IconHeart, IconHeartOff, IconTrash } from 'twenty-ui';
import {
IconChevronDown,
IconChevronUp,
IconHeart,
IconHeartOff,
IconTrash,
} from 'twenty-ui';

export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
string,
Expand All @@ -20,30 +29,69 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
scope: ActionMenuEntryScope.RecordSelection,
key: 'add-to-favorites-single-record',
label: 'Add to favorites',
shortLabel: 'Add to favorites',
position: 0,
isPinned: true,
Icon: IconHeart,
availableOn: [
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
ActionAvailableOn.SHOW_PAGE,
],
actionHook: useAddToFavoritesSingleRecordAction,
},
removeFromFavoritesSingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: 'remove-from-favorites-single-record',
label: 'Remove from favorites',
shortLabel: 'Remove from favorites',
isPinned: true,
position: 1,
Icon: IconHeartOff,
availableOn: [
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
ActionAvailableOn.SHOW_PAGE,
],
actionHook: useRemoveFromFavoritesSingleRecordAction,
},
deleteSingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: 'delete-single-record',
label: 'Delete',
label: 'Delete record',
shortLabel: 'Delete',
position: 2,
Icon: IconTrash,
accent: 'danger',
isPinned: true,
availableOn: [
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
ActionAvailableOn.SHOW_PAGE,
],
actionHook: useDeleteSingleRecordAction,
},
navigateToPreviousRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: 'navigate-to-previous-record',
label: 'Navigate to previous record',
shortLabel: '',
position: 3,
isPinned: true,
Icon: IconChevronUp,
availableOn: [ActionAvailableOn.SHOW_PAGE],
actionHook: useNavigateToPreviousRecordSingleRecordAction,
},
navigateToNextRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: 'navigate-to-next-record',
label: 'Navigate to next record',
shortLabel: '',
position: 4,
isPinned: true,
Icon: IconChevronDown,
availableOn: [ActionAvailableOn.SHOW_PAGE],
actionHook: useNavigateToNextRecordSingleRecordAction,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';

export const useNavigateToNextRecordSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem =
({ recordId, objectMetadataItem }) => {
const { navigateToNextRecord } = useRecordShowPagePagination(
objectMetadataItem.nameSingular,
recordId,
);

return {
shouldBeRegistered: true,
onClick: navigateToNextRecord,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';

export const useNavigateToPreviousRecordSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem =
({ recordId, objectMetadataItem }) => {
const { navigateToPreviousRecord } = useRecordShowPagePagination(
objectMetadataItem.nameSingular,
recordId,
);

return {
shouldBeRegistered: true,
onClick: navigateToPreviousRecord,
};
};
Loading

0 comments on commit b033a50

Please sign in to comment.