diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/AddToNavigationDragHandle.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/AddToNavigationDragHandle.tsx index d45d940539..1c44d56297 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/AddToNavigationDragHandle.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/AddToNavigationDragHandle.tsx @@ -5,7 +5,7 @@ import { IconGripVertical, type IconComponent } from 'twenty-ui/display'; import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload'; import { getEffectiveNavigationMenuItemColor } from '@/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor'; @@ -86,7 +86,7 @@ export const AddToNavigationDragHandle = ({ ? payload.iconColor : undefined; const effectiveColor = getEffectiveNavigationMenuItemColor( - { itemType: payload.type as NavigationMenuItemType }, + { type: payload.type as NavigationMenuItemType }, objectColor, ); const hasBackgroundColor = diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx index 22563dc233..3d83433085 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx @@ -1,11 +1,14 @@ import { Droppable } from '@hello-pangea/dnd'; import { useLingui } from '@lingui/react/macro'; import { useContext, useState } from 'react'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants'; import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection'; import { createPortal } from 'react-dom'; import { useLocation, useNavigate } from 'react-router-dom'; +import { NavigationMenuItemType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; import { IconFolder, IconFolderOpen, IconHeartOff } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; @@ -18,16 +21,17 @@ import { NavigationMenuItemFolderNavigationDrawerItemDropdown } from '@/navigati import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultColorFolder'; import { NAVIGATION_MENU_ITEM_FOLDER_DELETE_MODAL_ID } from '@/navigation-menu-item/constants/NavigationMenuItemFolderDeleteModalId'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext'; import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItem'; import { useDeleteNavigationMenuItemFolder } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItemFolder'; import { useRenameNavigationMenuItemFolder } from '@/navigation-menu-item/hooks/useRenameNavigationMenuItemFolder'; import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState'; import { getEffectiveNavigationMenuItemColor } from '@/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor'; +import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/utils/getNavigationMenuItemComputedLink'; +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; +import { getNavigationMenuItemObjectNameSingular } from '@/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular'; import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel'; import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; @@ -45,14 +49,15 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; +import { ViewKey } from '@/views/types/ViewKey'; import { isNonEmptyString } from '@sniptt/guards'; -import { isDefined } from 'twenty-shared/utils'; type CurrentWorkspaceMemberNavigationMenuItemsProps = { folder: { id: string; folderName: string; - navigationMenuItems: ProcessedNavigationMenuItem[]; + navigationMenuItems: NavigationMenuItem[]; }; isGroup: boolean; isWorkspaceFolder?: boolean; @@ -65,6 +70,7 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({ }: CurrentWorkspaceMemberNavigationMenuItemsProps) => { const { t } = useLingui(); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); const location = useLocation(); const navigate = useNavigate(); const currentPath = location.pathname; @@ -105,13 +111,26 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({ } if (!isOpen) { - const firstNonLinkItem = folder.navigationMenuItems.find( - (item) => - item.itemType !== NavigationMenuItemType.LINK && - isNonEmptyString(item.link), - ); - if (isDefined(firstNonLinkItem?.link)) { - navigate(firstNonLinkItem.link); + const firstNonLinkItem = folder.navigationMenuItems.find((item) => { + if (item.type === NavigationMenuItemType.LINK) { + return false; + } + const computedLink = getNavigationMenuItemComputedLink( + item, + objectMetadataItems, + views, + ); + return isNonEmptyString(computedLink); + }); + if (isDefined(firstNonLinkItem)) { + const link = getNavigationMenuItemComputedLink( + firstNonLinkItem, + objectMetadataItems, + views, + ); + if (isNonEmptyString(link)) { + navigate(link); + } } } }; @@ -131,8 +150,19 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({ const { closeDropdown } = useCloseDropdown(); const selectedNavigationMenuItemIndex = folder.navigationMenuItems.findIndex( - (item) => - isLocationMatchingNavigationMenuItem(currentPath, currentViewPath, item), + (item) => { + const computedLink = getNavigationMenuItemComputedLink( + item, + objectMetadataItems, + views, + ); + return isLocationMatchingNavigationMenuItem( + currentPath, + currentViewPath, + item.type, + computedLink, + ); + }, ); const { deleteNavigationMenuItem } = useDeleteNavigationMenuItem(); @@ -261,53 +291,84 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({ // oxlint-disable-next-line react/jsx-props-no-spreading {...provided.droppableProps} > - {folder.navigationMenuItems.map((navigationMenuItem, index) => ( - ( - - )} - to={isDragging ? undefined : navigationMenuItem.link} - active={index === selectedNavigationMenuItemIndex} - subItemState={getNavigationSubItemLeftAdornment({ - index, - arrayLength: navigationMenuItemFolderContentLength, - selectedIndex: selectedNavigationMenuItemIndex, - })} - rightOptions={ - isWorkspaceFolder ? undefined : ( - { - e.stopPropagation(); - deleteNavigationMenuItem(navigationMenuItem.id); - }} - accent="tertiary" + {folder.navigationMenuItems.map((navigationMenuItem, index) => { + const label = getNavigationMenuItemLabel( + navigationMenuItem, + objectMetadataItems, + views, + ); + const computedLink = getNavigationMenuItemComputedLink( + navigationMenuItem, + objectMetadataItems, + views, + ); + const objectNameSingular = + getNavigationMenuItemObjectNameSingular( + navigationMenuItem, + objectMetadataItems, + views, + ); + const view = isDefined(navigationMenuItem.viewId) + ? views.find( + (view) => view.id === navigationMenuItem.viewId, + ) + : undefined; + const isIndexView = view?.key === ViewKey.INDEX; + + return ( + ( + - ) - } - isDragging={isDragging} - triggerEvent="CLICK" - iconColor={getEffectiveNavigationMenuItemColor( - navigationMenuItem, - )} - /> - } - /> - ))} + )} + to={isDragging ? undefined : computedLink} + active={index === selectedNavigationMenuItemIndex} + subItemState={getNavigationSubItemLeftAdornment({ + index, + arrayLength: navigationMenuItemFolderContentLength, + selectedIndex: selectedNavigationMenuItemIndex, + })} + rightOptions={ + isWorkspaceFolder ? undefined : ( + { + e.stopPropagation(); + deleteNavigationMenuItem( + navigationMenuItem.id, + ); + }} + accent="tertiary" + /> + ) + } + isDragging={isDragging} + triggerEvent="CLICK" + iconColor={getEffectiveNavigationMenuItemColor( + navigationMenuItem, + )} + /> + } + /> + ); + })} {provided.placeholder} )} diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems.tsx index 012bac477d..c72d44f2f4 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems.tsx @@ -14,10 +14,14 @@ import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/N import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItem'; import { useSortedNavigationMenuItems } from '@/navigation-menu-item/hooks/useSortedNavigationMenuItems'; import { getEffectiveNavigationMenuItemColor } from '@/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor'; +import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/utils/getNavigationMenuItemComputedLink'; +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; +import { getNavigationMenuItemObjectNameSingular } from '@/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular'; import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel'; import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; @@ -31,6 +35,7 @@ const StyledOrphanNavigationMenuItemsContainer = styled.div` export const CurrentWorkspaceMemberOrphanNavigationMenuItems = () => { const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); const { navigationMenuItemsSorted } = useSortedNavigationMenuItems(); const { deleteNavigationMenuItem } = useDeleteNavigationMenuItem(); const currentPath = useLocation().pathname; @@ -48,58 +53,77 @@ export const CurrentWorkspaceMemberOrphanNavigationMenuItems = () => { > {orphanNavigationMenuItems.length > 0 ? ( <> - {orphanNavigationMenuItems.map((navigationMenuItem, index) => ( - - { + const label = getNavigationMenuItemLabel( + navigationMenuItem, + objectMetadataItems, + views, + ); + const computedLink = getNavigationMenuItemComputedLink( + navigationMenuItem, + objectMetadataItems, + views, + ); + const objectNameSingular = getNavigationMenuItemObjectNameSingular( + navigationMenuItem, + objectMetadataItems, + views, + ); + + return ( + - ( - - )} - iconColor={getEffectiveNavigationMenuItemColor( - navigationMenuItem, - )} - active={isLocationMatchingNavigationMenuItem( - currentPath, - currentViewPath, - navigationMenuItem, - )} - to={isDragging ? undefined : navigationMenuItem.link} - rightOptions={ - { - e.stopPropagation(); - deleteNavigationMenuItem(navigationMenuItem.id); - }} - accent="tertiary" - /> - } - isDragging={isDragging} - triggerEvent="CLICK" - /> - - } - /> - - ))} + sectionId={NavigationSections.FAVORITES} + > + + ( + + )} + iconColor={getEffectiveNavigationMenuItemColor( + navigationMenuItem, + )} + active={isLocationMatchingNavigationMenuItem( + currentPath, + currentViewPath, + navigationMenuItem.type, + computedLink, + )} + to={isDragging ? undefined : computedLink} + rightOptions={ + { + e.stopPropagation(); + deleteNavigationMenuItem(navigationMenuItem.id); + }} + accent="tertiary" + /> + } + isDragging={isDragging} + triggerEvent="CLICK" + /> + + } + /> + + ); + })} { const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); const { deleteNavigationMenuItem } = useDeleteNavigationMenuItem(); return ( @@ -36,41 +41,60 @@ export const NavigationMenuItemFolderContent = ({ // oxlint-disable-next-line react/jsx-props-no-spreading {...provided.droppableProps} > - {navigationMenuItems.map((navigationMenuItem, index) => ( - ( - - )} - rightOptions={ - { - e.stopPropagation(); - deleteNavigationMenuItem(navigationMenuItem.id); - }} - accent="tertiary" - /> - } - triggerEvent="CLICK" - to={navigationMenuItem.link} - /> - } - /> - ))} + {navigationMenuItems.map((navigationMenuItem, index) => { + const label = getNavigationMenuItemLabel( + navigationMenuItem, + objectMetadataItems, + views, + ); + const computedLink = getNavigationMenuItemComputedLink( + navigationMenuItem, + objectMetadataItems, + views, + ); + const objectNameSingular = + getNavigationMenuItemObjectNameSingular( + navigationMenuItem, + objectMetadataItems, + views, + ); + + return ( + ( + + )} + rightOptions={ + { + e.stopPropagation(); + deleteNavigationMenuItem(navigationMenuItem.id); + }} + accent="tertiary" + /> + } + triggerEvent="CLICK" + to={computedLink} + /> + } + /> + ); + })} {provided.placeholder} )} diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemFolderContentDispatcher.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemFolderContentDispatcher.tsx index e25a31b9f2..ee2e30d3c1 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemFolderContentDispatcher.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemFolderContentDispatcher.tsx @@ -1,12 +1,12 @@ import { isDefined } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { NavigationMenuItemFolderContent } from '@/navigation-menu-item/components/NavigationMenuItemFolderContent'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; type NavigationMenuItemFolderContentDispatcherEffectProps = { folderName: string; folderId: string; - navigationMenuItems?: ProcessedNavigationMenuItem[]; + navigationMenuItems?: NavigationMenuItem[]; }; export const NavigationMenuItemFolderContentDispatcherEffect = ({ diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemIcon.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemIcon.tsx index e3ede99707..81ed74d94a 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemIcon.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemIcon.tsx @@ -1,68 +1,86 @@ import { isNonEmptyString } from '@sniptt/guards'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { Avatar, IconLink, IconWorld, useIcons } from 'twenty-ui/display'; import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { LinkIconWithLinkOverlay } from '@/navigation-menu-item/components/LinkIconWithLinkOverlay'; import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer'; import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { useObjectNavItemColor } from '@/navigation-menu-item/hooks/useObjectNavItemColor'; import { getEffectiveNavigationMenuItemColor } from '@/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor'; +import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/utils/getNavigationMenuItemComputedLink'; import { getNavigationMenuItemIconStyleFromColor } from '@/navigation-menu-item/utils/getNavigationMenuItemIconStyleFromColor'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; +import { getNavigationMenuItemObjectNameSingular } from '@/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular'; +import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/utils/recordIdentifierToObjectRecordIdentifier'; import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { ViewKey } from '@/views/types/ViewKey'; export const NavigationMenuItemIcon = ({ navigationMenuItem, }: { - navigationMenuItem: ProcessedNavigationMenuItem; + navigationMenuItem: NavigationMenuItem; }) => { const { getIcon } = useIcons(); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); - const { Icon: StandardIcon, IconColor } = useGetStandardObjectIcon( - navigationMenuItem.objectNameSingular ?? '', - ); + const views = useAtomStateValue(viewsSelector); - const isRecord = - navigationMenuItem.itemType === NavigationMenuItemType.RECORD; + const objectNameSingular = + getNavigationMenuItemObjectNameSingular( + navigationMenuItem, + objectMetadataItems, + views, + ) ?? ''; + + const { Icon: StandardIcon, IconColor } = + useGetStandardObjectIcon(objectNameSingular); + + const isRecord = navigationMenuItem.type === NavigationMenuItemType.RECORD; + + const view = isDefined(navigationMenuItem.viewId) + ? views.find((view) => view.id === navigationMenuItem.viewId) + : undefined; const isViewWithOverlay = - navigationMenuItem.itemType === NavigationMenuItemType.VIEW && - navigationMenuItem.viewKey !== ViewKey.INDEX; + navigationMenuItem.type === NavigationMenuItemType.VIEW && + isDefined(view) && + view.key !== ViewKey.INDEX; const objectMetadataItem = objectMetadataItems.find( - (item) => item.nameSingular === navigationMenuItem.objectNameSingular, - ); - const objectNavItemColor = useObjectNavItemColor( - navigationMenuItem.objectNameSingular ?? '', + (item) => item.nameSingular === objectNameSingular, ); + const objectNavItemColor = useObjectNavItemColor(objectNameSingular); const objectIconForView = objectMetadataItem?.icon != null ? getIcon(objectMetadataItem.icon) : StandardIcon; const canShowViewOverlay = - isViewWithOverlay && - isDefined(objectIconForView) && - isDefined(navigationMenuItem.Icon); + isViewWithOverlay && isDefined(objectIconForView) && isDefined(view?.icon); if (canShowViewOverlay) { return ( ); } - if (navigationMenuItem.itemType === NavigationMenuItemType.LINK) { + if (navigationMenuItem.type === NavigationMenuItemType.LINK) { + const computedLink = getNavigationMenuItemComputedLink( + navigationMenuItem, + objectMetadataItems, + views, + ); return ( ); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone.tsx index 7e19f194e5..9d857698ae 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone.tsx @@ -6,21 +6,24 @@ import { import { useContext } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { ThemeContext } from 'twenty-ui/theme-constants'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; +import { getNavigationMenuItemObjectNameSingular } from '@/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular'; import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { ViewKey } from '@/views/types/ViewKey'; type WorkspaceNavigationMenuItemFolderDragCloneProps = { draggableProvided: DraggableProvided; draggableSnapshot: DraggableStateSnapshot; rubric: DraggableRubric; - navigationMenuItems: ProcessedNavigationMenuItem[]; + navigationMenuItems: NavigationMenuItem[]; navigationMenuItemFolderContentLength: number; selectedNavigationMenuItemIndex: number; }; @@ -35,12 +38,28 @@ export const WorkspaceNavigationMenuItemFolderDragClone = ({ }: WorkspaceNavigationMenuItemFolderDragCloneProps) => { const { theme } = useContext(ThemeContext); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); const navigationMenuItem = navigationMenuItems[rubric.source.index]; if (!isDefined(navigationMenuItem)) { return null; } + const label = getNavigationMenuItemLabel( + navigationMenuItem, + objectMetadataItems, + views, + ); + const objectNameSingular = getNavigationMenuItemObjectNameSingular( + navigationMenuItem, + objectMetadataItems, + views, + ); + const view = isDefined(navigationMenuItem.viewId) + ? views.find((view) => view.id === navigationMenuItem.viewId) + : undefined; + const isIndexView = view?.key === ViewKey.INDEX; + return (
( )} diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderSubItem.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderSubItem.tsx index 907a13c401..07dc2be02c 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderSubItem.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderSubItem.tsx @@ -1,12 +1,15 @@ +import { NavigationMenuItemType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { type NavigationMenuItemClickParams } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState'; +import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/utils/getNavigationMenuItemComputedLink'; +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; +import { getNavigationMenuItemObjectNameSingular } from '@/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular'; import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel'; import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; @@ -15,7 +18,7 @@ import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { ViewKey } from '@/views/types/ViewKey'; type WorkspaceNavigationMenuItemFolderSubItemProps = { - navigationMenuItem: ProcessedNavigationMenuItem; + navigationMenuItem: NavigationMenuItem; index: number; arrayLength: number; selectedNavigationMenuItemIndex: number; @@ -38,20 +41,22 @@ export const WorkspaceNavigationMenuItemFolderSubItem = ({ ); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); const views = useAtomStateValue(viewsSelector); + const objectMetadataItem = - navigationMenuItem.itemType === NavigationMenuItemType.OBJECT || - navigationMenuItem.itemType === NavigationMenuItemType.VIEW || - navigationMenuItem.itemType === NavigationMenuItemType.RECORD + navigationMenuItem.type === NavigationMenuItemType.OBJECT || + navigationMenuItem.type === NavigationMenuItemType.VIEW || + navigationMenuItem.type === NavigationMenuItemType.RECORD ? getObjectMetadataForNavigationMenuItem( navigationMenuItem, objectMetadataItems, views, ) : null; + const isEditableInEditMode = isNavigationMenuInEditMode && isDefined(onNavigationMenuItemClick) && - (navigationMenuItem.itemType === NavigationMenuItemType.LINK || + (navigationMenuItem.type === NavigationMenuItemType.LINK || isDefined(objectMetadataItem)); const handleEditModeClick = @@ -63,26 +68,42 @@ export const WorkspaceNavigationMenuItemFolderSubItem = ({ }) : undefined; + const label = getNavigationMenuItemLabel( + navigationMenuItem, + objectMetadataItems, + views, + ); + const computedLink = getNavigationMenuItemComputedLink( + navigationMenuItem, + objectMetadataItems, + views, + ); + const objectNameSingular = getNavigationMenuItemObjectNameSingular( + navigationMenuItem, + objectMetadataItems, + views, + ); + + const view = isDefined(navigationMenuItem.viewId) + ? views.find((view) => view.id === navigationMenuItem.viewId) + : undefined; + const isIndexView = view?.key === ViewKey.INDEX; + return ( ( )} - to={ - isContextDragging || handleEditModeClick - ? undefined - : navigationMenuItem.link - } + to={isContextDragging || handleEditModeClick ? undefined : computedLink} onClick={handleEditModeClick} active={index === selectedNavigationMenuItemIndex} isSelectedInEditMode={ diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx index 97f3ab89b9..6185f50fb1 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx @@ -13,7 +13,7 @@ import { LightIconButton } from 'twenty-ui/input'; import { themeCssVariables } from 'twenty-ui/theme-constants'; import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType, SidePanelPages } from 'twenty-shared/types'; import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInSidePanel'; import { useSortedNavigationMenuItems } from '@/navigation-menu-item/hooks/useSortedNavigationMenuItems'; import { @@ -25,16 +25,19 @@ import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/nav import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems'; +import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/utils/getNavigationMenuItemComputedLink'; +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; import { preloadWorkspaceDndKit } from '@/navigation/preloadWorkspaceDndKit'; import { NavigationDrawerSectionForWorkspaceItems } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItems'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { navigationMenuItemsSelector } from '@/navigation-menu-item/states/navigationMenuItemsSelector'; import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { useStore } from 'jotai'; -import { SidePanelPages } from 'twenty-shared/types'; const StyledRightIconsContainer = styled.div` align-items: center; @@ -45,6 +48,8 @@ const StyledRightIconsContainer = styled.div` export const WorkspaceNavigationMenuItems = () => { const items = useWorkspaceSectionItems(); const { workspaceNavigationMenuItemsSorted } = useSortedNavigationMenuItems(); + const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); const store = useStore(); const enterEditMode = () => { const currentNavigationMenuItems = store.get( @@ -88,16 +93,27 @@ export const WorkspaceNavigationMenuItems = () => { ); openNavigationMenuItemInSidePanel({ pageTitle: t`Edit folder`, - pageIcon: getIcon(item.icon ?? item.Icon ?? FOLDER_ICON_DEFAULT), + pageIcon: getIcon(item.icon ?? FOLDER_ICON_DEFAULT), }); - const firstChild = workspaceNavigationMenuItemsSorted.find( - (navItem) => - navItem.folderId === folderId && - navItem.itemType !== NavigationMenuItemType.LINK && - isNonEmptyString(navItem.link), - ); - if (firstChild?.link) { - navigate(firstChild.link); + const firstChild = workspaceNavigationMenuItemsSorted.find((navItem) => { + if (navItem.folderId !== folderId) return false; + if (navItem.type === NavigationMenuItemType.LINK) return false; + const link = getNavigationMenuItemComputedLink( + navItem, + objectMetadataItems, + views, + ); + return isNonEmptyString(link); + }); + if (firstChild) { + const link = getNavigationMenuItemComputedLink( + firstChild, + objectMetadataItems, + views, + ); + if (isNonEmptyString(link)) { + navigate(link); + } } }; @@ -106,16 +122,21 @@ export const WorkspaceNavigationMenuItems = () => { objectMetadataItem: ObjectMetadataItem | null | undefined, ) => { if (objectMetadataItem) { + const label = + item.type === NavigationMenuItemType.VIEW || + item.type === NavigationMenuItemType.OBJECT + ? getNavigationMenuItemLabel(item, objectMetadataItems, views) + : objectMetadataItem.labelSingular; openNavigationMenuItemInSidePanel({ - pageTitle: - item.itemType === NavigationMenuItemType.VIEW || - item.itemType === NavigationMenuItemType.OBJECT - ? item.labelIdentifier - : objectMetadataItem.labelSingular, + pageTitle: label, pageIcon: getIcon(objectMetadataItem.icon), }); } - const link = 'link' in item ? item.link : undefined; + const link = getNavigationMenuItemComputedLink( + item, + objectMetadataItems, + views, + ); if (isNonEmptyString(link)) { navigate(link); } @@ -127,7 +148,7 @@ export const WorkspaceNavigationMenuItems = () => { const { item, objectMetadataItem } = params; setSelectedNavigationMenuItemInEditMode(item.id); - switch (item.itemType) { + switch (item.type) { case NavigationMenuItemType.FOLDER: openFolderAndNavigateToFirstChild(item.id, item); break; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder.tsx index 27219b45cd..0761e505e4 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder.tsx @@ -36,8 +36,8 @@ import { SortableDropTargetRefContext } from '@/navigation-menu-item/contexts/So import { type NavigationMenuItemClickParams } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState'; import { getDndKitDropTargetId } from '@/navigation-menu-item/utils/getDndKitDropTargetId'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; @@ -76,7 +76,7 @@ type WorkspaceNavigationMenuItemsFolderProps = { folderName: string; folderIconKey?: string | null; folderColor?: string | null; - navigationMenuItems: ProcessedNavigationMenuItem[]; + navigationMenuItems: NavigationMenuItem[]; isGroup: boolean; isSelectedInEditMode?: boolean; onEditModeClick?: () => void; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationMenuItemType.ts b/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationMenuItemType.ts deleted file mode 100644 index 53b05a3f6d..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationMenuItemType.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum NavigationMenuItemType { - FOLDER = 'FOLDER', - LINK = 'LINK', - OBJECT = 'OBJECT', - RECORD = 'RECORD', - VIEW = 'VIEW', -} diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts index ae2ba5137d..2552a30436 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts @@ -5,7 +5,7 @@ import { isDefined } from 'twenty-shared/utils'; import { IconFolder, IconLink, useIcons } from 'twenty-ui/display'; import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/constants/AddToNavSourceDroppableId'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft'; import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft'; import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft'; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts index d6c0b9ef98..ae5f3495aa 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts @@ -1,11 +1,9 @@ import { isDefined } from 'twenty-shared/utils'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; +import { filterAndSortNavigationMenuItems } from '@/navigation-menu-item/utils/filterAndSortNavigationMenuItems'; import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder'; -import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/utils/recordIdentifierToObjectRecordIdentifier'; -import { sortNavigationMenuItems } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -16,7 +14,7 @@ type NavigationMenuItemFolder = { folderName: string; icon?: string | null; color?: string | null; - navigationMenuItems: ReturnType[number][]; + navigationMenuItems: NavigationMenuItem[]; }; type NavigationMenuItemFolderEntry = Pick< @@ -85,43 +83,10 @@ export const useNavigationMenuItemsByFolder = () => { return sortedFolders.reduce((acc, folder) => { const itemsInFolder = itemsByFolderId.get(folder.id) || []; - const targetRecordIdentifiersMap = itemsInFolder.reduce< - Map - >((map, item) => { - const itemTargetRecordId = item.targetRecordId; - if (!isDefined(itemTargetRecordId) || isDefined(item.viewId)) { - return map; - } - - const targetRecordIdentifier = item.targetRecordIdentifier; - - if (!isDefined(targetRecordIdentifier)) { - return map; - } - - const itemObjectMetadata = objectMetadataItems.find( - (meta) => meta.id === item.targetObjectMetadataId, - ); - - if (isDefined(itemObjectMetadata)) { - const objectRecordIdentifier = - recordIdentifierToObjectRecordIdentifier({ - recordIdentifier: targetRecordIdentifier, - objectMetadataItem: itemObjectMetadata, - }); - - map.set(itemTargetRecordId, objectRecordIdentifier); - } - - return map; - }, new Map()); - - const sortedItems = sortNavigationMenuItems( + const sortedItems = filterAndSortNavigationMenuItems( itemsInFolder, - true, views, objectMetadataItems, - targetRecordIdentifiersMap, ); acc.push({ diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel.ts index ce05a9976f..9b332f7770 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel.ts @@ -1,20 +1,31 @@ -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -const getLabelForItemType = ( - itemType: NavigationMenuItemType, - item: { name?: string | null; labelIdentifier?: string | null }, +const getLabelForItem = ( + item: NavigationMenuItem, + objectMetadataItems: Parameters[1], + views: Parameters[2], objectLabelSingular?: string | null, ): string => { - switch (itemType) { + switch (item.type) { case NavigationMenuItemType.FOLDER: return item.name ?? 'Folder'; case NavigationMenuItemType.LINK: return item.name ?? 'Link'; case NavigationMenuItemType.OBJECT: case NavigationMenuItemType.VIEW: - return item.labelIdentifier ?? objectLabelSingular ?? ''; + return ( + getNavigationMenuItemLabel(item, objectMetadataItems, views) || + objectLabelSingular || + '' + ); default: return objectLabelSingular ?? ''; } @@ -24,11 +35,14 @@ export const useSelectedNavigationMenuItemEditItemLabel = () => { const { selectedItem } = useSelectedNavigationMenuItemEditItem(); const { selectedItemObjectMetadata } = useSelectedNavigationMenuItemEditItemObjectMetadata(); + const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); const selectedItemLabel = selectedItem - ? getLabelForItemType( - selectedItem.itemType, + ? getLabelForItem( selectedItem, + objectMetadataItems, + views, selectedItemObjectMetadata?.labelSingular, ) : null; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts index c164077553..5f00fa6d33 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts @@ -1,13 +1,7 @@ import { useMemo } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder'; -import { isNavigationMenuItemLink } from '@/navigation-menu-item/utils/isNavigationMenuItemLink'; -import { isNavigationMenuItemObject } from '@/navigation-menu-item/utils/isNavigationMenuItemObject'; -import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/utils/recordIdentifierToObjectRecordIdentifier'; -import { sortNavigationMenuItems } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; +import { filterAndSortNavigationMenuItems } from '@/navigation-menu-item/utils/filterAndSortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -19,99 +13,21 @@ export const useSortedNavigationMenuItems = () => { const views = useAtomStateValue(viewsSelector); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); - const targetRecordIdentifiers = useMemo(() => { - const identifiersMap = new Map(); - - [...navigationMenuItems, ...workspaceNavigationMenuItems].forEach( - (navigationMenuItem) => { - if (isDefined(navigationMenuItem.viewId)) { - return; - } - - const itemTargetRecordId = navigationMenuItem.targetRecordId; - if (!isDefined(itemTargetRecordId)) { - return; - } - - const targetRecordIdentifier = - navigationMenuItem.targetRecordIdentifier; - if (!isDefined(targetRecordIdentifier)) { - return; - } - - const objectMetadataItem = objectMetadataItems.find( - (item) => item.id === navigationMenuItem.targetObjectMetadataId, - ); - - if (!isDefined(objectMetadataItem)) { - return; - } - - const objectRecordIdentifier = recordIdentifierToObjectRecordIdentifier( - { - recordIdentifier: targetRecordIdentifier, - objectMetadataItem, - }, - ); - - identifiersMap.set(itemTargetRecordId, objectRecordIdentifier); - }, - ); - - return identifiersMap; - }, [navigationMenuItems, workspaceNavigationMenuItems, objectMetadataItems]); - const navigationMenuItemsSorted = useMemo(() => { - return sortNavigationMenuItems( + return filterAndSortNavigationMenuItems( navigationMenuItems, - true, views, objectMetadataItems, - targetRecordIdentifiers, ); - }, [ - navigationMenuItems, - views, - objectMetadataItems, - targetRecordIdentifiers, - ]); + }, [navigationMenuItems, views, objectMetadataItems]); const workspaceNavigationMenuItemsSorted = useMemo(() => { - const filtered = workspaceNavigationMenuItems.filter((item) => { - if (isNavigationMenuItemFolder(item)) { - return true; - } - if (isNavigationMenuItemLink(item)) { - return true; - } - if (isNavigationMenuItemObject(item)) { - return isDefined(item.targetObjectMetadataId); - } - if (isDefined(item.viewId)) { - return views.some((view) => view.id === item.viewId); - } - - const itemTargetRecordId = item.targetRecordId; - if (!isDefined(itemTargetRecordId)) { - return false; - } - const matchesTargetRecord = - targetRecordIdentifiers.has(itemTargetRecordId); - return matchesTargetRecord; - }); - return sortNavigationMenuItems( - filtered, - true, + return filterAndSortNavigationMenuItems( + workspaceNavigationMenuItems, views, objectMetadataItems, - targetRecordIdentifiers, ); - }, [ - workspaceNavigationMenuItems, - views, - objectMetadataItems, - targetRecordIdentifiers, - ]); + }, [workspaceNavigationMenuItems, views, objectMetadataItems]); return { navigationMenuItemsSorted, diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceFolderOpenState.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceFolderOpenState.ts index fa1078afb2..8add083ab9 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceFolderOpenState.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceFolderOpenState.ts @@ -1,19 +1,23 @@ import { useLocation, useNavigate } from 'react-router-dom'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useIsMobile } from 'twenty-ui/utilities'; import { isNonEmptyString } from '@sniptt/guards'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState'; +import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/utils/getNavigationMenuItemComputedLink'; import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem'; -import type { ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { currentNavigationMenuItemFolderIdState } from '@/ui/navigation/navigation-drawer/states/currentNavigationMenuItemFolderIdState'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; type UseWorkspaceFolderOpenStateParams = { folderId: string; - navigationMenuItems: ProcessedNavigationMenuItem[]; + navigationMenuItems: NavigationMenuItem[]; }; export const useWorkspaceFolderOpenState = ({ @@ -25,6 +29,8 @@ export const useWorkspaceFolderOpenState = ({ const currentPath = location.pathname; const currentViewPath = location.pathname + location.search; const isMobile = useIsMobile(); + const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); const [openNavigationMenuItemFolderIds, setOpenNavigationMenuItemFolderIds] = useAtomState(openNavigationMenuItemFolderIdsState); @@ -48,20 +54,44 @@ export const useWorkspaceFolderOpenState = ({ } if (!isOpen) { - const firstNonLinkItem = navigationMenuItems.find( - (item) => - item.itemType !== NavigationMenuItemType.LINK && - isNonEmptyString(item.link), - ); - if (isDefined(firstNonLinkItem?.link)) { - navigate(firstNonLinkItem.link); + const firstNonLinkItem = navigationMenuItems.find((item) => { + if (item.type === NavigationMenuItemType.LINK) { + return false; + } + const computedLink = getNavigationMenuItemComputedLink( + item, + objectMetadataItems, + views, + ); + return isNonEmptyString(computedLink); + }); + if (isDefined(firstNonLinkItem)) { + const link = getNavigationMenuItemComputedLink( + firstNonLinkItem, + objectMetadataItems, + views, + ); + if (isNonEmptyString(link)) { + navigate(link); + } } } }; const selectedNavigationMenuItemIndex = navigationMenuItems.findIndex( - (item) => - isLocationMatchingNavigationMenuItem(currentPath, currentViewPath, item), + (item) => { + const computedLink = getNavigationMenuItemComputedLink( + item, + objectMetadataItems, + views, + ); + return isLocationMatchingNavigationMenuItem( + currentPath, + currentViewPath, + item.type, + computedLink, + ); + }, ); return { diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts index b8485f346b..9bc6a062c3 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts @@ -1,8 +1,7 @@ import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder'; import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject'; @@ -17,19 +16,12 @@ import { useNavigationMenuItemsByFolder } from './useNavigationMenuItemsByFolder import { useNavigationMenuItemsData } from './useNavigationMenuItemsData'; import { useSortedNavigationMenuItems } from './useSortedNavigationMenuItems'; -export type FlatWorkspaceItem = - | ProcessedNavigationMenuItem - | (NavigationMenuItem & { - itemType: NavigationMenuItemType.FOLDER; - Icon: string; - }); - export type NavigationMenuItemClickParams = { - item: FlatWorkspaceItem; + item: NavigationMenuItem; objectMetadataItem?: ObjectMetadataItem | null; }; -export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { +export const useWorkspaceSectionItems = (): NavigationMenuItem[] => { const { workspaceNavigationMenuItems } = useNavigationMenuItemsData(); const { workspaceNavigationMenuItemsSorted } = useSortedNavigationMenuItems(); const { workspaceNavigationMenuItemsByFolder } = @@ -42,7 +34,7 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { .filter((item) => !isDefined(item.folderId)) .sort((a, b) => a.position - b.position); - const processedObjectViewsById = new Map( + const processedItemsById = new Map( workspaceNavigationMenuItemsSorted.map((item) => [item.id, item]), ); @@ -53,25 +45,24 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { ]), ); - const flatItems: FlatWorkspaceItem[] = flatWorkspaceItems.reduce< - FlatWorkspaceItem[] + const flatItems: NavigationMenuItem[] = flatWorkspaceItems.reduce< + NavigationMenuItem[] >((acc, item) => { if (isNavigationMenuItemFolder(item)) { acc.push({ ...item, - itemType: NavigationMenuItemType.FOLDER, - Icon: item.icon ?? FOLDER_ICON_DEFAULT, + icon: item.icon ?? FOLDER_ICON_DEFAULT, }); } else { - const processedItem = processedObjectViewsById.get(item.id); - if (!isDefined(processedItem)) { + const validItem = processedItemsById.get(item.id); + if (!isDefined(validItem)) { return acc; } - if (processedItem.itemType === NavigationMenuItemType.LINK) { - acc.push(processedItem); + if (validItem.type === NavigationMenuItemType.LINK) { + acc.push(validItem); } else { const objectMetadataItem = getObjectMetadataForNavigationMenuItem( - processedItem, + validItem, objectMetadataItems, views, ); @@ -82,7 +73,7 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { objectMetadataItem.id, ).canReadObjectRecords ) { - acc.push(processedItem); + acc.push(validItem); } } } @@ -90,7 +81,7 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { }, []); return flatItems.flatMap((item) => - item.itemType === NavigationMenuItemType.FOLDER + item.type === NavigationMenuItemType.FOLDER ? [item, ...(folderChildrenById.get(item.id) ?? [])] : [item], ); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/types/processed-navigation-menu-item.ts b/packages/twenty-front/src/modules/navigation-menu-item/types/processed-navigation-menu-item.ts deleted file mode 100644 index fbe2cb33e0..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/types/processed-navigation-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type ViewKey } from '@/views/types/ViewKey'; -import { type NavigationMenuItem } from '~/generated-metadata/graphql'; - -import { type NavigationMenuItemDisplayFields } from '@/navigation-menu-item/utils/computeNavigationMenuItemDisplayFields'; -import { type NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; - -export type ProcessedNavigationMenuItem = NavigationMenuItem & - NavigationMenuItemDisplayFields & { - viewKey?: ViewKey | null; - itemType: NavigationMenuItemType; - }; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/computeNavigationMenuItemDisplayFields.test.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/computeNavigationMenuItemDisplayFields.test.ts deleted file mode 100644 index 907aa76b62..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/computeNavigationMenuItemDisplayFields.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { computeNavigationMenuItemDisplayFields } from '@/navigation-menu-item/utils/computeNavigationMenuItemDisplayFields'; -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; - -describe('computeNavigationMenuItemDisplayFields', () => { - const mockObjectMetadataItem: ObjectMetadataItem = { - id: 'metadata-id', - nameSingular: 'person', - namePlural: 'people', - labelSingular: 'Person', - labelPlural: 'People', - } as ObjectMetadataItem; - - const mockObjectRecordIdentifier: ObjectRecordIdentifier = { - id: 'record-id', - name: 'John Doe', - avatarUrl: 'https://example.com/avatar.jpg', - avatarType: 'rounded', - linkToShowPage: '/app/objects/people/record-id', - }; - - it('should return null when objectMetadataItem or objectRecordIdentifier is null', () => { - expect( - computeNavigationMenuItemDisplayFields(null, mockObjectRecordIdentifier), - ).toBeNull(); - expect( - computeNavigationMenuItemDisplayFields(mockObjectMetadataItem, null), - ).toBeNull(); - }); - - it('should return display fields from metadata and record identifier', () => { - const result = computeNavigationMenuItemDisplayFields( - mockObjectMetadataItem, - mockObjectRecordIdentifier, - ); - - expect(result).toEqual({ - labelIdentifier: 'John Doe', - avatarUrl: 'https://example.com/avatar.jpg', - avatarType: 'rounded', - link: '/app/objects/people/record-id', - objectNameSingular: 'person', - }); - }); - - it('should default optional identifier fields to empty string or icon', () => { - const minimal: ObjectRecordIdentifier = { - id: 'record-id', - name: 'Jane Doe', - }; - const result = computeNavigationMenuItemDisplayFields( - mockObjectMetadataItem, - minimal, - ); - - expect(result?.avatarUrl).toBe(''); - expect(result?.avatarType).toBe('icon'); - expect(result?.link).toBe(''); - }); -}); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/filterAndSortNavigationMenuItems.test.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/filterAndSortNavigationMenuItems.test.ts new file mode 100644 index 0000000000..f39c721a50 --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/filterAndSortNavigationMenuItems.test.ts @@ -0,0 +1,242 @@ +import { NavigationMenuItemType } from 'twenty-shared/types'; + +import { filterAndSortNavigationMenuItems } from '@/navigation-menu-item/utils/filterAndSortNavigationMenuItems'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { type View } from '@/views/types/View'; +import { ViewKey } from '@/views/types/ViewKey'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +describe('filterAndSortNavigationMenuItems', () => { + const mockObjectMetadataItem: ObjectMetadataItem = { + id: 'metadata-id', + nameSingular: 'person', + namePlural: 'people', + labelPlural: 'People', + icon: 'IconUser', + } as ObjectMetadataItem; + + const mockView: Pick = { + id: 'view-id', + objectMetadataId: 'metadata-id', + key: ViewKey.INDEX, + }; + + it('should return empty array when navigationMenuItems is empty', () => { + const result = filterAndSortNavigationMenuItems([], [], []); + expect(result).toEqual([]); + }); + + it('should keep view items when view and objectMetadata exist', () => { + const navigationMenuItem = { + id: 'item-id', + type: NavigationMenuItemType.VIEW, + viewId: 'view-id', + position: 1, + } as NavigationMenuItem; + + const result = filterAndSortNavigationMenuItems( + [navigationMenuItem], + [mockView], + [mockObjectMetadataItem], + ); + + expect(result).toHaveLength(1); + expect(result[0].id).toBe('item-id'); + }); + + it('should filter out view items when view is not found', () => { + const navigationMenuItem = { + id: 'item-id', + type: NavigationMenuItemType.VIEW, + viewId: 'non-existent-view-id', + position: 1, + } as NavigationMenuItem; + + const result = filterAndSortNavigationMenuItems( + [navigationMenuItem], + [], + [mockObjectMetadataItem], + ); + + expect(result).toEqual([]); + }); + + it('should keep record items when targetRecordIdentifier exists', () => { + const navigationMenuItem = { + id: 'item-id', + type: NavigationMenuItemType.RECORD, + targetRecordId: 'record-id', + targetObjectMetadataId: 'metadata-id', + targetRecordIdentifier: { + id: 'record-id', + labelIdentifier: 'John Doe', + }, + position: 2, + } as NavigationMenuItem; + + const result = filterAndSortNavigationMenuItems( + [navigationMenuItem], + [], + [mockObjectMetadataItem], + ); + + expect(result).toHaveLength(1); + expect(result[0].id).toBe('item-id'); + }); + + it('should filter out record items when targetRecordId is not defined', () => { + const navigationMenuItem = { + id: 'item-id', + type: NavigationMenuItemType.RECORD, + targetObjectMetadataId: 'metadata-id', + position: 1, + } as NavigationMenuItem; + + const result = filterAndSortNavigationMenuItems( + [navigationMenuItem], + [], + [mockObjectMetadataItem], + ); + + expect(result).toEqual([]); + }); + + it('should filter out record items when objectMetadataItem is not found', () => { + const navigationMenuItem = { + id: 'item-id', + type: NavigationMenuItemType.RECORD, + targetRecordId: 'record-id', + targetObjectMetadataId: 'non-existent-metadata-id', + targetRecordIdentifier: { + id: 'record-id', + labelIdentifier: 'John', + }, + position: 1, + } as NavigationMenuItem; + + const result = filterAndSortNavigationMenuItems( + [navigationMenuItem], + [], + [mockObjectMetadataItem], + ); + + expect(result).toEqual([]); + }); + + it('should sort navigation menu items by position', () => { + const navigationMenuItems = [ + { + id: 'item-3', + type: NavigationMenuItemType.RECORD, + targetRecordId: 'record-id-3', + targetObjectMetadataId: 'metadata-id', + targetRecordIdentifier: { + id: 'record-id-3', + labelIdentifier: 'C', + }, + position: 3, + }, + { + id: 'item-1', + type: NavigationMenuItemType.RECORD, + targetRecordId: 'record-id-1', + targetObjectMetadataId: 'metadata-id', + targetRecordIdentifier: { + id: 'record-id-1', + labelIdentifier: 'A', + }, + position: 1, + }, + { + id: 'item-2', + type: NavigationMenuItemType.RECORD, + targetRecordId: 'record-id-2', + targetObjectMetadataId: 'metadata-id', + targetRecordIdentifier: { + id: 'record-id-2', + labelIdentifier: 'B', + }, + position: 2, + }, + ] as NavigationMenuItem[]; + + const result = filterAndSortNavigationMenuItems( + navigationMenuItems, + [], + [mockObjectMetadataItem], + ); + + expect(result).toHaveLength(3); + expect(result[0].id).toBe('item-1'); + expect(result[1].id).toBe('item-2'); + expect(result[2].id).toBe('item-3'); + }); + + it('should keep link items', () => { + const result = filterAndSortNavigationMenuItems( + [ + { + id: 'link-1', + type: NavigationMenuItemType.LINK, + link: 'https://example.com', + name: 'My Link', + position: 1, + } as NavigationMenuItem, + ], + [], + [], + ); + expect(result).toHaveLength(1); + expect(result[0].id).toBe('link-1'); + }); + + it('should keep object items when objectMetadata exists', () => { + const result = filterAndSortNavigationMenuItems( + [ + { + id: 'obj-1', + type: NavigationMenuItemType.OBJECT, + targetObjectMetadataId: 'metadata-id', + position: 1, + } as NavigationMenuItem, + ], + [], + [mockObjectMetadataItem], + ); + expect(result).toHaveLength(1); + expect(result[0].id).toBe('obj-1'); + }); + + it('should filter out object items when objectMetadata is not found', () => { + const result = filterAndSortNavigationMenuItems( + [ + { + id: 'obj-1', + type: NavigationMenuItemType.OBJECT, + targetObjectMetadataId: 'non-existent-id', + position: 1, + } as NavigationMenuItem, + ], + [], + [mockObjectMetadataItem], + ); + expect(result).toEqual([]); + }); + + it('should keep folder items', () => { + const result = filterAndSortNavigationMenuItems( + [ + { + id: 'folder-1', + type: NavigationMenuItemType.FOLDER, + name: 'My Folder', + position: 1, + } as NavigationMenuItem, + ], + [], + [], + ); + expect(result).toHaveLength(1); + expect(result[0].id).toBe('folder-1'); + }); +}); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/getObjectMetadataForNavigationMenuItem.test.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/getObjectMetadataForNavigationMenuItem.test.ts index a74f357fda..262aa58a10 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/getObjectMetadataForNavigationMenuItem.test.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/getObjectMetadataForNavigationMenuItem.test.ts @@ -1,4 +1,4 @@ -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type View } from '@/views/types/View'; @@ -30,7 +30,7 @@ const mockViews: View[] = [ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for link item type', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: NavigationMenuItemType.LINK }, + { type: NavigationMenuItemType.LINK }, mockObjectMetadataItems, mockViews, ); @@ -39,7 +39,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return object metadata for view item when view and metadata exist', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: NavigationMenuItemType.VIEW, viewId: 'view-1' }, + { type: NavigationMenuItemType.VIEW, viewId: 'view-1' }, mockObjectMetadataItems, mockViews, ); @@ -49,7 +49,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for view item when view is not found', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: NavigationMenuItemType.VIEW, viewId: 'non-existent-view' }, + { type: NavigationMenuItemType.VIEW, viewId: 'non-existent-view' }, mockObjectMetadataItems, mockViews, ); @@ -61,7 +61,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { { id: 'orphan-view', objectMetadataId: 'non-existent-metadata' } as View, ]; const result = getObjectMetadataForNavigationMenuItem( - { itemType: NavigationMenuItemType.VIEW, viewId: 'orphan-view' }, + { type: NavigationMenuItemType.VIEW, viewId: 'orphan-view' }, mockObjectMetadataItems, viewsWithOrphanView, ); @@ -71,7 +71,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return object metadata for record item when metadata exists', () => { const result = getObjectMetadataForNavigationMenuItem( { - itemType: NavigationMenuItemType.RECORD, + type: NavigationMenuItemType.RECORD, targetObjectMetadataId: 'metadata-2', }, mockObjectMetadataItems, @@ -83,7 +83,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for record item when targetObjectMetadataId is undefined', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: NavigationMenuItemType.RECORD }, + { type: NavigationMenuItemType.RECORD }, mockObjectMetadataItems, mockViews, ); @@ -93,7 +93,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for record item when metadata is not found', () => { const result = getObjectMetadataForNavigationMenuItem( { - itemType: NavigationMenuItemType.RECORD, + type: NavigationMenuItemType.RECORD, targetObjectMetadataId: 'non-existent-metadata', }, mockObjectMetadataItems, @@ -104,7 +104,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for view item when viewId is undefined', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: NavigationMenuItemType.VIEW }, + { type: NavigationMenuItemType.VIEW }, mockObjectMetadataItems, mockViews, ); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/isLocationMatchingNavigationMenuItem.test.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/isLocationMatchingNavigationMenuItem.test.ts index 93fe33d938..c279e589de 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/isLocationMatchingNavigationMenuItem.test.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/isLocationMatchingNavigationMenuItem.test.ts @@ -1,4 +1,4 @@ -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem'; describe('isLocationMatchingNavigationMenuItem', () => { @@ -7,20 +7,16 @@ describe('isLocationMatchingNavigationMenuItem', () => { isLocationMatchingNavigationMenuItem( '/app/objects/people', '/app/objects/people?viewId=123', - { - itemType: NavigationMenuItemType.RECORD, - link: '/app/objects/people', - }, + NavigationMenuItemType.RECORD, + '/app/objects/people', ), ).toBe(true); expect( isLocationMatchingNavigationMenuItem( '/app/objects/companies', '/app/objects/companies?viewId=123', - { - itemType: NavigationMenuItemType.VIEW, - link: '/app/objects/companies?viewId=123', - }, + NavigationMenuItemType.VIEW, + '/app/objects/companies?viewId=123', ), ).toBe(true); }); @@ -30,20 +26,16 @@ describe('isLocationMatchingNavigationMenuItem', () => { isLocationMatchingNavigationMenuItem( '/app/objects/people', '/app/objects/people?viewId=123', - { - itemType: NavigationMenuItemType.RECORD, - link: '/app/objects/company', - }, + NavigationMenuItemType.RECORD, + '/app/objects/company', ), ).toBe(false); expect( isLocationMatchingNavigationMenuItem( '/app/objects/companies', '/app/objects/companies?viewId=123', - { - itemType: NavigationMenuItemType.VIEW, - link: '/app/objects/companies?viewId=456', - }, + NavigationMenuItemType.VIEW, + '/app/objects/companies?viewId=456', ), ).toBe(false); }); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/sortNavigationMenuItems.test.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/sortNavigationMenuItems.test.ts deleted file mode 100644 index 14460be873..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/sortNavigationMenuItems.test.ts +++ /dev/null @@ -1,360 +0,0 @@ -import { NavigationMenuItemType } from 'twenty-shared/types'; - -import { sortNavigationMenuItems } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; -import { type View } from '@/views/types/View'; -import { ViewKey } from '@/views/types/ViewKey'; -import { type NavigationMenuItem } from '~/generated-metadata/graphql'; - -jest.mock('twenty-shared/utils', () => { - const actual = jest.requireActual('twenty-shared/utils'); - return { - ...actual, - getAppPath: jest.fn((path, params, query) => { - const basePath = `/app/objects/${params.objectNamePlural}`; - const viewId = query?.viewId; - if (viewId !== undefined && viewId !== null) { - return `${basePath}?viewId=${viewId}`; - } - return basePath; - }), - }; -}); - -describe('sortNavigationMenuItems', () => { - const mockObjectMetadataItem: ObjectMetadataItem = { - id: 'metadata-id', - nameSingular: 'person', - namePlural: 'people', - labelPlural: 'People', - icon: 'IconUser', - } as ObjectMetadataItem; - - const mockView: Pick< - View, - 'id' | 'name' | 'objectMetadataId' | 'icon' | 'key' - > = { - id: 'view-id', - name: 'All People', - objectMetadataId: 'metadata-id', - icon: 'IconUser', - key: ViewKey.INDEX, - }; - - const mockObjectRecordIdentifier: ObjectRecordIdentifier = { - id: 'record-id', - name: 'John Doe', - avatarUrl: 'https://example.com/avatar.jpg', - avatarType: 'rounded', - linkToShowPage: '/app/objects/people/record-id', - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should return empty array when navigationMenuItems is empty', () => { - const result = sortNavigationMenuItems([], true, [], [], new Map()); - - expect(result).toEqual([]); - }); - - it('should process view link navigation menu items', () => { - const navigationMenuItem: NavigationMenuItem = { - id: 'item-id', - viewId: 'view-id', - position: 1, - } as NavigationMenuItem; - - const result = sortNavigationMenuItems( - [navigationMenuItem], - true, - [mockView], - [mockObjectMetadataItem], - new Map(), - ); - - expect(result).toHaveLength(1); - expect(result[0]).toMatchObject({ - id: 'item-id', - viewId: 'view-id', - position: 1, - labelIdentifier: 'People', - objectNameSingular: 'person', - Icon: 'IconUser', - }); - expect(result[0].link).toContain('viewId=view-id'); - }); - - it('should return null for view link when view is not found', () => { - const navigationMenuItem: NavigationMenuItem = { - id: 'item-id', - viewId: 'non-existent-view-id', - position: 1, - } as NavigationMenuItem; - - const result = sortNavigationMenuItems( - [navigationMenuItem], - true, - [], - [mockObjectMetadataItem], - new Map(), - ); - - expect(result).toEqual([]); - }); - - it('should process record link navigation menu items', () => { - const navigationMenuItem: NavigationMenuItem = { - id: 'item-id', - targetRecordId: 'record-id', - targetObjectMetadataId: 'metadata-id', - position: 2, - } as NavigationMenuItem; - - const targetRecordIdentifiers = new Map([ - ['record-id', mockObjectRecordIdentifier], - ]); - - const result = sortNavigationMenuItems( - [navigationMenuItem], - true, - [], - [mockObjectMetadataItem], - targetRecordIdentifiers, - ); - - expect(result).toHaveLength(1); - expect(result[0]).toMatchObject({ - id: 'item-id', - targetRecordId: 'record-id', - targetObjectMetadataId: 'metadata-id', - position: 2, - labelIdentifier: 'John Doe', - avatarUrl: 'https://example.com/avatar.jpg', - avatarType: 'rounded', - link: '/app/objects/people/record-id', - objectNameSingular: 'person', - }); - }); - - it('should return null for record link when targetRecordId is not defined', () => { - const navigationMenuItem: NavigationMenuItem = { - id: 'item-id', - targetObjectMetadataId: 'metadata-id', - position: 1, - } as NavigationMenuItem; - - const result = sortNavigationMenuItems( - [navigationMenuItem], - true, - [], - [mockObjectMetadataItem], - new Map(), - ); - - expect(result).toEqual([]); - }); - - it('should return null for record link when objectMetadataItem is not found', () => { - const navigationMenuItem: NavigationMenuItem = { - id: 'item-id', - targetRecordId: 'record-id', - targetObjectMetadataId: 'non-existent-metadata-id', - position: 1, - } as NavigationMenuItem; - - const targetRecordIdentifiers = new Map([ - ['record-id', mockObjectRecordIdentifier], - ]); - - const result = sortNavigationMenuItems( - [navigationMenuItem], - true, - [], - [mockObjectMetadataItem], - targetRecordIdentifiers, - ); - - expect(result).toEqual([]); - }); - - it('should return null for record link when targetRecordIdentifier is not found', () => { - const navigationMenuItem: NavigationMenuItem = { - id: 'item-id', - targetRecordId: 'non-existent-record-id', - targetObjectMetadataId: 'metadata-id', - position: 1, - } as NavigationMenuItem; - - const result = sortNavigationMenuItems( - [navigationMenuItem], - true, - [], - [mockObjectMetadataItem], - new Map(), - ); - - expect(result).toEqual([]); - }); - - it('should return empty link when hasLinkToShowPage is false', () => { - const navigationMenuItem: NavigationMenuItem = { - id: 'item-id', - targetRecordId: 'record-id', - targetObjectMetadataId: 'metadata-id', - position: 2, - } as NavigationMenuItem; - - const targetRecordIdentifiers = new Map([ - ['record-id', mockObjectRecordIdentifier], - ]); - - const result = sortNavigationMenuItems( - [navigationMenuItem], - false, - [], - [mockObjectMetadataItem], - targetRecordIdentifiers, - ); - - expect(result).toHaveLength(1); - expect(result[0].link).toBe(''); - }); - - it('should sort navigation menu items by position', () => { - const navigationMenuItems: NavigationMenuItem[] = [ - { - id: 'item-3', - targetRecordId: 'record-id-3', - targetObjectMetadataId: 'metadata-id', - position: 3, - }, - { - id: 'item-1', - targetRecordId: 'record-id-1', - targetObjectMetadataId: 'metadata-id', - position: 1, - }, - { - id: 'item-2', - targetRecordId: 'record-id-2', - targetObjectMetadataId: 'metadata-id', - position: 2, - }, - ] as NavigationMenuItem[]; - - const targetRecordIdentifiers = new Map([ - ['record-id-1', { ...mockObjectRecordIdentifier, id: 'record-id-1' }], - ['record-id-2', { ...mockObjectRecordIdentifier, id: 'record-id-2' }], - ['record-id-3', { ...mockObjectRecordIdentifier, id: 'record-id-3' }], - ]); - - const result = sortNavigationMenuItems( - navigationMenuItems, - true, - [], - [mockObjectMetadataItem], - targetRecordIdentifiers, - ); - - expect(result).toHaveLength(3); - expect(result[0].id).toBe('item-1'); - expect(result[1].id).toBe('item-2'); - expect(result[2].id).toBe('item-3'); - }); - - it('should handle mixed view and record link items', () => { - const navigationMenuItems: NavigationMenuItem[] = [ - { - id: 'view-item', - viewId: 'view-id', - position: 1, - }, - { - id: 'record-item', - targetRecordId: 'record-id', - targetObjectMetadataId: 'metadata-id', - position: 2, - }, - ] as NavigationMenuItem[]; - - const targetRecordIdentifiers = new Map([ - ['record-id', mockObjectRecordIdentifier], - ]); - - const result = sortNavigationMenuItems( - navigationMenuItems, - true, - [mockView], - [mockObjectMetadataItem], - targetRecordIdentifiers, - ); - - expect(result).toHaveLength(2); - expect(result[0].id).toBe('view-item'); - expect(result[0].objectNameSingular).toBe('person'); - expect(result[1].id).toBe('record-item'); - expect(result[1].objectNameSingular).toBe('person'); - }); - - it('should handle navigationMenuItem with both viewId and targetRecordId (viewId takes precedence)', () => { - const navigationMenuItem: NavigationMenuItem = { - id: 'item-id', - viewId: 'view-id', - targetRecordId: 'record-id', - targetObjectMetadataId: 'metadata-id', - position: 1, - } as NavigationMenuItem; - - const result = sortNavigationMenuItems( - [navigationMenuItem], - true, - [mockView], - [mockObjectMetadataItem], - new Map(), - ); - - expect(result).toHaveLength(1); - expect(result[0].objectNameSingular).toBe('person'); - expect(result[0].viewId).toBe('view-id'); - }); - - it('should process link items with protocol normalization and label from name or link', () => { - const withProtocol = sortNavigationMenuItems( - [ - { - id: 'link-1', - type: NavigationMenuItemType.LINK, - link: 'https://example.com', - name: 'My Link', - position: 1, - } as NavigationMenuItem, - ], - true, - [], - [], - new Map(), - ); - expect(withProtocol[0].labelIdentifier).toBe('My Link'); - expect(withProtocol[0].link).toBe('https://example.com'); - - const noProtocol = sortNavigationMenuItems( - [ - { - id: 'link-2', - type: NavigationMenuItemType.LINK, - link: 'example.com', - position: 2, - } as NavigationMenuItem, - ], - true, - [], - [], - new Map(), - ); - expect(noProtocol[0].link).toBe('https://example.com'); - expect(noProtocol[0].labelIdentifier).toBe('example.com'); - }); -}); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/computeNavigationMenuItemDisplayFields.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/computeNavigationMenuItemDisplayFields.ts deleted file mode 100644 index 167589423a..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/computeNavigationMenuItemDisplayFields.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; -import { isDefined } from 'twenty-shared/utils'; - -export type NavigationMenuItemDisplayFields = { - labelIdentifier: string; - avatarUrl: string; - avatarType: 'icon' | 'rounded' | 'squared'; - link: string; - objectNameSingular: string; - Icon?: string; -}; - -export const computeNavigationMenuItemDisplayFields = ( - objectMetadataItem: ObjectMetadataItem | null, - objectRecordIdentifier: ObjectRecordIdentifier | null, -): NavigationMenuItemDisplayFields | null => { - if (!isDefined(objectMetadataItem) || !isDefined(objectRecordIdentifier)) { - return null; - } - - const objectNameSingular = objectMetadataItem.nameSingular; - - return { - labelIdentifier: objectRecordIdentifier.name, - avatarUrl: objectRecordIdentifier.avatarUrl ?? '', - avatarType: objectRecordIdentifier.avatarType ?? 'icon', - link: objectRecordIdentifier.linkToShowPage ?? '', - objectNameSingular, - }; -}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/filterAndSortNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/filterAndSortNavigationMenuItems.ts new file mode 100644 index 0000000000..af29fb5f1a --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/filterAndSortNavigationMenuItems.ts @@ -0,0 +1,51 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { type View } from '@/views/types/View'; +import { NavigationMenuItemType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +export const filterAndSortNavigationMenuItems = ( + navigationMenuItems: NavigationMenuItem[], + views: Pick[], + objectMetadataItems: Pick[], +): NavigationMenuItem[] => { + return navigationMenuItems + .filter((item) => { + if (item.type === NavigationMenuItemType.FOLDER) { + return true; + } + if (item.type === NavigationMenuItemType.LINK) { + return true; + } + if (item.type === NavigationMenuItemType.OBJECT) { + return ( + isDefined(item.targetObjectMetadataId) && + objectMetadataItems.some( + (meta) => meta.id === item.targetObjectMetadataId, + ) + ); + } + if (item.type === NavigationMenuItemType.VIEW) { + if (!isDefined(item.viewId)) { + return false; + } + const view = views.find((view) => view.id === item.viewId); + return ( + isDefined(view) && + objectMetadataItems.some((meta) => meta.id === view.objectMetadataId) + ); + } + if (item.type === NavigationMenuItemType.RECORD) { + return ( + isDefined(item.targetRecordId) && + isDefined(item.targetObjectMetadataId) && + isDefined(item.targetRecordIdentifier) && + objectMetadataItems.some( + (meta) => meta.id === item.targetObjectMetadataId, + ) + ); + } + return false; + }) + .sort((a, b) => a.position - b.position); +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor.ts index 0156896f40..697f9228a1 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor.ts @@ -2,27 +2,27 @@ import { isNonEmptyString } from '@sniptt/guards'; import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultColorFolder'; import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultColorLink'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; export const getEffectiveNavigationMenuItemColor = ( navigationMenuItem: { - itemType: NavigationMenuItemType; + type: NavigationMenuItemType; color?: string | null; }, objectColor?: string, ): string | undefined => { - if (navigationMenuItem.itemType === NavigationMenuItemType.FOLDER) { + if (navigationMenuItem.type === NavigationMenuItemType.FOLDER) { return isNonEmptyString(navigationMenuItem.color) ? navigationMenuItem.color : DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER; } - if (navigationMenuItem.itemType === NavigationMenuItemType.OBJECT) { + if (navigationMenuItem.type === NavigationMenuItemType.OBJECT) { return objectColor; } - if (navigationMenuItem.itemType === NavigationMenuItemType.VIEW) { + if (navigationMenuItem.type === NavigationMenuItemType.VIEW) { return objectColor; } - if (navigationMenuItem.itemType === NavigationMenuItemType.LINK) { + if (navigationMenuItem.type === NavigationMenuItemType.LINK) { return DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK; } return undefined; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemComputedLink.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemComputedLink.ts new file mode 100644 index 0000000000..58823ab98b --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemComputedLink.ts @@ -0,0 +1,76 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/utils/recordIdentifierToObjectRecordIdentifier'; +import { type View } from '@/views/types/View'; +import { ViewKey } from '@/views/types/ViewKey'; +import { AppPath, NavigationMenuItemType } from 'twenty-shared/types'; +import { getAppPath, isDefined } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +export const getNavigationMenuItemComputedLink = ( + item: NavigationMenuItem, + objectMetadataItems: ObjectMetadataItem[], + views: Pick[], +): string => { + switch (item.type) { + case NavigationMenuItemType.OBJECT: { + const objectMetadataItem = objectMetadataItems.find( + (meta) => meta.id === item.targetObjectMetadataId, + ); + if (!isDefined(objectMetadataItem)) { + return ''; + } + const indexView = views.find( + (view) => + view.objectMetadataId === objectMetadataItem.id && + view.key === ViewKey.INDEX, + ); + return getAppPath( + AppPath.RecordIndexPage, + { objectNamePlural: objectMetadataItem.namePlural }, + indexView ? { viewId: indexView.id } : {}, + ); + } + case NavigationMenuItemType.VIEW: { + const view = views.find((view) => view.id === item.viewId); + if (!isDefined(view)) { + return ''; + } + const objectMetadataItem = objectMetadataItems.find( + (meta) => meta.id === view.objectMetadataId, + ); + if (!isDefined(objectMetadataItem)) { + return ''; + } + return getAppPath( + AppPath.RecordIndexPage, + { objectNamePlural: objectMetadataItem.namePlural }, + { viewId: item.viewId! }, + ); + } + case NavigationMenuItemType.LINK: { + const linkUrl = (item.link ?? '').trim(); + if (linkUrl.startsWith('http://') || linkUrl.startsWith('https://')) { + return linkUrl; + } + return linkUrl ? `https://${linkUrl}` : ''; + } + case NavigationMenuItemType.RECORD: { + const objectMetadataItem = objectMetadataItems.find( + (meta) => meta.id === item.targetObjectMetadataId, + ); + if ( + !isDefined(objectMetadataItem) || + !isDefined(item.targetRecordIdentifier) + ) { + return ''; + } + const objectRecordIdentifier = recordIdentifierToObjectRecordIdentifier({ + recordIdentifier: item.targetRecordIdentifier, + objectMetadataItem, + }); + return objectRecordIdentifier.linkToShowPage ?? ''; + } + default: + return ''; + } +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemLabel.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemLabel.ts new file mode 100644 index 0000000000..cb9999a0ac --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemLabel.ts @@ -0,0 +1,49 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { type View } from '@/views/types/View'; +import { ViewKey } from '@/views/types/ViewKey'; +import { NavigationMenuItemType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +export const getNavigationMenuItemLabel = ( + item: NavigationMenuItem, + objectMetadataItems: Pick< + ObjectMetadataItem, + 'id' | 'labelPlural' | 'nameSingular' + >[], + views: Pick[], +): string => { + switch (item.type) { + case NavigationMenuItemType.OBJECT: { + const objectMetadataItem = objectMetadataItems.find( + (meta) => meta.id === item.targetObjectMetadataId, + ); + return objectMetadataItem?.labelPlural ?? ''; + } + case NavigationMenuItemType.VIEW: { + const view = views.find((view) => view.id === item.viewId); + if (!isDefined(view)) { + return ''; + } + if (view.key === ViewKey.INDEX) { + const objectMetadataItem = objectMetadataItems.find( + (meta) => meta.id === view.objectMetadataId, + ); + return objectMetadataItem?.labelPlural ?? view.name; + } + return view.name; + } + case NavigationMenuItemType.LINK: { + const linkUrl = (item.link ?? '').trim(); + return (item.name ?? linkUrl) || 'Link'; + } + case NavigationMenuItemType.RECORD: { + return item.targetRecordIdentifier?.labelIdentifier ?? ''; + } + case NavigationMenuItemType.FOLDER: { + return item.name ?? 'Folder'; + } + default: + return item.name ?? ''; + } +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular.ts new file mode 100644 index 0000000000..1282453778 --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular.ts @@ -0,0 +1,33 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { type View } from '@/views/types/View'; +import { NavigationMenuItemType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +export const getNavigationMenuItemObjectNameSingular = ( + item: NavigationMenuItem, + objectMetadataItems: Pick[], + views: Pick[], +): string | undefined => { + switch (item.type) { + case NavigationMenuItemType.OBJECT: + case NavigationMenuItemType.RECORD: { + const objectMetadataItem = objectMetadataItems.find( + (meta) => meta.id === item.targetObjectMetadataId, + ); + return objectMetadataItem?.nameSingular; + } + case NavigationMenuItemType.VIEW: { + const view = views.find((view) => view.id === item.viewId); + if (!isDefined(view)) { + return undefined; + } + const objectMetadataItem = objectMetadataItems.find( + (meta) => meta.id === view.objectMetadataId, + ); + return objectMetadataItem?.nameSingular; + } + default: + return undefined; + } +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem.ts index f66c2509fa..eb300cb3d8 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem.ts @@ -1,56 +1,56 @@ +import { NavigationMenuItemType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type View } from '@/views/types/View'; - -type NavigationMenuItemWithItemType = Pick< - ProcessedNavigationMenuItem, - 'itemType' | 'viewId' | 'targetObjectMetadataId' ->; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; export const getObjectMetadataForNavigationMenuItem = ( - navigationMenuItem: NavigationMenuItemWithItemType, + navigationMenuItem: Pick< + NavigationMenuItem, + 'type' | 'viewId' | 'targetObjectMetadataId' + >, objectMetadataItems: ObjectMetadataItem[], - views: View[], + views: Pick[], ): ObjectMetadataItem | null => { - if (navigationMenuItem.itemType === NavigationMenuItemType.LINK) { + if (navigationMenuItem.type === NavigationMenuItemType.LINK) { return null; } if ( - navigationMenuItem.itemType === NavigationMenuItemType.OBJECT && + navigationMenuItem.type === NavigationMenuItemType.OBJECT && isDefined(navigationMenuItem.targetObjectMetadataId) ) { - const objectMetadataItem = objectMetadataItems.find( - (meta) => meta.id === navigationMenuItem.targetObjectMetadataId, + return ( + objectMetadataItems.find( + (meta) => meta.id === navigationMenuItem.targetObjectMetadataId, + ) ?? null ); - return objectMetadataItem ?? null; } if ( - navigationMenuItem.itemType === NavigationMenuItemType.VIEW && + navigationMenuItem.type === NavigationMenuItemType.VIEW && isDefined(navigationMenuItem.viewId) ) { const view = views.find((view) => view.id === navigationMenuItem.viewId); if (!isDefined(view)) { return null; } - const objectMetadataItem = objectMetadataItems.find( - (meta) => meta.id === view.objectMetadataId, + return ( + objectMetadataItems.find((meta) => meta.id === view.objectMetadataId) ?? + null ); - return objectMetadataItem ?? null; } if ( - navigationMenuItem.itemType === NavigationMenuItemType.RECORD && + navigationMenuItem.type === NavigationMenuItemType.RECORD && isDefined(navigationMenuItem.targetObjectMetadataId) ) { - const objectMetadataItem = objectMetadataItems.find( - (meta) => meta.id === navigationMenuItem.targetObjectMetadataId, + return ( + objectMetadataItems.find( + (meta) => meta.id === navigationMenuItem.targetObjectMetadataId, + ) ?? null ); - return objectMetadataItem ?? null; } return null; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem.ts index f3b04b19ad..1dcee88bc6 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem.ts @@ -1,15 +1,15 @@ -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; +import { NavigationMenuItemType } from 'twenty-shared/types'; export const isLocationMatchingNavigationMenuItem = ( currentPath: string, currentViewPath: string, - navigationMenuItem: Pick, + navigationMenuItemType: NavigationMenuItemType, + computedLink: string, ) => { const isViewBasedItem = - navigationMenuItem.itemType === NavigationMenuItemType.VIEW || - navigationMenuItem.itemType === NavigationMenuItemType.OBJECT; + navigationMenuItemType === NavigationMenuItemType.VIEW || + navigationMenuItemType === NavigationMenuItemType.OBJECT; return isViewBasedItem - ? navigationMenuItem.link === currentViewPath - : navigationMenuItem.link === currentPath; + ? computedLink === currentViewPath + : computedLink === currentPath; }; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/sortNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/sortNavigationMenuItems.ts deleted file mode 100644 index 92115d5dbc..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/sortNavigationMenuItems.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; -import { type View } from '@/views/types/View'; -import { ViewKey } from '@/views/types/ViewKey'; -import { AppPath } from 'twenty-shared/types'; -import { getAppPath, isDefined } from 'twenty-shared/utils'; -import { type NavigationMenuItem } from '~/generated-metadata/graphql'; - -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; -import { - computeNavigationMenuItemDisplayFields, - type NavigationMenuItemDisplayFields, -} from './computeNavigationMenuItemDisplayFields'; -import { isNavigationMenuItemLink } from './isNavigationMenuItemLink'; - -export { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; -export type { ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; - -export const sortNavigationMenuItems = ( - navigationMenuItems: NavigationMenuItem[], - hasLinkToShowPage: boolean, - views: Pick[], - objectMetadataItems: ObjectMetadataItem[], - targetRecordIdentifiers: Map, -): ProcessedNavigationMenuItem[] => { - return navigationMenuItems - .map((navigationMenuItem) => { - if ( - navigationMenuItem.type === NavigationMenuItemType.OBJECT && - isDefined(navigationMenuItem.targetObjectMetadataId) - ) { - const objectMetadataItem = objectMetadataItems.find( - (meta) => meta.id === navigationMenuItem.targetObjectMetadataId, - ); - - if (!isDefined(objectMetadataItem)) { - return null; - } - - const indexView = views.find( - (view) => - view.objectMetadataId === objectMetadataItem.id && - view.key === ViewKey.INDEX, - ); - - const displayFields: NavigationMenuItemDisplayFields = { - labelIdentifier: objectMetadataItem.labelPlural, - avatarUrl: '', - avatarType: 'icon', - link: getAppPath( - AppPath.RecordIndexPage, - { objectNamePlural: objectMetadataItem.namePlural }, - indexView ? { viewId: indexView.id } : {}, - ), - objectNameSingular: objectMetadataItem.nameSingular, - Icon: objectMetadataItem.icon ?? 'IconBox', - }; - - return { - ...navigationMenuItem, - ...displayFields, - viewKey: ViewKey.INDEX, - itemType: NavigationMenuItemType.OBJECT, - }; - } - - if (isDefined(navigationMenuItem.viewId)) { - const view = views.find( - (view) => view.id === navigationMenuItem.viewId, - ); - - if (isDefined(view)) { - const objectMetadataItem = objectMetadataItems.find( - (meta) => meta.id === view.objectMetadataId, - ); - - if (!isDefined(objectMetadataItem)) { - return null; - } - - const namePlural = objectMetadataItem.namePlural; - - const isIndexView = view.key === ViewKey.INDEX; - const labelIdentifier = - isIndexView && isDefined(objectMetadataItem) - ? objectMetadataItem.labelPlural - : view.name; - const icon = - isIndexView && - isDefined(objectMetadataItem) && - isDefined(objectMetadataItem.icon) - ? objectMetadataItem.icon - : view.icon; - - const displayFields: NavigationMenuItemDisplayFields = { - labelIdentifier, - avatarUrl: '', - avatarType: 'icon', - link: getAppPath( - AppPath.RecordIndexPage, - { objectNamePlural: namePlural }, - { viewId: navigationMenuItem.viewId }, - ), - objectNameSingular: objectMetadataItem?.nameSingular ?? 'view', - Icon: icon, - }; - - return { - ...navigationMenuItem, - ...displayFields, - viewKey: view.key, - itemType: NavigationMenuItemType.VIEW, - }; - } - - return null; - } - - if (isNavigationMenuItemLink(navigationMenuItem)) { - const linkUrl = (navigationMenuItem.link ?? '').trim(); - const normalizedLink = - linkUrl.startsWith('http://') || linkUrl.startsWith('https://') - ? linkUrl - : `https://${linkUrl}`; - const displayFields: NavigationMenuItemDisplayFields = { - labelIdentifier: (navigationMenuItem.name ?? linkUrl) || 'Link', - avatarUrl: '', - avatarType: 'icon', - link: normalizedLink, - objectNameSingular: 'link', - Icon: 'IconLink', - }; - return { - ...navigationMenuItem, - ...displayFields, - itemType: NavigationMenuItemType.LINK, - }; - } - - if (!isDefined(navigationMenuItem.targetRecordId)) { - return null; - } - - const objectMetadataItem = objectMetadataItems.find( - (item) => item.id === navigationMenuItem.targetObjectMetadataId, - ); - - if (!isDefined(objectMetadataItem)) { - return null; - } - - const objectRecordIdentifier = targetRecordIdentifiers.get( - navigationMenuItem.targetRecordId, - ); - - if (!isDefined(objectRecordIdentifier)) { - return null; - } - - const displayFields = computeNavigationMenuItemDisplayFields( - objectMetadataItem, - objectRecordIdentifier, - ); - - if (!isDefined(displayFields)) { - return null; - } - - return { - ...navigationMenuItem, - ...displayFields, - link: hasLinkToShowPage ? displayFields.link : '', - itemType: NavigationMenuItemType.RECORD, - }; - }) - .filter(isDefined) - .sort((a, b) => a.position - b.position); -}; diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx index 416124e8d1..690f32ecc6 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx @@ -1,21 +1,29 @@ import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { useObjectNavItemColor } from '@/navigation-menu-item/hooks/useObjectNavItemColor'; import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; +import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/utils/getNavigationMenuItemComputedLink'; +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; +import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/utils/recordIdentifierToObjectRecordIdentifier'; import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { ViewKey } from '@/views/types/ViewKey'; import { useLocation } from 'react-router-dom'; -import { AppPath, CoreObjectNameSingular } from 'twenty-shared/types'; +import { + AppPath, + CoreObjectNameSingular, + NavigationMenuItemType, +} from 'twenty-shared/types'; import { getAppPath, isDefined } from 'twenty-shared/utils'; import { Avatar, useIcons } from 'twenty-ui/display'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; export type NavigationDrawerItemForObjectMetadataItemProps = { objectMetadataItem: ObjectMetadataItem; - navigationMenuItem?: ProcessedNavigationMenuItem; + navigationMenuItem?: NavigationMenuItem; isSelectedInEditMode?: boolean; onEditModeClick?: () => void; onActiveItemClickWhenNotInEditMode?: () => void; @@ -36,6 +44,8 @@ export const NavigationDrawerItemForObjectMetadataItem = ({ const lastVisitedViewPerObjectMetadataItem = useAtomStateValue( lastVisitedViewPerObjectMetadataItemState, ); + const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); const lastVisitedViewId = lastVisitedViewPerObjectMetadataItem?.[objectMetadataItem.id]; @@ -48,24 +58,28 @@ export const NavigationDrawerItemForObjectMetadataItem = ({ const currentPath = location.pathname; const currentPathWithSearch = `${location.pathname}${location.search}`; - const isRecord = - navigationMenuItem?.itemType === NavigationMenuItemType.RECORD; - const isView = navigationMenuItem?.itemType === NavigationMenuItemType.VIEW; - const isObject = - navigationMenuItem?.itemType === NavigationMenuItemType.OBJECT; + const isRecord = navigationMenuItem?.type === NavigationMenuItemType.RECORD; + const isView = navigationMenuItem?.type === NavigationMenuItemType.VIEW; + const isObject = navigationMenuItem?.type === NavigationMenuItemType.OBJECT; const hasCustomLink = isRecord || isView || isObject; const navigationPath = hasCustomLink - ? navigationMenuItem!.link + ? getNavigationMenuItemComputedLink( + navigationMenuItem!, + objectMetadataItems, + views, + ) : getAppPath( AppPath.RecordIndexPage, { objectNamePlural: objectMetadataItem.namePlural }, lastVisitedViewId ? { viewId: lastVisitedViewId } : undefined, ); + const computedLink = hasCustomLink ? navigationPath : ''; + const isActive = hasCustomLink ? (isView || isObject ? currentPathWithSearch : currentPath) === - navigationMenuItem!.link + computedLink : currentPath === getAppPath(AppPath.RecordIndexPage, { objectNamePlural: objectMetadataItem.namePlural, @@ -81,17 +95,32 @@ export const NavigationDrawerItemForObjectMetadataItem = ({ const shouldNavigate = !isNavigationMenuInEditMode; + const view = isDefined(navigationMenuItem?.viewId) + ? views.find((view) => view.id === navigationMenuItem!.viewId) + : undefined; + const viewKey = view?.key ?? null; + const isViewWithCustomName = - isView && - navigationMenuItem?.viewKey !== ViewKey.INDEX && - isDefined(navigationMenuItem?.labelIdentifier); + isView && viewKey !== ViewKey.INDEX && isDefined(view); + + const itemLabel = isDefined(navigationMenuItem) + ? getNavigationMenuItemLabel(navigationMenuItem, objectMetadataItems, views) + : objectMetadataItem.labelPlural; const label = isRecord - ? navigationMenuItem!.labelIdentifier + ? itemLabel : isViewWithCustomName - ? navigationMenuItem!.labelIdentifier + ? itemLabel : objectMetadataItem.labelPlural; + const recordIdentifier = + isRecord && isDefined(navigationMenuItem?.targetRecordIdentifier) + ? recordIdentifierToObjectRecordIdentifier({ + recordIdentifier: navigationMenuItem!.targetRecordIdentifier!, + objectMetadataItem, + }) + : null; + const Icon = isRecord ? () => ( ) - : isViewWithCustomName && isDefined(navigationMenuItem?.Icon) + : isViewWithCustomName && isDefined(view?.icon) ? () => ( ) diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemContent.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemContent.tsx index 0abc818035..da87abeb4c 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemContent.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemContent.tsx @@ -1,4 +1,4 @@ -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { NavigationDrawerSectionForWorkspaceItemFolderContent } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItemFolderContent'; import { NavigationDrawerSectionForWorkspaceItemLinkContent } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItemLinkContent'; import { NavigationDrawerSectionForWorkspaceItemObjectContent } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent'; @@ -18,7 +18,7 @@ export const NavigationDrawerSectionForWorkspaceItemContent = ({ onActiveObjectMetadataItemClick, readOnly, }: NavigationDrawerSectionForWorkspaceItemContentProps) => { - switch (item.itemType) { + switch (item.type) { case NavigationMenuItemType.FOLDER: return ( { const folderId = item.id; const folderName = item.name ?? 'Folder'; - const folderIconKey = item.Icon; + const folderIconKey = item.icon; const folderColor = 'color' in item ? item.color : undefined; const navigationMenuItems = folderChildrenById.get(item.id) ?? []; const isGroup = folderCount > 1; diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemLinkContent.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemLinkContent.tsx index 1025e2441c..479e0860a5 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemLinkContent.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemLinkContent.tsx @@ -1,9 +1,12 @@ import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState'; -import type { ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; +import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/utils/getNavigationMenuItemComputedLink'; +import { getNavigationMenuItemLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemLabel'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import type { WorkspaceSectionItemContentProps } from '@/object-metadata/components/WorkspaceSectionItemContentProps'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { IconArrowUpRight } from 'twenty-ui/display'; import { themeCssVariables } from 'twenty-ui/theme-constants'; @@ -18,15 +21,24 @@ export const NavigationDrawerSectionForWorkspaceItemLinkContent = ({ const isNavigationMenuInEditMode = useAtomStateValue( isNavigationMenuInEditModeState, ); - const linkItem = item as ProcessedNavigationMenuItem; + const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); + + const label = getNavigationMenuItemLabel(item, objectMetadataItems, views); + const computedLink = getNavigationMenuItemComputedLink( + item, + objectMetadataItems, + views, + ); + return ( } + Icon={() => } active={false} isSelectedInEditMode={editModeProps.isSelectedInEditMode} isDragging={isDragging} diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent.tsx index 0f5d87efbd..e488acaf4e 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent.tsx @@ -1,5 +1,4 @@ import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; -import type { ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { NavigationDrawerItemForObjectMetadataItem } from '@/object-metadata/components/NavigationDrawerItemForObjectMetadataItem'; import type { WorkspaceSectionItemContentProps } from '@/object-metadata/components/WorkspaceSectionItemContentProps'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -18,7 +17,7 @@ export const NavigationDrawerSectionForWorkspaceItemObjectContent = ({ const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); const views = useAtomStateValue(viewsSelector); const objectMetadataItem = getObjectMetadataForNavigationMenuItem( - item as ProcessedNavigationMenuItem, + item, objectMetadataItems, views, ); @@ -28,7 +27,7 @@ export const NavigationDrawerSectionForWorkspaceItemObjectContent = ({ return ( void; @@ -75,33 +72,34 @@ export const NavigationDrawerSectionForWorkspaceItems = ({ const isAddToNavigationDropTargetVisible = addToNavigationFallbackDestination?.droppableId === NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS; - const folderChildrenById = items.reduce< - Map - >((acc, item) => { - const folderId = item.folderId; - if (isDefined(folderId)) { - const children = acc.get(folderId) ?? []; - children.push(item as ProcessedNavigationMenuItem); - acc.set(folderId, children); - } - return acc; - }, new Map()); + const folderChildrenById = items.reduce>( + (acc, item) => { + const folderId = item.folderId; + if (isDefined(folderId)) { + const children = acc.get(folderId) ?? []; + children.push(item); + acc.set(folderId, children); + } + return acc; + }, + new Map(), + ); const filteredItems = flatItems.filter((item) => { - const type = item.itemType; + const itemType = item.type; if ( - type === NavigationMenuItemType.FOLDER || - type === NavigationMenuItemType.LINK + itemType === NavigationMenuItemType.FOLDER || + itemType === NavigationMenuItemType.LINK ) { return true; } if ( - type === NavigationMenuItemType.OBJECT || - type === NavigationMenuItemType.VIEW || - type === NavigationMenuItemType.RECORD + itemType === NavigationMenuItemType.OBJECT || + itemType === NavigationMenuItemType.VIEW || + itemType === NavigationMenuItemType.RECORD ) { const objectMetadataItem = getObjectMetadataForNavigationMenuItem( - item as ProcessedNavigationMenuItem, + item, objectMetadataItems, views, ); @@ -116,17 +114,19 @@ export const NavigationDrawerSectionForWorkspaceItems = ({ return false; }); - const getEditModeProps = (item: FlatWorkspaceItem): EditModeProps => { + const getEditModeProps = (item: NavigationMenuItem): EditModeProps => { const itemId = item.id; return { isSelectedInEditMode: selectedNavigationMenuItemId === itemId, onEditModeClick: onNavigationMenuItemClick ? () => { - const type = item.itemType; + const itemType = item.type; const objectMetadataItem = - type === 'OBJECT' || type === 'VIEW' || type === 'RECORD' + itemType === 'OBJECT' || + itemType === 'VIEW' || + itemType === 'RECORD' ? getObjectMetadataForNavigationMenuItem( - item as ProcessedNavigationMenuItem, + item, objectMetadataItems, views, ) diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListDndKit.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListDndKit.tsx index 7cc9d99223..1c65fa3ee4 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListDndKit.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListDndKit.tsx @@ -1,7 +1,7 @@ import { WorkspaceDndKitDroppableSlot } from '@/navigation-menu-item/components/WorkspaceDndKitDroppableSlot'; import { WorkspaceDndKitSortableItem } from '@/navigation-menu-item/components/WorkspaceDndKitSortableItem'; import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext'; import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection'; import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState'; @@ -46,7 +46,7 @@ export const WorkspaceSectionListDndKit = ({ NavigationDropTargetContext, ); const folderCount = filteredItems.filter( - (item) => item.itemType === NavigationMenuItemType.FOLDER, + (item) => item.type === NavigationMenuItemType.FOLDER, ).length; const isAddMenuItemButtonVisible = isNavigationMenuInEditMode; return ( diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListReadOnly.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListReadOnly.tsx index d3ed513b0f..033040e748 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListReadOnly.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListReadOnly.tsx @@ -1,8 +1,8 @@ import { styled } from '@linaria/react'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; -import type { FlatWorkspaceItem } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; +import type { NavigationMenuItem } from '~/generated-metadata/graphql'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import type { WorkspaceSectionListDndKitProps } from '@/object-metadata/components/WorkspaceSectionListDndKitProps'; import { NavigationDrawerSectionForWorkspaceItemContent } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItemContent'; @@ -29,12 +29,12 @@ export const NavigationDrawerSectionForWorkspaceItemsListReadOnly = ({ onActiveObjectMetadataItemClick, }: NavigationDrawerSectionForWorkspaceItemsListReadOnlyProps) => { const folderCount = filteredItems.filter( - (item) => item.itemType === NavigationMenuItemType.FOLDER, + (item) => item.type === NavigationMenuItemType.FOLDER, ).length; return ( - {filteredItems.map((item: FlatWorkspaceItem) => ( + {filteredItems.map((item: NavigationMenuItem) => ( ; + folderChildrenById: Map; folderCount: number; selectedNavigationMenuItemId: string | null; onNavigationMenuItemClick?: (params: NavigationMenuItemClickParams) => void; diff --git a/packages/twenty-front/src/modules/object-metadata/components/WorkspaceSectionListDndKitProps.ts b/packages/twenty-front/src/modules/object-metadata/components/WorkspaceSectionListDndKitProps.ts index 32d3a54bda..c9bfe31474 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/WorkspaceSectionListDndKitProps.ts +++ b/packages/twenty-front/src/modules/object-metadata/components/WorkspaceSectionListDndKitProps.ts @@ -1,16 +1,13 @@ -import type { - FlatWorkspaceItem, - NavigationMenuItemClickParams, -} from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; -import type { ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; +import type { NavigationMenuItemClickParams } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import type { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import type { NavigationMenuItem } from '~/generated-metadata/graphql'; import type { EditModeProps } from '@/object-metadata/components/EditModeProps'; export type WorkspaceSectionListDndKitProps = { - filteredItems: FlatWorkspaceItem[]; - getEditModeProps: (item: FlatWorkspaceItem) => EditModeProps; - folderChildrenById: Map; + filteredItems: NavigationMenuItem[]; + getEditModeProps: (item: NavigationMenuItem) => EditModeProps; + folderChildrenById: Map; selectedNavigationMenuItemId: string | null; onNavigationMenuItemClick?: (params: NavigationMenuItemClickParams) => void; onActiveObjectMetadataItemClick?: ( diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelFolderInfo.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelFolderInfo.tsx index a28185ce1d..e54310d16f 100644 --- a/packages/twenty-front/src/modules/side-panel/components/SidePanelFolderInfo.tsx +++ b/packages/twenty-front/src/modules/side-panel/components/SidePanelFolderInfo.tsx @@ -7,7 +7,7 @@ import { sidePanelPageInfoState } from '@/side-panel/states/sidePanelPageInfoSta import { sidePanelShouldFocusTitleInputComponentState } from '@/side-panel/states/sidePanelShouldFocusTitleInputComponentState'; import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon'; import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { useUpdateFolderInDraft } from '@/navigation-menu-item/hooks/useUpdateFolderInDraft'; import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; @@ -41,7 +41,7 @@ export const SidePanelFolderInfo = () => { const selectedItem = selectedNavigationMenuItemInEditMode ? items.find( (item) => - item.itemType === NavigationMenuItemType.FOLDER && + item.type === NavigationMenuItemType.FOLDER && item.id === selectedNavigationMenuItemInEditMode, ) : undefined; diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelLinkInfo.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelLinkInfo.tsx index 05e0ff00d1..e2473bf052 100644 --- a/packages/twenty-front/src/modules/side-panel/components/SidePanelLinkInfo.tsx +++ b/packages/twenty-front/src/modules/side-panel/components/SidePanelLinkInfo.tsx @@ -2,7 +2,7 @@ import { useLingui } from '@lingui/react/macro'; import { IconLink, IconWorld } from 'twenty-ui/display'; import { LinkIconWithLinkOverlay } from '@/navigation-menu-item/components/LinkIconWithLinkOverlay'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft'; import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; @@ -33,7 +33,7 @@ export const SidePanelLinkInfo = () => { const selectedItem = selectedNavigationMenuItemInEditMode ? items.find( (item) => - item.itemType === NavigationMenuItemType.LINK && + item.type === NavigationMenuItemType.LINK && item.id === selectedNavigationMenuItemInEditMode, ) : undefined; diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelObjectViewRecordInfo.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelObjectViewRecordInfo.tsx index eb5b509b40..2f7be5bfa5 100644 --- a/packages/twenty-front/src/modules/side-panel/components/SidePanelObjectViewRecordInfo.tsx +++ b/packages/twenty-front/src/modules/side-panel/components/SidePanelObjectViewRecordInfo.tsx @@ -3,7 +3,7 @@ import { OverflowingTextWithTooltip } from 'twenty-ui/display'; import { SidePanelPageInfoLayout } from '@/side-panel/components/SidePanelPageInfoLayout'; import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel'; import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata'; @@ -15,12 +15,12 @@ export const SidePanelObjectViewRecordInfo = () => { const { selectedItemObjectMetadata } = useSelectedNavigationMenuItemEditItemObjectMetadata(); - const processedItem = - selectedItem && selectedItem.itemType !== NavigationMenuItemType.FOLDER + const navItem = + selectedItem && selectedItem.type !== NavigationMenuItemType.FOLDER ? selectedItem : undefined; - if (!processedItem || !selectedItemLabel) { + if (!navItem || !selectedItemLabel) { return null; } @@ -28,22 +28,22 @@ export const SidePanelObjectViewRecordInfo = () => { NavigationMenuItemType.OBJECT, NavigationMenuItemType.VIEW, NavigationMenuItemType.RECORD, - ].includes(processedItem.itemType); + ].includes(navItem.type); if (!isObjectViewOrRecord) { return null; } const label = - processedItem.itemType === NavigationMenuItemType.RECORD + navItem.type === NavigationMenuItemType.RECORD ? selectedItemObjectMetadata?.labelSingular - : processedItem.itemType === NavigationMenuItemType.OBJECT + : navItem.type === NavigationMenuItemType.OBJECT ? t`Object` : t`View`; return ( } + icon={} title={} label={label} /> diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelPageInfo.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelPageInfo.tsx index 55d40adfa3..a3d32140f4 100644 --- a/packages/twenty-front/src/modules/side-panel/components/SidePanelPageInfo.tsx +++ b/packages/twenty-front/src/modules/side-panel/components/SidePanelPageInfo.tsx @@ -5,7 +5,7 @@ import { OverflowingTextWithTooltip, } from 'twenty-ui/display'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType, SidePanelPages } from 'twenty-shared/types'; import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; import { SidePanelAskAIInfo } from '@/side-panel/components/SidePanelAskAIInfo'; @@ -18,7 +18,6 @@ import { SidePanelPageLayoutInfo } from '@/side-panel/components/SidePanelPageLa import { SidePanelRecordInfo } from '@/side-panel/components/SidePanelRecordInfo'; import { SidePanelWorkflowStepInfo } from '@/side-panel/components/SidePanelWorkflowStepInfo'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { SidePanelPages } from 'twenty-shared/types'; import { type SidePanelContextChipProps } from '@/side-panel/components/SidePanelContextChip'; import { useContext } from 'react'; @@ -51,20 +50,20 @@ export const SidePanelPageInfo = ({ pageChip }: SidePanelPageInfoProps) => { : undefined; if (isNavigationMenuItemEditPage && isDefined(selectedNavItem)) { - const itemType = selectedNavItem.itemType; + const navItemType = selectedNavItem.type; - if (itemType === NavigationMenuItemType.FOLDER) { + if (navItemType === NavigationMenuItemType.FOLDER) { return ; } - if (itemType === NavigationMenuItemType.LINK) { + if (navItemType === NavigationMenuItemType.LINK) { return ; } if ( - itemType === NavigationMenuItemType.OBJECT || - itemType === NavigationMenuItemType.VIEW || - itemType === NavigationMenuItemType.RECORD + navItemType === NavigationMenuItemType.OBJECT || + navItemType === NavigationMenuItemType.VIEW || + navItemType === NavigationMenuItemType.RECORD ) { return ; } diff --git a/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditLinkItemView.tsx b/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditLinkItemView.tsx index de5c8b0196..48eb88f343 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditLinkItemView.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditLinkItemView.tsx @@ -2,8 +2,8 @@ import { useLingui } from '@lingui/react/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { useState } from 'react'; import { getAbsoluteUrl } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; import { extractDomainFromUrl } from '@/navigation-menu-item/utils/extractDomainFromUrl'; import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; import { SidePanelList } from '@/side-panel/components/SidePanelList'; @@ -16,7 +16,7 @@ import { getOrganizeActionsSelectableItemIds } from '@/side-panel/pages/navigati import { TextInput } from '@/ui/input/components/TextInput'; type SidePanelEditLinkItemViewProps = OrganizeActionsProps & { - selectedItem: ProcessedNavigationMenuItem; + selectedItem: NavigationMenuItem; onUpdateLink: ( linkId: string, updates: { link?: string; name?: string }, @@ -78,7 +78,7 @@ export const SidePanelEditLinkItemView = ({ diff --git a/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditObjectViewBase.tsx b/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditObjectViewBase.tsx index eff37ae6ea..f271974b55 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditObjectViewBase.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditObjectViewBase.tsx @@ -1,8 +1,9 @@ import { useObjectNavItemColor } from '@/navigation-menu-item/hooks/useObjectNavItemColor'; import { navigationMenuItemsSelector } from '@/navigation-menu-item/states/navigationMenuItemsSelector'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; import { getEffectiveNavigationMenuItemColor } from '@/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor'; +import { getNavigationMenuItemObjectNameSingular } from '@/navigation-menu-item/utils/getNavigationMenuItemObjectNameSingular'; import { parseThemeColor } from '@/navigation-menu-item/utils/parseThemeColor'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; import { SidePanelList } from '@/side-panel/components/SidePanelList'; import { SidePanelEditColorOption } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditColorOption'; @@ -12,14 +13,16 @@ import { } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditOrganizeActions'; import { getOrganizeActionsSelectableItemIds } from '@/side-panel/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { viewsSelector } from '@/views/states/selectors/viewsSelector'; import { useLingui } from '@lingui/react/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; type SidePanelEditObjectViewBaseProps = OrganizeActionsProps & { onOpenFolderPicker: () => void; showColorOption?: boolean; - selectedItem?: ProcessedNavigationMenuItem | null; + selectedItem?: NavigationMenuItem | null; }; export const SidePanelEditObjectViewBase = ({ @@ -36,9 +39,18 @@ export const SidePanelEditObjectViewBase = ({ }: SidePanelEditObjectViewBaseProps) => { const { t } = useLingui(); const selectableItemIds = getOrganizeActionsSelectableItemIds(true); - const objectColor = useObjectNavItemColor( - selectedItem?.objectNameSingular ?? '', - ); + const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const views = useAtomStateValue(viewsSelector); + + const objectNameSingular = isDefined(selectedItem) + ? (getNavigationMenuItemObjectNameSingular( + selectedItem, + objectMetadataItems, + views, + ) ?? '') + : ''; + + const objectColor = useObjectNavItemColor(objectNameSingular); const navigationMenuItems = useAtomStateValue(navigationMenuItemsSelector); const persistedNavItem = navigationMenuItems.find( diff --git a/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNavigationMenuItemEditPage.tsx b/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNavigationMenuItemEditPage.tsx index e1db41b202..3051fb26ff 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNavigationMenuItemEditPage.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/navigation-menu-item/components/SidePanelNavigationMenuItemEditPage.tsx @@ -1,12 +1,11 @@ import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; -import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { NavigationMenuItemType } from 'twenty-shared/types'; import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState'; import { useOpenAddItemToFolderPage } from '@/navigation-menu-item/hooks/useOpenAddItemToFolderPage'; import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel'; import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; -import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; import { parseThemeColor } from '@/navigation-menu-item/utils/parseThemeColor'; import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; import { SidePanelList } from '@/side-panel/components/SidePanelList'; @@ -46,7 +45,7 @@ export const SidePanelNavigationMenuItemEditPage = () => { ); const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel(); const { selectedItem } = useSelectedNavigationMenuItemEditItem(); - const selectedItemType = selectedItem?.itemType ?? null; + const selectedItemType = selectedItem?.type ?? null; const { navigateToSidePanelSubPage } = useSidePanelSubPageHistory(); const openFolderPicker = () => @@ -67,10 +66,7 @@ export const SidePanelNavigationMenuItemEditPage = () => { const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState(); const handleAddItemToFolder = () => { - if ( - !selectedItem || - selectedItem.itemType !== NavigationMenuItemType.FOLDER - ) { + if (!selectedItem || selectedItem.type !== NavigationMenuItemType.FOLDER) { return; } const folderItemCount = workspaceNavigationMenuItems.filter( @@ -106,7 +102,7 @@ export const SidePanelNavigationMenuItemEditPage = () => { onAddBefore={onAddBefore} onAddAfter={onAddAfter} showColorOption={isDefined(selectedItem)} - selectedItem={selectedItem as ProcessedNavigationMenuItem | undefined} + selectedItem={selectedItem} /> ); case NavigationMenuItemType.VIEW: @@ -125,7 +121,7 @@ export const SidePanelNavigationMenuItemEditPage = () => { case NavigationMenuItemType.LINK: if ( isDefined(selectedItem) && - selectedItem.itemType === NavigationMenuItemType.LINK + selectedItem.type === NavigationMenuItemType.LINK ) { return ( { selectedNavigationMenuItemInEditModeState, ); const { selectedItem } = useSelectedNavigationMenuItemEditItem(); - const selectedItemType = selectedItem?.itemType ?? null; + const selectedItemType = selectedItem?.type ?? null; const { allFolders } = useDraftNavigationMenuItemsAllFolders(); const { workspaceFolders } = useDraftNavigationMenuItemsWorkspaceFolders();