From 27d0a3766ff3a683e7edae7876254a3020a2e3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Wed, 11 Jun 2025 18:01:03 +0200 Subject: [PATCH] Make filters and sorts work on record page pagination (#12460) Fixes #7929 This PR implements a system to capture and preserve the filters and sorts when navigating from an index view to a record show page. This information is stored in a context store component state. This allows users to navigate between records inside the record page while maintaining context from the index view. --- .../RecordShowRightDrawerOpenRecordButton.tsx | 80 ++++++++++------ ...StoreRecordShowParentViewComponentState.ts | 22 +++++ .../object-record/components/RecordChip.tsx | 21 +++-- .../components/RecordBoardCard.tsx | 28 +----- .../components/RecordBoardCardHeader.tsx | 16 +--- .../record-field/contexts/FieldContext.ts | 3 +- .../display/components/ChipFieldDisplay.tsx | 2 + .../meta-types/hooks/useChipFieldDisplay.ts | 2 + .../hooks/useOpenRecordFromIndexView.ts | 94 +++++++++++++++++++ .../hooks/useRecordShowPagePagination.ts | 9 +- .../computeRecordShowComponentInstanceId.ts | 5 + ...rdTableCellFieldContextLabelIdentifier.tsx | 6 ++ .../hooks/useOpenRecordTableCellV2.ts | 39 +++----- ...blesFromActiveFieldsOfViewOrDefaultView.ts | 27 ------ .../hooks/useQueryVariablesFromParentView.ts | 32 +++++++ .../views/hooks/useQueryVariablesFromView.ts | 58 ------------ .../getQueryVariablesFromFiltersAndSorts.ts | 35 +++++++ .../pages/object-record/RecordShowPage.tsx | 12 ++- 18 files changed, 296 insertions(+), 195 deletions(-) create mode 100644 packages/twenty-front/src/modules/context-store/states/contextStoreRecordShowParentViewComponentState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-index/hooks/useOpenRecordFromIndexView.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-show/utils/computeRecordShowComponentInstanceId.ts delete mode 100644 packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts create mode 100644 packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromParentView.ts delete mode 100644 packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromView.ts create mode 100644 packages/twenty-front/src/modules/views/utils/getQueryVariablesFromFiltersAndSorts.ts diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx index 8a00a26c9..769a3c38c 100644 --- a/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx @@ -2,6 +2,8 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; +import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; +import { contextStoreRecordShowParentViewComponentState } from '@/context-store/states/contextStoreRecordShowParentViewComponentState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; @@ -13,10 +15,10 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { useCallback } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilCallback, useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconBrowserMaximize } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; @@ -60,6 +62,11 @@ export const RecordShowRightDrawerOpenRecordButton = ({ tabListComponentIdInRecordPage, ); + const parentViewState = useRecoilComponentCallbackStateV2( + contextStoreRecordShowParentViewComponentState, + MAIN_CONTEXT_STORE_INSTANCE_ID, + ); + const navigate = useNavigateApp(); const actionMenuId = useAvailableComponentInstanceIdOrThrow( @@ -68,43 +75,54 @@ export const RecordShowRightDrawerOpenRecordButton = ({ const { closeDropdown } = useDropdownV2(); - const handleOpenRecord = useCallback(() => { - const tabIdToOpen = - activeTabIdInRightDrawer === 'home' - ? objectNameSingular === CoreObjectNameSingular.Note || - objectNameSingular === CoreObjectNameSingular.Task - ? 'richText' - : 'timeline' - : activeTabIdInRightDrawer; + const handleOpenRecord = useRecoilCallback( + ({ snapshot, reset }) => + () => { + const tabIdToOpen = + activeTabIdInRightDrawer === 'home' + ? objectNameSingular === CoreObjectNameSingular.Note || + objectNameSingular === CoreObjectNameSingular.Task + ? 'richText' + : 'timeline' + : activeTabIdInRightDrawer; - setActiveTabIdInRecordPage(tabIdToOpen); + setActiveTabIdInRecordPage(tabIdToOpen); - navigate(AppPath.RecordShowPage, { + const parentView = snapshot.getLoadable(parentViewState).getValue(); + + if (parentView?.parentViewObjectNameSingular !== objectNameSingular) { + reset(parentViewState); + } + + navigate(AppPath.RecordShowPage, { + objectNameSingular, + objectRecordId: recordId, + }); + + closeDropdown( + getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId), + ); + + closeCommandMenu(); + }, + [ + actionMenuId, + activeTabIdInRightDrawer, + closeCommandMenu, + closeDropdown, + navigate, objectNameSingular, - objectRecordId: recordId, - }); - - closeDropdown( - getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId), - ); - - closeCommandMenu(); - }, [ - actionMenuId, - activeTabIdInRightDrawer, - closeCommandMenu, - closeDropdown, - navigate, - objectNameSingular, - recordId, - setActiveTabIdInRecordPage, - ]); + parentViewState, + recordId, + setActiveTabIdInRecordPage, + ], + ); useScopedHotkeys( ['ctrl+Enter,meta+Enter'], handleOpenRecord, AppHotkeyScope.CommandMenuOpen, - [closeCommandMenu, navigate, objectNameSingular, recordId], + [handleOpenRecord], ); if (!isDefined(record)) { diff --git a/packages/twenty-front/src/modules/context-store/states/contextStoreRecordShowParentViewComponentState.ts b/packages/twenty-front/src/modules/context-store/states/contextStoreRecordShowParentViewComponentState.ts new file mode 100644 index 000000000..6afd0c141 --- /dev/null +++ b/packages/twenty-front/src/modules/context-store/states/contextStoreRecordShowParentViewComponentState.ts @@ -0,0 +1,22 @@ +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +type RecordShowParentViewComponentState = { + parentViewComponentId: string; + parentViewObjectNameSingular: string; + parentViewFilterGroups: RecordFilterGroup[]; + parentViewFilters: RecordFilter[]; + parentViewSorts: RecordSort[]; +}; + +export const contextStoreRecordShowParentViewComponentState = + createComponentStateV2( + { + key: 'contextStoreRecordShowParentViewComponentState', + defaultValue: undefined, + componentInstanceContext: ContextStoreComponentInstanceContext, + }, + ); diff --git a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx index 9509e9ccb..f2be7ca9d 100644 --- a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx +++ b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx @@ -7,6 +7,7 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { MouseEvent } from 'react'; import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; import { AvatarChip, AvatarChipVariant, @@ -27,6 +28,7 @@ export type RecordChipProps = { size?: ChipSize; isLabelHidden?: boolean; triggerEvent?: TriggerEventType; + onClick?: (event: MouseEvent) => void; }; export const RecordChip = ({ @@ -40,6 +42,7 @@ export const RecordChip = ({ forceDisableClick = false, isLabelHidden = false, triggerEvent = 'MOUSE_DOWN', + onClick, }: RecordChipProps) => { const { recordChipData } = useRecordChipData({ objectNameSingular, @@ -53,14 +56,16 @@ export const RecordChip = ({ const isSidePanelViewOpenRecordInType = recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL; - const handleCustomClick = isSidePanelViewOpenRecordInType - ? (_event: MouseEvent) => { - openRecordInCommandMenu({ - recordId: record.id, - objectNameSingular, - }); - } - : undefined; + const handleCustomClick = isDefined(onClick) + ? onClick + : isSidePanelViewOpenRecordInType + ? (_event: MouseEvent) => { + openRecordInCommandMenu({ + recordId: record.id, + objectNameSingular, + }); + } + : undefined; // TODO temporary until we create a record show page for Workspaces members diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index 8353f2262..f995d3eaa 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -10,13 +10,10 @@ import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/re import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector'; import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope'; -import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody'; import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader'; import { RECORD_BOARD_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardClickOutsideId'; -import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; -import { AppPath } from '@/types/AppPath'; +import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement'; @@ -24,14 +21,12 @@ import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component- import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; -import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import styled from '@emotion/styled'; import { useContext, useState } from 'react'; import { InView, useInView } from 'react-intersection-observer'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; import { AnimatedEaseInOut } from 'twenty-ui/utilities'; import { useDebouncedCallback } from 'use-debounce'; -import { useNavigateApp } from '~/hooks/useNavigateApp'; const StyledBoardCard = styled.div<{ isDragging?: boolean; @@ -92,9 +87,6 @@ const StyledBoardCardWrapper = styled.div` `; export const RecordBoardCard = () => { - const navigate = useNavigateApp(); - const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); - const { recordId, rowIndex, columnIndex } = useContext( RecordBoardCardContext, ); @@ -131,8 +123,6 @@ export const RecordBoardCard = () => { }, ); - const { objectNameSingular } = useRecordIndexContextOrThrow(); - const recordBoardId = useAvailableScopeIdOrThrow( RecordBoardScopeInternalContext, ); @@ -151,7 +141,7 @@ export const RecordBoardCard = () => { const { openDropdown } = useDropdownV2(); - const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState); + const { openRecordFromIndexView } = useOpenRecordFromIndexView(); const handleActionMenuDropdown = (event: React.MouseEvent) => { event.preventDefault(); @@ -166,17 +156,7 @@ export const RecordBoardCard = () => { }; const handleCardClick = () => { - if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) { - openRecordInCommandMenu({ - recordId, - objectNameSingular, - }); - } else { - navigate(AppPath.RecordShowPage, { - objectNameSingular, - objectRecordId: recordId, - }); - } + openRecordFromIndexView({ recordId }); }; const onMouseLeaveBoard = useDebouncedCallback(() => { diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardHeader.tsx index 6864e1eff..9851bd0d2 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardHeader.tsx @@ -8,13 +8,11 @@ import { RecordBoardScopeInternalContext } from '@/object-record/record-board/sc import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState'; import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState'; -import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; +import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import styled from '@emotion/styled'; import { Dispatch, SetStateAction, useContext } from 'react'; import { useRecoilValue } from 'recoil'; @@ -45,8 +43,6 @@ export const RecordBoardCardHeader = ({ }: RecordBoardCardHeaderProps) => { const { recordId } = useContext(RecordBoardCardContext); - const { indexIdentifierUrl } = useRecordIndexContextOrThrow(); - const record = useRecoilValue(recordStoreFamilyState(recordId)); const { objectMetadataItem } = useContext(RecordBoardContext); @@ -68,7 +64,7 @@ export const RecordBoardCardHeader = ({ recordId, ); - const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState); + const { openRecordFromIndexView } = useOpenRecordFromIndexView(); return ( @@ -79,11 +75,9 @@ export const RecordBoardCardHeader = ({ record={record} variant={AvatarChipVariant.Transparent} maxWidth={150} - to={ - recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE - ? indexIdentifierUrl(recordId) - : undefined - } + onClick={() => { + openRecordFromIndexView({ recordId }); + }} triggerEvent="CLICK" /> )} diff --git a/packages/twenty-front/src/modules/object-record/record-field/contexts/FieldContext.ts b/packages/twenty-front/src/modules/object-record/record-field/contexts/FieldContext.ts index ad5a09f36..c0e82dd3e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/contexts/FieldContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/contexts/FieldContext.ts @@ -1,4 +1,4 @@ -import { createContext } from 'react'; +import { createContext, MouseEvent } from 'react'; import { TriggerEventType } from 'twenty-ui/utilities'; import { FieldDefinition } from '../types/FieldDefinition'; @@ -35,6 +35,7 @@ export type GenericFieldContextType = { isDisplayModeFixHeight?: boolean; isReadOnly: boolean; disableChipClick?: boolean; + onRecordChipClick?: (event: MouseEvent) => void; onOpenEditMode?: () => void; onCloseEditMode?: () => void; triggerEvent?: TriggerEventType; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx index 84b4d0db4..fd1bf2256 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx @@ -12,6 +12,7 @@ export const ChipFieldDisplay = () => { disableChipClick, maxWidth, triggerEvent, + onRecordChipClick, } = useChipFieldDisplay(); if (!isDefined(recordValue)) { @@ -28,6 +29,7 @@ export const ChipFieldDisplay = () => { isLabelHidden={isLabelIdentifierCompact} forceDisableClick={disableChipClick} triggerEvent={triggerEvent} + onClick={onRecordChipClick} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts index 5ff0b7149..36f42831d 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useChipFieldDisplay.ts @@ -21,6 +21,7 @@ export const useChipFieldDisplay = () => { disableChipClick, maxWidth, triggerEvent, + onRecordChipClick, } = useContext(FieldContext); const { chipGeneratorPerObjectPerField } = useContext( @@ -54,5 +55,6 @@ export const useChipFieldDisplay = () => { disableChipClick, maxWidth, triggerEvent, + onRecordChipClick, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useOpenRecordFromIndexView.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useOpenRecordFromIndexView.ts new file mode 100644 index 000000000..c4723338d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useOpenRecordFromIndexView.ts @@ -0,0 +1,94 @@ +import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; +import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; +import { contextStoreRecordShowParentViewComponentState } from '@/context-store/states/contextStoreRecordShowParentViewComponentState'; +import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; +import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; +import { AppPath } from '@/types/AppPath'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; +import { useRecoilCallback } from 'recoil'; +import { useNavigateApp } from '~/hooks/useNavigateApp'; + +export const useOpenRecordFromIndexView = () => { + const { recordIndexId } = useRecordIndexContextOrThrow(); + + const { objectNameSingular } = useRecordIndexContextOrThrow(); + + const navigate = useNavigateApp(); + const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); + + const currentRecordFilters = useRecoilComponentCallbackStateV2( + currentRecordFiltersComponentState, + recordIndexId, + ); + + const currentRecordSorts = useRecoilComponentCallbackStateV2( + currentRecordSortsComponentState, + recordIndexId, + ); + + const currentRecordFilterGroups = useRecoilComponentCallbackStateV2( + currentRecordFilterGroupsComponentState, + recordIndexId, + ); + + const openRecordFromIndexView = useRecoilCallback( + ({ snapshot, set }) => + ({ recordId }: { recordId: string }) => { + const recordIndexOpenRecordIn = snapshot + .getLoadable(recordIndexOpenRecordInState) + .getValue(); + + const parentViewFilters = snapshot + .getLoadable(currentRecordFilters) + .getValue(); + + const parentViewSorts = snapshot + .getLoadable(currentRecordSorts) + .getValue(); + + const parentViewFilterGroups = snapshot + .getLoadable(currentRecordFilterGroups) + .getValue(); + + set( + contextStoreRecordShowParentViewComponentState.atomFamily({ + instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID, + }), + { + parentViewComponentId: recordIndexId, + parentViewObjectNameSingular: objectNameSingular, + parentViewFilterGroups, + parentViewFilters, + parentViewSorts, + }, + ); + + if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) { + openRecordInCommandMenu({ + recordId, + objectNameSingular, + }); + } else { + navigate(AppPath.RecordShowPage, { + objectNameSingular, + objectRecordId: recordId, + }); + } + }, + [ + currentRecordFilters, + currentRecordSorts, + currentRecordFilterGroups, + recordIndexId, + objectNameSingular, + navigate, + openRecordInCommandMenu, + ], + ); + + return { openRecordFromIndexView }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts index 33c9bc74b..845e7afcf 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts @@ -8,7 +8,7 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId'; import { useRecordIdsFromFindManyCacheRootQuery } from '@/object-record/record-show/hooks/useRecordIdsFromFindManyCacheRootQuery'; import { AppPath } from '@/types/AppPath'; -import { useQueryVariablesFromActiveFieldsOfViewOrDefaultView } from '@/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView'; +import { useQueryVariablesFromParentView } from '@/views/hooks/useQueryVariablesFromParentView'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; @@ -36,10 +36,9 @@ export const useRecordShowPagePagination = ( const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular }); - const { filter, orderBy } = - useQueryVariablesFromActiveFieldsOfViewOrDefaultView({ - objectMetadataItem, - }); + const { filter, orderBy } = useQueryVariablesFromParentView({ + objectMetadataItem, + }); const { loading: loadingCursor, pageInfo: currentRecordsPageInfo } = useFindManyRecords({ diff --git a/packages/twenty-front/src/modules/object-record/record-show/utils/computeRecordShowComponentInstanceId.ts b/packages/twenty-front/src/modules/object-record/record-show/utils/computeRecordShowComponentInstanceId.ts new file mode 100644 index 000000000..5a0a68630 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-show/utils/computeRecordShowComponentInstanceId.ts @@ -0,0 +1,5 @@ +export const computeRecordShowComponentInstanceId = ( + objectRecordId: string, +) => { + return `record-show-${objectRecordId}`; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier.tsx index 98c601151..8735a177e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier.tsx @@ -2,6 +2,7 @@ import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObject import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; @@ -48,6 +49,8 @@ export const RecordTableCellFieldContextLabelIdentifier = ({ const isLabelIdentifierCompact = isMobile && !isRecordTableScrolledLeftComponent; + const { openRecordFromIndexView } = useOpenRecordFromIndexView(); + return ( { + openRecordFromIndexView({ recordId }); + }, isForbidden: !hasObjectReadPermissions, }} > diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts index 3bddfb32a..a24729c4c 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts @@ -14,9 +14,7 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode'; -import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId'; @@ -25,6 +23,7 @@ import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropd import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId'; import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; +import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { useSetRecordTableFocusPosition } from '@/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; @@ -33,8 +32,8 @@ import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/po import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; -import { useNavigate } from 'react-router-dom'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; + export const DEFAULT_CELL_SCOPE: HotkeyScope = { scope: TableHotkeyScope.CellEditMode, }; @@ -51,21 +50,20 @@ export type OpenTableCellArgs = { isNavigating: boolean; }; -export const useOpenRecordTableCellV2 = (tableScopeId: string) => { +export const useOpenRecordTableCellV2 = (recordTableId: string) => { const clickOutsideListenerIsActivatedState = useRecoilComponentCallbackStateV2( clickOutsideListenerIsActivatedComponentState, RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, ); - const { indexIdentifierUrl } = useRecordIndexContextOrThrow(); const setCurrentTableCellInEditModePosition = useSetRecoilComponentStateV2( recordTableCellEditModePositionComponentState, - tableScopeId, + recordTableId, ); const { setDragSelectionStartEnabled } = useDragSelect(); - const leaveTableFocus = useLeaveTableFocus(tableScopeId); + const leaveTableFocus = useLeaveTableFocus(recordTableId); const { toggleClickOutside } = useClickOutsideListener( FOCUS_CLICK_OUTSIDE_LISTENER_ID, ); @@ -77,27 +75,25 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => { viewableRecordNameSingularState, ); - const navigate = useNavigate(); - const { setActiveDropdownFocusIdAndMemorizePrevious } = useSetActiveDropdownFocusIdAndMemorizePrevious(); - const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); - const { openFieldInput } = useOpenFieldInputEditMode(); const { activateRecordTableRow, deactivateRecordTableRow } = - useActiveRecordTableRow(tableScopeId); + useActiveRecordTableRow(recordTableId); - const { unfocusRecordTableRow } = useFocusedRecordTableRow(tableScopeId); + const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId); const setIsRowFocusActive = useSetRecoilComponentStateV2( isRecordTableRowFocusActiveComponentState, - tableScopeId, + recordTableId, ); const setFocusPosition = useSetRecordTableFocusPosition(); + const { openRecordFromIndexView } = useOpenRecordFromIndexView(); + const openTableCell = useRecoilCallback( ({ snapshot, set }) => ({ @@ -141,20 +137,13 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => { .getLoadable(recordIndexOpenRecordInState) .getValue(); - if (openRecordIn === ViewOpenRecordInType.RECORD_PAGE) { - navigate(indexIdentifierUrl(recordId)); - } - if (openRecordIn === ViewOpenRecordInType.SIDE_PANEL) { - openRecordInCommandMenu({ - recordId, - objectNameSingular, - }); - activateRecordTableRow(cellPosition.row); unfocusRecordTableRow(); } + openRecordFromIndexView({ recordId }); + return; } @@ -214,13 +203,11 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => { toggleClickOutside, setActiveDropdownFocusIdAndMemorizePrevious, leaveTableFocus, - navigate, - indexIdentifierUrl, - openRecordInCommandMenu, activateRecordTableRow, unfocusRecordTableRow, setViewableRecordId, setViewableRecordNameSingular, + openRecordFromIndexView, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts deleted file mode 100644 index 6a7d8b044..000000000 --- a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; -import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews'; -import { useQueryVariablesFromView } from './useQueryVariablesFromView'; - -export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { view } = useViewOrDefaultViewFromPrefetchedViews({ - objectMetadataItemId: objectMetadataItem.id, - }); - - const { filterValueDependencies } = useFilterValueDependencies(); - - const { filter, orderBy } = useQueryVariablesFromView({ - objectMetadataItem, - view, - filterValueDependencies, - }); - - return { - filter, - orderBy, - }; -}; diff --git a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromParentView.ts b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromParentView.ts new file mode 100644 index 000000000..3d68741d3 --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromParentView.ts @@ -0,0 +1,32 @@ +import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; +import { contextStoreRecordShowParentViewComponentState } from '@/context-store/states/contextStoreRecordShowParentViewComponentState'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { getQueryVariablesFromFiltersAndSorts } from '../utils/getQueryVariablesFromFiltersAndSorts'; + +export const useQueryVariablesFromParentView = ({ + objectMetadataItem, +}: { + objectMetadataItem: ObjectMetadataItem; +}) => { + const recordShowParentView = useRecoilComponentValueV2( + contextStoreRecordShowParentViewComponentState, + MAIN_CONTEXT_STORE_INSTANCE_ID, + ); + + const { filterValueDependencies } = useFilterValueDependencies(); + + const { filter, orderBy } = getQueryVariablesFromFiltersAndSorts({ + recordFilterGroups: recordShowParentView?.parentViewFilterGroups ?? [], + recordFilters: recordShowParentView?.parentViewFilters ?? [], + recordSorts: recordShowParentView?.parentViewSorts ?? [], + objectMetadataItem, + filterValueDependencies, + }); + + return { + filter, + orderBy, + }; +}; diff --git a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromView.ts b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromView.ts deleted file mode 100644 index 63c2d10d3..000000000 --- a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromView.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; -import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies'; -import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter'; -import { View } from '@/views/types/View'; -import { getFilterableFieldsWithVectorSearch } from '@/views/utils/getFilterableFieldsWithVectorSearch'; -import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups'; -import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; -import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; -import { isDefined } from 'twenty-shared/utils'; - -export const useQueryVariablesFromView = ({ - view, - objectMetadataItem, - filterValueDependencies, -}: { - view: View | null | undefined; - objectMetadataItem: ObjectMetadataItem; - filterValueDependencies: RecordFilterValueDependencies; -}) => { - if (!isDefined(view)) { - return { - filter: undefined, - orderBy: undefined, - }; - } - - const { viewFilterGroups, viewFilters, viewSorts } = view; - - const recordFilterGroups = mapViewFilterGroupsToRecordFilterGroups( - viewFilterGroups ?? [], - ); - - const filterableFieldMetadataItems = - getFilterableFieldsWithVectorSearch(objectMetadataItem); - - const recordFilters = mapViewFiltersToFilters( - viewFilters, - filterableFieldMetadataItems, - ); - - const filter = computeRecordGqlOperationFilter({ - fields: objectMetadataItem?.fields ?? [], - filterValueDependencies, - recordFilterGroups, - recordFilters, - }); - - const orderBy = turnSortsIntoOrderBy( - objectMetadataItem, - mapViewSortsToSorts(viewSorts), - ); - - return { - filter, - orderBy, - }; -}; diff --git a/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromFiltersAndSorts.ts b/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromFiltersAndSorts.ts new file mode 100644 index 000000000..49c02afdf --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromFiltersAndSorts.ts @@ -0,0 +1,35 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; +import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies'; +import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; + +export const getQueryVariablesFromFiltersAndSorts = ({ + recordFilterGroups, + recordFilters, + recordSorts, + objectMetadataItem, + filterValueDependencies, +}: { + recordFilterGroups: RecordFilterGroup[]; + recordFilters: RecordFilter[]; + recordSorts: RecordSort[]; + objectMetadataItem: ObjectMetadataItem; + filterValueDependencies: RecordFilterValueDependencies; +}) => { + const filter = computeRecordGqlOperationFilter({ + fields: objectMetadataItem?.fields ?? [], + filterValueDependencies, + recordFilterGroups, + recordFilters, + }); + + const orderBy = turnSortsIntoOrderBy(objectMetadataItem, recordSorts); + + return { + filter, + orderBy, + }; +}; diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx index 6f521617a..328c7c188 100644 --- a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx @@ -10,6 +10,7 @@ import { RecordFiltersComponentInstanceContext } from '@/object-record/record-fi import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer'; import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect'; import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage'; +import { computeRecordShowComponentInstanceId } from '@/object-record/record-show/utils/computeRecordShowComponentInstanceId'; import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext'; import { PageHeaderToggleCommandMenuButton } from '@/ui/layout/page-header/components/PageHeaderToggleCommandMenuButton'; import { PageBody } from '@/ui/layout/page/components/PageBody'; @@ -28,21 +29,24 @@ export const RecordShowPage = () => { parameters.objectRecordId ?? '', ); + const recordShowComponentInstanceId = + computeRecordShowComponentInstanceId(objectRecordId); + return (