8978 add navigation inside the command menu for showpage (#9103)
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`
This commit is contained in:
@ -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';
|
||||
@ -46,6 +49,10 @@ const ActionEffects = ({
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewType = useRecoilComponentValueV2(
|
||||
contextStoreCurrentViewTypeComponentState,
|
||||
);
|
||||
|
||||
const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
|
||||
|
||||
return (
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -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';
|
||||
@ -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({
|
||||
|
||||
@ -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,
|
||||
@ -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: {
|
||||
@ -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: {
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@ -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,
|
||||
@ -20,9 +29,14 @@ 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: {
|
||||
@ -30,20 +44,54 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -1,3 +1,5 @@
|
||||
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 { useActivateDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction';
|
||||
import { useActivateLastPublishedVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction';
|
||||
import { useDeactivateWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction';
|
||||
@ -6,6 +8,7 @@ import { useSeeActiveVersionWorkflowSingleRecordAction } from '@/action-menu/act
|
||||
import { useSeeRunsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction';
|
||||
import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction';
|
||||
import { useTestWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction';
|
||||
import { ActionAvailableOn } from '@/action-menu/actions/types/actionAvailableOn';
|
||||
import { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook';
|
||||
import {
|
||||
ActionMenuEntry,
|
||||
@ -13,6 +16,8 @@ import {
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconHistory,
|
||||
IconHistoryToggle,
|
||||
IconPlayerPause,
|
||||
@ -30,81 +35,143 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
|
||||
activateWorkflowDraftSingleRecord: {
|
||||
key: 'activate-workflow-draft-single-record',
|
||||
label: 'Activate Draft',
|
||||
shortLabel: 'Activate Draft',
|
||||
isPinned: true,
|
||||
position: 1,
|
||||
Icon: IconPower,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useActivateDraftWorkflowSingleRecordAction,
|
||||
},
|
||||
activateWorkflowLastPublishedVersionSingleRecord: {
|
||||
key: 'activate-workflow-last-published-version-single-record',
|
||||
label: 'Activate last published version',
|
||||
shortLabel: 'Activate last version',
|
||||
isPinned: true,
|
||||
position: 2,
|
||||
Icon: IconPower,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useActivateLastPublishedVersionWorkflowSingleRecordAction,
|
||||
},
|
||||
deactivateWorkflowSingleRecord: {
|
||||
key: 'deactivate-workflow-single-record',
|
||||
label: 'Deactivate Workflow',
|
||||
shortLabel: 'Deactivate',
|
||||
isPinned: true,
|
||||
position: 3,
|
||||
Icon: IconPlayerPause,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useDeactivateWorkflowSingleRecordAction,
|
||||
},
|
||||
discardWorkflowDraftSingleRecord: {
|
||||
key: 'discard-workflow-draft-single-record',
|
||||
label: 'Discard Draft',
|
||||
shortLabel: 'Discard Draft',
|
||||
isPinned: true,
|
||||
position: 4,
|
||||
Icon: IconTrash,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useDiscardDraftWorkflowSingleRecordAction,
|
||||
},
|
||||
seeWorkflowActiveVersionSingleRecord: {
|
||||
key: 'see-workflow-active-version-single-record',
|
||||
label: 'See active version',
|
||||
shortLabel: 'See active version',
|
||||
isPinned: false,
|
||||
position: 5,
|
||||
Icon: IconHistory,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useSeeActiveVersionWorkflowSingleRecordAction,
|
||||
},
|
||||
seeWorkflowRunsSingleRecord: {
|
||||
key: 'see-workflow-runs-single-record',
|
||||
label: 'See runs',
|
||||
shortLabel: 'See runs',
|
||||
isPinned: false,
|
||||
position: 6,
|
||||
Icon: IconHistoryToggle,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useSeeRunsWorkflowSingleRecordAction,
|
||||
},
|
||||
seeWorkflowVersionsHistorySingleRecord: {
|
||||
key: 'see-workflow-versions-history-single-record',
|
||||
label: 'See versions history',
|
||||
shortLabel: 'See versions',
|
||||
isPinned: false,
|
||||
position: 7,
|
||||
Icon: IconHistory,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useSeeVersionsWorkflowSingleRecordAction,
|
||||
},
|
||||
testWorkflowSingleRecord: {
|
||||
key: 'test-workflow-single-record',
|
||||
label: 'Test Workflow',
|
||||
shortLabel: 'Test',
|
||||
isPinned: true,
|
||||
position: 8,
|
||||
Icon: IconPlayerPlay,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useTestWorkflowSingleRecordAction,
|
||||
},
|
||||
navigateToPreviousRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'navigate-to-previous-record',
|
||||
label: 'Navigate to previous workflow',
|
||||
shortLabel: '',
|
||||
position: 9,
|
||||
Icon: IconChevronUp,
|
||||
availableOn: [ActionAvailableOn.SHOW_PAGE],
|
||||
actionHook: useNavigateToPreviousRecordSingleRecordAction,
|
||||
},
|
||||
navigateToNextRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'navigate-to-next-record',
|
||||
label: 'Navigate to next workflow',
|
||||
shortLabel: '',
|
||||
position: 10,
|
||||
Icon: IconChevronDown,
|
||||
availableOn: [ActionAvailableOn.SHOW_PAGE],
|
||||
actionHook: useNavigateToNextRecordSingleRecordAction,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
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 { useSeeExecutionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeExecutionsWorkflowVersionSingleRecordAction';
|
||||
import { useSeeVersionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction';
|
||||
import { useUseAsDraftWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction';
|
||||
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 { IconHistory, IconHistoryToggle, IconPencil } from 'twenty-ui';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconHistory,
|
||||
IconHistoryToggle,
|
||||
IconPencil,
|
||||
} from 'twenty-ui';
|
||||
|
||||
export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
|
||||
string,
|
||||
@ -23,6 +32,10 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconPencil,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useUseAsDraftWorkflowVersionSingleRecordAction,
|
||||
},
|
||||
seeWorkflowExecutionsSingleRecord: {
|
||||
@ -32,6 +45,10 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconHistoryToggle,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useSeeExecutionsWorkflowVersionSingleRecordAction,
|
||||
},
|
||||
seeWorkflowVersionsHistorySingleRecord: {
|
||||
@ -41,6 +58,32 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
Icon: IconHistory,
|
||||
availableOn: [
|
||||
ActionAvailableOn.SHOW_PAGE,
|
||||
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
],
|
||||
actionHook: useSeeVersionsWorkflowVersionSingleRecordAction,
|
||||
},
|
||||
navigateToPreviousRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'navigate-to-previous-record',
|
||||
label: 'Navigate to previous version',
|
||||
shortLabel: '',
|
||||
position: 9,
|
||||
Icon: IconChevronUp,
|
||||
availableOn: [ActionAvailableOn.SHOW_PAGE],
|
||||
actionHook: useNavigateToPreviousRecordSingleRecordAction,
|
||||
},
|
||||
navigateToNextRecord: {
|
||||
type: ActionMenuEntryType.Standard,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
key: 'navigate-to-next-record',
|
||||
label: 'Navigate to next version',
|
||||
shortLabel: '',
|
||||
position: 10,
|
||||
Icon: IconChevronDown,
|
||||
availableOn: [ActionAvailableOn.SHOW_PAGE],
|
||||
actionHook: useNavigateToNextRecordSingleRecordAction,
|
||||
},
|
||||
};
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
export enum ActionAvailableOn {
|
||||
INDEX_PAGE_BULK_SELECTION = 'INDEX_PAGE_BULK_SELECTION',
|
||||
INDEX_PAGE_SINGLE_RECORD_SELECTION = 'INDEX_PAGE_SINGLE_RECORD_SELECTION',
|
||||
INDEX_PAGE_NO_SELECTION = 'INDEX_PAGE_NO_SELECTION',
|
||||
SHOW_PAGE = 'SHOW_PAGE',
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||
import { PageHeaderOpenCommandMenuButton } from '@/ui/layout/page-header/components/PageHeaderOpenCommandMenuButton';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { Button, useIsMobile } from 'twenty-ui';
|
||||
import { Button, IconButton, useIsMobile } from 'twenty-ui';
|
||||
|
||||
export const RecordShowActionMenuButtons = () => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
@ -15,18 +15,29 @@ export const RecordShowActionMenuButtons = () => {
|
||||
return (
|
||||
<>
|
||||
{!isMobile &&
|
||||
pinnedEntries.map((entry, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
Icon={entry.Icon}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
title={entry.label}
|
||||
onClick={() => entry.onClick?.()}
|
||||
ariaLabel={entry.label}
|
||||
/>
|
||||
))}
|
||||
pinnedEntries.map((entry, index) =>
|
||||
entry.shortLabel ? (
|
||||
<Button
|
||||
key={index}
|
||||
Icon={entry.Icon}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
title={entry.shortLabel}
|
||||
onClick={() => entry.onClick?.()}
|
||||
ariaLabel={entry.label}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
Icon={entry.Icon}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
onClick={() => entry.onClick?.()}
|
||||
ariaLabel={entry.label}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<PageHeaderOpenCommandMenuButton key="more" />
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ActionAvailableOn } from '@/action-menu/actions/types/actionAvailableOn';
|
||||
import { MouseEvent, ReactElement } from 'react';
|
||||
import { IconComponent, MenuItemAccent } from 'twenty-ui';
|
||||
|
||||
@ -16,10 +17,12 @@ export type ActionMenuEntry = {
|
||||
scope: ActionMenuEntryScope;
|
||||
key: string;
|
||||
label: string;
|
||||
shortLabel?: string;
|
||||
position: number;
|
||||
Icon: IconComponent;
|
||||
isPinned?: boolean;
|
||||
accent?: MenuItemAccent;
|
||||
availableOn?: ActionAvailableOn[];
|
||||
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
||||
ConfirmationModal?: ReactElement;
|
||||
};
|
||||
|
||||
@ -11,6 +11,7 @@ import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
@ -108,6 +109,21 @@ export const useCommandMenu = () => {
|
||||
}),
|
||||
contextStoreCurrentViewId,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewType = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: mainContextStoreComponentInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
contextStoreCurrentViewType,
|
||||
);
|
||||
}
|
||||
|
||||
setIsCommandMenuOpened(true);
|
||||
@ -165,6 +181,13 @@ export const useCommandMenu = () => {
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
if (isCommandMenuOpened) {
|
||||
setIsCommandMenuOpened(false);
|
||||
resetSelectedItem();
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const ContextStoreCurrentViewTypeEffect = ({
|
||||
viewType,
|
||||
}: {
|
||||
viewType: ContextStoreViewType | null;
|
||||
}) => {
|
||||
const setContextStoreCurrentViewType = useSetRecoilComponentStateV2(
|
||||
contextStoreCurrentViewTypeComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setContextStoreCurrentViewType(viewType);
|
||||
|
||||
return () => {
|
||||
setContextStoreCurrentViewType(null);
|
||||
};
|
||||
}, [setContextStoreCurrentViewType, viewType]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const contextStoreCurrentViewTypeComponentState =
|
||||
createComponentStateV2<ContextStoreViewType | null>({
|
||||
key: 'contextStoreCurrentViewTypeComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: ContextStoreComponentInstanceContext,
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
export enum ContextStoreViewType {
|
||||
Table = 'table',
|
||||
Kanban = 'kanban',
|
||||
ShowPage = 'show-page',
|
||||
}
|
||||
@ -23,7 +23,9 @@ import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTabl
|
||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||
|
||||
import { RecordIndexActionMenu } from '@/action-menu/components/RecordIndexActionMenu';
|
||||
import { ContextStoreCurrentViewTypeEffect } from '@/context-store/components/ContextStoreCurrentViewTypeEffect';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup';
|
||||
import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect';
|
||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||
@ -164,89 +166,98 @@ export const RecordIndexContainer = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<InformationBannerWrapper />
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<SpreadsheetImportProvider>
|
||||
<ViewBar
|
||||
viewBarId={recordIndexId}
|
||||
optionsDropdownButton={
|
||||
<ObjectOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
viewType={recordIndexViewType ?? ViewType.Table}
|
||||
/>
|
||||
}
|
||||
onCurrentViewChange={(view) => {
|
||||
if (!view) {
|
||||
return;
|
||||
<>
|
||||
<ContextStoreCurrentViewTypeEffect
|
||||
viewType={
|
||||
recordIndexViewType === ViewType.Table
|
||||
? ContextStoreViewType.Table
|
||||
: ContextStoreViewType.Kanban
|
||||
}
|
||||
/>
|
||||
<StyledContainer>
|
||||
<InformationBannerWrapper />
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<SpreadsheetImportProvider>
|
||||
<ViewBar
|
||||
viewBarId={recordIndexId}
|
||||
optionsDropdownButton={
|
||||
<ObjectOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
viewType={recordIndexViewType ?? ViewType.Table}
|
||||
/>
|
||||
}
|
||||
onCurrentViewChange={(view) => {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
onViewFieldsChange(view.viewFields);
|
||||
onViewGroupsChange(view.viewGroups);
|
||||
setTableViewFilterGroups(view.viewFilterGroups ?? []);
|
||||
setTableFilters(
|
||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||
);
|
||||
setRecordIndexFilters(
|
||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||
);
|
||||
setRecordIndexViewFilterGroups(view.viewFilterGroups ?? []);
|
||||
setContextStoreTargetedRecordsRule((prev) => ({
|
||||
...prev,
|
||||
filters: mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterDefinitions,
|
||||
),
|
||||
}));
|
||||
setTableSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexViewType(view.type);
|
||||
setRecordIndexViewKanbanFieldMetadataIdState(
|
||||
view.kanbanFieldMetadataId,
|
||||
);
|
||||
setRecordIndexViewKanbanAggregateOperationState({
|
||||
operation: view.kanbanAggregateOperation,
|
||||
fieldMetadataId: view.kanbanAggregateOperationFieldMetadataId,
|
||||
});
|
||||
setRecordIndexIsCompactModeActive(view.isCompact);
|
||||
}}
|
||||
/>
|
||||
<RecordIndexViewBarEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</SpreadsheetImportProvider>
|
||||
<RecordIndexFiltersToContextStoreEffect />
|
||||
{recordIndexViewType === ViewType.Table && (
|
||||
<>
|
||||
<RecordIndexTableContainer
|
||||
recordTableId={recordIndexId}
|
||||
onViewFieldsChange(view.viewFields);
|
||||
onViewGroupsChange(view.viewGroups);
|
||||
setTableViewFilterGroups(view.viewFilterGroups ?? []);
|
||||
setTableFilters(
|
||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||
);
|
||||
setRecordIndexFilters(
|
||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||
);
|
||||
setRecordIndexViewFilterGroups(view.viewFilterGroups ?? []);
|
||||
setContextStoreTargetedRecordsRule((prev) => ({
|
||||
...prev,
|
||||
filters: mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterDefinitions,
|
||||
),
|
||||
}));
|
||||
setTableSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexViewType(view.type);
|
||||
setRecordIndexViewKanbanFieldMetadataIdState(
|
||||
view.kanbanFieldMetadataId,
|
||||
);
|
||||
setRecordIndexViewKanbanAggregateOperationState({
|
||||
operation: view.kanbanAggregateOperation,
|
||||
fieldMetadataId: view.kanbanAggregateOperationFieldMetadataId,
|
||||
});
|
||||
setRecordIndexIsCompactModeActive(view.isCompact);
|
||||
}}
|
||||
/>
|
||||
<RecordIndexViewBarEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexTableContainerEffect />
|
||||
</>
|
||||
)}
|
||||
{recordIndexViewType === ViewType.Kanban && (
|
||||
<StyledContainerWithPadding>
|
||||
<RecordIndexBoardContainer
|
||||
recordBoardId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
<RecordIndexBoardDataLoader
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordBoardId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexBoardDataLoaderEffect recordBoardId={recordIndexId} />
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
{!isPageHeaderV2Enabled && <RecordIndexActionMenu />}
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
</StyledContainer>
|
||||
</SpreadsheetImportProvider>
|
||||
<RecordIndexFiltersToContextStoreEffect />
|
||||
{recordIndexViewType === ViewType.Table && (
|
||||
<>
|
||||
<RecordIndexTableContainer
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexTableContainerEffect />
|
||||
</>
|
||||
)}
|
||||
{recordIndexViewType === ViewType.Kanban && (
|
||||
<StyledContainerWithPadding>
|
||||
<RecordIndexBoardContainer
|
||||
recordBoardId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
<RecordIndexBoardDataLoader
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordBoardId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexBoardDataLoaderEffect recordBoardId={recordIndexId} />
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
{!isPageHeaderV2Enabled && <RecordIndexActionMenu />}
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
</StyledContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @nx/workspace-no-navigate-prefer-link */
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
@ -11,6 +10,7 @@ import { useRecordIdsFromFindManyCacheRootQuery } from '@/object-record/record-s
|
||||
import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
|
||||
import { buildIndexTablePageURL } from '@/object-record/record-table/utils/buildIndexTableURL';
|
||||
import { useQueryVariablesFromActiveFieldsOfViewOrDefaultView } from '@/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useRecordShowPagePagination = (
|
||||
@ -100,22 +100,43 @@ export const useRecordShowPagePagination = (
|
||||
|
||||
const loading = loadingRecordAfter || loadingRecordBefore || loadingCursor;
|
||||
|
||||
const isThereARecordBefore = recordsBefore.length > 0;
|
||||
const isThereARecordAfter = recordsAfter.length > 0;
|
||||
|
||||
const recordBefore = recordsBefore[0];
|
||||
const recordAfter = recordsAfter[0];
|
||||
|
||||
const { recordIdsInCache } = useRecordIdsFromFindManyCacheRootQuery({
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
fieldVariables: {
|
||||
filter,
|
||||
orderBy,
|
||||
},
|
||||
});
|
||||
|
||||
const navigateToPreviousRecord = () => {
|
||||
navigate(
|
||||
buildShowPageURL(objectNameSingular, recordBefore.id, viewIdQueryParam),
|
||||
);
|
||||
if (isDefined(recordBefore)) {
|
||||
navigate(
|
||||
buildShowPageURL(objectNameSingular, recordBefore.id, viewIdQueryParam),
|
||||
);
|
||||
}
|
||||
if (!loadingRecordBefore && !isDefined(recordBefore)) {
|
||||
const firstRecordId = recordIdsInCache[recordIdsInCache.length - 1];
|
||||
navigate(
|
||||
buildShowPageURL(objectNameSingular, firstRecordId, viewIdQueryParam),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToNextRecord = () => {
|
||||
navigate(
|
||||
buildShowPageURL(objectNameSingular, recordAfter.id, viewIdQueryParam),
|
||||
);
|
||||
if (isDefined(recordAfter)) {
|
||||
navigate(
|
||||
buildShowPageURL(objectNameSingular, recordAfter.id, viewIdQueryParam),
|
||||
);
|
||||
}
|
||||
if (!loadingRecordAfter && !isDefined(recordAfter)) {
|
||||
const lastRecordId = recordIdsInCache[0];
|
||||
navigate(
|
||||
buildShowPageURL(objectNameSingular, lastRecordId, viewIdQueryParam),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToIndexView = () => {
|
||||
@ -129,31 +150,21 @@ export const useRecordShowPagePagination = (
|
||||
navigate(indexTableURL);
|
||||
};
|
||||
|
||||
const { recordIdsInCache } = useRecordIdsFromFindManyCacheRootQuery({
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
fieldVariables: {
|
||||
filter,
|
||||
orderBy,
|
||||
},
|
||||
});
|
||||
|
||||
const rankInView = recordIdsInCache.findIndex((id) => id === objectRecordId);
|
||||
|
||||
const rankFoundInFiew = rankInView > -1;
|
||||
const rankFoundInView = rankInView > -1;
|
||||
|
||||
const objectLabel = capitalize(objectMetadataItem.labelPlural);
|
||||
|
||||
const totalCount = Math.max(1, totalCountBefore, totalCountAfter);
|
||||
|
||||
const viewNameWithCount = rankFoundInFiew
|
||||
const viewNameWithCount = rankFoundInView
|
||||
? `${rankInView + 1} of ${totalCount} in ${objectLabel}`
|
||||
: `${objectLabel} (${totalCount})`;
|
||||
|
||||
return {
|
||||
viewName: viewNameWithCount,
|
||||
hasPreviousRecord: isThereARecordBefore,
|
||||
isLoadingPagination: loading,
|
||||
hasNextRecord: isThereARecordAfter,
|
||||
navigateToPreviousRecord,
|
||||
navigateToNextRecord,
|
||||
navigateToIndexView,
|
||||
|
||||
@ -99,8 +99,6 @@ export const PageHeader = ({
|
||||
hasClosePageButton,
|
||||
onClosePage,
|
||||
hasPaginationButtons,
|
||||
hasPreviousRecord,
|
||||
hasNextRecord,
|
||||
navigateToPreviousRecord,
|
||||
navigateToNextRecord,
|
||||
Icon,
|
||||
@ -140,14 +138,12 @@ export const PageHeader = ({
|
||||
Icon={IconChevronUp}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
disabled={!hasPreviousRecord}
|
||||
onClick={() => navigateToPreviousRecord?.()}
|
||||
/>
|
||||
<IconButton
|
||||
Icon={IconChevronDown}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
disabled={!hasNextRecord}
|
||||
onClick={() => navigateToNextRecord?.()}
|
||||
/>
|
||||
</>
|
||||
@ -166,24 +162,6 @@ export const PageHeader = ({
|
||||
</StyledLeftContainer>
|
||||
|
||||
<StyledPageActionContainer className="page-action-container">
|
||||
{isPageHeaderV2Enabled && hasPaginationButtons && (
|
||||
<>
|
||||
<IconButton
|
||||
Icon={IconChevronUp}
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
variant="secondary"
|
||||
disabled={!hasPreviousRecord}
|
||||
onClick={() => navigateToPreviousRecord?.()}
|
||||
/>
|
||||
<IconButton
|
||||
Icon={IconChevronDown}
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
variant="secondary"
|
||||
disabled={!hasNextRecord}
|
||||
onClick={() => navigateToNextRecord?.()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{children}
|
||||
</StyledPageActionContainer>
|
||||
</StyledTopBarContainer>
|
||||
|
||||
@ -86,6 +86,7 @@ export const RecordIndexPage = () => {
|
||||
<RecordIndexContainerContextStoreObjectMetadataEffect />
|
||||
<RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect />
|
||||
<MainContextStoreComponentInstanceIdSetterEffect />
|
||||
|
||||
<RecordIndexContainer />
|
||||
</StyledIndexContainer>
|
||||
</PageBody>
|
||||
|
||||
@ -3,7 +3,9 @@ import { useParams } from 'react-router-dom';
|
||||
import { RecordShowActionMenu } from '@/action-menu/components/RecordShowActionMenu';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
|
||||
import { ContextStoreCurrentViewTypeEffect } from '@/context-store/components/ContextStoreCurrentViewTypeEffect';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||
@ -54,6 +56,9 @@ export const RecordShowPage = () => {
|
||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
||||
>
|
||||
<RecordValueSetterEffect recordId={objectRecordId} />
|
||||
<ContextStoreCurrentViewTypeEffect
|
||||
viewType={ContextStoreViewType.ShowPage}
|
||||
/>
|
||||
<PageContainer>
|
||||
<PageTitle title={pageTitle} />
|
||||
<RecordShowPageHeader
|
||||
|
||||
@ -14,8 +14,6 @@ export const RecordShowPageHeader = ({
|
||||
}) => {
|
||||
const {
|
||||
viewName,
|
||||
hasPreviousRecord,
|
||||
hasNextRecord,
|
||||
navigateToPreviousRecord,
|
||||
navigateToNextRecord,
|
||||
navigateToIndexView,
|
||||
@ -29,9 +27,7 @@ export const RecordShowPageHeader = ({
|
||||
hasPaginationButtons
|
||||
hasClosePageButton
|
||||
onClosePage={navigateToIndexView}
|
||||
hasPreviousRecord={hasPreviousRecord}
|
||||
navigateToPreviousRecord={navigateToPreviousRecord}
|
||||
hasNextRecord={hasNextRecord}
|
||||
navigateToNextRecord={navigateToNextRecord}
|
||||
Icon={headerIcon}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user