refactor: remove ProcessedNavigationMenuItem, derive display fields at point of use (#18687)

## Summary

- Removes `ProcessedNavigationMenuItem` type and the
`sortNavigationMenuItems` enrichment pipeline that pre-computed display
fields (label, link, icon, avatarUrl, etc.) for every navigation menu
item upfront
- Replaces with `filterAndSortNavigationMenuItems` (pure filter+sort
returning raw `NavigationMenuItem[]`) and small utility functions
(`getNavigationMenuItemLabel`, `getNavigationMenuItemComputedLink`,
`getNavigationMenuItemObjectNameSingular`) that components call on
demand
- Eliminates the redundant `itemType` field (was identical to the raw
`type` field) across ~30 consumer files
- Each type-specific renderer
(`NavigationDrawerItemForObjectMetadataItem`, `NavigationMenuItemIcon`,
link/folder components) now derives only the 1-2 display fields it
actually needs from the raw item + globally available Jotai atoms

Net result: -1199 / +1177 lines, 7 files deleted, 4 new utility files.
This commit is contained in:
Charles Bochet
2026-03-17 00:33:06 +01:00
committed by GitHub
parent 48285be164
commit 1bb642d7cd
55 changed files with 1188 additions and 1227 deletions
@@ -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;
@@ -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;
@@ -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 (
<SidePanelPageInfoLayout
icon={<NavigationMenuItemIcon navigationMenuItem={processedItem} />}
icon={<NavigationMenuItemIcon navigationMenuItem={navItem} />}
title={<OverflowingTextWithTooltip text={selectedItemLabel} />}
label={label}
/>
@@ -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 <SidePanelFolderInfo />;
}
if (itemType === NavigationMenuItemType.LINK) {
if (navItemType === NavigationMenuItemType.LINK) {
return <SidePanelLinkInfo />;
}
if (
itemType === NavigationMenuItemType.OBJECT ||
itemType === NavigationMenuItemType.VIEW ||
itemType === NavigationMenuItemType.RECORD
navItemType === NavigationMenuItemType.OBJECT ||
navItemType === NavigationMenuItemType.VIEW ||
navItemType === NavigationMenuItemType.RECORD
) {
return <SidePanelObjectViewRecordInfo />;
}
@@ -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 = ({
<TextInput
fullWidth
placeholder="www.google.com"
value={urlEditInput || selectedItem.link}
value={urlEditInput || selectedItem.link || ''}
onChange={handleUrlChange}
onBlur={handleUrlBlur}
/>
@@ -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(
@@ -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 (
<SidePanelEditLinkItemView
@@ -11,7 +11,7 @@ import { themeCssVariables } from 'twenty-ui/theme-constants';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { NavigationMenuItemType } from 'twenty-shared/types';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
@@ -1,6 +1,5 @@
import { Avatar, IconFolder, useIcons } from 'twenty-ui/display';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useAddRecordToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInSidePanel';
@@ -11,7 +10,10 @@ import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/Si
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import {
CoreObjectNameSingular,
NavigationMenuItemType,
} from 'twenty-shared/types';
type SearchRecord = {
recordId: string;
@@ -1,5 +1,5 @@
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { NavigationMenuItemType } from 'twenty-shared/types';
import { useAddViewToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
@@ -4,7 +4,7 @@ import { useIcons } from 'twenty-ui/display';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { NavigationMenuItemType } from 'twenty-shared/types';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
@@ -1,6 +1,6 @@
import { isDefined } from 'twenty-shared/utils';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { NavigationMenuItemType } from 'twenty-shared/types';
import { useDraftNavigationMenuItemsAllFolders } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItemsAllFolders';
import { useDraftNavigationMenuItemsWorkspaceFolders } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItemsWorkspaceFolders';
import { useNavigationMenuItemMoveRemove } from '@/navigation-menu-item/hooks/useNavigationMenuItemMoveRemove';
@@ -45,7 +45,7 @@ export const useFolderPickerSelectionData = () => {
selectedNavigationMenuItemInEditModeState,
);
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const selectedItemType = selectedItem?.itemType ?? null;
const selectedItemType = selectedItem?.type ?? null;
const { allFolders } = useDraftNavigationMenuItemsAllFolders();
const { workspaceFolders } = useDraftNavigationMenuItemsWorkspaceFolders();