Enhance navigation menu item matching logic

- Updated `isLocationMatchingNavigationMenuItem` to include additional parameters for better matching of object record show paths.
- Introduced logging for debugging navigation matches, capturing relevant data for analysis.
- Added tests to ensure correct behavior for object navigation items on record show pages, distinguishing between view-only and object pins.
- Refactored related components to utilize the updated matching logic.

This improves the accuracy of navigation item states and enhances debugging capabilities.
This commit is contained in:
Abdul Rahman
2026-04-03 11:05:21 +05:30
parent 2ae6a9bb98
commit 918fdcbf87
4 changed files with 76 additions and 7 deletions
@@ -39,4 +39,36 @@ describe('isLocationMatchingNavigationMenuItem', () => {
),
).toBe(false);
});
it('should match object or view nav item when on a record show page for that object', () => {
expect(
isLocationMatchingNavigationMenuItem(
'/object/company/rec-1',
'/object/company/rec-1',
NavigationMenuItemType.OBJECT,
'/objects/companies?viewId=idx',
'company',
),
).toBe(true);
expect(
isLocationMatchingNavigationMenuItem(
'/object/person/rec-1',
'/object/person/rec-1',
NavigationMenuItemType.VIEW,
'/objects/people?viewId=v1',
'person',
),
).toBe(true);
expect(
isLocationMatchingNavigationMenuItem(
'/object/person/rec-1',
'/object/person/rec-1',
NavigationMenuItemType.OBJECT,
'/objects/companies?viewId=idx',
'company',
),
).toBe(false);
});
});
@@ -1,15 +1,38 @@
import { NavigationMenuItemType } from 'twenty-shared/types';
import { isNonEmptyString } from '@sniptt/guards';
import { AppPath, NavigationMenuItemType } from 'twenty-shared/types';
import { getAppPath } from 'twenty-shared/utils';
export const isLocationMatchingNavigationMenuItem = (
currentPath: string,
currentViewPath: string,
navigationMenuItemType: NavigationMenuItemType,
computedLink: string,
objectNameSingularForRecordShowMatch: string | null = null,
) => {
const isViewBasedItem =
navigationMenuItemType === NavigationMenuItemType.VIEW ||
navigationMenuItemType === NavigationMenuItemType.OBJECT;
return isViewBasedItem
const matchesListDestination = isViewBasedItem
? computedLink === currentViewPath
: computedLink === currentPath;
if (matchesListDestination) {
return true;
}
if (
isViewBasedItem &&
isNonEmptyString(objectNameSingularForRecordShowMatch)
) {
const recordShowBase = getAppPath(AppPath.RecordShowPage, {
objectNameSingular: objectNameSingularForRecordShowMatch,
objectRecordId: '',
});
return currentPath.startsWith(`${recordShowBase}/`);
}
return false;
};
@@ -6,6 +6,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/common/states/openNavigationMenuItemFolderIdsState';
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/display/object/utils/getObjectMetadataForNavigationMenuItem';
import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink';
import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/common/utils/isLocationMatchingNavigationMenuItem';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
@@ -85,11 +86,18 @@ export const useNavigationMenuItemFolderOpenState = ({
objectMetadataItems,
views,
);
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
item,
objectMetadataItems,
views,
);
return isLocationMatchingNavigationMenuItem(
currentPath,
currentViewPath,
item.type,
computedLink,
objectMetadataItem?.nameSingular ?? null,
);
},
);
@@ -3,6 +3,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import { Fragment, type ReactNode, useContext } from 'react';
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/common/utils/isLocationMatchingNavigationMenuItem';
import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/common/utils/recordIdentifierToObjectRecordIdentifier';
import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink';
import { getNavigationMenuItemLabel } from '@/navigation-menu-item/display/utils/getNavigationMenuItemLabel';
@@ -91,17 +92,22 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
const computedLink = hasCustomLink ? navigationPath : '';
const isActive = hasCustomLink
? (isView || isObject ? currentPathWithSearch : currentPath) ===
computedLink
? isLocationMatchingNavigationMenuItem(
currentPath,
currentPathWithSearch,
navigationMenuItem!.type,
computedLink,
isObject || isView ? objectMetadataItem.nameSingular : null,
)
: currentPath ===
getAppPath(AppPath.RecordIndexPage, {
objectNamePlural: objectMetadataItem.namePlural,
}) ||
currentPath.includes(
getAppPath(AppPath.RecordShowPage, {
currentPath.startsWith(
`${getAppPath(AppPath.RecordShowPage, {
objectNameSingular: objectMetadataItem.nameSingular,
objectRecordId: '',
}) + '/',
})}/`,
);
const handleClick = isLayoutCustomizationModeEnabled