fix: defaultHomePagePath to be last visited page or alphatically first active object with the name (#6629)

### ISSUE 

- Closes #6612
- Closes #6125
- Closes #5949
- Closes #6652 

### Description 

- [x] need to check changes in jest test.
- [x] fallback to alphabetically firstActiveObject with the name if no
last visited exist


https://github.com/user-attachments/assets/dd11480b-c47f-4393-9857-8a55467061e3

- [x] fallback to last visited page with the last visited view by
default if no views would have toggled with subNav or viewChangeDropdown
it will fallback to INDEX or if no INDEX view then zero position view,
works with both subNavViewBar and viewChangeDropdown.



https://github.com/user-attachments/assets/33e97e55-2aa2-4c45-a3ab-fc8e43f4964c



https://github.com/user-attachments/assets/d1db76a2-da59-4cd2-81bf-d6119408fbbf

- [x] lastVisited view across the objects have been persisted so now
navigating back from x object to y or z to x will open always last
visited view and defaults to index or zero position view.



https://github.com/user-attachments/assets/70a01a11-a7ef-4031-926e-02923551466c

- [x] lastVisited Page with view has been persisted across the
workspace, scope is per workspace so jumping between workspace will also
work to have lastvisited object of particular workspace.



https://github.com/user-attachments/assets/25107339-8ec1-4421-9f6e-1da43b8f4816

- [x] when lastVisitedObject has been deactivated and going back from
settings should have a fallback Object that is alphabetically First
activeObject.



https://github.com/user-attachments/assets/6b24a933-b139-49ac-82b2-eac5e4848516


- [x] Creation of new View of **anyType** should also get persist and
that should get lastVisitedObject with View in case the user leaves from
there right away.



https://github.com/user-attachments/assets/80ff7114-051d-4e9b-ab58-0e1e3a7d328c

- [x] Similarly deleted view also works. 


https://github.com/user-attachments/assets/cb0b8043-fba4-4a66-941d-b3fa0a57eb22


- [x] fixed active subnav background when opening object directly with
root path **/** , it wasn't showing active subNav background.

Before: 


https://github.com/user-attachments/assets/db341c4a-f1f9-43c4-9838-37d1a1f5ab8e

Fixed: 


https://github.com/user-attachments/assets/0f0fd492-bc5d-4efe-b695-bee4e3f41d4e

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Nabhag Motivaras
2024-08-28 20:45:54 +05:30
committed by GitHub
parent 5deb0abe4d
commit 747a1549e9
21 changed files with 457 additions and 66 deletions

View File

@@ -1,9 +1,10 @@
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { AppPath } from '@/types/AppPath';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql';
import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
import { UNTESTED_APP_PATHS } from '~/testing/constants/UntestedAppPaths';
@@ -38,7 +39,7 @@ const setupMockIsLogged = (isLogged: boolean) => {
const defaultHomePagePath = '/objects/companies';
jest.mock('~/hooks/useDefaultHomePagePath');
jest.mock('@/navigation/hooks/useDefaultHomePagePath');
jest.mocked(useDefaultHomePagePath).mockReturnValue({
defaultHomePagePath,
});

View File

@@ -1,31 +0,0 @@
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { AppPath } from '@/types/AppPath';
import { isDefined } from '~/utils/isDefined';
export const useDefaultHomePagePath = () => {
const currentUser = useRecoilValue(currentUserState);
const { objectMetadataItem: companyObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Company,
});
const { records } = usePrefetchedData(PrefetchKey.AllViews);
if (!isDefined(currentUser)) {
return { defaultHomePagePath: AppPath.SignInUp };
}
const companyViewId = records.find(
(view: any) => view?.objectMetadataId === companyObjectMetadataItem.id,
)?.id;
return {
defaultHomePagePath:
'/objects/companies' + (companyViewId ? `?view=${companyViewId}` : ''),
};
};

View File

@@ -1,10 +1,10 @@
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql';
import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
export const usePageChangeEffectNavigateLocation = () => {
@@ -107,12 +107,13 @@ export const usePageChangeEffectNavigateLocation = () => {
if (
onboardingStatus === OnboardingStatus.Completed &&
isMatchingOnboardingRoute
isMatchingOnboardingRoute &&
isLoggedIn
) {
return defaultHomePagePath;
}
if (isMatchingLocation(AppPath.Index)) {
if (isMatchingLocation(AppPath.Index) && isLoggedIn) {
return defaultHomePagePath;
}

View File

@@ -2,19 +2,16 @@ import { renderHook } from '@testing-library/react';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import {
COMPANY_OBJECT_METADATA_ID,
getObjectMetadataItemsMock,
} from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { AppPath } from '@/types/AppPath';
import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
import { mockedUserData } from '~/testing/mock-data/users';
const objectMetadataItem = getObjectMetadataItemsMock()[0];
jest.mock('@/object-metadata/hooks/useObjectMetadataItem');
jest.mocked(useObjectMetadataItem).mockReturnValue({
objectMetadataItem,
});
jest.mock('@/prefetch/hooks/usePrefetchedData');
const setupMockPrefetchedData = (viewId?: string) => {
jest.mocked(usePrefetchedData).mockReturnValue({
@@ -24,7 +21,7 @@ const setupMockPrefetchedData = (viewId?: string) => {
{
id: viewId,
__typename: 'object',
objectMetadataId: objectMetadataItem.id,
objectMetadataId: COMPANY_OBJECT_METADATA_ID,
},
]
: [],
@@ -35,6 +32,12 @@ const renderHooks = (withCurrentUser: boolean) => {
const { result } = renderHook(
() => {
const setCurrentUser = useSetRecoilState(currentUserState);
const setObjectMetadataItems = useSetRecoilState(
objectMetadataItemsState,
);
setObjectMetadataItems(getObjectMetadataItemsMock());
if (withCurrentUser) {
setCurrentUser(mockedUserData);
}

View File

@@ -0,0 +1,90 @@
import { currentUserState } from '@/auth/states/currentUserState';
import { useLastVisitedObjectMetadataItem } from '@/navigation/hooks/useLastVisitedObjectMetadataItem';
import { ObjectPathInfo } from '@/navigation/types/ObjectPathInfo';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { AppPath } from '@/types/AppPath';
import { View } from '@/views/types/View';
import { useCallback, useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from '~/utils/isDefined';
export const useDefaultHomePagePath = () => {
const currentUser = useRecoilValue(currentUserState);
const { activeObjectMetadataItems, alphaSortedActiveObjectMetadataItems } =
useFilteredObjectMetadataItems();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const { lastVisitedObjectMetadataItemId } =
useLastVisitedObjectMetadataItem();
const getActiveObjectMetadataItemMatchingId = useCallback(
(objectMetadataId: string) => {
return activeObjectMetadataItems.find(
(item) => item.id === objectMetadataId,
);
},
[activeObjectMetadataItems],
);
const getFirstView = useCallback(
(objectMetadataItemId: string | undefined | null) =>
views.find((view) => view.objectMetadataId === objectMetadataItemId),
[views],
);
const firstObjectPathInfo = useMemo<ObjectPathInfo | null>(() => {
const [firstObjectMetadataItem] = alphaSortedActiveObjectMetadataItems;
if (!isDefined(firstObjectMetadataItem)) {
return null;
}
const view = getFirstView(firstObjectMetadataItem?.id);
return { objectMetadataItem: firstObjectMetadataItem, view };
}, [alphaSortedActiveObjectMetadataItems, getFirstView]);
const defaultObjectPathInfo = useMemo<ObjectPathInfo | null>(() => {
if (!isDefined(lastVisitedObjectMetadataItemId)) {
return firstObjectPathInfo;
}
const lastVisitedObjectMetadataItem = getActiveObjectMetadataItemMatchingId(
lastVisitedObjectMetadataItemId,
);
if (isDefined(lastVisitedObjectMetadataItem)) {
return {
view: getFirstView(lastVisitedObjectMetadataItemId),
objectMetadataItem: lastVisitedObjectMetadataItem,
};
}
return firstObjectPathInfo;
}, [
firstObjectPathInfo,
getActiveObjectMetadataItemMatchingId,
getFirstView,
lastVisitedObjectMetadataItemId,
]);
const defaultHomePagePath = useMemo(() => {
if (!isDefined(currentUser)) {
return AppPath.SignInUp;
}
if (!isDefined(defaultObjectPathInfo)) {
return AppPath.NotFound;
}
const namePlural = defaultObjectPathInfo.objectMetadataItem?.namePlural;
const viewParam = defaultObjectPathInfo.view
? `?view=${defaultObjectPathInfo.view.id}`
: '';
return `/objects/${namePlural}${viewParam}`;
}, [currentUser, defaultObjectPathInfo]);
return { defaultHomePagePath };
};

View File

@@ -0,0 +1,66 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { lastVisitedObjectMetadataItemIdStateSelector } from '@/navigation/states/selectors/lastVisitedObjectMetadataItemIdStateSelector';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-ui';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useLastVisitedObjectMetadataItem = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const scopeId = currentWorkspace?.id ?? '';
const lastVisitedObjectMetadataItemIdState = extractComponentState(
lastVisitedObjectMetadataItemIdStateSelector,
scopeId,
);
const [lastVisitedObjectMetadataItemId, setLastVisitedObjectMetadataItemId] =
useRecoilState(lastVisitedObjectMetadataItemIdState);
const {
findActiveObjectMetadataItemBySlug,
alphaSortedActiveObjectMetadataItems,
} = useFilteredObjectMetadataItems();
const setNavigationMemorizedUrl = useSetRecoilState(
navigationMemorizedUrlState,
);
const setFallbackForLastVisitedObjectMetadataItem = (
objectMetadataItemId: string,
) => {
const isDeactivateDefault = isDeeplyEqual(
lastVisitedObjectMetadataItemId,
objectMetadataItemId,
);
const [newFallbackObjectMetadataItem] =
alphaSortedActiveObjectMetadataItems.filter(
(item) => item.id !== objectMetadataItemId,
);
if (isDeactivateDefault) {
setLastVisitedObjectMetadataItemId(newFallbackObjectMetadataItem.id);
setNavigationMemorizedUrl(
`/objects/${newFallbackObjectMetadataItem.namePlural}`,
);
}
};
const setLastVisitedObjectMetadataItem = (objectNamePlural: string) => {
const fallbackObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectNamePlural);
if (isDefined(fallbackObjectMetadataItem)) {
setLastVisitedObjectMetadataItemId(fallbackObjectMetadataItem.id);
}
};
return {
lastVisitedObjectMetadataItemId,
setLastVisitedObjectMetadataItem,
setFallbackForLastVisitedObjectMetadataItem,
};
};

View File

@@ -0,0 +1,92 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { lastVisitedObjectMetadataItemIdStateSelector } from '@/navigation/states/selectors/lastVisitedObjectMetadataItemIdStateSelector';
import { lastVisitedViewPerObjectMetadataItemStateSelector } from '@/navigation/states/selectors/lastVisitedViewPerObjectMetadataItemStateSelector';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui';
export const useLastVisitedView = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const scopeId = currentWorkspace?.id ?? '';
const lastVisitedObjectMetadataItemIdState = extractComponentState(
lastVisitedObjectMetadataItemIdStateSelector,
scopeId,
);
const lastVisitedViewPerObjectMetadataItemState = extractComponentState(
lastVisitedViewPerObjectMetadataItemStateSelector,
scopeId,
);
const lastVisitedObjectMetadataItemId = useRecoilValue(
lastVisitedObjectMetadataItemIdState,
);
const [
lastVisitedViewPerObjectMetadataItem,
setLastVisitedViewPerObjectMetadataItem,
] = useRecoilState(lastVisitedViewPerObjectMetadataItemState);
const { findActiveObjectMetadataItemBySlug } =
useFilteredObjectMetadataItems();
const setFallbackForLastVisitedView = (objectMetadataItemId: string) => {
/* ...{} allows us to pass value as undefined to remove that particular key
even though param type is of type Record<string,string> */
setLastVisitedViewPerObjectMetadataItem({
...{},
[objectMetadataItemId]: undefined,
});
};
const setLastVisitedView = ({
objectNamePlural,
viewId,
}: {
objectNamePlural: string;
viewId: string;
}) => {
const fallbackObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectNamePlural);
if (isDefined(fallbackObjectMetadataItem)) {
/* when both are equal meaning there was change in view else
there was a object page change from nav
*/
const fallbackViewId =
lastVisitedObjectMetadataItemId === fallbackObjectMetadataItem.id
? viewId
: (lastVisitedViewPerObjectMetadataItem?.[
fallbackObjectMetadataItem.id
] ?? viewId);
setLastVisitedViewPerObjectMetadataItem({
[fallbackObjectMetadataItem.id]: fallbackViewId,
});
}
};
const getLastVisitedViewIdFromObjectNamePlural = (
objectNamePlural: string,
) => {
const objectMetadataItemId: string | undefined =
findActiveObjectMetadataItemBySlug(objectNamePlural)?.id;
return objectMetadataItemId
? lastVisitedViewPerObjectMetadataItem?.[objectMetadataItemId]
: undefined;
};
const getLastVisitedViewIdFromObjectMetadataItemId = (
objectMetadataItemId: string,
) => {
return lastVisitedViewPerObjectMetadataItem?.[objectMetadataItemId];
};
return {
setLastVisitedView,
getLastVisitedViewIdFromObjectNamePlural,
getLastVisitedViewIdFromObjectMetadataItemId,
setFallbackForLastVisitedView,
};
};

View File

@@ -0,0 +1,11 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { localStorageEffect } from '~/utils/recoil-effects';
export const lastVisitedObjectMetadataItemIdState = createComponentState<Record<
string,
string
> | null>({
key: 'lastVisitedObjectMetadataItemIdState',
defaultValue: null,
effects: [localStorageEffect()],
});

View File

@@ -0,0 +1,9 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { localStorageEffect } from '~/utils/recoil-effects';
export const lastVisitedViewPerObjectMetadataItemState =
createComponentState<Record<string, string> | null>({
key: 'lastVisitedViewPerObjectMetadataItemState',
defaultValue: null,
effects: [localStorageEffect()],
});

View File

@@ -0,0 +1,24 @@
import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState';
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
export const lastVisitedObjectMetadataItemIdStateSelector =
createComponentSelector<string | null>({
key: 'lastVisitedObjectMetadataItemIdStateSelector',
get:
({ scopeId }: { scopeId: string }) =>
({ get }) => {
const state = get(lastVisitedObjectMetadataItemIdState({ scopeId }));
return state?.['last_visited_object']
? state['last_visited_object']
: null;
},
set:
({ scopeId }: { scopeId: string }) =>
({ set }, newValue) => {
set(lastVisitedObjectMetadataItemIdState({ scopeId }), {
...(typeof newValue === 'string' && {
last_visited_object: newValue,
}),
});
},
});

View File

@@ -0,0 +1,34 @@
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
import { isDefined } from 'twenty-ui';
export const lastVisitedViewPerObjectMetadataItemStateSelector =
createComponentSelector<Record<string, string> | null>({
key: 'lastVisitedViewPerObjectMetadataItemStateSelector',
get:
({ scopeId }: { scopeId: string }) =>
({ get }) => {
const state = get(
lastVisitedViewPerObjectMetadataItemState({ scopeId }),
);
if (isDefined(state?.['last_visited_object'])) {
const { last_visited_object: _last_visited_object, ...rest } = state;
return rest;
}
return state;
},
set:
({ scopeId }: { scopeId: string }) =>
({ set, get }, newValue) => {
const currentLastVisitedViewPerObjectMetadataItems = get(
lastVisitedViewPerObjectMetadataItemStateSelector({ scopeId }),
);
set(lastVisitedViewPerObjectMetadataItemState({ scopeId }), {
...currentLastVisitedViewPerObjectMetadataItems,
...newValue,
});
},
});

View File

@@ -0,0 +1,7 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { View } from '@/views/types/View';
export type ObjectPathInfo = {
objectMetadataItem: ObjectMetadataItem;
view: View | undefined;
};

View File

@@ -1,6 +1,7 @@
import { useEffect } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
@@ -13,10 +14,11 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const ObjectMetadataItemsLoadEffect = () => {
const currentUser = useRecoilValue(currentUserState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const isLoggedIn = useIsLogged();
const { objectMetadataItems: newObjectMetadataItems, loading } =
useFindManyObjectMetadataItems({
skip: isUndefinedOrNull(currentUser),
skip: !isLoggedIn,
});
const [objectMetadataItems, setObjectMetadataItems] = useRecoilState(

View File

@@ -4,6 +4,7 @@ import { useRecoilValue } from 'recoil';
import { isDefined, useIcons } from 'twenty-ui';
import { currentUserState } from '@/auth/states/currentUserState';
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
import { ObjectMetadataNavItemsSkeletonLoader } from '@/object-metadata/components/ObjectMetadataNavItemsSkeletonLoader';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
@@ -52,12 +53,12 @@ export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
);
const { getIcon } = useIcons();
const currentPath = useLocation().pathname;
const currentPathWithSearch = currentPath + useLocation().search;
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const loading = useIsPrefetchLoading();
const theme = useTheme();
const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView();
if (loading && isDefined(currentUser)) {
return <ObjectMetadataNavItemsSkeletonLoader />;
@@ -106,7 +107,11 @@ export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
objectMetadataItem.id,
views,
);
const viewId = objectMetadataViews[0]?.id;
const lastVisitedViewId =
getLastVisitedViewIdFromObjectMetadataItemId(
objectMetadataItem.id,
);
const viewId = lastVisitedViewId ?? objectMetadataViews[0]?.id;
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
viewId ? `?view=${viewId}` : ''
@@ -146,10 +151,7 @@ export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
<NavigationDrawerSubItem
label={view.name}
to={`/objects/${objectMetadataItem.namePlural}?view=${view.id}`}
active={
currentPathWithSearch ===
`/objects/${objectMetadataItem.namePlural}?view=${view.id}`
}
active={viewId === view.id}
Icon={getIcon(view.icon)}
key={view.id}
/>

View File

@@ -10,6 +10,19 @@ export const useFilteredObjectMetadataItems = () => {
const activeObjectMetadataItems = objectMetadataItems.filter(
({ isActive, isSystem }) => isActive && !isSystem,
);
const alphaSortedActiveObjectMetadataItems = activeObjectMetadataItems.sort(
(a, b) => {
if (a.nameSingular < b.nameSingular) {
return -1;
}
if (a.nameSingular > b.nameSingular) {
return 1;
}
return 0;
},
);
const inactiveObjectMetadataItems = objectMetadataItems.filter(
({ isActive, isSystem }) => !isActive && !isSystem,
);
@@ -37,5 +50,6 @@ export const useFilteredObjectMetadataItems = () => {
findObjectMetadataItemByNamePlural,
inactiveObjectMetadataItems,
objectMetadataItems,
alphaSortedActiveObjectMetadataItems,
};
};

View File

@@ -1,6 +1,7 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID = '39403bee-314b-4f14-bc91-70d500397517';
export const COMPANY_OBJECT_METADATA_ID = 'f1231579-8e7d-4b84-9a60-41844902f2c4';
export const getObjectMetadataItemsMock = () => {
const mockArray = [

View File

@@ -2,6 +2,8 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconArchive, IconDotsVertical, IconPencil, useIcons } from 'twenty-ui';
import { useLastVisitedObjectMetadataItem } from '@/navigation/hooks/useLastVisitedObjectMetadataItem';
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SettingsSummaryCard } from '@/settings/components/SettingsSummaryCard';
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/SettingsDataModelObjectTypeTag';
@@ -38,8 +40,12 @@ export const SettingsObjectSummaryCard = ({
const theme = useTheme();
const { getIcon } = useIcons();
const Icon = getIcon(iconKey);
const objectMetadataItemId = objectMetadataItem.id;
const { closeDropdown } = useDropdown(dropdownId);
const { setFallbackForLastVisitedView } = useLastVisitedView();
const { setFallbackForLastVisitedObjectMetadataItem } =
useLastVisitedObjectMetadataItem();
const handleEdit = () => {
onEdit();
@@ -47,6 +53,8 @@ export const SettingsObjectSummaryCard = ({
};
const handleDeactivate = () => {
setFallbackForLastVisitedObjectMetadataItem(objectMetadataItemId);
setFallbackForLastVisitedView(objectMetadataItemId);
onDeactivate();
closeDropdown();
};

View File

@@ -1,3 +1,5 @@
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import isPropValid from '@emotion/is-prop-valid';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@@ -5,9 +7,6 @@ import { isNonEmptyString } from '@sniptt/guards';
import { Link, useNavigate } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { IconComponent, MOBILE_VIEWPORT, Pill } from 'twenty-ui';
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { isDefined } from '~/utils/isDefined';
export type NavigationDrawerItemProps = {
@@ -147,7 +146,9 @@ export const NavigationDrawerItem = ({
return;
}
if (isNonEmptyString(to)) navigate(to);
if (isNonEmptyString(to)) {
navigate(to);
}
};
return (

View File

@@ -5,6 +5,9 @@ import {
import styled from '@emotion/styled';
const StyledItem = styled.div`
&:not(:last-child) {
margin-bottom: ${({ theme }) => theme.spacing(0.5)};
}
margin-left: ${({ theme }) => theme.spacing(4)};
`;

View File

@@ -1,35 +1,90 @@
import { useEffect } from 'react';
import { isUndefined } from '@sniptt/guards';
import { useRecoilState } from 'recoil';
import { useLastVisitedObjectMetadataItem } from '@/navigation/hooks/useLastVisitedObjectMetadataItem';
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isUndefined } from '@sniptt/guards';
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isDefined } from '~/utils/isDefined';
export const QueryParamsViewIdEffect = () => {
const { getFiltersFromQueryParams, viewIdQueryParam } =
useViewFromQueryParams();
const { currentViewIdState } = useViewStates();
const { currentViewIdState, componentId: objectNamePlural } = useViewStates();
const [currentViewId, setCurrentViewId] = useRecoilState(currentViewIdState);
const { viewsOnCurrentObject } = useGetCurrentView();
const { findObjectMetadataItemByNamePlural } =
useFilteredObjectMetadataItems();
const objectMetadataItemId =
findObjectMetadataItemByNamePlural(objectNamePlural);
const { getLastVisitedViewIdFromObjectNamePlural, setLastVisitedView } =
useLastVisitedView();
const { lastVisitedObjectMetadataItemId, setLastVisitedObjectMetadataItem } =
useLastVisitedObjectMetadataItem();
const lastVisitedViewId =
getLastVisitedViewIdFromObjectNamePlural(objectNamePlural);
const isLastVisitedObjectMetadataItemDifferent = !isDeeplyEqual(
objectMetadataItemId?.id,
lastVisitedObjectMetadataItemId,
);
useEffect(() => {
const indexView = viewsOnCurrentObject.find((view) => view.key === 'INDEX');
if (isUndefined(viewIdQueryParam) && isDefined(indexView)) {
setCurrentViewId(indexView.id);
if (isUndefined(viewIdQueryParam) && isDefined(lastVisitedViewId)) {
if (isLastVisitedObjectMetadataItemDifferent) {
setLastVisitedObjectMetadataItem(objectNamePlural);
setLastVisitedView({
objectNamePlural,
viewId: lastVisitedViewId,
});
}
setCurrentViewId(lastVisitedViewId);
return;
}
if (isDefined(viewIdQueryParam)) {
if (isLastVisitedObjectMetadataItemDifferent) {
setLastVisitedObjectMetadataItem(objectNamePlural);
}
if (!isDeeplyEqual(viewIdQueryParam, lastVisitedViewId)) {
setLastVisitedView({
objectNamePlural,
viewId: viewIdQueryParam,
});
}
setCurrentViewId(viewIdQueryParam);
return;
}
if (isDefined(indexView)) {
if (isLastVisitedObjectMetadataItemDifferent) {
setLastVisitedObjectMetadataItem(objectNamePlural);
}
if (!isDeeplyEqual(indexView.id, lastVisitedViewId)) {
setLastVisitedView({
objectNamePlural,
viewId: indexView.id,
});
}
setCurrentViewId(indexView.id);
return;
}
}, [
currentViewId,
getFiltersFromQueryParams,
isLastVisitedObjectMetadataItemDifferent,
lastVisitedViewId,
objectMetadataItemId?.id,
objectNamePlural,
setCurrentViewId,
setLastVisitedObjectMetadataItem,
setLastVisitedView,
viewIdQueryParam,
viewsOnCurrentObject,
]);

View File

@@ -28,7 +28,6 @@ export const ViewPickerListContent = () => {
const { currentViewWithCombinedFiltersAndSorts, viewsOnCurrentObject } =
useGetCurrentView();
const { viewPickerReferenceViewIdState } = useViewPickerStates();
const setViewPickerReferenceViewId = useSetRecoilState(
viewPickerReferenceViewIdState,
@@ -37,7 +36,6 @@ export const ViewPickerListContent = () => {
const { setViewPickerMode } = useViewPickerMode();
const { closeDropdown } = useDropdown(VIEW_PICKER_DROPDOWN_ID);
const { updateView } = useHandleViews();
const handleViewSelect = (viewId: string) => {