Refacto views (#10272)

In this huge (sorry!) PR:
- introducing objectMetadataItem in contextStore instead of
objectMetadataId which is more convenient
- splitting some big hooks into smaller parts to avoid re-renders
- removing Effects to avoid re-renders (especially onViewChange)
- making the view prefetch separate from favorites to avoid re-renders
- making the view prefetch load a state and add selectors on top of it
to avoir re-renders

As a result, the performance is WAY better (I suspect the favorite
implementation to trigger a lot of re-renders unfortunately).
However, we are still facing a random app freeze on view creation. I
could not investigate the root cause. As this seems to be already there
in the precedent release, we can move forward but this seems a urgent
follow up to me ==> EDIT: I've found the root cause after a few ours of
deep dive... an infinite loop in RecordTableNoRecordGroupBodyEffect...

prastoin edit: close https://github.com/twentyhq/twenty/issues/10253

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
Charles Bochet
2025-02-18 13:51:07 +01:00
committed by GitHub
parent 103dff4bd0
commit fb42046033
125 changed files with 1607 additions and 1582 deletions

View File

@ -2,25 +2,17 @@ import { RegisterRecordActionEffect } from '@/action-menu/actions/record-actions
import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter'; import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter';
import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig'; import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig';
import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType'; import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
import { FeatureFlagKey } from '~/generated/graphql'; import { FeatureFlagKey } from '~/generated/graphql';
export const RecordActionMenuEntriesSetter = () => { export const RecordActionMenuEntriesSetter = () => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataItemComponentState,
);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === contextStoreCurrentObjectMetadataId,
); );
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
@ -39,10 +31,7 @@ export const RecordActionMenuEntriesSetter = () => {
FeatureFlagKey.IsCommandMenuV2Enabled, FeatureFlagKey.IsCommandMenuV2Enabled,
); );
if ( if (!isDefined(contextStoreCurrentObjectMetadataItem)) {
!isDefined(contextStoreCurrentObjectMetadataId) ||
!isDefined(objectMetadataItem)
) {
return null; return null;
} }
@ -52,7 +41,7 @@ export const RecordActionMenuEntriesSetter = () => {
); );
const actionConfig = getActionConfig( const actionConfig = getActionConfig(
objectMetadataItem, contextStoreCurrentObjectMetadataItem,
isCommandMenuV2Enabled, isCommandMenuV2Enabled,
); );
@ -68,7 +57,7 @@ export const RecordActionMenuEntriesSetter = () => {
<RegisterRecordActionEffect <RegisterRecordActionEffect
key={action.key} key={action.key}
action={action} action={action}
objectMetadataItem={objectMetadataItem} objectMetadataItem={contextStoreCurrentObjectMetadataItem}
/> />
))} ))}
@ -76,7 +65,7 @@ export const RecordActionMenuEntriesSetter = () => {
contextStoreTargetedRecordsRule?.mode === 'selection' && contextStoreTargetedRecordsRule?.mode === 'selection' &&
contextStoreTargetedRecordsRule?.selectedRecordIds.length === 1 && ( contextStoreTargetedRecordsRule?.selectedRecordIds.length === 1 && (
<WorkflowRunRecordActionMenuEntrySetterEffect <WorkflowRunRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem} objectMetadataItem={contextStoreCurrentObjectMetadataItem}
/> />
)} )}
</> </>

View File

@ -8,8 +8,7 @@ import { RecordIndexActionMenuButtons } from '@/action-menu/components/RecordInd
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown'; import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect'; import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { isRecordIndexLoadMoreLockedComponentState } from '@/object-record/record-index/states/isRecordIndexLoadMoreLockedComponentState'; import { isRecordIndexLoadMoreLockedComponentState } from '@/object-record/record-index/states/isRecordIndexLoadMoreLockedComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@ -18,8 +17,8 @@ import { useIsMobile } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated/graphql'; import { FeatureFlagKey } from '~/generated/graphql';
export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => { export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataItemComponentState,
); );
const isCommandMenuV2Enabled = useIsFeatureEnabled( const isCommandMenuV2Enabled = useIsFeatureEnabled(
@ -39,7 +38,7 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
return ( return (
<> <>
{contextStoreCurrentObjectMetadataId && ( {contextStoreCurrentObjectMetadataItem && (
<ActionMenuContext.Provider <ActionMenuContext.Provider
value={{ value={{
isInRightDrawer: false, isInRightDrawer: false,

View File

@ -4,8 +4,7 @@ import { RunWorkflowRecordAgnosticActionMenuEntriesSetter } from '@/action-menu/
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals'; import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
import { RecordShowActionMenuButtons } from '@/action-menu/components/RecordShowActionMenuButtons'; import { RecordShowActionMenuButtons } from '@/action-menu/components/RecordShowActionMenuButtons';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -26,8 +25,8 @@ export const RecordShowActionMenu = ({
objectNameSingular: string; objectNameSingular: string;
handleFavoriteButtonClick: () => void; handleFavoriteButtonClick: () => void;
}) => { }) => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataItemComponentState,
); );
const isCommandMenuV2Enabled = useIsFeatureEnabled( const isCommandMenuV2Enabled = useIsFeatureEnabled(
@ -42,7 +41,7 @@ export const RecordShowActionMenu = ({
return ( return (
<> <>
{contextStoreCurrentObjectMetadataId && ( {contextStoreCurrentObjectMetadataItem && (
<ActionMenuContext.Provider <ActionMenuContext.Provider
value={{ value={{
isInRightDrawer: false, isInRightDrawer: false,

View File

@ -4,15 +4,14 @@ import { RunWorkflowRecordAgnosticActionMenuEntriesSetter } from '@/action-menu/
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals'; import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
import { RightDrawerActionMenuDropdown } from '@/action-menu/components/RightDrawerActionMenuDropdown'; import { RightDrawerActionMenuDropdown } from '@/action-menu/components/RightDrawerActionMenuDropdown';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/graphql'; import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const RecordShowRightDrawerActionMenu = () => { export const RecordShowRightDrawerActionMenu = () => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataItemComponentState,
); );
const isWorkflowEnabled = useIsFeatureEnabled( const isWorkflowEnabled = useIsFeatureEnabled(
@ -21,7 +20,7 @@ export const RecordShowRightDrawerActionMenu = () => {
return ( return (
<> <>
{contextStoreCurrentObjectMetadataId && ( {contextStoreCurrentObjectMetadataItem && (
<ActionMenuContext.Provider value={{ isInRightDrawer: true }}> <ActionMenuContext.Provider value={{ isInRightDrawer: true }}>
<RightDrawerActionMenuDropdown /> <RightDrawerActionMenuDropdown />
<ActionMenuConfirmationModals /> <ActionMenuConfirmationModals />

View File

@ -66,6 +66,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
}); });
cache.modify<StoreObject>({ cache.modify<StoreObject>({
broadcast: false,
fields: { fields: {
[objectMetadataItem.namePlural]: ( [objectMetadataItem.namePlural]: (
rootQueryCachedResponse, rootQueryCachedResponse,

View File

@ -59,10 +59,10 @@ export const AppRouterProviders = () => {
</DialogManager> </DialogManager>
</DialogManagerScope> </DialogManagerScope>
</SnackBarProvider> </SnackBarProvider>
<MainContextStoreProvider />
</PrefetchDataProvider> </PrefetchDataProvider>
</ObjectMetadataItemsGater> </ObjectMetadataItemsGater>
<PageChangeEffect /> <PageChangeEffect />
<MainContextStoreProvider />
</ObjectMetadataItemsProvider> </ObjectMetadataItemsProvider>
</ApolloMetadataClientProvider> </ApolloMetadataClientProvider>
</AuthProvider> </AuthProvider>

View File

@ -5,12 +5,10 @@ import { RESET_CONTEXT_TO_SELECTION } from '@/command-menu/constants/ResetContex
import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands'; import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { Command } from '@/command-menu/types/Command'; import { Command } from '@/command-menu/types/Command';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
@ -50,23 +48,19 @@ export const CommandMenu = () => {
) )
.filter(isDefined); .filter(isDefined);
const previousContextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( const previousContextStoreCurrentObjectMetadataItem =
contextStoreCurrentObjectMetadataIdComponentState, useRecoilComponentValueV2(
'command-menu-previous', contextStoreCurrentObjectMetadataItemComponentState,
); 'command-menu-previous',
);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const currentObjectMetadataItem = useRecoilComponentValueV2(
const currentObjectMetadataId = useRecoilComponentValueV2( contextStoreCurrentObjectMetadataItemComponentState,
contextStoreCurrentObjectMetadataIdComponentState,
);
const currentObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) => objectMetadataItem.id === currentObjectMetadataId,
); );
const selectableItemIds = selectableItems.map((item) => item.id); const selectableItemIds = selectableItems.map((item) => item.id);
if (isNonEmptyString(previousContextStoreCurrentObjectMetadataId)) { if (isDefined(previousContextStoreCurrentObjectMetadataItem)) {
selectableItemIds.unshift(RESET_CONTEXT_TO_SELECTION); selectableItemIds.unshift(RESET_CONTEXT_TO_SELECTION);
} }
@ -103,7 +97,7 @@ export const CommandMenu = () => {
selectableItemIds={selectableItemIds} selectableItemIds={selectableItemIds}
noResults={noResults} noResults={noResults}
> >
{isNonEmptyString(previousContextStoreCurrentObjectMetadataId) && ( {isDefined(previousContextStoreCurrentObjectMetadataItem) && (
<CommandGroup heading={t`Context`}> <CommandGroup heading={t`Context`}>
<SelectableItem itemId={RESET_CONTEXT_TO_SELECTION}> <SelectableItem itemId={RESET_CONTEXT_TO_SELECTION}>
<ResetContextToSelectionCommandButton /> <ResetContextToSelectionCommandButton />

View File

@ -9,7 +9,7 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
@ -96,8 +96,8 @@ export const CommandMenuTopBar = () => {
const { closeCommandMenu, goBackFromCommandMenu } = useCommandMenu(); const { closeCommandMenu, goBackFromCommandMenu } = useCommandMenu();
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataItemComponentState,
); );
const commandMenuPage = useRecoilValue(commandMenuPageState); const commandMenuPage = useRecoilValue(commandMenuPageState);
@ -137,11 +137,11 @@ export const CommandMenuTopBar = () => {
testId="command-menu-go-back-button" testId="command-menu-go-back-button"
/> />
)} )}
{isDefined(contextStoreCurrentObjectMetadataId) && {isDefined(contextStoreCurrentObjectMetadataItem) &&
commandMenuPage !== CommandMenuPages.SearchRecords ? ( commandMenuPage !== CommandMenuPages.SearchRecords ? (
<CommandMenuContextChipGroupsWithRecordSelection <CommandMenuContextChipGroupsWithRecordSelection
contextChips={contextChips} contextChips={contextChips}
objectMetadataItemId={contextStoreCurrentObjectMetadataId} objectMetadataItemId={contextStoreCurrentObjectMetadataItem.id}
/> />
) : ( ) : (
<CommandMenuContextChipGroups contextChips={contextChips} /> <CommandMenuContextChipGroups contextChips={contextChips} />

View File

@ -2,7 +2,7 @@ import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandM
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { RESET_CONTEXT_TO_SELECTION } from '@/command-menu/constants/ResetContextToSelection'; import { RESET_CONTEXT_TO_SELECTION } from '@/command-menu/constants/ResetContextToSelection';
import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext'; import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -17,8 +17,8 @@ export const ResetContextToSelectionCommandButton = () => {
'command-menu-previous', 'command-menu-previous',
); );
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataItemComponentState,
'command-menu-previous', 'command-menu-previous',
); );
@ -26,7 +26,7 @@ export const ResetContextToSelectionCommandButton = () => {
const objectMetadataItem = objectMetadataItems.find( const objectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) => (objectMetadataItem) =>
objectMetadataItem.id === contextStoreCurrentObjectMetadataId, objectMetadataItem.id === contextStoreCurrentObjectMetadataItem?.id,
); );
const { resetPreviousCommandMenuContext } = const { resetPreviousCommandMenuContext } =

View File

@ -1,5 +1,5 @@
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
@ -17,19 +17,19 @@ export const useCopyContextStoreStates = () => {
instanceIdToCopyFrom: string; instanceIdToCopyFrom: string;
instanceIdToCopyTo: string; instanceIdToCopyTo: string;
}) => { }) => {
const contextStoreCurrentObjectMetadataId = snapshot const contextStoreCurrentObjectMetadataItem = snapshot
.getLoadable( .getLoadable(
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ contextStoreCurrentObjectMetadataItemComponentState.atomFamily({
instanceId: instanceIdToCopyFrom, instanceId: instanceIdToCopyFrom,
}), }),
) )
.getValue(); .getValue();
set( set(
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ contextStoreCurrentObjectMetadataItemComponentState.atomFamily({
instanceId: instanceIdToCopyTo, instanceId: instanceIdToCopyTo,
}), }),
contextStoreCurrentObjectMetadataId, contextStoreCurrentObjectMetadataItem,
); );
const contextStoreTargetedRecordsRule = snapshot const contextStoreTargetedRecordsRule = snapshot

View File

@ -1,5 +1,5 @@
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
@ -11,10 +11,10 @@ export const useResetContextStoreStates = () => {
const resetContextStoreStates = useRecoilCallback(({ set }) => { const resetContextStoreStates = useRecoilCallback(({ set }) => {
return (instanceId: string) => { return (instanceId: string) => {
set( set(
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ contextStoreCurrentObjectMetadataItemComponentState.atomFamily({
instanceId, instanceId,
}), }),
null, undefined,
); );
set( set(
@ -45,7 +45,7 @@ export const useResetContextStoreStates = () => {
contextStoreCurrentViewIdComponentState.atomFamily({ contextStoreCurrentViewIdComponentState.atomFamily({
instanceId, instanceId,
}), }),
null, undefined,
); );
set( set(

View File

@ -1,24 +0,0 @@
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useEffect } from 'react';
export const ContextStoreCurrentViewTypeEffect = ({
viewType,
}: {
viewType: ContextStoreViewType | null;
}) => {
const setContextStoreCurrentViewType = useSetRecoilComponentStateV2(
contextStoreCurrentViewTypeComponentState,
);
useEffect(() => {
setContextStoreCurrentViewType(viewType);
return () => {
setContextStoreCurrentViewType(null);
};
}, [setContextStoreCurrentViewType, viewType]);
return null;
};

View File

@ -1,20 +1,16 @@
import { MainContextStoreProviderEffect } from '@/context-store/components/MainContextStoreProviderEffect'; import { MainContextStoreProviderEffect } from '@/context-store/components/MainContextStoreProviderEffect';
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView'; import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { View } from '@/views/types/View';
import { isNonEmptyString } from '@sniptt/guards';
import { useParams, useSearchParams } from 'react-router-dom'; import { useParams, useSearchParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
import { undefined } from 'zod';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
const getViewId = ( const getViewId = (
viewIdFromQueryParams: string | null, viewIdFromQueryParams: string | null,
indexView?: View, indexViewId?: string,
lastVisitedViewId?: string, lastVisitedViewId?: string,
) => { ) => {
if (isDefined(viewIdFromQueryParams)) { if (isDefined(viewIdFromQueryParams)) {
@ -25,8 +21,8 @@ const getViewId = (
return lastVisitedViewId; return lastVisitedViewId;
} }
if (isDefined(indexView)) { if (isDefined(indexViewId)) {
return indexView.id; return indexViewId;
} }
return undefined; return undefined;
@ -44,17 +40,17 @@ export const MainContextStoreProvider = () => {
: undefined; : undefined;
const objectNamePlural = useParams().objectNamePlural ?? ''; const objectNamePlural = useParams().objectNamePlural ?? '';
const objectNameSingular = useParams().objectNameSingular ?? '';
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const viewIdQueryParam = searchParams.get('viewId'); const viewIdQueryParam = searchParams.get('viewId');
// Todo: this is triggering a lot of re-renders as we update the viewFields, we should introduce a state here
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const objectMetadataItem = objectMetadataItems.find( const objectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) => objectMetadataItem.namePlural === objectNamePlural, (objectMetadataItem) =>
objectMetadataItem.namePlural === objectNamePlural ||
objectMetadataItem.nameSingular === objectNameSingular,
); );
const { getLastVisitedViewIdFromObjectNamePlural } = useLastVisitedView(); const { getLastVisitedViewIdFromObjectNamePlural } = useLastVisitedView();
@ -63,30 +59,24 @@ export const MainContextStoreProvider = () => {
objectMetadataItem?.namePlural ?? '', objectMetadataItem?.namePlural ?? '',
); );
const viewsOnCurrentObject = views.filter( const indexViewId = useRecoilValue(
(view) => view.objectMetadataId === objectMetadataItem?.id, prefetchIndexViewIdFromObjectMetadataItemFamilySelector({
objectMetadataItemId: objectMetadataItem?.id,
}),
); );
const indexView = viewsOnCurrentObject.find((view) => view.key === 'INDEX');
const viewId = getViewId(viewIdQueryParam, indexView, lastVisitedViewId); const viewId = getViewId(viewIdQueryParam, indexViewId, lastVisitedViewId);
const mainContextStoreComponentInstanceId = `${pageName}-${objectMetadataItem?.namePlural}-${viewId}`; if (!isDefined(pageName) || !isDefined(objectMetadataItem)) {
if (
!isDefined(pageName) ||
!isDefined(objectMetadataItem) ||
!isNonEmptyString(viewId)
) {
return null; return null;
} }
return ( return (
<MainContextStoreProviderEffect <MainContextStoreProviderEffect
mainContextStoreComponentInstanceIdToSet={ mainContextStoreComponentInstanceIdToSet={'main-context-store'}
mainContextStoreComponentInstanceId
}
viewId={viewId} viewId={viewId}
objectMetadataItem={objectMetadataItem} objectMetadataItem={objectMetadataItem}
pageName={pageName}
/> />
); );
}; };

View File

@ -1,21 +1,27 @@
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useSetLastVisitedObjectMetadataId } from '@/navigation/hooks/useSetLastVisitedObjectMetadataId'; import { useSetLastVisitedObjectMetadataId } from '@/navigation/hooks/useSetLastVisitedObjectMetadataId';
import { useSetLastVisitedViewForObjectMetadataNamePlural } from '@/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural'; import { useSetLastVisitedViewForObjectMetadataNamePlural } from '@/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { ViewType } from '@/views/types/ViewType';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
export const MainContextStoreProviderEffect = ({ export const MainContextStoreProviderEffect = ({
mainContextStoreComponentInstanceIdToSet, mainContextStoreComponentInstanceIdToSet,
viewId, viewId,
objectMetadataItem, objectMetadataItem,
pageName,
}: { }: {
mainContextStoreComponentInstanceIdToSet: string; mainContextStoreComponentInstanceIdToSet: string;
viewId: string; viewId?: string;
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
pageName: string;
}) => { }) => {
const [ const [
mainContextStoreComponentInstanceId, mainContextStoreComponentInstanceId,
@ -34,17 +40,29 @@ export const MainContextStoreProviderEffect = ({
mainContextStoreComponentInstanceId, mainContextStoreComponentInstanceId,
); );
const [contextStoreCurrentViewType, setContextStoreCurrentViewType] =
useRecoilComponentStateV2(
contextStoreCurrentViewTypeComponentState,
mainContextStoreComponentInstanceId,
);
const [ const [
contextStoreCurrentObjectMetadataId, contextStoreCurrentObjectMetadataItem,
setContextStoreCurrentObjectMetadataId, setContextStoreCurrentObjectMetadataItem,
] = useRecoilComponentStateV2( ] = useRecoilComponentStateV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataItemComponentState,
mainContextStoreComponentInstanceId, mainContextStoreComponentInstanceId,
); );
const view = useRecoilValue(
prefetchViewFromViewIdFamilySelector({
viewId: viewId ?? '',
}),
);
useEffect(() => { useEffect(() => {
if (contextStoreCurrentObjectMetadataId !== objectMetadataItem.id) { if (contextStoreCurrentObjectMetadataItem?.id !== objectMetadataItem.id) {
setContextStoreCurrentObjectMetadataId(objectMetadataItem.id); setContextStoreCurrentObjectMetadataItem(objectMetadataItem);
} }
if ( if (
@ -58,7 +76,7 @@ export const MainContextStoreProviderEffect = ({
setLastVisitedViewForObjectMetadataNamePlural({ setLastVisitedViewForObjectMetadataNamePlural({
objectNamePlural: objectMetadataItem.namePlural, objectNamePlural: objectMetadataItem.namePlural,
viewId: viewId, viewId: viewId ?? '',
}); });
setLastVisitedObjectMetadataId({ setLastVisitedObjectMetadataId({
@ -69,13 +87,13 @@ export const MainContextStoreProviderEffect = ({
setContextStoreCurrentViewId(viewId); setContextStoreCurrentViewId(viewId);
} }
}, [ }, [
contextStoreCurrentObjectMetadataId, contextStoreCurrentObjectMetadataItem,
contextStoreCurrentViewId, contextStoreCurrentViewId,
mainContextStoreComponentInstanceId, mainContextStoreComponentInstanceId,
mainContextStoreComponentInstanceIdToSet, mainContextStoreComponentInstanceIdToSet,
objectMetadataItem, objectMetadataItem,
objectMetadataItem.namePlural, objectMetadataItem.namePlural,
setContextStoreCurrentObjectMetadataId, setContextStoreCurrentObjectMetadataItem,
setContextStoreCurrentViewId, setContextStoreCurrentViewId,
setLastVisitedObjectMetadataId, setLastVisitedObjectMetadataId,
setLastVisitedViewForObjectMetadataNamePlural, setLastVisitedViewForObjectMetadataNamePlural,
@ -83,5 +101,23 @@ export const MainContextStoreProviderEffect = ({
viewId, viewId,
]); ]);
useEffect(() => {
const viewType =
pageName === 'record-show'
? ContextStoreViewType.ShowPage
: view && view.type === ViewType.Kanban
? ContextStoreViewType.Kanban
: ContextStoreViewType.Table;
if (contextStoreCurrentViewType !== viewType) {
setContextStoreCurrentViewType(viewType);
}
}, [
contextStoreCurrentViewType,
pageName,
setContextStoreCurrentViewType,
view,
]);
return null; return null;
}; };

View File

@ -1,17 +0,0 @@
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const useContextStoreCurrentObjectMetadataIdOrThrow = (
instanceId?: string,
) => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState,
instanceId,
);
if (!contextStoreCurrentObjectMetadataId) {
throw new Error('contextStoreCurrentObjectMetadataIdComponent is not set');
}
return contextStoreCurrentObjectMetadataId;
};

View File

@ -0,0 +1,17 @@
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const useContextStoreObjectMetadataItemOrThrow = (
contextStoreInstanceId?: string,
) => {
const objectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemComponentState,
contextStoreInstanceId,
);
if (!objectMetadataItem) {
throw new Error('Object metadata item is not set in context store');
}
return { objectMetadataItem };
};

View File

@ -1,8 +1,7 @@
import { useContextStoreCurrentObjectMetadataIdOrThrow } from '@/context-store/hooks/useContextStoreCurrentObjectMetadataIdOrThrow'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -14,12 +13,10 @@ export const useFindManyRecordsSelectedInContextStore = ({
instanceId?: string; instanceId?: string;
limit?: number; limit?: number;
}) => { }) => {
const objectMetadataId = const objectMetadataItem = useRecoilComponentValueV2(
useContextStoreCurrentObjectMetadataIdOrThrow(instanceId); contextStoreCurrentObjectMetadataItemComponentState,
instanceId,
const { objectMetadataItem } = useObjectMetadataItemById({ );
objectId: objectMetadataId,
});
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState, contextStoreTargetedRecordsRuleComponentState,
@ -36,12 +33,13 @@ export const useFindManyRecordsSelectedInContextStore = ({
const queryFilter = computeContextStoreFilters( const queryFilter = computeContextStoreFilters(
contextStoreTargetedRecordsRule, contextStoreTargetedRecordsRule,
contextStoreFilters, contextStoreFilters,
objectMetadataItem, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
objectMetadataItem!,
filterValueDependencies, filterValueDependencies,
); );
const { records, loading, totalCount } = useFindManyRecords({ const { records, loading, totalCount } = useFindManyRecords({
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem?.nameSingular ?? '',
filter: queryFilter, filter: queryFilter,
withSoftDeleted: true, withSoftDeleted: true,
orderBy: [ orderBy: [

View File

@ -1,9 +1,10 @@
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const contextStoreCurrentObjectMetadataIdComponentState = export const contextStoreCurrentObjectMetadataItemComponentState =
createComponentStateV2<string | null>({ createComponentStateV2<ObjectMetadataItem | undefined>({
key: 'contextStoreCurrentObjectMetadataIdComponentState', key: 'contextStoreCurrentObjectMetadataItemComponentState',
defaultValue: null, defaultValue: undefined,
componentInstanceContext: ContextStoreComponentInstanceContext, componentInstanceContext: ContextStoreComponentInstanceContext,
}); });

View File

@ -2,9 +2,9 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const contextStoreCurrentViewIdComponentState = createComponentStateV2< export const contextStoreCurrentViewIdComponentState = createComponentStateV2<
string | null string | undefined
>({ >({
key: 'contextStoreCurrentViewIdComponentState', key: 'contextStoreCurrentViewIdComponentState',
defaultValue: null, defaultValue: undefined,
componentInstanceContext: ContextStoreComponentInstanceContext, componentInstanceContext: ContextStoreComponentInstanceContext,
}); });

View File

@ -1,47 +1,13 @@
import { Favorite } from '@/favorites/types/Favorite';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
export const useDeleteFavoriteFolder = () => { export const useDeleteFavoriteFolder = () => {
const { deleteOneRecord } = useDeleteOneRecord({ const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.FavoriteFolder, objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
}); });
const { upsertRecordsInCache } =
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
prefetchKey: PrefetchKey.AllFavorites,
});
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular:
PREFETCH_CONFIG[PrefetchKey.AllFavorites].objectNameSingular,
});
const { readFindManyRecordsQueryInCache } =
useReadFindManyRecordsQueryInCache({
objectMetadataItem,
});
const deleteFavoriteFolder = async (folderId: string): Promise<void> => { const deleteFavoriteFolder = async (folderId: string): Promise<void> => {
await deleteOneRecord(folderId); await deleteOneRecord(folderId);
const allFavorites = readFindManyRecordsQueryInCache<Favorite>({
queryVariables: {},
recordGqlFields: PREFETCH_CONFIG[
PrefetchKey.AllFavorites
].operationSignatureFactory({ objectMetadataItem }).fields,
});
const updatedFavorites = allFavorites.filter(
(favorite) => favorite.favoriteFolderId !== folderId,
);
upsertRecordsInCache(updatedFavorites);
}; };
return { return {

View File

@ -1,11 +1,9 @@
import { favoriteViewsWithMinimalDataSelector } from '@/favorites/states/selectors/favoriteViewsWithMinimalDataSelector';
import { sortFavorites } from '@/favorites/utils/sortFavorites'; import { sortFavorites } from '@/favorites/utils/sortFavorites';
import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -13,7 +11,9 @@ import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useFavorites = () => { export const useFavorites = () => {
const { favorites } = usePrefetchedFavoritesData(); const { favorites } = usePrefetchedFavoritesData();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); const favoriteViewsWithMinimalData = useRecoilValue(
favoriteViewsWithMinimalDataSelector,
);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectMetadataItem: favoriteObjectMetadataItem } = const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({ useObjectMetadataItem({
@ -40,14 +40,14 @@ export const useFavorites = () => {
favoriteRelationFieldMetadataItems, favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
true, true,
views, favoriteViewsWithMinimalData,
objectMetadataItems, objectMetadataItems,
), ),
[ [
favorites, favorites,
favoriteRelationFieldMetadataItems, favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
views, favoriteViewsWithMinimalData,
objectMetadataItems, objectMetadataItems,
], ],
); );

View File

@ -1,4 +1,6 @@
import { sortFavorites } from '@/favorites/utils/sortFavorites'; import { sortFavorites } from '@/favorites/utils/sortFavorites';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { useRecoilValue } from 'recoil';
import { useFavoritesMetadata } from './useFavoritesMetadata'; import { useFavoritesMetadata } from './useFavoritesMetadata';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
import { usePrefetchedFavoritesFoldersData } from './usePrefetchedFavoritesFoldersData'; import { usePrefetchedFavoritesFoldersData } from './usePrefetchedFavoritesFoldersData';
@ -7,12 +9,13 @@ export const useFavoritesByFolder = () => {
const { favorites } = usePrefetchedFavoritesData(); const { favorites } = usePrefetchedFavoritesData();
const { favoriteFolders } = usePrefetchedFavoritesFoldersData(); const { favoriteFolders } = usePrefetchedFavoritesFoldersData();
const { const {
views,
objectMetadataItems, objectMetadataItems,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
favoriteRelationFields, favoriteRelationFields,
} = useFavoritesMetadata(); } = useFavoritesMetadata();
const prefetchViews = useRecoilValue(prefetchViewsState);
const favoritesByFolder = favoriteFolders.map((folder) => ({ const favoritesByFolder = favoriteFolders.map((folder) => ({
folderId: folder.id, folderId: folder.id,
folderName: folder.name, folderName: folder.name,
@ -21,7 +24,7 @@ export const useFavoritesByFolder = () => {
favoriteRelationFields, favoriteRelationFields,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
true, true,
views, prefetchViews,
objectMetadataItems, objectMetadataItems,
), ),
})); }));

View File

@ -2,14 +2,10 @@ import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/ho
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
export const useFavoritesMetadata = () => { export const useFavoritesMetadata = () => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const getObjectRecordIdentifierByNameSingular = const getObjectRecordIdentifierByNameSingular =
useGetObjectRecordIdentifierByNameSingular(); useGetObjectRecordIdentifierByNameSingular();
@ -27,7 +23,6 @@ export const useFavoritesMetadata = () => {
); );
return { return {
views,
objectMetadataItems, objectMetadataItems,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
favoriteRelationFields, favoriteRelationFields,

View File

@ -1,46 +1,30 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { Favorite } from '@/favorites/types/Favorite'; import { Favorite } from '@/favorites/types/Favorite';
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey'; import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
type PrefetchedFavoritesData = { type PrefetchedFavoritesData = {
favorites: Favorite[]; favorites: Favorite[];
workspaceFavorites: Favorite[]; workspaceFavorites: Favorite[];
upsertFavorites: (records: Favorite[]) => void;
currentWorkspaceMemberId: string | undefined; currentWorkspaceMemberId: string | undefined;
}; };
export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => { export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMemberId = currentWorkspaceMember?.id; const currentWorkspaceMemberId = currentWorkspaceMember?.id;
const { records: _favorites } = usePrefetchedData<Favorite>( const prefetchFavorites = useRecoilValue(prefetchFavoritesState);
PrefetchKey.AllFavorites,
{
workspaceMemberId: {
eq: currentWorkspaceMemberId,
},
},
);
const favorites = _favorites.filter( const favorites = prefetchFavorites.filter(
(favorite) => favorite.workspaceMemberId === currentWorkspaceMemberId, (favorite) => favorite.workspaceMemberId === currentWorkspaceMemberId,
); );
const workspaceFavorites = _favorites.filter( const workspaceFavorites = prefetchFavorites.filter(
(favorite) => favorite.workspaceMemberId === null, (favorite) => favorite.workspaceMemberId === null,
); );
const { upsertRecordsInCache: upsertFavorites } =
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
prefetchKey: PrefetchKey.AllFavorites,
});
return { return {
favorites, favorites,
workspaceFavorites, workspaceFavorites,
upsertFavorites,
currentWorkspaceMemberId, currentWorkspaceMemberId,
}; };
}; };

View File

@ -1,13 +1,10 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder'; import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey'; import { prefetchFavoriteFoldersState } from '@/prefetch/states/prefetchFavoriteFoldersState';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
type PrefetchedFavoritesFoldersData = { type PrefetchedFavoritesFoldersData = {
favoriteFolders: FavoriteFolder[]; favoriteFolders: FavoriteFolder[];
upsertFavoriteFolders: (records: FavoriteFolder[]) => void;
currentWorkspaceMemberId: string | undefined; currentWorkspaceMemberId: string | undefined;
}; };
@ -16,23 +13,12 @@ export const usePrefetchedFavoritesFoldersData =
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMemberId = currentWorkspaceMember?.id; const currentWorkspaceMemberId = currentWorkspaceMember?.id;
const { records: favoriteFolders } = usePrefetchedData<FavoriteFolder>( const prefetchFavoriteFolders = useRecoilValue(
PrefetchKey.AllFavoritesFolders, prefetchFavoriteFoldersState,
{
workspaceMemberId: {
eq: currentWorkspaceMemberId,
},
},
); );
const { upsertRecordsInCache: upsertFavoriteFolders } =
useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
prefetchKey: PrefetchKey.AllFavoritesFolders,
});
return { return {
favoriteFolders, favoriteFolders: prefetchFavoriteFolders,
upsertFavoriteFolders,
currentWorkspaceMemberId, currentWorkspaceMemberId,
}; };
}; };

View File

@ -1,32 +1,35 @@
import { sortFavorites } from '@/favorites/utils/sortFavorites'; import { sortFavorites } from '@/favorites/utils/sortFavorites';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { useFavoritesMetadata } from './useFavoritesMetadata'; import { useFavoritesMetadata } from './useFavoritesMetadata';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useSortedFavorites = () => { export const useSortedFavorites = () => {
const { favorites, workspaceFavorites } = usePrefetchedFavoritesData(); const { favorites, workspaceFavorites } = usePrefetchedFavoritesData();
const { const {
views,
objectMetadataItems, objectMetadataItems,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
favoriteRelationFields, favoriteRelationFields,
} = useFavoritesMetadata(); } = useFavoritesMetadata();
const prefetchViews = useRecoilValue(prefetchViewsState);
const favoritesSorted = useMemo(() => { const favoritesSorted = useMemo(() => {
return sortFavorites( return sortFavorites(
favorites, favorites,
favoriteRelationFields, favoriteRelationFields,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
true, true,
views, prefetchViews,
objectMetadataItems, objectMetadataItems,
); );
}, [ }, [
favoriteRelationFields, favoriteRelationFields,
favorites, favorites,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
views,
objectMetadataItems, objectMetadataItems,
prefetchViews,
]); ]);
const workspaceFavoritesSorted = useMemo(() => { const workspaceFavoritesSorted = useMemo(() => {
@ -35,14 +38,14 @@ export const useSortedFavorites = () => {
favoriteRelationFields, favoriteRelationFields,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
false, false,
views, prefetchViews,
objectMetadataItems, objectMetadataItems,
); );
}, [ }, [
workspaceFavorites,
favoriteRelationFields, favoriteRelationFields,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
workspaceFavorites, prefetchViews,
views,
objectMetadataItems, objectMetadataItems,
]); ]);

View File

@ -4,9 +4,7 @@ import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/ho
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -14,8 +12,7 @@ import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useWorkspaceFavorites = () => { export const useWorkspaceFavorites = () => {
const { workspaceFavorites } = usePrefetchedFavoritesData(); const { workspaceFavorites } = usePrefetchedFavoritesData();
const prefetchViews = useRecoilValue(prefetchViewsState);
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectMetadataItem: favoriteObjectMetadataItem } = const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({ useObjectMetadataItem({
@ -42,14 +39,14 @@ export const useWorkspaceFavorites = () => {
favoriteRelationFieldMetadataItems, favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
false, false,
views, prefetchViews,
objectMetadataItems, objectMetadataItems,
), ),
[ [
workspaceFavorites, workspaceFavorites,
favoriteRelationFieldMetadataItems, favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular, getObjectRecordIdentifierByNameSingular,
views, prefetchViews,
objectMetadataItems, objectMetadataItems,
], ],
); );
@ -59,7 +56,7 @@ export const useWorkspaceFavorites = () => {
); );
const favoriteViewObjectMetadataIds = new Set( const favoriteViewObjectMetadataIds = new Set(
views.reduce<string[]>((acc, view) => { prefetchViews.reduce<string[]>((acc, view) => {
if (workspaceFavoriteIds.has(view.id)) { if (workspaceFavoriteIds.has(view.id)) {
acc.push(view.objectMetadataId); acc.push(view.objectMetadataId);
} }

View File

@ -0,0 +1,18 @@
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { View } from '@/views/types/View';
import { selector } from 'recoil';
export const favoriteViewsWithMinimalDataSelector = selector<
Pick<View, 'id' | 'name' | 'objectMetadataId' | 'icon'>[]
>({
key: 'favoriteViewsWithMinimalDataSelector',
get: ({ get }) => {
const views = get(prefetchViewsState);
return views.map((view) => ({
id: view.id,
name: view.name,
objectMetadataId: view.objectMetadataId,
icon: view.icon,
}));
},
});

View File

@ -4,23 +4,13 @@ import { isDefined } from 'twenty-shared';
type ReturnType = { type ReturnType = {
labelPlural: string; labelPlural: string;
view: View | null; view: Pick<View, 'id' | 'name' | 'objectMetadataId'>;
}; };
export const getObjectMetadataLabelPluralFromViewId = ( export const getObjectMetadataLabelPluralFromViewId = (
views: View[], view: Pick<View, 'id' | 'name' | 'objectMetadataId'>,
objectMetadataItems: ObjectMetadataItem[], objectMetadataItems: ObjectMetadataItem[],
viewId: string,
): ReturnType => { ): ReturnType => {
const view = views.find((view) => view.id === viewId);
if (!view) {
return {
labelPlural: '',
view: null,
};
}
const objectMetadataItem = objectMetadataItems.find( const objectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) => objectMetadataItem.id === view.objectMetadataId, (objectMetadataItem) => objectMetadataItem.id === view.objectMetadataId,
); );

View File

@ -22,16 +22,23 @@ export const sortFavorites = (
objectNameSingular: string, objectNameSingular: string,
) => ObjectRecordIdentifier, ) => ObjectRecordIdentifier,
hasLinkToShowPage: boolean, hasLinkToShowPage: boolean,
views: View[], views: Pick<View, 'id' | 'name' | 'objectMetadataId' | 'icon'>[],
objectMetadataItems: ObjectMetadataItem[], objectMetadataItems: ObjectMetadataItem[],
) => { ) => {
return favorites return favorites
.map((favorite) => { .map((favorite) => {
if (isDefined(favorite.viewId) && isDefined(favorite.workspaceMemberId)) { if (isDefined(favorite.viewId) && isDefined(favorite.workspaceMemberId)) {
const { labelPlural, view } = getObjectMetadataLabelPluralFromViewId( const view = views.find((view) => view.id === favorite.viewId);
views,
if (!isDefined(view)) {
return {
...favorite,
} as ProcessedFavorite;
}
const { labelPlural } = getObjectMetadataLabelPluralFromViewId(
view,
objectMetadataItems, objectMetadataItems,
favorite.viewId,
); );
return { return {

View File

@ -4,41 +4,54 @@ import { RecoilRoot, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { ViewType } from '@/views/types/ViewType';
import { getCompanyObjectMetadataItem } from '~/testing/mock-data/companies';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { mockedUserData } from '~/testing/mock-data/users'; import { mockedUserData } from '~/testing/mock-data/users';
jest.mock('@/prefetch/hooks/usePrefetchedData'); const renderHooks = ({
const setupMockPrefetchedData = (viewId?: string) => { withCurrentUser,
const companyObjectMetadata = generatedMockObjectMetadataItems.find( withExistingView,
(item) => item.nameSingular === 'company', }: {
); withCurrentUser: boolean;
withExistingView: boolean;
jest.mocked(usePrefetchedData).mockReturnValue({ }) => {
isDataPrefetched: true,
records: viewId
? [
{
id: viewId,
__typename: 'object',
objectMetadataId: companyObjectMetadata?.id,
},
]
: [],
});
};
const renderHooks = (withCurrentUser: boolean) => {
const { result } = renderHook( const { result } = renderHook(
() => { () => {
const setCurrentUser = useSetRecoilState(currentUserState); const setCurrentUser = useSetRecoilState(currentUserState);
const setObjectMetadataItems = useSetRecoilState( const setObjectMetadataItems = useSetRecoilState(
objectMetadataItemsState, objectMetadataItemsState,
); );
const setPrefetchViews = useSetRecoilState(prefetchViewsState);
setObjectMetadataItems(generatedMockObjectMetadataItems); setObjectMetadataItems(generatedMockObjectMetadataItems);
if (withExistingView) {
setPrefetchViews([
{
id: 'viewId',
name: 'Test View',
objectMetadataId: getCompanyObjectMetadataItem().id,
type: ViewType.Table,
key: null,
isCompact: false,
viewFields: [],
viewGroups: [],
viewSorts: [],
kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
icon: '',
kanbanAggregateOperationFieldMetadataId: '',
position: 0,
viewFilters: [],
__typename: 'View',
},
]);
}
if (withCurrentUser) { if (withCurrentUser) {
setCurrentUser(mockedUserData); setCurrentUser(mockedUserData);
} }
@ -52,23 +65,31 @@ const renderHooks = (withCurrentUser: boolean) => {
}; };
describe('useDefaultHomePagePath', () => { describe('useDefaultHomePagePath', () => {
it('should return proper path when no currentUser', () => { it('should return proper path when no currentUser', () => {
setupMockPrefetchedData(); const { result } = renderHooks({
const { result } = renderHooks(false); withCurrentUser: false,
withExistingView: false,
});
expect(result.current.defaultHomePagePath).toEqual(AppPath.SignInUp); expect(result.current.defaultHomePagePath).toEqual(AppPath.SignInUp);
}); });
it('should return proper path when no currentUser and existing view', () => { it('should return proper path when no currentUser and existing view', () => {
setupMockPrefetchedData('viewId'); const { result } = renderHooks({
const { result } = renderHooks(false); withCurrentUser: false,
withExistingView: true,
});
expect(result.current.defaultHomePagePath).toEqual(AppPath.SignInUp); expect(result.current.defaultHomePagePath).toEqual(AppPath.SignInUp);
}); });
it('should return proper path when currentUser is defined', () => { it('should return proper path when currentUser is defined', () => {
setupMockPrefetchedData(); const { result } = renderHooks({
const { result } = renderHooks(true); withCurrentUser: true,
withExistingView: false,
});
expect(result.current.defaultHomePagePath).toEqual('/objects/companies'); expect(result.current.defaultHomePagePath).toEqual('/objects/companies');
}); });
it('should return proper path when currentUser is defined and view exists', () => { it('should return proper path when currentUser is defined and view exists', () => {
setupMockPrefetchedData('viewId'); const { result } = renderHooks({
const { result } = renderHooks(true); withCurrentUser: true,
withExistingView: true,
});
expect(result.current.defaultHomePagePath).toEqual( expect(result.current.defaultHomePagePath).toEqual(
'/objects/companies?viewId=viewId', '/objects/companies?viewId=viewId',
); );

View File

@ -2,10 +2,8 @@ import { currentUserState } from '@/auth/states/currentUserState';
import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState'; import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState';
import { ObjectPathInfo } from '@/navigation/types/ObjectPathInfo'; import { ObjectPathInfo } from '@/navigation/types/ObjectPathInfo';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { View } from '@/views/types/View';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
@ -15,7 +13,7 @@ export const useDefaultHomePagePath = () => {
const currentUser = useRecoilValue(currentUserState); const currentUser = useRecoilValue(currentUserState);
const { activeObjectMetadataItems, alphaSortedActiveObjectMetadataItems } = const { activeObjectMetadataItems, alphaSortedActiveObjectMetadataItems } =
useFilteredObjectMetadataItems(); useFilteredObjectMetadataItems();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); const prefetchViews = useRecoilValue(prefetchViewsState);
const lastVisitedObjectMetadataItemId = useRecoilValue( const lastVisitedObjectMetadataItemId = useRecoilValue(
lastVisitedObjectMetadataItemIdState, lastVisitedObjectMetadataItemIdState,
); );
@ -31,8 +29,10 @@ export const useDefaultHomePagePath = () => {
const getFirstView = useCallback( const getFirstView = useCallback(
(objectMetadataItemId: string | undefined | null) => (objectMetadataItemId: string | undefined | null) =>
views.find((view) => view.objectMetadataId === objectMetadataItemId), prefetchViews.find(
[views], (view) => view.objectMetadataId === objectMetadataItemId,
),
[prefetchViews],
); );
const firstObjectPathInfo = useMemo<ObjectPathInfo | null>(() => { const firstObjectPathInfo = useMemo<ObjectPathInfo | null>(() => {

View File

@ -1,16 +1,11 @@
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState'; import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useLazyPrefetchedData } from '@/prefetch/hooks/useLazyPrefetchData'; import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
export const useSetLastVisitedViewForObjectMetadataNamePlural = () => { export const useSetLastVisitedViewForObjectMetadataNamePlural = () => {
const { records: views, findManyRecords } = useLazyPrefetchedData<View>(
PrefetchKey.AllViews,
);
const setLastVisitedViewForObjectMetadataNamePlural = useRecoilCallback( const setLastVisitedViewForObjectMetadataNamePlural = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
async ({ async ({
@ -20,7 +15,7 @@ export const useSetLastVisitedViewForObjectMetadataNamePlural = () => {
objectNamePlural: string; objectNamePlural: string;
viewId: string; viewId: string;
}) => { }) => {
await findManyRecords(); const views = snapshot.getLoadable(prefetchViewsState).getValue();
const view = views.find((view: View) => view.id === viewId); const view = views.find((view: View) => view.id === viewId);
@ -54,7 +49,7 @@ export const useSetLastVisitedViewForObjectMetadataNamePlural = () => {
}); });
} }
}, },
[findManyRecords, views], [],
); );
return { return {

View File

@ -2,16 +2,13 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState'; import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewsFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchViewsFromObjectMetadataItemFamilySelector';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer'; import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer';
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { View } from '@/views/types/View';
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { AnimatedExpandableContainer, useIcons } from 'twenty-ui'; import { AnimatedExpandableContainer, useIcons } from 'twenty-ui';
@ -24,11 +21,10 @@ export type NavigationDrawerItemForObjectMetadataItemProps = {
export const NavigationDrawerItemForObjectMetadataItem = ({ export const NavigationDrawerItemForObjectMetadataItem = ({
objectMetadataItem, objectMetadataItem,
}: NavigationDrawerItemForObjectMetadataItemProps) => { }: NavigationDrawerItemForObjectMetadataItemProps) => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); const views = useRecoilValue(
prefetchViewsFromObjectMetadataItemFamilySelector({
const objectMetadataViews = getObjectMetadataItemViews( objectMetadataItemId: objectMetadataItem.id,
objectMetadataItem.id, }),
views,
); );
const mainContextStoreComponentInstanceId = useRecoilValue( const mainContextStoreComponentInstanceId = useRecoilValue(
@ -68,9 +64,9 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
}) + '/', }) + '/',
); );
const shouldSubItemsBeDisplayed = isActive && objectMetadataViews.length > 1; const shouldSubItemsBeDisplayed = isActive && views.length > 1;
const sortedObjectMetadataViews = [...objectMetadataViews].sort( const sortedObjectMetadataViews = [...views].sort(
(viewA, viewB) => viewA.position - viewB.position, (viewA, viewB) => viewA.position - viewB.position,
); );

View File

@ -1,5 +1,3 @@
import { useMemo } from 'react';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
@ -13,14 +11,8 @@ import { formatFieldMetadataItemsAsSortDefinitions } from '../utils/formatFieldM
export const useColumnDefinitionsFromFieldMetadata = ( export const useColumnDefinitionsFromFieldMetadata = (
objectMetadataItem: ObjectMetadataItem, objectMetadataItem: ObjectMetadataItem,
) => { ) => {
const activeFieldMetadataItems = useMemo( const activeFieldMetadataItems = objectMetadataItem.fields.filter(
() => ({ isActive, isSystem }) => isActive && !isSystem,
objectMetadataItem
? objectMetadataItem.fields.filter(
({ isActive, isSystem }) => isActive && !isSystem,
)
: [],
[objectMetadataItem],
); );
const filterableFieldMetadataItems = useRecoilValue( const filterableFieldMetadataItems = useRecoilValue(
@ -33,43 +25,30 @@ export const useColumnDefinitionsFromFieldMetadata = (
fields: activeFieldMetadataItems, fields: activeFieldMetadataItems,
}); });
const columnDefinitions: ColumnDefinition<FieldMetadata>[] = useMemo( const columnDefinitions: ColumnDefinition<FieldMetadata>[] =
() => activeFieldMetadataItems
objectMetadataItem .map((field, index) =>
? activeFieldMetadataItems formatFieldMetadataItemAsColumnDefinition({
.map((field, index) => position: index,
formatFieldMetadataItemAsColumnDefinition({ field,
position: index, objectMetadataItem,
field, }),
objectMetadataItem, )
}), .filter(filterAvailableTableColumns)
) .map((column) => {
.filter(filterAvailableTableColumns) const existsInFilterDefinitions = filterableFieldMetadataItems.some(
.map((column) => { (fieldMetadataItem) =>
const existsInFilterDefinitions = fieldMetadataItem.id === column.fieldMetadataId,
filterableFieldMetadataItems.some( );
(fieldMetadataItem) => const existsInSortDefinitions = sortDefinitions.some(
fieldMetadataItem.id === column.fieldMetadataId, (sort) => sort.fieldMetadataId === column.fieldMetadataId,
); );
return {
const existsInSortDefinitions = sortDefinitions.some( ...column,
(sort) => sort.fieldMetadataId === column.fieldMetadataId, isFilterable: existsInFilterDefinitions,
); isSortable: existsInSortDefinitions,
};
return { });
...column,
isFilterable: existsInFilterDefinitions,
isSortable: existsInSortDefinitions,
};
})
: [],
[
filterableFieldMetadataItems,
activeFieldMetadataItems,
objectMetadataItem,
sortDefinitions,
],
);
return { return {
columnDefinitions, columnDefinitions,

View File

@ -1,9 +1,9 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache'; import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState'; import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState'; import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
@ -24,11 +24,10 @@ export const useDeleteCombinedViewFilterGroup = (
); );
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2( const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
currentViewIdComponentState, contextStoreCurrentViewIdComponentState,
viewBarComponentId,
); );
const { getViewFromCache } = useGetViewFromCache(); const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
const deleteCombinedViewFilterGroup = useRecoilCallback( const deleteCombinedViewFilterGroup = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
@ -56,7 +55,7 @@ export const useDeleteCombinedViewFilterGroup = (
return; return;
} }
const currentView = await getViewFromCache(currentViewId); const currentView = await getViewFromPrefetchState(currentViewId);
if (!currentView) { if (!currentView) {
return; return;
@ -99,7 +98,7 @@ export const useDeleteCombinedViewFilterGroup = (
}, },
[ [
currentViewIdCallbackState, currentViewIdCallbackState,
getViewFromCache, getViewFromPrefetchState,
unsavedToDeleteViewFilterGroupIdsCallbackState, unsavedToDeleteViewFilterGroupIdsCallbackState,
unsavedToUpsertViewFilterGroupsCallbackState, unsavedToUpsertViewFilterGroupsCallbackState,
], ],

View File

@ -116,6 +116,7 @@ export const useCreateOneRecord = <
objectMetadataItem, objectMetadataItem,
objectMetadataItems, objectMetadataItems,
record: recordCreatedInCache, record: recordCreatedInCache,
recordGqlFields: computedRecordGqlFields,
computeReferences: false, computeReferences: false,
}); });

View File

@ -17,6 +17,7 @@ import {
ComponentDecorator, ComponentDecorator,
getCanvasElementForDropdownTesting, getCanvasElementForDropdownTesting,
} from 'twenty-ui'; } from 'twenty-ui';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
@ -79,6 +80,7 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
</RecordIndexContextProvider> </RecordIndexContextProvider>
); );
}, },
ContextStoreDecorator,
ObjectMetadataItemsDecorator, ObjectMetadataItemsDecorator,
SnackBarDecorator, SnackBarDecorator,
ComponentDecorator, ComponentDecorator,

View File

@ -36,7 +36,6 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
const { const {
viewType, viewType,
currentContentId, currentContentId,
recordIndexId,
objectMetadataItem, objectMetadataItem,
onContentChange, onContentChange,
resetContent, resetContent,
@ -64,9 +63,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
const { const {
handleRecordGroupFieldChange: setRecordGroupField, handleRecordGroupFieldChange: setRecordGroupField,
resetRecordGroupField, resetRecordGroupField,
} = useHandleRecordGroupField({ } = useHandleRecordGroupField();
viewBarComponentId: recordIndexId,
});
const newSelectFieldSettingsUrl = getSettingsPath( const newSelectFieldSettingsUrl = getSettingsPath(
SettingsPath.ObjectNewFieldConfigure, SettingsPath.ObjectNewFieldConfigure,

View File

@ -1,7 +1,6 @@
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from 'twenty-ui'; import { ComponentDecorator } from 'twenty-ui';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectOptionsDropdownContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownContent'; import { ObjectOptionsDropdownContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownContent';
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId'; import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
@ -16,6 +15,7 @@ import { ViewType } from '@/views/types/ViewType';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@ -45,21 +45,18 @@ const meta: Meta<typeof ObjectOptionsDropdownContent> = {
value={{ instanceId, onColumnsChange: () => {} }} value={{ instanceId, onColumnsChange: () => {} }}
> >
<ViewComponentInstanceContext.Provider value={{ instanceId }}> <ViewComponentInstanceContext.Provider value={{ instanceId }}>
<ContextStoreComponentInstanceContext.Provider <MemoryRouter
value={{ instanceId }} initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
> >
<MemoryRouter <Story />
initialEntries={['/one', '/two', { pathname: '/three' }]} </MemoryRouter>
initialIndex={1}
>
<Story />
</MemoryRouter>
</ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider> </ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider> </RecordTableComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider> </RecordFiltersComponentInstanceContext.Provider>
); );
}, },
ContextStoreDecorator,
ObjectMetadataItemsDecorator, ObjectMetadataItemsDecorator,
SnackBarDecorator, SnackBarDecorator,
ComponentDecorator, ComponentDecorator,

View File

@ -26,13 +26,12 @@ type useObjectOptionsForBoardParams = {
export const useObjectOptionsForBoard = ({ export const useObjectOptionsForBoard = ({
objectNameSingular, objectNameSingular,
recordBoardId, recordBoardId,
viewBarId,
}: useObjectOptionsForBoardParams) => { }: useObjectOptionsForBoardParams) => {
const [recordIndexFieldDefinitions, setRecordIndexFieldDefinitions] = const [recordIndexFieldDefinitions, setRecordIndexFieldDefinitions] =
useRecoilState(recordIndexFieldDefinitionsState); useRecoilState(recordIndexFieldDefinitionsState);
const { saveViewFields } = useSaveCurrentViewFields(viewBarId); const { saveViewFields } = useSaveCurrentViewFields();
const { updateCurrentView } = useUpdateCurrentView(viewBarId); const { updateCurrentView } = useUpdateCurrentView();
const [isCompactModeActive, setIsCompactModeActive] = const [isCompactModeActive, setIsCompactModeActive] =
useRecoilComponentStateV2( useRecoilComponentStateV2(

View File

@ -23,7 +23,7 @@ export const useRecordGroupReorder = ({
viewBarId, viewBarId,
viewType, viewType,
}: UseRecordGroupHandlersParams) => { }: UseRecordGroupHandlersParams) => {
const setRecordGroup = useSetRecordGroup(viewBarId); const setRecordGroup = useSetRecordGroup();
const visibleRecordGroupIdsFamilySelector = useRecoilComponentCallbackStateV2( const visibleRecordGroupIdsFamilySelector = useRecoilComponentCallbackStateV2(
visibleRecordGroupIdsComponentFamilySelector, visibleRecordGroupIdsComponentFamilySelector,
@ -78,7 +78,7 @@ export const useRecordGroupReorder = ({
]; ];
}, []); }, []);
setRecordGroup(updatedRecordGroups); setRecordGroup(updatedRecordGroups, viewBarId);
saveViewGroups( saveViewGroups(
mapRecordGroupDefinitionsToViewGroups(updatedRecordGroups), mapRecordGroupDefinitionsToViewGroups(updatedRecordGroups),
); );
@ -86,6 +86,7 @@ export const useRecordGroupReorder = ({
[ [
saveViewGroups, saveViewGroups,
setRecordGroup, setRecordGroup,
viewBarId,
viewType, viewType,
visibleRecordGroupIdsFamilySelector, visibleRecordGroupIdsFamilySelector,
], ],

View File

@ -1,32 +1,33 @@
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState'; import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useSetRecordGroup = (viewId?: string) => { export const useSetRecordGroup = () => {
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const recordIndexRecordGroupIdsState = useRecoilComponentCallbackStateV2(
recordGroupIdsComponentState,
viewId,
);
const recordGroupFieldMetadataState = useRecoilComponentCallbackStateV2(
recordGroupFieldMetadataComponentState,
viewId,
);
return useRecoilCallback( return useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
(recordGroups: RecordGroupDefinition[]) => { (recordGroups: RecordGroupDefinition[], recordIndexId: string) => {
const objectMetadataItem = snapshot
.getLoadable(
contextStoreCurrentObjectMetadataItemComponentState.atomFamily({
instanceId: 'main-context-store',
}),
)
.getValue();
if (!objectMetadataItem) {
return;
}
const currentRecordGroupIds = getSnapshotValue( const currentRecordGroupIds = getSnapshotValue(
snapshot, snapshot,
recordIndexRecordGroupIdsState, recordGroupIdsComponentState.atomFamily({
instanceId: recordIndexId,
}),
); );
const fieldMetadataId = recordGroups?.[0]?.fieldMetadataId; const fieldMetadataId = recordGroups?.[0]?.fieldMetadataId;
const fieldMetadata = fieldMetadataId const fieldMetadata = fieldMetadataId
@ -36,12 +37,19 @@ export const useSetRecordGroup = (viewId?: string) => {
: undefined; : undefined;
const currentFieldMetadata = getSnapshotValue( const currentFieldMetadata = getSnapshotValue(
snapshot, snapshot,
recordGroupFieldMetadataState, recordGroupFieldMetadataComponentState.atomFamily({
instanceId: recordIndexId,
}),
); );
// Set the field metadata linked to the record groups // Set the field metadata linked to the record groups
if (!isDeeplyEqual(fieldMetadata, currentFieldMetadata)) { if (!isDeeplyEqual(fieldMetadata, currentFieldMetadata)) {
set(recordGroupFieldMetadataState, fieldMetadata); set(
recordGroupFieldMetadataComponentState.atomFamily({
instanceId: recordIndexId,
}),
fieldMetadata,
);
} }
// Set the record groups by id // Set the record groups by id
@ -75,12 +83,13 @@ export const useSetRecordGroup = (viewId?: string) => {
} }
// Set the record group ids // Set the record group ids
set(recordIndexRecordGroupIdsState, recordGroupIds); set(
recordGroupIdsComponentState.atomFamily({
instanceId: recordIndexId,
}),
recordGroupIds,
);
}, },
[ [],
objectMetadataItem.fields,
recordGroupFieldMetadataState,
recordIndexRecordGroupIdsState,
],
); );
}; };

View File

@ -1,52 +1,26 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { ObjectOptionsDropdown } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdown'; import { ObjectOptionsDropdown } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdown';
import { RecordIndexBoardContainer } from '@/object-record/record-index/components/RecordIndexBoardContainer'; import { RecordIndexBoardContainer } from '@/object-record/record-index/components/RecordIndexBoardContainer';
import { RecordIndexBoardDataLoader } from '@/object-record/record-index/components/RecordIndexBoardDataLoader'; import { RecordIndexBoardDataLoader } from '@/object-record/record-index/components/RecordIndexBoardDataLoader';
import { RecordIndexBoardDataLoaderEffect } from '@/object-record/record-index/components/RecordIndexBoardDataLoaderEffect'; import { RecordIndexBoardDataLoaderEffect } from '@/object-record/record-index/components/RecordIndexBoardDataLoaderEffect';
import { RecordIndexTableContainer } from '@/object-record/record-index/components/RecordIndexTableContainer'; import { RecordIndexTableContainer } from '@/object-record/record-index/components/RecordIndexTableContainer';
import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect'; import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexIsCompactModeActiveState } from '@/object-record/record-index/states/recordIndexIsCompactModeActiveState';
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper'; import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider'; import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
import { RecordIndexActionMenu } from '@/action-menu/components/RecordIndexActionMenu'; import { RecordIndexActionMenu } from '@/action-menu/components/RecordIndexActionMenu';
import { ContextStoreCurrentViewTypeEffect } from '@/context-store/components/ContextStoreCurrentViewTypeEffect';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems';
import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup';
import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect'; import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect';
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect'; import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { ViewBar } from '@/views/components/ViewBar'; import { ViewBar } from '@/views/components/ViewBar';
import { ViewField } from '@/views/types/ViewField';
import { ViewGroup } from '@/views/types/ViewGroup';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewGroupsToRecordGroupDefinitions } from '@/views/utils/mapViewGroupsToRecordGroupDefinitions';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useCallback } from 'react';
import { FeatureFlagKey } from '~/generated/graphql'; import { FeatureFlagKey } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
const StyledContainer = styled.div` const StyledContainer = styled.div`
display: flex; display: flex;
@ -64,9 +38,7 @@ const StyledContainerWithPadding = styled.div`
`; `;
export const RecordIndexContainer = () => { export const RecordIndexContainer = () => {
const [recordIndexViewType, setRecordIndexViewType] = useRecoilState( const [recordIndexViewType] = useRecoilState(recordIndexViewTypeState);
recordIndexViewTypeState,
);
const { const {
objectNamePlural, objectNamePlural,
@ -75,128 +47,12 @@ export const RecordIndexContainer = () => {
objectNameSingular, objectNameSingular,
} = useRecordIndexContextOrThrow(); } = useRecordIndexContextOrThrow();
const setRecordGroup = useSetRecordGroup(recordIndexId);
const { columnDefinitions, sortDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const setRecordIndexViewFilterGroups = useSetRecoilState(
recordIndexViewFilterGroupsState,
);
const setRecordIndexFilters = useSetRecoilState(recordIndexFiltersState);
const setRecordIndexSorts = useSetRecoilState(recordIndexSortsState);
const setRecordIndexIsCompactModeActive = useSetRecoilState(
recordIndexIsCompactModeActiveState,
);
const setRecordIndexViewKanbanFieldMetadataIdState = useSetRecoilState(
recordIndexKanbanFieldMetadataIdState,
);
const setRecordIndexViewKanbanAggregateOperationState = useSetRecoilState(
recordIndexKanbanAggregateOperationState,
);
const {
setTableViewFilterGroups,
setTableFilters,
setTableSorts,
setTableColumns,
} = useRecordTable({
recordTableId: recordIndexId,
});
const onViewFieldsChange = useRecoilCallback(
({ set, snapshot }) =>
(viewFields: ViewField[]) => {
const newFieldDefinitions = mapViewFieldsToColumnDefinitions({
viewFields,
columnDefinitions,
});
setTableColumns(newFieldDefinitions);
const existingRecordIndexFieldDefinitions = snapshot
.getLoadable(recordIndexFieldDefinitionsState)
.getValue();
if (
!isDeeplyEqual(
existingRecordIndexFieldDefinitions,
newFieldDefinitions,
)
) {
set(recordIndexFieldDefinitionsState, newFieldDefinitions);
}
for (const viewField of viewFields) {
const viewFieldMetadataType = objectMetadataItem.fields?.find(
(field) => field.id === viewField.fieldMetadataId,
)?.type;
const aggregateOperationForViewField = snapshot
.getLoadable(
viewFieldAggregateOperationState({
viewFieldId: viewField.id,
}),
)
.getValue();
const convertedViewFieldAggregateOperation = isDefined(
viewField.aggregateOperation,
)
? convertAggregateOperationToExtendedAggregateOperation(
viewField.aggregateOperation,
viewFieldMetadataType,
)
: viewField.aggregateOperation;
if (
aggregateOperationForViewField !==
convertedViewFieldAggregateOperation
) {
set(
viewFieldAggregateOperationState({
viewFieldId: viewField.id,
}),
convertedViewFieldAggregateOperation,
);
}
}
},
[columnDefinitions, objectMetadataItem.fields, setTableColumns],
);
const onViewGroupsChange = useCallback(
(viewGroups: ViewGroup[]) => {
const newGroupDefinitions = mapViewGroupsToRecordGroupDefinitions({
objectMetadataItem,
viewGroups,
});
setRecordGroup(newGroupDefinitions);
},
[objectMetadataItem, setRecordGroup],
);
const setContextStoreTargetedRecordsRule = useSetRecoilComponentStateV2(
contextStoreTargetedRecordsRuleComponentState,
);
const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(
objectMetadataItem.id,
);
const isCommandMenuV2Enabled = useIsFeatureEnabled( const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled, FeatureFlagKey.IsCommandMenuV2Enabled,
); );
return ( return (
<> <>
<ContextStoreCurrentViewTypeEffect
viewType={
recordIndexViewType === ViewType.Table
? ContextStoreViewType.Table
: ContextStoreViewType.Kanban
}
/>
<StyledContainer> <StyledContainer>
<InformationBannerWrapper /> <InformationBannerWrapper />
<RecordFieldValueSelectorContextProvider> <RecordFieldValueSelectorContextProvider>
@ -210,60 +66,6 @@ export const RecordIndexContainer = () => {
viewType={recordIndexViewType ?? ViewType.Table} viewType={recordIndexViewType ?? ViewType.Table}
/> />
} }
onCurrentViewChange={(view) => {
if (!view) {
return;
}
onViewFieldsChange(view.viewFields);
onViewGroupsChange(view.viewGroups);
setTableViewFilterGroups(view.viewFilterGroups ?? []);
setTableFilters(
mapViewFiltersToFilters(
view.viewFilters,
filterableFieldMetadataItems,
),
);
setRecordIndexFilters(
mapViewFiltersToFilters(
view.viewFilters,
filterableFieldMetadataItems,
),
);
setRecordIndexViewFilterGroups(view.viewFilterGroups ?? []);
setContextStoreTargetedRecordsRule((prev) => ({
...prev,
filters: mapViewFiltersToFilters(
view.viewFilters,
filterableFieldMetadataItems,
),
}));
setTableSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexViewType(view.type);
setRecordIndexViewKanbanFieldMetadataIdState(
view.kanbanFieldMetadataId,
);
const kanbanAggregateOperationFieldMetadataType =
objectMetadataItem.fields?.find(
(field) =>
field.id === view.kanbanAggregateOperationFieldMetadataId,
)?.type;
setRecordIndexViewKanbanAggregateOperationState({
operation: isDefined(view.kanbanAggregateOperation)
? convertAggregateOperationToExtendedAggregateOperation(
view.kanbanAggregateOperation,
kanbanAggregateOperationFieldMetadataType,
)
: view.kanbanAggregateOperation,
fieldMetadataId: view.kanbanAggregateOperationFieldMetadataId,
});
setRecordIndexIsCompactModeActive(view.isCompact);
}}
/> />
<RecordIndexViewBarEffect <RecordIndexViewBarEffect
objectNamePlural={objectNamePlural} objectNamePlural={objectNamePlural}

View File

@ -1,31 +0,0 @@
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useEffect } from 'react';
export const RecordIndexContainerContextStoreObjectMetadataEffect = () => {
const setContextStoreCurrentObjectMetadataItem = useSetRecoilComponentStateV2(
contextStoreCurrentObjectMetadataIdComponentState,
);
const { objectNamePlural } = useRecordIndexContextOrThrow();
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
useEffect(() => {
setContextStoreCurrentObjectMetadataItem(objectMetadataItem.id);
return () => {
setContextStoreCurrentObjectMetadataItem(null);
};
}, [objectMetadataItem.id, setContextStoreCurrentObjectMetadataItem]);
return null;
};

View File

@ -0,0 +1,97 @@
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect';
import { RecordIndexLoadBaseOnContextStoreEffect } from '@/object-record/record-index/components/RecordIndexLoadBaseOnContextStoreEffect';
import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader';
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { capitalize } from 'twenty-shared';
const StyledIndexContainer = styled.div`
display: flex;
height: 100%;
width: 100%;
`;
export const RecordIndexContainerGater = () => {
const mainContextStoreComponentInstanceId = useRecoilValue(
mainContextStoreComponentInstanceIdState,
);
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
mainContextStoreComponentInstanceId,
);
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const recordIndexId = `${objectMetadataItem.namePlural}-${contextStoreCurrentViewId}`;
const handleIndexRecordsLoaded = useRecoilCallback(
({ set }) =>
() => {
// TODO: find a better way to reset this state ?
set(lastShowPageRecordIdState, null);
},
[],
);
const { indexIdentifierUrl } = useHandleIndexIdentifierClick({
objectMetadataItem,
recordIndexId,
});
return (
<>
<RecordIndexContextProvider
value={{
recordIndexId,
objectNamePlural: objectMetadataItem.namePlural,
objectNameSingular: objectMetadataItem.nameSingular,
objectMetadataItem,
onIndexRecordsLoaded: handleIndexRecordsLoaded,
indexIdentifierUrl,
}}
>
<ViewComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
>
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
>
<ActionMenuComponentInstanceContext.Provider
value={{
instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
}}
>
<PageTitle
title={`${capitalize(objectMetadataItem.namePlural)}`}
/>
<RecordIndexPageHeader />
<PageBody>
<StyledIndexContainer>
<RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect />
<RecordIndexContainer />
</StyledIndexContainer>
</PageBody>
</ActionMenuComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
<RecordIndexLoadBaseOnContextStoreEffect />
</ViewComponentInstanceContext.Provider>
</RecordIndexContextProvider>
</>
);
};

View File

@ -0,0 +1,52 @@
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
export const RecordIndexLoadBaseOnContextStoreEffect = () => {
const { loadRecordIndexStates } = useLoadRecordIndexStates();
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
const [loadedViewId, setLoadedViewId] = useState<string | undefined>(
undefined,
);
const view = useRecoilValue(
prefetchViewFromViewIdFamilySelector({
viewId: contextStoreCurrentViewId ?? '',
}),
);
const objectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemComponentState,
);
useEffect(() => {
if (loadedViewId === contextStoreCurrentViewId) {
return;
}
if (!isDefined(objectMetadataItem)) {
return;
}
if (isDefined(view)) {
loadRecordIndexStates(view, objectMetadataItem);
setLoadedViewId(contextStoreCurrentViewId);
}
}, [
contextStoreCurrentViewId,
loadRecordIndexStates,
loadedViewId,
objectMetadataItem,
view,
]);
return <></>;
};

View File

@ -1,7 +1,7 @@
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { getAppPath } from '~/utils/navigation/getAppPath'; import { getAppPath } from '~/utils/navigation/getAppPath';
export const useHandleIndexIdentifierClick = ({ export const useHandleIndexIdentifierClick = ({
@ -12,7 +12,7 @@ export const useHandleIndexIdentifierClick = ({
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
}) => { }) => {
const currentViewId = useRecoilComponentValueV2( const currentViewId = useRecoilComponentValueV2(
currentViewIdComponentState, contextStoreCurrentViewIdComponentState,
recordIndexId, recordIndexId,
); );

View File

@ -1,29 +1,22 @@
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords'; import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache'; import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { ViewGroup } from '@/views/types/ViewGroup'; import { ViewGroup } from '@/views/types/ViewGroup';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
type UseHandleRecordGroupFieldParams = { export const useHandleRecordGroupField = () => {
viewBarComponentId: string;
};
export const useHandleRecordGroupField = ({
viewBarComponentId,
}: UseHandleRecordGroupFieldParams) => {
const { createViewGroupRecords, deleteViewGroupRecords } = const { createViewGroupRecords, deleteViewGroupRecords } =
usePersistViewGroupRecords(); usePersistViewGroupRecords();
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2( const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
currentViewIdComponentState, contextStoreCurrentViewIdComponentState,
viewBarComponentId,
); );
const { getViewFromCache } = useGetViewFromCache(); const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
const handleRecordGroupFieldChange = useRecoilCallback( const handleRecordGroupFieldChange = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
@ -36,7 +29,7 @@ export const useHandleRecordGroupField = ({
return; return;
} }
const view = await getViewFromCache(currentViewId); const view = await getViewFromPrefetchState(currentViewId);
if (isUndefinedOrNull(view)) { if (isUndefinedOrNull(view)) {
return; return;
@ -105,7 +98,7 @@ export const useHandleRecordGroupField = ({
createViewGroupRecords, createViewGroupRecords,
deleteViewGroupRecords, deleteViewGroupRecords,
currentViewIdCallbackState, currentViewIdCallbackState,
getViewFromCache, getViewFromPrefetchState,
], ],
); );
@ -120,7 +113,7 @@ export const useHandleRecordGroupField = ({
return; return;
} }
const view = await getViewFromCache(currentViewId); const view = await getViewFromPrefetchState(currentViewId);
if (isUndefinedOrNull(view)) { if (isUndefinedOrNull(view)) {
return; return;
@ -132,7 +125,11 @@ export const useHandleRecordGroupField = ({
await deleteViewGroupRecords(view.viewGroups); await deleteViewGroupRecords(view.viewGroups);
}, },
[deleteViewGroupRecords, currentViewIdCallbackState, getViewFromCache], [
deleteViewGroupRecords,
currentViewIdCallbackState,
getViewFromPrefetchState,
],
); );
return { handleRecordGroupFieldChange, resetRecordGroupField }; return { handleRecordGroupFieldChange, resetRecordGroupField };

View File

@ -6,13 +6,14 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { useSetRecordIdsForColumn } from '@/object-record/record-board/hooks/useSetRecordIdsForColumn'; import { useSetRecordIdsForColumn } from '@/object-record/record-board/hooks/useSetRecordIdsForColumn';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields'; import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState'; import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState'; import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
type UseLoadRecordIndexBoardProps = { type UseLoadRecordIndexBoardProps = {
@ -41,14 +42,16 @@ export const useLoadRecordIndexBoardColumn = ({
const recordIndexViewFilterGroups = useRecoilValue( const recordIndexViewFilterGroups = useRecoilValue(
recordIndexViewFilterGroupsState, recordIndexViewFilterGroupsState,
); );
const recordIndexFilters = useRecoilValue(recordIndexFiltersState); const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const recordIndexSorts = useRecoilValue(recordIndexSortsState); const recordIndexSorts = useRecoilValue(recordIndexSortsState);
const { filterValueDependencies } = useFilterValueDependencies(); const { filterValueDependencies } = useFilterValueDependencies();
const requestFilters = computeViewRecordGqlOperationFilter( const requestFilters = computeViewRecordGqlOperationFilter(
filterValueDependencies, filterValueDependencies,
recordIndexFilters, currentRecordFilters,
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
recordIndexViewFilterGroups, recordIndexViewFilterGroups,
); );

View File

@ -0,0 +1,281 @@
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexIsCompactModeActiveState } from '@/object-record/record-index/states/recordIndexIsCompactModeActiveState';
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState';
import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { View } from '@/views/types/View';
import { ViewField } from '@/views/types/ViewField';
import { ViewGroup } from '@/views/types/ViewGroup';
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewGroupsToRecordGroupDefinitions } from '@/views/utils/mapViewGroupsToRecordGroupDefinitions';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { useCallback } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useLoadRecordIndexStates = () => {
const setContextStoreTargetedRecordsRuleComponentState =
useSetRecoilComponentStateV2(contextStoreTargetedRecordsRuleComponentState);
const setRecordIndexViewFilterGroups = useSetRecoilState(
recordIndexViewFilterGroupsState,
);
const setRecordIndexFilters = useSetRecoilState(recordIndexFiltersState);
const setRecordIndexSorts = useSetRecoilState(recordIndexSortsState);
const setRecordIndexIsCompactModeActive = useSetRecoilState(
recordIndexIsCompactModeActiveState,
);
const setRecordIndexViewType = useSetRecoilState(recordIndexViewTypeState);
const setRecordIndexViewKanbanFieldMetadataIdState = useSetRecoilState(
recordIndexKanbanFieldMetadataIdState,
);
const setRecordIndexViewKanbanAggregateOperationState = useSetRecoilState(
recordIndexKanbanAggregateOperationState,
);
const setRecordGroup = useSetRecordGroup();
const { setTableColumns } = useSetTableColumns();
const onViewFieldsChange = useRecoilCallback(
({ set, snapshot }) =>
(
viewFields: ViewField[],
objectMetadataItem: ObjectMetadataItem,
recordIndexId: string,
) => {
const activeFieldMetadataItems = objectMetadataItem.fields.filter(
({ isActive, isSystem }) => isActive && !isSystem,
);
const filterableFieldMetadataItems = snapshot
.getLoadable(
availableFieldMetadataItemsForFilterFamilySelector({
objectMetadataItemId: objectMetadataItem.id,
}),
)
.getValue();
const sortDefinitions = formatFieldMetadataItemsAsSortDefinitions({
fields: activeFieldMetadataItems,
});
const columnDefinitions: ColumnDefinition<FieldMetadata>[] =
activeFieldMetadataItems
.map((field, index) =>
formatFieldMetadataItemAsColumnDefinition({
position: index,
field,
objectMetadataItem,
}),
)
.filter(filterAvailableTableColumns)
.map((column) => {
const existsInFilterDefinitions =
filterableFieldMetadataItems.some(
(fieldMetadataItem) =>
fieldMetadataItem.id === column.fieldMetadataId,
);
const existsInSortDefinitions = sortDefinitions.some(
(sort) => sort.fieldMetadataId === column.fieldMetadataId,
);
return {
...column,
isFilterable: existsInFilterDefinitions,
isSortable: existsInSortDefinitions,
};
});
const newFieldDefinitions = mapViewFieldsToColumnDefinitions({
viewFields,
columnDefinitions,
});
setTableColumns(newFieldDefinitions, recordIndexId);
const existingRecordIndexFieldDefinitions = snapshot
.getLoadable(recordIndexFieldDefinitionsState)
.getValue();
if (
!isDeeplyEqual(
existingRecordIndexFieldDefinitions,
newFieldDefinitions,
)
) {
set(recordIndexFieldDefinitionsState, newFieldDefinitions);
}
for (const viewField of viewFields) {
const viewFieldMetadataType = objectMetadataItem.fields?.find(
(field) => field.id === viewField.fieldMetadataId,
)?.type;
const aggregateOperationForViewField = snapshot
.getLoadable(
viewFieldAggregateOperationState({
viewFieldId: viewField.id,
}),
)
.getValue();
const convertedViewFieldAggregateOperation = isDefined(
viewField.aggregateOperation,
)
? convertAggregateOperationToExtendedAggregateOperation(
viewField.aggregateOperation,
viewFieldMetadataType,
)
: viewField.aggregateOperation;
if (
aggregateOperationForViewField !==
convertedViewFieldAggregateOperation
) {
set(
viewFieldAggregateOperationState({
viewFieldId: viewField.id,
}),
convertedViewFieldAggregateOperation,
);
}
}
},
[setTableColumns],
);
const onViewGroupsChange = useCallback(
(
viewGroups: ViewGroup[],
objectMetadataItem: ObjectMetadataItem,
recordIndexId: string,
) => {
const newGroupDefinitions = mapViewGroupsToRecordGroupDefinitions({
objectMetadataItem,
viewGroups,
});
setRecordGroup(newGroupDefinitions, recordIndexId);
},
[setRecordGroup],
);
const loadRecordIndexStates = useRecoilCallback(
({ snapshot, set }) =>
async (view: View, objectMetadataItem: ObjectMetadataItem) => {
const recordIndexId = `${objectMetadataItem.namePlural}-${view.id}`;
const filterableFieldMetadataItems = snapshot
.getLoadable(
availableFieldMetadataItemsForFilterFamilySelector({
objectMetadataItemId: objectMetadataItem.id,
}),
)
.getValue();
onViewFieldsChange(view.viewFields, objectMetadataItem, recordIndexId);
onViewGroupsChange(view.viewGroups, objectMetadataItem, recordIndexId);
set(
tableViewFilterGroupsComponentState.atomFamily({
instanceId: recordIndexId,
}),
view.viewFilterGroups ?? [],
);
set(
tableFiltersComponentState.atomFamily({
instanceId: recordIndexId,
}),
mapViewFiltersToFilters(
view.viewFilters,
filterableFieldMetadataItems,
),
);
setRecordIndexFilters(
mapViewFiltersToFilters(
view.viewFilters,
filterableFieldMetadataItems,
),
);
setRecordIndexViewFilterGroups(view.viewFilterGroups ?? []);
setContextStoreTargetedRecordsRuleComponentState((prev) => ({
...prev,
filters: mapViewFiltersToFilters(
view.viewFilters,
filterableFieldMetadataItems,
),
}));
const activeFieldMetadataItems = objectMetadataItem.fields.filter(
({ isActive, isSystem }) => isActive && !isSystem,
);
const sortDefinitions = formatFieldMetadataItemsAsSortDefinitions({
fields: activeFieldMetadataItems,
});
set(
tableSortsComponentState.atomFamily({
instanceId: recordIndexId,
}),
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexViewType(view.type);
setRecordIndexViewKanbanFieldMetadataIdState(
view.kanbanFieldMetadataId,
);
const kanbanAggregateOperationFieldMetadataType =
objectMetadataItem.fields?.find(
(field) =>
field.id === view.kanbanAggregateOperationFieldMetadataId,
)?.type;
setRecordIndexViewKanbanAggregateOperationState({
operation: isDefined(view.kanbanAggregateOperation)
? convertAggregateOperationToExtendedAggregateOperation(
view.kanbanAggregateOperation,
kanbanAggregateOperationFieldMetadataType,
)
: view.kanbanAggregateOperation,
fieldMetadataId: view.kanbanAggregateOperationFieldMetadataId,
});
setRecordIndexIsCompactModeActive(view.isCompact);
},
[
onViewFieldsChange,
onViewGroupsChange,
setContextStoreTargetedRecordsRuleComponentState,
setRecordIndexFilters,
setRecordIndexIsCompactModeActive,
setRecordIndexSorts,
setRecordIndexViewFilterGroups,
setRecordIndexViewKanbanAggregateOperationState,
setRecordIndexViewKanbanFieldMetadataIdState,
setRecordIndexViewType,
],
);
return {
loadRecordIndexStates,
};
};

View File

@ -3,9 +3,6 @@ import { ShowPageContainer } from '@/ui/layout/page/components/ShowPageContainer
import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord'; import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord';
import { ContextStoreCurrentViewTypeEffect } from '@/context-store/components/ContextStoreCurrentViewTypeEffect';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { RecordShowContainerContextStoreObjectMetadataIdEffect } from '@/object-record/record-show/components/RecordShowContainerContextStoreObjectMetadataIdEffect';
import { RecordShowContainerContextStoreTargetedRecordsEffect } from '@/object-record/record-show/components/RecordShowContainerContextStoreTargetedRecordsEffect'; import { RecordShowContainerContextStoreTargetedRecordsEffect } from '@/object-record/record-show/components/RecordShowContainerContextStoreTargetedRecordsEffect';
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData'; import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
import { useRecordShowContainerTabs } from '@/object-record/record-show/hooks/useRecordShowContainerTabs'; import { useRecordShowContainerTabs } from '@/object-record/record-show/hooks/useRecordShowContainerTabs';
@ -45,16 +42,9 @@ export const RecordShowContainer = ({
return ( return (
<> <>
<RecordShowContainerContextStoreObjectMetadataIdEffect
recordId={objectRecordId}
objectNameSingular={objectNameSingular}
/>
<RecordShowContainerContextStoreTargetedRecordsEffect <RecordShowContainerContextStoreTargetedRecordsEffect
recordId={objectRecordId} recordId={objectRecordId}
/> />
<ContextStoreCurrentViewTypeEffect
viewType={ContextStoreViewType.ShowPage}
/>
{recordFromStore && recordFromStore.deletedAt && ( {recordFromStore && recordFromStore.deletedAt && (
<InformationBannerDeletedRecord <InformationBannerDeletedRecord
recordId={objectRecordId} recordId={objectRecordId}

View File

@ -1,30 +0,0 @@
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useEffect } from 'react';
export const RecordShowContainerContextStoreObjectMetadataIdEffect = ({
recordId,
objectNameSingular,
}: {
recordId: string;
objectNameSingular: string;
}) => {
const setContextStoreCurrentObjectMetadataId = useSetRecoilComponentStateV2(
contextStoreCurrentObjectMetadataIdComponentState,
);
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: objectNameSingular,
});
useEffect(() => {
setContextStoreCurrentObjectMetadataId(objectMetadataItem?.id);
return () => {
setContextStoreCurrentObjectMetadataId(null);
};
}, [recordId, setContextStoreCurrentObjectMetadataId, objectMetadataItem.id]);
return null;
};

View File

@ -39,7 +39,6 @@ export const useRecordShowPagePagination = (
const { filter, orderBy } = const { filter, orderBy } =
useQueryVariablesFromActiveFieldsOfViewOrDefaultView({ useQueryVariablesFromActiveFieldsOfViewOrDefaultView({
objectMetadataItem, objectMetadataItem,
viewId: viewIdQueryParam,
}); });
const { loading: loadingCursor, pageInfo: currentRecordsPageInfo } = const { loading: loadingCursor, pageInfo: currentRecordsPageInfo } =

View File

@ -23,14 +23,12 @@ import { useRecordPicker } from '@/object-record/relation-picker/hooks/useRecord
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { View } from '@/views/types/View';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { RelationDefinitionType } from '~/generated-metadata/graphql'; import { RelationDefinitionType } from '~/generated-metadata/graphql';
@ -123,12 +121,10 @@ export const RecordDetailRelationSection = ({
scopeId: dropdownId, scopeId: dropdownId,
}); });
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); const indexViewId = useRecoilValue(
prefetchIndexViewIdFromObjectMetadataItemFamilySelector({
const indexView = views.find( objectMetadataItemId: relationObjectMetadataItem.id,
(view) => }),
view.key === 'INDEX' &&
view.objectMetadataId === relationObjectMetadataItem.id,
); );
const filterQueryParams = { const filterQueryParams = {
@ -139,7 +135,7 @@ export const RecordDetailRelationSection = ({
}, },
}, },
}, },
view: indexView?.id, view: indexViewId,
}; };
const filterLinkHref = getAppPath( const filterLinkHref = getAppPath(

View File

@ -75,7 +75,7 @@ export const RecordTableWithWrappers = ({
ActionBarHotkeyScope.ActionBar, ActionBarHotkeyScope.ActionBar,
); );
const { saveViewFields } = useSaveCurrentViewFields(viewBarId); const { saveViewFields } = useSaveCurrentViewFields();
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular }); const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });

View File

@ -22,11 +22,9 @@ import { onColumnsChangeComponentState } from '@/object-record/record-table/stat
import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState'; import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState';
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState'; import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState'; import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState'; import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState'; import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState'; import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState';
import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@ -53,11 +51,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
recordTableId, recordTableId,
); );
const tableColumnsState = useRecoilComponentCallbackStateV2(
tableColumnsComponentState,
recordTableId,
);
const setAvailableTableColumns = useRecoilCallback( const setAvailableTableColumns = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
(columns: ColumnDefinition<FieldMetadata>[]) => { (columns: ColumnDefinition<FieldMetadata>[]) => {
@ -74,29 +67,11 @@ export const useRecordTable = (props?: useRecordTableProps) => {
[availableTableColumnsState], [availableTableColumnsState],
); );
const setTableColumns = useRecoilCallback(
({ snapshot, set }) =>
(columns: ColumnDefinition<FieldMetadata>[]) => {
const tableColumns = getSnapshotValue(snapshot, tableColumnsState);
if (isDeeplyEqual(tableColumns, columns)) {
return;
}
set(tableColumnsState, columns);
},
[tableColumnsState],
);
const setOnEntityCountChange = useSetRecoilComponentStateV2( const setOnEntityCountChange = useSetRecoilComponentStateV2(
onEntityCountChangeComponentState, onEntityCountChangeComponentState,
recordTableId, recordTableId,
); );
const setTableViewFilterGroups = useSetRecoilComponentStateV2(
tableViewFilterGroupsComponentState,
recordTableId,
);
const setTableFilters = useSetRecoilComponentStateV2( const setTableFilters = useSetRecoilComponentStateV2(
tableFiltersComponentState, tableFiltersComponentState,
recordTableId, recordTableId,
@ -255,12 +230,10 @@ export const useRecordTable = (props?: useRecordTableProps) => {
return { return {
onColumnsChange, onColumnsChange,
setAvailableTableColumns, setAvailableTableColumns,
setTableViewFilterGroups,
setTableFilters, setTableFilters,
setTableSorts, setTableSorts,
setOnEntityCountChange, setOnEntityCountChange,
setRecordTableData, setRecordTableData,
setTableColumns,
leaveTableFocus, leaveTableFocus,
setRowSelected, setRowSelected,
resetTableRowSelection, resetTableRowSelection,

View File

@ -0,0 +1,33 @@
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useRecoilCallback } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useSetTableColumns = () => {
const setTableColumns = useRecoilCallback(
({ snapshot, set }) =>
(columns: ColumnDefinition<FieldMetadata>[], recordTableId: string) => {
const tableColumns = getSnapshotValue(
snapshot,
tableColumnsComponentState.atomFamily({
instanceId: recordTableId,
}),
);
if (isDeeplyEqual(tableColumns, columns)) {
return;
}
set(
tableColumnsComponentState.atomFamily({
instanceId: recordTableId,
}),
columns,
);
},
[],
);
return { setTableColumns };
};

View File

@ -4,9 +4,12 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns'; import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState'; import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ColumnDefinition } from '../types/ColumnDefinition'; import { ColumnDefinition } from '../types/ColumnDefinition';
@ -15,10 +18,12 @@ type useRecordTableProps = {
}; };
export const useTableColumns = (props?: useRecordTableProps) => { export const useTableColumns = (props?: useRecordTableProps) => {
const { onColumnsChange, setTableColumns } = useRecordTable({ const { onColumnsChange } = useRecordTable({
recordTableId: props?.recordTableId, recordTableId: props?.recordTableId,
}); });
const { setTableColumns } = useSetTableColumns();
const availableTableColumns = useRecoilComponentValueV2( const availableTableColumns = useRecoilComponentValueV2(
availableTableColumnsComponentState, availableTableColumnsComponentState,
props?.recordTableId, props?.recordTableId,
@ -35,13 +40,18 @@ export const useTableColumns = (props?: useRecordTableProps) => {
const { handleColumnMove } = useMoveViewColumns(); const { handleColumnMove } = useMoveViewColumns();
const instanceId = useAvailableComponentInstanceIdOrThrow(
RecordTableComponentInstanceContext,
props?.recordTableId,
);
const handleColumnsChange = useCallback( const handleColumnsChange = useCallback(
async (columns: ColumnDefinition<FieldMetadata>[]) => { async (columns: ColumnDefinition<FieldMetadata>[]) => {
setTableColumns(columns); setTableColumns(columns, instanceId);
await onColumnsChange?.(columns); await onColumnsChange?.(columns);
}, },
[onColumnsChange, setTableColumns], [setTableColumns, instanceId, onColumnsChange],
); );
const handleColumnVisibilityChange = useCallback( const handleColumnVisibilityChange = useCallback(

View File

@ -55,6 +55,8 @@ export const RecordTableNoRecordGroupBodyEffect = () => {
lastShowPageRecordIdState, lastShowPageRecordIdState,
); );
const [hasInitialized, setHasInitialized] = useState(false);
const { scrollToPosition } = useScrollToPosition(); const { scrollToPosition } = useScrollToPosition();
useEffect(() => { useEffect(() => {
@ -141,8 +143,11 @@ export const RecordTableNoRecordGroupBodyEffect = () => {
return; return;
} }
findManyRecords(); if (!hasInitialized) {
}, [currentWorkspaceMember, findManyRecords]); findManyRecords();
setHasInitialized(true);
}
}, [currentWorkspaceMember, findManyRecords, hasInitialized]);
return <></>; return <></>;
}; };

View File

@ -1,10 +1,12 @@
import { PrefetchRunQueriesEffect } from '@/prefetch/components/PrefetchRunQueriesEffect'; import { PrefetchRunFavoriteQueriesEffect } from '@/prefetch/components/PrefetchRunFavoriteQueriesEffect';
import { PrefetchRunViewQueryEffect } from '@/prefetch/components/PrefetchRunViewQueryEffect';
import React from 'react'; import React from 'react';
export const PrefetchDataProvider = ({ children }: React.PropsWithChildren) => { export const PrefetchDataProvider = ({ children }: React.PropsWithChildren) => {
return ( return (
<> <>
<PrefetchRunQueriesEffect /> <PrefetchRunFavoriteQueriesEffect />
<PrefetchRunViewQueryEffect />
{children} {children}
</> </>
); );

View File

@ -0,0 +1,110 @@
import { useEffect } from 'react';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { Favorite } from '@/favorites/types/Favorite';
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { findAllFavoritesFolderOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesFolderOperationSignatureFactory';
import { findAllFavoritesOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesOperationSignatureFactory';
import { prefetchFavoriteFoldersState } from '@/prefetch/states/prefetchFavoriteFoldersState';
import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState';
import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
import { isDefined } from 'twenty-shared';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const PrefetchRunFavoriteQueriesEffect = () => {
const currentUser = useRecoilValue(currentUserState);
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
const { objectMetadataItems } = useObjectMetadataItems();
const setIsPrefetchFavoritesLoaded = useSetRecoilState(
prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites),
);
const setIsPrefetchFavoritesFoldersLoaded = useSetRecoilState(
prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders),
);
const findAllFavoritesOperationSignature =
findAllFavoritesOperationSignatureFactory({
objectMetadataItem: objectMetadataItems.find(
(item) => item.nameSingular === CoreObjectNameSingular.Favorite,
),
});
const findAllFavoriteFoldersOperationSignature =
findAllFavoritesFolderOperationSignatureFactory({
objectMetadataItem: objectMetadataItems.find(
(item) => item.nameSingular === CoreObjectNameSingular.FavoriteFolder,
),
});
const { records: favorites } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.Favorite,
filter: findAllFavoritesOperationSignature.variables.filter,
recordGqlFields: findAllFavoritesOperationSignature.fields,
skip: !currentUser || isWorkspaceSuspended,
});
const { records: favoriteFolders } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
filter: findAllFavoriteFoldersOperationSignature.variables.filter,
recordGqlFields: findAllFavoriteFoldersOperationSignature.fields,
skip: !currentUser || isWorkspaceSuspended,
});
const setPrefetchFavoritesState = useRecoilCallback(
({ set, snapshot }) =>
(favorites: Favorite[]) => {
const existingFavorites = snapshot
.getLoadable(prefetchFavoritesState)
.getValue();
if (!isDeeplyEqual(existingFavorites, favorites)) {
set(prefetchFavoritesState, favorites);
}
},
[],
);
const setPrefetchFavoriteFoldersState = useRecoilCallback(
({ set, snapshot }) =>
(favoriteFolders: FavoriteFolder[]) => {
const existingFavoriteFolders = snapshot
.getLoadable(prefetchFavoriteFoldersState)
.getValue();
if (!isDeeplyEqual(existingFavoriteFolders, favoriteFolders)) {
set(prefetchFavoriteFoldersState, favoriteFolders);
}
},
[],
);
useEffect(() => {
if (isDefined(favorites)) {
setPrefetchFavoritesState(favorites as Favorite[]);
setIsPrefetchFavoritesLoaded(true);
}
}, [favorites, setPrefetchFavoritesState, setIsPrefetchFavoritesLoaded]);
useEffect(() => {
if (isDefined(favoriteFolders)) {
setPrefetchFavoriteFoldersState(favoriteFolders as FavoriteFolder[]);
setIsPrefetchFavoritesFoldersLoaded(true);
}
}, [
favoriteFolders,
setPrefetchFavoriteFoldersState,
setIsPrefetchFavoritesFoldersLoaded,
]);
return <></>;
};

View File

@ -1,70 +0,0 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { Favorite } from '@/favorites/types/Favorite';
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
import { isDefined } from 'twenty-shared';
export const PrefetchRunQueriesEffect = () => {
const currentUser = useRecoilValue(currentUserState);
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
const { upsertRecordsInCache: upsertViewsInCache } =
useUpsertRecordsInCacheForPrefetchKey<View>({
prefetchKey: PrefetchKey.AllViews,
});
const { upsertRecordsInCache: upsertFavoritesInCache } =
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
prefetchKey: PrefetchKey.AllFavorites,
});
const { upsertRecordsInCache: upsertFavoritesFoldersInCache } =
useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
prefetchKey: PrefetchKey.AllFavoritesFolders,
});
const { objectMetadataItems } = useObjectMetadataItems();
const operationSignatures = Object.values(PREFETCH_CONFIG)
.map(({ objectNameSingular, operationSignatureFactory }) => {
const objectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === objectNameSingular,
);
return operationSignatureFactory({ objectMetadataItem });
});
const { result } = useCombinedFindManyRecords({
operationSignatures,
skip: !currentUser || isWorkspaceSuspended,
});
useEffect(() => {
if (isDefined(result.views)) {
upsertViewsInCache(result.views as View[]);
}
if (isDefined(result.favorites)) {
upsertFavoritesInCache(result.favorites as Favorite[]);
}
if (isDefined(result.favoriteFolders)) {
upsertFavoritesFoldersInCache(result.favoriteFolders as FavoriteFolder[]);
}
}, [
result,
upsertViewsInCache,
upsertFavoritesInCache,
upsertFavoritesFoldersInCache,
]);
return <></>;
};

View File

@ -0,0 +1,59 @@
import { useEffect } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { findAllViewsOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllViewsOperationSignatureFactory';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
import { View } from '@/views/types/View';
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
import { isDefined } from 'twenty-shared';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const PrefetchRunViewQueryEffect = () => {
const currentUser = useRecoilValue(currentUserState);
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
const { objectMetadataItems } = useObjectMetadataItems();
const findAllViewsOperationSignature = findAllViewsOperationSignatureFactory({
objectMetadataItem: objectMetadataItems.find(
(item) => item.nameSingular === CoreObjectNameSingular.View,
),
});
const { records } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.View,
filter: findAllViewsOperationSignature.variables.filter,
recordGqlFields: findAllViewsOperationSignature.fields,
skip: !currentUser || isWorkspaceSuspended,
});
const setPrefetchViewsState = useRecoilCallback(
({ set, snapshot }) =>
(views: View[]) => {
const existingViews = snapshot
.getLoadable(prefetchViewsState)
.getValue();
if (!isDeeplyEqual(existingViews, views)) {
set(prefetchViewsState, views);
}
},
[],
);
const isPersistingViewFields = useRecoilValue(isPersistingViewFieldsState);
useEffect(() => {
if (isDefined(records) && !isPersistingViewFields) {
setPrefetchViewsState(records as View[]);
}
}, [isPersistingViewFields, records, setPrefetchViewsState]);
return <></>;
};

View File

@ -2,7 +2,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory'; import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
import { findAllFavoritesFolderOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesFolderOperationSignatureFactory'; import { findAllFavoritesFolderOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesFolderOperationSignatureFactory';
import { findAllFavoritesOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesOperationSignatureFactory'; import { findAllFavoritesOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesOperationSignatureFactory';
import { findAllViewsOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllViewsOperationSignatureFactory';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
export const PREFETCH_CONFIG: Record< export const PREFETCH_CONFIG: Record<
@ -12,10 +11,6 @@ export const PREFETCH_CONFIG: Record<
operationSignatureFactory: RecordGqlOperationSignatureFactory; operationSignatureFactory: RecordGqlOperationSignatureFactory;
} }
> = { > = {
ALL_VIEWS: {
objectNameSingular: CoreObjectNameSingular.View,
operationSignatureFactory: findAllViewsOperationSignatureFactory,
},
ALL_FAVORITES: { ALL_FAVORITES: {
objectNameSingular: CoreObjectNameSingular.Favorite, objectNameSingular: CoreObjectNameSingular.Favorite,
operationSignatureFactory: findAllFavoritesOperationSignatureFactory, operationSignatureFactory: findAllFavoritesOperationSignatureFactory,

View File

@ -1,51 +0,0 @@
import { useSetRecoilState } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
export type UsePrefetchRunQuery = {
prefetchKey: PrefetchKey;
};
export const useUpsertRecordsInCacheForPrefetchKey = <T extends ObjectRecord>({
prefetchKey,
}: UsePrefetchRunQuery) => {
const setPrefetchDataIsLoaded = useSetRecoilState(
prefetchIsLoadedFamilyState(prefetchKey),
);
const { operationSignatureFactory, objectNameSingular } =
PREFETCH_CONFIG[prefetchKey];
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const operationSignature = operationSignatureFactory({ objectMetadataItem });
const { upsertFindManyRecordsQueryInCache } =
useUpsertFindManyRecordsQueryInCache({
objectMetadataItem: objectMetadataItem,
});
const upsertRecordsInCache = (records: T[]) => {
setPrefetchDataIsLoaded(false);
upsertFindManyRecordsQueryInCache({
queryVariables: operationSignature.variables,
recordGqlFields: operationSignature.fields,
objectRecordsToOverwrite: records,
computeReferences: false,
});
setPrefetchDataIsLoaded(true);
};
return {
objectMetadataItem,
upsertRecordsInCache,
};
};

View File

@ -9,17 +9,12 @@ export const useIsPrefetchLoading = () => {
prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders), prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders),
); );
const areViewsPrefetched = useRecoilValue(
prefetchIsLoadedFamilyState(PrefetchKey.AllViews),
);
const areFavoritesPrefetched = useRecoilValue( const areFavoritesPrefetched = useRecoilValue(
prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites), prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites),
); );
return ( return (
!isWorkspaceSuspended && !isWorkspaceSuspended &&
(!areViewsPrefetched || (!areFavoritesPrefetched || !isFavoriteFoldersPrefetched)
!areFavoritesPrefetched ||
!isFavoriteFoldersPrefetched)
); );
}; };

View File

@ -1,30 +0,0 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
export const useLazyPrefetchedData = <T extends ObjectRecord>(
prefetchKey: PrefetchKey,
filter?: RecordGqlOperationFilter,
) => {
const { operationSignatureFactory, objectNameSingular } =
PREFETCH_CONFIG[prefetchKey];
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const recordGqlFields =
operationSignatureFactory({ objectMetadataItem }).fields ?? filter;
const { records, findManyRecords } = useLazyFindManyRecords<T>({
objectNameSingular: objectNameSingular,
recordGqlFields,
});
return {
findManyRecords,
records,
};
};

View File

@ -1,38 +0,0 @@
import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
export const usePrefetchedData = <T extends ObjectRecord>(
prefetchKey: PrefetchKey,
filter?: RecordGqlOperationFilter,
) => {
const isDataPrefetched = useRecoilValue(
prefetchIsLoadedFamilyState(prefetchKey),
);
const { operationSignatureFactory, objectNameSingular } =
PREFETCH_CONFIG[prefetchKey];
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const recordGqlFields =
operationSignatureFactory({ objectMetadataItem }).fields ?? filter;
const { records } = useFindManyRecords<T>({
skip: !isDataPrefetched,
objectNameSingular: objectNameSingular,
recordGqlFields,
});
return {
isDataPrefetched,
records,
};
};

View File

@ -0,0 +1,7 @@
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { createState } from 'twenty-ui';
export const prefetchFavoriteFoldersState = createState<FavoriteFolder[]>({
key: 'prefetchFavoriteFoldersState',
defaultValue: [],
});

View File

@ -0,0 +1,7 @@
import { Favorite } from '@/favorites/types/Favorite';
import { createState } from 'twenty-ui';
export const prefetchFavoritesState = createState<Favorite[]>({
key: 'prefetchFavoritesState',
defaultValue: [],
});

View File

@ -0,0 +1,7 @@
import { View } from '@/views/types/View';
import { createState } from 'twenty-ui';
export const prefetchViewsState = createState<View[]>({
key: 'prefetchViewsState',
defaultValue: [],
});

View File

@ -0,0 +1,18 @@
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { ViewKey } from '@/views/types/ViewKey';
import { selectorFamily } from 'recoil';
export const prefetchIndexViewIdFromObjectMetadataItemFamilySelector =
selectorFamily<string | undefined, { objectMetadataItemId: string }>({
key: 'prefetchIndexViewIdFromObjectMetadataItemFamilySelector',
get:
({ objectMetadataItemId }) =>
({ get }) => {
const views = get(prefetchViewsState);
return views?.find(
(view) =>
view.objectMetadataId === objectMetadataItemId &&
view.key === ViewKey.Index,
)?.id;
},
});

View File

@ -0,0 +1,16 @@
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { View } from '@/views/types/View';
import { selectorFamily } from 'recoil';
export const prefetchViewFromViewIdFamilySelector = selectorFamily<
View | undefined,
{ viewId: string }
>({
key: 'prefetchViewFromViewIdFamilySelector',
get:
({ viewId }) =>
({ get }) => {
const views = get(prefetchViewsState);
return views?.find((view) => view.id === viewId);
},
});

View File

@ -0,0 +1,15 @@
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { selectorFamily } from 'recoil';
export const prefetchViewIdsFromObjectMetadataItemFamilySelector =
selectorFamily<string[], { objectMetadataItemId: string }>({
key: 'prefetchViewIdsFromObjectMetadataItemFamilySelector',
get:
({ objectMetadataItemId }) =>
({ get }) => {
const views = get(prefetchViewsState);
return views
.filter((view) => view.objectMetadataId === objectMetadataItemId)
.map((view) => view.id);
},
});

View File

@ -0,0 +1,10 @@
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { selector } from 'recoil';
export const prefetchViewLengthSelector = selector<number>({
key: 'prefetchViewLengthSelector',
get: ({ get }) => {
const views = get(prefetchViewsState);
return views?.length ?? 0;
},
});

View File

@ -0,0 +1,18 @@
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { View } from '@/views/types/View';
import { selectorFamily } from 'recoil';
export const prefetchViewsFromObjectMetadataItemFamilySelector = selectorFamily<
View[],
{ objectMetadataItemId: string }
>({
key: 'prefetchViewsFromObjectMetadataItemFamilySelector',
get:
({ objectMetadataItemId }) =>
({ get }) => {
const views = get(prefetchViewsState);
return views.filter(
(view) => view.objectMetadataId === objectMetadataItemId,
);
},
});

View File

@ -1,5 +1,4 @@
export enum PrefetchKey { export enum PrefetchKey {
AllViews = 'ALL_VIEWS',
AllFavorites = 'ALL_FAVORITES', AllFavorites = 'ALL_FAVORITES',
AllFavoritesFolders = 'ALL_FAVORITES_FOLDERS', AllFavoritesFolders = 'ALL_FAVORITES_FOLDERS',
} }

View File

@ -6,8 +6,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { useDeleteRecordFromCache } from '@/object-record/cache/hooks/useDeleteRecordFromCache'; import { useDeleteRecordFromCache } from '@/object-record/cache/hooks/useDeleteRecordFromCache';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown'; import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown';
import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown'; import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown';
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState'; import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
@ -16,11 +15,10 @@ import { SettingsPath } from '@/types/SettingsPath';
import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow'; import { TableRow } from '@/ui/layout/table/components/TableRow';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { View } from '@/views/types/View';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
import { import {
IconMinus, IconMinus,
@ -121,8 +119,7 @@ export const SettingsObjectFieldItemTableRow = ({
deleteMetadataField, deleteMetadataField,
} = useFieldMetadataItem(); } = useFieldMetadataItem();
const { records: allViews } = usePrefetchedData<View>(PrefetchKey.AllViews); const prefetchViews = useRecoilValue(prefetchViewsState);
const deleteViewFromCache = useDeleteRecordFromCache({ const deleteViewFromCache = useDeleteRecordFromCache({
objectNameSingular: CoreObjectNameSingular.View, objectNameSingular: CoreObjectNameSingular.View,
}); });
@ -135,7 +132,7 @@ export const SettingsObjectFieldItemTableRow = ({
objectMetadataItem.id, objectMetadataItem.id,
); );
const deletedViewIds = allViews const deletedViewIds = prefetchViews
.map((view) => { .map((view) => {
if (view.kanbanFieldMetadataId === activeFieldMetadatItem.id) { if (view.kanbanFieldMetadataId === activeFieldMetadatItem.id) {
deleteViewFromCache(view); deleteViewFromCache(view);

View File

@ -1,14 +1,18 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers'; import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect'; import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewBar } from '@/views/components/ViewBar'; import { ViewBar } from '@/views/components/ViewBar';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
const StyledContainer = styled.div` const StyledContainer = styled.div`
display: flex; display: flex;
@ -23,9 +27,12 @@ export const SignInBackgroundMockContainer = () => {
const recordIndexId = 'sign-up-mock-record-table-id'; const recordIndexId = 'sign-up-mock-record-table-id';
const viewBarId = 'companies-mock'; const viewBarId = 'companies-mock';
const { objectMetadataItem } = useObjectMetadataItem({ const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
objectNameSingular,
}); const objectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemComponentState,
'main-context-store',
);
return ( return (
<StyledContainer> <StyledContainer>
@ -34,7 +41,7 @@ export const SignInBackgroundMockContainer = () => {
recordIndexId, recordIndexId,
objectNamePlural, objectNamePlural,
objectNameSingular, objectNameSingular,
objectMetadataItem, objectMetadataItem: objectMetadataItem ?? objectMetadataItems[0],
onIndexRecordsLoaded: () => {}, onIndexRecordsLoaded: () => {},
indexIdentifierUrl: () => '', indexIdentifierUrl: () => '',
}} }}
@ -47,28 +54,32 @@ export const SignInBackgroundMockContainer = () => {
> >
<ContextStoreComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ value={{
instanceId: recordIndexId, instanceId: 'main-context-store',
}} }}
> >
<SignInBackgroundMockContainerEffect
objectNamePlural={objectNamePlural}
recordTableId={recordIndexId}
viewId={viewBarId}
/>
<ActionMenuComponentInstanceContext.Provider <ActionMenuComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }} value={{ instanceId: recordIndexId }}
> >
<ViewBar {isDefined(objectMetadataItem) && (
viewBarId={viewBarId} <>
onCurrentViewChange={() => {}} <ViewBar
optionsDropdownButton={<></>} viewBarId={viewBarId}
/> optionsDropdownButton={<></>}
<SignInBackgroundMockContainerEffect />
objectNamePlural={objectNamePlural}
recordTableId={recordIndexId} <RecordTableWithWrappers
viewId={viewBarId} objectNameSingular={objectNameSingular}
/> recordTableId={recordIndexId}
<RecordTableWithWrappers viewBarId={viewBarId}
objectNameSingular={objectNameSingular} updateRecordMutation={() => {}}
recordTableId={recordIndexId} />
viewBarId={viewBarId} </>
updateRecordMutation={() => {}} )}
/>
</ActionMenuComponentInstanceContext.Provider> </ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider> </ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider> </RecordFiltersComponentInstanceContext.Provider>

View File

@ -1,12 +1,15 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useSetRecordIndexEntityCount } from '@/object-record/record-index/hooks/useSetRecordIndexEntityCount'; import { useSetRecordIndexEntityCount } from '@/object-record/record-index/hooks/useSetRecordIndexEntityCount';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
import { SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions'; import { SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions';
import { SIGN_IN_BACKGROUND_MOCK_SORT_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockSortDefinitions'; import { SIGN_IN_BACKGROUND_MOCK_SORT_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockSortDefinitions';
import { SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS } from '@/sign-in-background-mock/constants/SignInBackgroundMockViewFields'; import { SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS } from '@/sign-in-background-mock/constants/SignInBackgroundMockViewFields';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useInitViewBar } from '@/views/hooks/useInitViewBar'; import { useInitViewBar } from '@/views/hooks/useInitViewBar';
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions'; import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
@ -21,10 +24,16 @@ export const SignInBackgroundMockContainerEffect = ({
recordTableId, recordTableId,
viewId, viewId,
}: SignInBackgroundMockContainerEffectProps) => { }: SignInBackgroundMockContainerEffectProps) => {
const { setAvailableTableColumns, setOnEntityCountChange, setTableColumns } = const setContextStoreCurrentObjectMetadataItem = useSetRecoilComponentStateV2(
useRecordTable({ contextStoreCurrentObjectMetadataItemComponentState,
recordTableId, 'main-context-store',
}); );
const { setAvailableTableColumns, setOnEntityCountChange } = useRecordTable({
recordTableId,
});
const { setTableColumns } = useSetTableColumns();
const { objectNameSingular } = useObjectNameSingularFromPlural({ const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural, objectNamePlural,
@ -55,7 +64,10 @@ export const SignInBackgroundMockContainerEffect = ({
viewFields: SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS, viewFields: SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS,
columnDefinitions: SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS, columnDefinitions: SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS,
}), }),
recordTableId,
); );
setContextStoreCurrentObjectMetadataItem(objectMetadataItem);
}, [ }, [
setViewObjectMetadataId, setViewObjectMetadataId,
setAvailableSortDefinitions, setAvailableSortDefinitions,
@ -63,6 +75,8 @@ export const SignInBackgroundMockContainerEffect = ({
objectMetadataItem, objectMetadataItem,
setAvailableTableColumns, setAvailableTableColumns,
setTableColumns, setTableColumns,
recordTableId,
setContextStoreCurrentObjectMetadataItem,
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -1,8 +1,6 @@
import { styled } from '@linaria/react'; import { styled } from '@linaria/react';
import { IconCheck, IconX, THEME_COMMON } from 'twenty-ui'; import { IconCheck, IconX, THEME_COMMON } from 'twenty-ui';
import { isDefined } from 'twenty-shared';
const spacing = THEME_COMMON.spacingMultiplicator * 1; const spacing = THEME_COMMON.spacingMultiplicator * 1;
const iconSizeSm = THEME_COMMON.icon.size.sm; const iconSizeSm = THEME_COMMON.icon.size.sm;
@ -15,7 +13,7 @@ type BooleanDisplayProps = {
}; };
export const BooleanDisplay = ({ value }: BooleanDisplayProps) => { export const BooleanDisplay = ({ value }: BooleanDisplayProps) => {
if (!isDefined(value)) { if (value === null || value === undefined) {
return <></>; return <></>;
} }

View File

@ -1,18 +1,20 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2'; import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
import { useApplyViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyViewFiltersToCurrentRecordFilters'; import { useApplyViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyViewFiltersToCurrentRecordFilters';
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates'; import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState'; import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
export const QueryParamsFiltersEffect = () => { export const QueryParamsFiltersEffect = () => {
const { hasFiltersQueryParams, getFiltersFromQueryParams, viewIdQueryParam } = const { hasFiltersQueryParams, getFiltersFromQueryParams, viewIdQueryParam } =
useViewFromQueryParams(); useViewFromQueryParams();
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
const setUnsavedViewFilter = useSetRecoilComponentFamilyStateV2( const setUnsavedViewFilter = useSetRecoilComponentFamilyStateV2(
unsavedToUpsertViewFiltersComponentFamilyState, unsavedToUpsertViewFiltersComponentFamilyState,

View File

@ -1,35 +0,0 @@
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
// TODO: This whole code should be removed. currentViewId should be used directly to set the mainContextStore
// and viewbar / view tooling should be updated to use that state contextStore state directly.
export const QueryParamsViewIdEffect = () => {
const [currentViewId, setCurrentViewId] = useRecoilComponentStateV2(
currentViewIdComponentState,
);
const mainContextStoreComponentInstanceId = useRecoilValue(
mainContextStoreComponentInstanceIdState,
);
const contextStoreCurrentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
mainContextStoreComponentInstanceId,
);
useEffect(() => {
if (isDefined(contextStoreCurrentViewId)) {
if (currentViewId !== contextStoreCurrentViewId) {
setCurrentViewId(contextStoreCurrentViewId);
}
}
}, [contextStoreCurrentViewId, currentViewId, setCurrentViewId]);
return <></>;
};

View File

@ -7,6 +7,7 @@ import {
MenuItem, MenuItem,
} from 'twenty-ui'; } from 'twenty-ui';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@ -19,7 +20,6 @@ import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAr
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts'; import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts'; import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId'; import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode'; import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState'; import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
@ -44,7 +44,9 @@ export const UpdateViewButtonGroup = ({
const { setViewPickerMode } = useViewPickerMode(); const { setViewPickerMode } = useViewPickerMode();
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
const { closeDropdown: closeUpdateViewButtonDropdown } = useDropdown( const { closeDropdown: closeUpdateViewButtonDropdown } = useDropdown(
UPDATE_VIEW_BUTTON_DROPDOWN_ID, UPDATE_VIEW_BUTTON_DROPDOWN_ID,

View File

@ -7,13 +7,10 @@ import { ObjectSortDropdownButton } from '@/object-record/object-sort-dropdown/c
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { TopBar } from '@/ui/layout/top-bar/components/TopBar'; import { TopBar } from '@/ui/layout/top-bar/components/TopBar';
import { QueryParamsFiltersEffect } from '@/views/components/QueryParamsFiltersEffect'; import { QueryParamsFiltersEffect } from '@/views/components/QueryParamsFiltersEffect';
import { QueryParamsViewIdEffect } from '@/views/components/QueryParamsViewIdEffect';
import { ViewBarEffect } from '@/views/components/ViewBarEffect';
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect'; import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
import { ViewBarPageTitle } from '@/views/components/ViewBarPageTitle'; import { ViewBarPageTitle } from '@/views/components/ViewBarPageTitle';
import { ViewBarSkeletonLoader } from '@/views/components/ViewBarSkeletonLoader'; import { ViewBarSkeletonLoader } from '@/views/components/ViewBarSkeletonLoader';
import { ViewBarSortEffect } from '@/views/components/ViewBarSortEffect'; import { ViewBarSortEffect } from '@/views/components/ViewBarSortEffect';
import { GraphQLView } from '@/views/types/GraphQLView';
import { ViewPickerDropdown } from '@/views/view-picker/components/ViewPickerDropdown'; import { ViewPickerDropdown } from '@/views/view-picker/components/ViewPickerDropdown';
import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope'; import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
@ -22,7 +19,6 @@ import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types
import { VIEW_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ViewSortDropdownId'; import { VIEW_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ViewSortDropdownId';
import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext'; import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext';
import { ViewBarRecordFilterEffect } from '@/views/components/ViewBarRecordFilterEffect'; import { ViewBarRecordFilterEffect } from '@/views/components/ViewBarRecordFilterEffect';
import { ViewEventContext } from '@/views/events/contexts/ViewEventContext';
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup'; import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
import { ViewBarDetails } from './ViewBarDetails'; import { ViewBarDetails } from './ViewBarDetails';
@ -30,14 +26,12 @@ export type ViewBarProps = {
viewBarId: string; viewBarId: string;
className?: string; className?: string;
optionsDropdownButton: ReactNode; optionsDropdownButton: ReactNode;
onCurrentViewChange: (view: GraphQLView | undefined) => void | Promise<void>;
}; };
export const ViewBar = ({ export const ViewBar = ({
viewBarId, viewBarId,
className, className,
optionsDropdownButton, optionsDropdownButton,
onCurrentViewChange,
}: ViewBarProps) => { }: ViewBarProps) => {
const { objectNamePlural } = useParams(); const { objectNamePlural } = useParams();
@ -53,53 +47,49 @@ export const ViewBar = ({
<ObjectSortDropdownComponentInstanceContext.Provider <ObjectSortDropdownComponentInstanceContext.Provider
value={{ instanceId: VIEW_SORT_DROPDOWN_ID }} value={{ instanceId: VIEW_SORT_DROPDOWN_ID }}
> >
<ViewEventContext.Provider value={{ onCurrentViewChange }}> <ViewBarRecordFilterEffect />
<ViewBarRecordFilterEffect /> <ViewBarFilterEffect filterDropdownId={filterDropdownId} />
<ViewBarEffect viewBarId={viewBarId} /> <ViewBarSortEffect />
<ViewBarFilterEffect filterDropdownId={filterDropdownId} /> <QueryParamsFiltersEffect />
<ViewBarSortEffect />
<QueryParamsFiltersEffect />
<QueryParamsViewIdEffect />
<ViewBarPageTitle viewBarId={viewBarId} /> <ViewBarPageTitle viewBarId={viewBarId} />
<TopBar <TopBar
className={className} className={className}
leftComponent={ leftComponent={
loading ? <ViewBarSkeletonLoader /> : <ViewPickerDropdown /> loading ? <ViewBarSkeletonLoader /> : <ViewPickerDropdown />
} }
rightComponent={ rightComponent={
<> <>
<ObjectFilterDropdownButton <ObjectFilterDropdownButton
filterDropdownId={filterDropdownId}
hotkeyScope={{
scope: FiltersHotkeyScope.ObjectFilterDropdownButton,
}}
/>
<ObjectSortDropdownButton
hotkeyScope={{
scope: FiltersHotkeyScope.ObjectSortDropdownButton,
}}
/>
{optionsDropdownButton}
</>
}
bottomComponent={
<ViewBarDetails
filterDropdownId={filterDropdownId} filterDropdownId={filterDropdownId}
hasFilterButton hotkeyScope={{
viewBarId={viewBarId} scope: FiltersHotkeyScope.ObjectFilterDropdownButton,
objectNamePlural={objectNamePlural} }}
rightComponent={
<UpdateViewButtonGroup
hotkeyScope={{
scope: ViewsHotkeyScope.UpdateViewButtonDropdown,
}}
/>
}
/> />
} <ObjectSortDropdownButton
/> hotkeyScope={{
</ViewEventContext.Provider> scope: FiltersHotkeyScope.ObjectSortDropdownButton,
}}
/>
{optionsDropdownButton}
</>
}
bottomComponent={
<ViewBarDetails
filterDropdownId={filterDropdownId}
hasFilterButton
viewBarId={viewBarId}
objectNamePlural={objectNamePlural}
rightComponent={
<UpdateViewButtonGroup
hotkeyScope={{
scope: ViewsHotkeyScope.UpdateViewButtonDropdown,
}}
/>
}
/>
}
/>
</ObjectSortDropdownComponentInstanceContext.Provider> </ObjectSortDropdownComponentInstanceContext.Provider>
); );
}; };

View File

@ -1,56 +0,0 @@
import { isUndefined } from '@sniptt/guards';
import { useContext, useEffect, useState } from 'react';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewEventContext } from '@/views/events/contexts/ViewEventContext';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isPersistingViewFieldsComponentState } from '@/views/states/isPersistingViewFieldsComponentState';
import { View } from '@/views/types/View';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
type ViewBarEffectProps = {
viewBarId: string;
};
export const ViewBarEffect = ({ viewBarId }: ViewBarEffectProps) => {
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(viewBarId);
const { onCurrentViewChange } = useContext(ViewEventContext);
const [currentViewSnapshot, setCurrentViewSnapshot] = useState<
View | undefined
>(undefined);
const isPersistingViewFields = useRecoilComponentValueV2(
isPersistingViewFieldsComponentState,
viewBarId,
);
useEffect(() => {
if (
!isDeeplyEqual(
currentViewWithCombinedFiltersAndSorts,
currentViewSnapshot,
)
) {
if (isUndefined(currentViewWithCombinedFiltersAndSorts)) {
setCurrentViewSnapshot(currentViewWithCombinedFiltersAndSorts);
onCurrentViewChange?.(undefined);
return;
}
if (!isPersistingViewFields) {
setCurrentViewSnapshot(currentViewWithCombinedFiltersAndSorts);
onCurrentViewChange?.(currentViewWithCombinedFiltersAndSorts);
}
}
}, [
currentViewSnapshot,
currentViewWithCombinedFiltersAndSorts,
isPersistingViewFields,
onCurrentViewChange,
]);
return <></>;
};

View File

@ -1,38 +1,24 @@
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems'; import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2'; import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { hasInitializedCurrentRecordFiltersComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFiltersComponentFamilyState'; import { hasInitializedCurrentRecordFiltersComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFiltersComponentFamilyState';
import { View } from '@/views/types/View';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
export const ViewBarRecordFilterEffect = () => { export const ViewBarRecordFilterEffect = () => {
const { records: views, isDataPrefetched } = usePrefetchedData<View>(
PrefetchKey.AllViews,
);
const currentViewId = useRecoilComponentValueV2( const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState, contextStoreCurrentViewIdComponentState,
); );
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataItemComponentState,
);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const objectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id === contextStoreCurrentObjectMetadataId,
); );
const [ const [
@ -54,14 +40,21 @@ export const ViewBarRecordFilterEffect = () => {
); );
const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems( const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(
objectMetadataItem?.id, contextStoreCurrentObjectMetadataItem?.id,
);
const currentView = useRecoilValue(
prefetchViewFromViewIdFamilySelector({
viewId: currentViewId ?? '',
}),
); );
useEffect(() => { useEffect(() => {
if (isDataPrefetched && !hasInitializedCurrentRecordFilters) { if (isDefined(currentView) && !hasInitializedCurrentRecordFilters) {
const currentView = views.find((view) => view.id === currentViewId); if (
currentView.objectMetadataId !==
if (currentView?.objectMetadataId !== objectMetadataItem?.id) { contextStoreCurrentObjectMetadataItem?.id
) {
return; return;
} }
@ -76,15 +69,14 @@ export const ViewBarRecordFilterEffect = () => {
} }
} }
}, [ }, [
isDataPrefetched,
views,
currentViewId, currentViewId,
setCurrentRecordFilters, setCurrentRecordFilters,
filterableFieldMetadataItems, filterableFieldMetadataItems,
currentRecordFilters, currentRecordFilters,
hasInitializedCurrentRecordFilters, hasInitializedCurrentRecordFilters,
setHasInitializedCurrentRecordFilters, setHasInitializedCurrentRecordFilters,
objectMetadataItem?.id, contextStoreCurrentObjectMetadataItem?.id,
currentView,
]); ]);
return null; return null;

View File

@ -1,8 +0,0 @@
import { View } from '@/views/types/View';
import { createEventContext } from '~/utils/createEventContext';
type ViewEventContextType = {
onCurrentViewChange: (view: View | undefined) => void | Promise<void>;
};
export const ViewEventContext = createEventContext<ViewEventContextType>();

View File

@ -1,20 +1,22 @@
import { act, renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { View } from '@/views/types/View';
import { ViewFilter } from '@/views/types/ViewFilter'; import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { ViewType } from '@/views/types/ViewType';
import { act } from 'react';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '../useApplyCurrentViewFiltersToCurrentRecordFilters'; import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '../useApplyCurrentViewFiltersToCurrentRecordFilters';
jest.mock('@/prefetch/hooks/usePrefetchedData');
const mockObjectMetadataItemNameSingular = 'company'; const mockObjectMetadataItemNameSingular = 'company';
describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
@ -41,18 +43,26 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
positionInViewFilterGroup: 0, positionInViewFilterGroup: 0,
}; };
const mockView = { const mockView: View = {
id: 'view-1', id: 'view-1',
name: 'Test View', name: 'Test View',
objectMetadataId: mockObjectMetadataItem.id, objectMetadataId: mockObjectMetadataItem.id,
viewFilters: [mockViewFilter], viewFilters: [mockViewFilter],
type: ViewType.Table,
key: null,
isCompact: false,
viewFields: [],
viewGroups: [],
viewSorts: [],
kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
icon: '',
kanbanAggregateOperationFieldMetadataId: '',
position: 0,
__typename: 'View',
}; };
it('should apply filters from current view', () => { it('should apply filters from current view', () => {
(usePrefetchedData as jest.Mock).mockReturnValue({
records: [mockView],
});
const { result } = renderHook( const { result } = renderHook(
() => { () => {
const { applyCurrentViewFiltersToCurrentRecordFilters } = const { applyCurrentViewFiltersToCurrentRecordFilters } =
@ -75,11 +85,12 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
mockObjectMetadataItemNameSingular, mockObjectMetadataItemNameSingular,
onInitializeRecoilSnapshot: (snapshot) => { onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set( snapshot.set(
currentViewIdComponentState.atomFamily({ contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: 'instanceId', instanceId: 'instanceId',
}), }),
mockView.id, mockView.id,
); );
snapshot.set(prefetchViewsState, [mockView]);
}, },
}), }),
}, },
@ -105,10 +116,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
}); });
it('should not apply filters when current view is not found', () => { it('should not apply filters when current view is not found', () => {
(usePrefetchedData as jest.Mock).mockReturnValue({
records: [],
});
const { result } = renderHook( const { result } = renderHook(
() => { () => {
const { applyCurrentViewFiltersToCurrentRecordFilters } = const { applyCurrentViewFiltersToCurrentRecordFilters } =
@ -131,11 +138,12 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
mockObjectMetadataItemNameSingular, mockObjectMetadataItemNameSingular,
onInitializeRecoilSnapshot: (snapshot) => { onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set( snapshot.set(
currentViewIdComponentState.atomFamily({ contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: 'instanceId', instanceId: 'instanceId',
}), }),
mockView.id, mockView.id,
); );
snapshot.set(prefetchViewsState, []);
}, },
}), }),
}, },
@ -149,15 +157,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
}); });
it('should handle view with empty filters', () => { it('should handle view with empty filters', () => {
const viewWithNoFilters = {
...mockView,
viewFilters: [],
};
(usePrefetchedData as jest.Mock).mockReturnValue({
records: [viewWithNoFilters],
});
const { result } = renderHook( const { result } = renderHook(
() => { () => {
const { applyCurrentViewFiltersToCurrentRecordFilters } = const { applyCurrentViewFiltersToCurrentRecordFilters } =
@ -180,11 +179,14 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
mockObjectMetadataItemNameSingular, mockObjectMetadataItemNameSingular,
onInitializeRecoilSnapshot: (snapshot) => { onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set( snapshot.set(
currentViewIdComponentState.atomFamily({ contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: 'instanceId', instanceId: 'instanceId',
}), }),
mockView.id, mockView.id,
); );
snapshot.set(prefetchViewsState, [
{ ...mockView, viewFilters: [] },
]);
}, },
}), }),
}, },

View File

@ -1,19 +1,18 @@
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext'; import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { View } from '@/views/types/View';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => { export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); );
const setCurrentRecordFilters = useSetRecoilComponentStateV2( const setCurrentRecordFilters = useSetRecoilComponentStateV2(
currentRecordFiltersComponentState, currentRecordFiltersComponentState,
@ -22,18 +21,28 @@ export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => {
const { filterableFieldMetadataItems } = const { filterableFieldMetadataItems } =
useFilterableFieldMetadataItemsInRecordIndexContext(); useFilterableFieldMetadataItemsInRecordIndexContext();
const applyCurrentViewFiltersToCurrentRecordFilters = () => { const applyCurrentViewFiltersToCurrentRecordFilters = useRecoilCallback(
const currentView = views.find((view) => view.id === currentViewId); ({ snapshot }) =>
() => {
const currentView = snapshot
.getLoadable(
prefetchViewFromViewIdFamilySelector({
viewId: currentViewId ?? '',
}),
)
.getValue();
if (isDefined(currentView)) { if (isDefined(currentView)) {
setCurrentRecordFilters( setCurrentRecordFilters(
mapViewFiltersToFilters( mapViewFiltersToFilters(
currentView.viewFilters, currentView.viewFilters,
filterableFieldMetadataItems, filterableFieldMetadataItems,
), ),
); );
} }
}; },
[currentViewId, filterableFieldMetadataItems, setCurrentRecordFilters],
);
return { return {
applyCurrentViewFiltersToCurrentRecordFilters, applyCurrentViewFiltersToCurrentRecordFilters,

View File

@ -1,6 +1,9 @@
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords'; import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
@ -11,9 +14,7 @@ import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistView
import { useGetViewFilterGroupsCombined } from '@/views/hooks/useGetCombinedViewFilterGroups'; import { useGetViewFilterGroupsCombined } from '@/views/hooks/useGetCombinedViewFilterGroups';
import { useGetViewFiltersCombined } from '@/views/hooks/useGetCombinedViewFilters'; import { useGetViewFiltersCombined } from '@/views/hooks/useGetCombinedViewFilters';
import { useGetViewSortsCombined } from '@/views/hooks/useGetCombinedViewSorts'; import { useGetViewSortsCombined } from '@/views/hooks/useGetCombinedViewSorts';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache'; import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { isPersistingViewFieldsComponentState } from '@/views/states/isPersistingViewFieldsComponentState';
import { GraphQLView } from '@/views/types/GraphQLView'; import { GraphQLView } from '@/views/types/GraphQLView';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { ViewGroup } from '@/views/types/ViewGroup'; import { ViewGroup } from '@/views/types/ViewGroup';
@ -25,17 +26,10 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2( const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
currentViewIdComponentState, contextStoreCurrentViewIdComponentState,
viewBarComponentId, viewBarComponentId,
); );
const isPersistingViewFieldsCallbackState = useRecoilComponentCallbackStateV2(
isPersistingViewFieldsComponentState,
viewBarComponentId,
);
const { getViewFromCache } = useGetViewFromCache();
const { createOneRecord } = useCreateOneRecord<View>({ const { createOneRecord } = useCreateOneRecord<View>({
objectNameSingular: CoreObjectNameSingular.View, objectNameSingular: CoreObjectNameSingular.View,
}); });
@ -58,6 +52,11 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const { objectMetadataItem } = useRecordIndexContextOrThrow(); const { objectMetadataItem } = useRecordIndexContextOrThrow();
const { findManyRecords } = useLazyFindManyRecords({
objectNameSingular: CoreObjectNameSingular.View,
fetchPolicy: 'network-only',
});
const createViewFromCurrentView = useRecoilCallback( const createViewFromCurrentView = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
async ( async (
@ -84,14 +83,19 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
return; return;
} }
// Here we might instead want to get view from unsaved filters ? const sourceView = snapshot
const sourceView = await getViewFromCache(currentViewId); .getLoadable(
prefetchViewFromViewIdFamilySelector({
viewId: currentViewId,
}),
)
.getValue();
if (!isDefined(sourceView)) { if (!isDefined(sourceView)) {
return; return;
} }
set(isPersistingViewFieldsCallbackState, true); set(isPersistingViewFieldsState, true);
const newView = await createOneRecord({ const newView = await createOneRecord({
id: id ?? v4(), id: id ?? v4(),
@ -123,7 +127,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
} }
const viewGroupsToCreate = const viewGroupsToCreate =
objectMetadataItem?.fields objectMetadataItem.fields
?.find((field) => field.id === kanbanFieldMetadataId) ?.find((field) => field.id === kanbanFieldMetadataId)
?.options?.map( ?.options?.map(
(option, index) => (option, index) =>
@ -169,21 +173,21 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
); );
} }
set(isPersistingViewFieldsCallbackState, false); await findManyRecords();
set(isPersistingViewFieldsState, false);
}, },
[ [
objectMetadataItem, currentViewIdCallbackState,
createViewSortRecords,
createViewFilterRecords,
createOneRecord, createOneRecord,
createViewFieldRecords, createViewFieldRecords,
getViewSortsCombined, findManyRecords,
getViewFiltersCombined, objectMetadataItem.fields,
getViewFilterGroupsCombined,
currentViewIdCallbackState,
getViewFromCache,
isPersistingViewFieldsCallbackState,
createViewGroupRecords, createViewGroupRecords,
getViewFilterGroupsCombined,
getViewFiltersCombined,
getViewSortsCombined,
createViewSortRecords,
createViewFilterRecords,
createViewFilterGroupRecords, createViewFilterGroupRecords,
], ],
); );

View File

@ -1,9 +1,9 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache'; import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState'; import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState'; import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
@ -22,11 +22,11 @@ export const useDeleteCombinedViewFilters = (viewBarComponentId?: string) => {
); );
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2( const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
currentViewIdComponentState, contextStoreCurrentViewIdComponentState,
viewBarComponentId, viewBarComponentId,
); );
const { getViewFromCache } = useGetViewFromCache(); const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
const deleteCombinedViewFilter = useRecoilCallback( const deleteCombinedViewFilter = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
@ -50,7 +50,7 @@ export const useDeleteCombinedViewFilters = (viewBarComponentId?: string) => {
return; return;
} }
const currentView = await getViewFromCache(currentViewId); const currentView = await getViewFromPrefetchState(currentViewId);
if (!currentView) { if (!currentView) {
return; return;
@ -89,7 +89,7 @@ export const useDeleteCombinedViewFilters = (viewBarComponentId?: string) => {
}, },
[ [
currentViewIdCallbackState, currentViewIdCallbackState,
getViewFromCache, getViewFromPrefetchState,
unsavedToDeleteViewFilterIdsCallbackState, unsavedToDeleteViewFilterIdsCallbackState,
unsavedToUpsertViewFiltersCallbackState, unsavedToUpsertViewFiltersCallbackState,
], ],

View File

@ -1,17 +1,16 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache'; import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState'; import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState';
import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState'; import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
export const useDeleteCombinedViewSorts = (viewBarComponentId?: string) => { export const useDeleteCombinedViewSorts = (viewBarComponentId?: string) => {
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2( const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
currentViewIdComponentState, contextStoreCurrentViewIdComponentState,
viewBarComponentId,
); );
const unsavedToUpsertViewSortsCallbackState = const unsavedToUpsertViewSortsCallbackState =
@ -26,7 +25,7 @@ export const useDeleteCombinedViewSorts = (viewBarComponentId?: string) => {
viewBarComponentId, viewBarComponentId,
); );
const { getViewFromCache } = useGetViewFromCache(); const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
const deleteCombinedViewSort = useRecoilCallback( const deleteCombinedViewSort = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
@ -50,7 +49,7 @@ export const useDeleteCombinedViewSorts = (viewBarComponentId?: string) => {
unsavedToDeleteViewSortIdsCallbackState({ viewId: currentViewId }), unsavedToDeleteViewSortIdsCallbackState({ viewId: currentViewId }),
); );
const currentView = await getViewFromCache(currentViewId); const currentView = await getViewFromPrefetchState(currentViewId);
if (!currentView) { if (!currentView) {
return; return;
@ -88,7 +87,7 @@ export const useDeleteCombinedViewSorts = (viewBarComponentId?: string) => {
}, },
[ [
currentViewIdCallbackState, currentViewIdCallbackState,
getViewFromCache, getViewFromPrefetchState,
unsavedToDeleteViewSortIdsCallbackState, unsavedToDeleteViewSortIdsCallbackState,
unsavedToUpsertViewSortsCallbackState, unsavedToUpsertViewSortsCallbackState,
], ],

View File

@ -1,18 +1,14 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState'; import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState'; import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { View } from '@/views/types/View';
import { getCombinedViewFilterGroups } from '@/views/utils/getCombinedViewFilterGroups'; import { getCombinedViewFilterGroups } from '@/views/utils/getCombinedViewFilterGroups';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
export const useGetViewFilterGroupsCombined = (viewBarComponentId?: string) => { export const useGetViewFilterGroupsCombined = (viewBarComponentId?: string) => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const unsavedToUpsertViewFilterGroupsCallbackState = const unsavedToUpsertViewFilterGroupsCallbackState =
useRecoilComponentCallbackStateV2( useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFilterGroupsComponentFamilyState, unsavedToUpsertViewFilterGroupsComponentFamilyState,
@ -28,7 +24,13 @@ export const useGetViewFilterGroupsCombined = (viewBarComponentId?: string) => {
const getViewFilterGroupsCombined = useRecoilCallback( const getViewFilterGroupsCombined = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
(viewId: string) => { (viewId: string) => {
const view = views.find((view) => view.id === viewId); const view = snapshot
.getLoadable(
prefetchViewFromViewIdFamilySelector({
viewId,
}),
)
.getValue();
if (!isDefined(view)) { if (!isDefined(view)) {
throw new Error( throw new Error(
@ -55,7 +57,6 @@ export const useGetViewFilterGroupsCombined = (viewBarComponentId?: string) => {
return combinedViewFilterGroups; return combinedViewFilterGroups;
}, },
[ [
views,
unsavedToDeleteViewFilterGroupIdsCallbackState, unsavedToDeleteViewFilterGroupIdsCallbackState,
unsavedToUpsertViewFilterGroupsCallbackState, unsavedToUpsertViewFilterGroupsCallbackState,
], ],

View File

@ -1,18 +1,14 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState'; import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState'; import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
import { View } from '@/views/types/View';
import { getCombinedViewFilters } from '@/views/utils/getCombinedViewFilters'; import { getCombinedViewFilters } from '@/views/utils/getCombinedViewFilters';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
export const useGetViewFiltersCombined = (viewBarComponentId?: string) => { export const useGetViewFiltersCombined = (viewBarComponentId?: string) => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const unsavedToUpsertViewFiltersCallbackState = const unsavedToUpsertViewFiltersCallbackState =
useRecoilComponentCallbackStateV2( useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFiltersComponentFamilyState, unsavedToUpsertViewFiltersComponentFamilyState,
@ -28,7 +24,13 @@ export const useGetViewFiltersCombined = (viewBarComponentId?: string) => {
const getViewFiltersCombined = useRecoilCallback( const getViewFiltersCombined = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
(viewId: string) => { (viewId: string) => {
const view = views.find((view) => view.id === viewId); const view = snapshot
.getLoadable(
prefetchViewFromViewIdFamilySelector({
viewId,
}),
)
.getValue();
if (!isDefined(view)) { if (!isDefined(view)) {
throw new Error( throw new Error(
@ -55,7 +57,6 @@ export const useGetViewFiltersCombined = (viewBarComponentId?: string) => {
return combinedViewFilters; return combinedViewFilters;
}, },
[ [
views,
unsavedToDeleteViewFilterIdsCallbackState, unsavedToDeleteViewFilterIdsCallbackState,
unsavedToUpsertViewFiltersCallbackState, unsavedToUpsertViewFiltersCallbackState,
], ],

View File

@ -1,19 +1,15 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState'; import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState';
import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState'; import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState';
import { View } from '@/views/types/View';
import { getCombinedViewSorts } from '@/views/utils/getCombinedViewSorts'; import { getCombinedViewSorts } from '@/views/utils/getCombinedViewSorts';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
// TODO: fix naming // TODO: fix naming
export const useGetViewSortsCombined = (viewBarComponentId?: string) => { export const useGetViewSortsCombined = (viewBarComponentId?: string) => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const unsavedToUpsertViewSortsCallbackState = const unsavedToUpsertViewSortsCallbackState =
useRecoilComponentCallbackStateV2( useRecoilComponentCallbackStateV2(
unsavedToUpsertViewSortsComponentFamilyState, unsavedToUpsertViewSortsComponentFamilyState,
@ -29,7 +25,13 @@ export const useGetViewSortsCombined = (viewBarComponentId?: string) => {
const getViewSortsCombined = useRecoilCallback( const getViewSortsCombined = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
(viewId: string) => { (viewId: string) => {
const view = views.find((view) => view.id === viewId); const view = snapshot
.getLoadable(
prefetchViewFromViewIdFamilySelector({
viewId,
}),
)
.getValue();
if (!isDefined(view)) { if (!isDefined(view)) {
throw new Error( throw new Error(
@ -56,7 +58,6 @@ export const useGetViewSortsCombined = (viewBarComponentId?: string) => {
return combinedViewSorts; return combinedViewSorts;
}, },
[ [
views,
unsavedToDeleteViewSortIdsCallbackState, unsavedToDeleteViewSortIdsCallbackState,
unsavedToUpsertViewSortsCallbackState, unsavedToUpsertViewSortsCallbackState,
], ],

View File

@ -1,13 +1,15 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector';
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { prefetchViewsFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchViewsFromObjectMetadataItemFamilySelector';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { isCurrentViewKeyIndexComponentState } from '@/views/states/isCurrentViewIndexComponentState'; import { isCurrentViewKeyIndexComponentState } from '@/views/states/isCurrentViewIndexComponentState';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState'; import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState'; import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
@ -15,12 +17,10 @@ import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/u
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState'; import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState'; import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState'; import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState';
import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState';
import { View } from '@/views/types/View';
import { getCombinedViewFilterGroups } from '@/views/utils/getCombinedViewFilterGroups'; import { getCombinedViewFilterGroups } from '@/views/utils/getCombinedViewFilterGroups';
import { getCombinedViewFilters } from '@/views/utils/getCombinedViewFilters'; import { getCombinedViewFilters } from '@/views/utils/getCombinedViewFilters';
import { getCombinedViewSorts } from '@/views/utils/getCombinedViewSorts'; import { getCombinedViewSorts } from '@/views/utils/getCombinedViewSorts';
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
export const useGetCurrentView = (viewBarInstanceId?: string) => { export const useGetCurrentView = (viewBarInstanceId?: string) => {
@ -29,16 +29,28 @@ export const useGetCurrentView = (viewBarInstanceId?: string) => {
viewBarInstanceId, viewBarInstanceId,
); );
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const currentViewId = useRecoilComponentValueV2( const currentViewId = useRecoilComponentValueV2(
currentViewIdComponentState, contextStoreCurrentViewIdComponentState,
instanceId,
); );
const viewObjectMetadataId = useRecoilComponentValueV2( const indexViewId = useRecoilValue(
viewObjectMetadataIdComponentState, prefetchIndexViewIdFromObjectMetadataItemFamilySelector({
instanceId, objectMetadataItemId: objectMetadataItem.id,
}),
);
const currentViewFromViewId = useRecoilValue(
prefetchViewFromViewIdFamilySelector({
viewId: currentViewId ?? '',
}),
);
const indexView = useRecoilValue(
prefetchViewFromViewIdFamilySelector({
viewId: indexViewId ?? '',
}),
); );
const setIsCurrentViewKeyIndex = useSetRecoilComponentStateV2( const setIsCurrentViewKeyIndex = useSetRecoilComponentStateV2(
@ -46,25 +58,17 @@ export const useGetCurrentView = (viewBarInstanceId?: string) => {
instanceId, instanceId,
); );
const currentViewFromCurrentViewId = views.find(
(view) => view.id === currentViewId,
);
const indexView = views.find(
(view) =>
view.key === 'INDEX' && view.objectMetadataId === viewObjectMetadataId,
);
const currentView = currentViewId ? currentViewFromCurrentViewId : indexView;
const viewId = currentViewId ?? indexView?.id; const viewId = currentViewId ?? indexView?.id;
const currentView = currentViewFromViewId ?? indexView;
useEffect(() => { useEffect(() => {
setIsCurrentViewKeyIndex(currentView?.key === 'INDEX'); setIsCurrentViewKeyIndex(currentView?.key === 'INDEX');
}, [currentView, setIsCurrentViewKeyIndex]); }, [currentView, setIsCurrentViewKeyIndex]);
const viewsOnCurrentObject = getObjectMetadataItemViews( const viewsOnCurrentObject = useRecoilValue(
viewObjectMetadataId ?? '', prefetchViewsFromObjectMetadataItemFamilySelector({
views, objectMetadataItemId: objectMetadataItem.id,
}),
); );
const unsavedToUpsertViewFilters = useRecoilComponentFamilyValueV2( const unsavedToUpsertViewFilters = useRecoilComponentFamilyValueV2(

View File

@ -1,19 +1,18 @@
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { View } from '@/views/types/View';
import { useMemo } from 'react'; import { useRecoilValue } from 'recoil';
export const useGetCurrentViewOnly = () => { export const useGetCurrentViewOnly = () => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); const currentViewId = useRecoilComponentValueV2(
contextStoreCurrentViewIdComponentState,
);
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); const currentView = useRecoilValue(
prefetchViewFromViewIdFamilySelector({
const currentView = useMemo( viewId: currentViewId ?? '',
() => views.find((view) => view.id === currentViewId), }),
[views, currentViewId],
); );
return { return {

View File

@ -1,26 +0,0 @@
import { useCallback } from 'react';
import { useApolloClient } from '@apollo/client';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { View } from '@/views/types/View';
export const useGetViewFromCache = () => {
const client = useApolloClient();
const cache = client.cache;
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular: CoreObjectNameSingular.View,
});
const getViewFromCache = useCallback(
async (viewId: string) => {
return getRecordFromCache<View>(viewId, cache);
},
[cache, getRecordFromCache],
);
return {
getViewFromCache,
};
};

Some files were not shown because too many files have changed in this diff Show More