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.
This commit is contained in:
Raphaël Bosi
2025-06-11 18:01:03 +02:00
committed by GitHub
parent 23cbeec227
commit 27d0a3766f
18 changed files with 296 additions and 195 deletions

View File

@ -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)) {

View File

@ -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<RecordShowParentViewComponentState | undefined | null>(
{
key: 'contextStoreRecordShowParentViewComponentState',
defaultValue: undefined,
componentInstanceContext: ContextStoreComponentInstanceContext,
},
);

View File

@ -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<HTMLElement>) => {
openRecordInCommandMenu({
recordId: record.id,
objectNameSingular,
});
}
: undefined;
const handleCustomClick = isDefined(onClick)
? onClick
: isSidePanelViewOpenRecordInType
? (_event: MouseEvent<HTMLElement>) => {
openRecordInCommandMenu({
recordId: record.id,
objectNameSingular,
});
}
: undefined;
// TODO temporary until we create a record show page for Workspaces members

View File

@ -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(() => {

View File

@ -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 (
<RecordBoardCardHeaderContainer showCompactView={showCompactView}>
@ -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"
/>
)}

View File

@ -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;

View File

@ -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}
/>
);
};

View File

@ -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,
};
};

View File

@ -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 };
};

View File

@ -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({

View File

@ -0,0 +1,5 @@
export const computeRecordShowComponentInstanceId = (
objectRecordId: string,
) => {
return `record-show-${objectRecordId}`;
};

View File

@ -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 (
<FieldContext.Provider
value={{
@ -60,6 +63,9 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
displayedMaxRows: 1,
isReadOnly: isFieldReadOnly,
maxWidth: columnDefinition.size,
onRecordChipClick: () => {
openRecordFromIndexView({ recordId });
},
isForbidden: !hasObjectReadPermissions,
}}
>

View File

@ -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,
],
);

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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 (
<RecordFilterGroupsComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }}
value={{ instanceId: recordShowComponentInstanceId }}
>
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }}
value={{ instanceId: recordShowComponentInstanceId }}
>
<RecordSortsComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }}
value={{ instanceId: recordShowComponentInstanceId }}
>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID }}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }}
value={{ instanceId: recordShowComponentInstanceId }}
>
<PageContainer>
<RecordShowPageTitle