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:
@ -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)) {
|
||||
|
||||
@ -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,
|
||||
},
|
||||
);
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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 };
|
||||
};
|
||||
@ -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({
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
export const computeRecordShowComponentInstanceId = (
|
||||
objectRecordId: string,
|
||||
) => {
|
||||
return `record-show-${objectRecordId}`;
|
||||
};
|
||||
@ -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,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user