diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuFolderLinkInfo.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuFolderInfo.tsx similarity index 72% rename from packages/twenty-front/src/modules/command-menu/components/CommandMenuFolderLinkInfo.tsx rename to packages/twenty-front/src/modules/command-menu/components/CommandMenuFolderInfo.tsx index 97ecfeaaa6..851c4b52a0 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuFolderLinkInfo.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuFolderInfo.tsx @@ -1,30 +1,21 @@ import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -import { IconFolder, IconLink } from 'twenty-ui/display'; +import { IconFolder } from 'twenty-ui/display'; import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout'; import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState'; import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer'; import { useUpdateFolderNameInDraft } from '@/navigation-menu-item/hooks/useUpdateFolderNameInDraft'; -import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft'; import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors'; import { TitleInput } from '@/ui/input/components/TitleInput'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; -const ICON_CONFIG = { - folder: { Icon: IconFolder, colorKey: 'folder' }, - link: { Icon: IconLink, colorKey: 'link' }, -} as const; - -export const CommandMenuFolderLinkInfo = ({ - type, -}: { - type: 'folder' | 'link'; -}) => { +export const CommandMenuFolderInfo = () => { const theme = useTheme(); const { t } = useLingui(); const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState); @@ -38,15 +29,14 @@ export const CommandMenuFolderLinkInfo = ({ ); const items = useWorkspaceSectionItems(); const { updateFolderNameInDraft } = useUpdateFolderNameInDraft(); - const { updateLinkInDraft } = useUpdateLinkInDraft(); - const defaultLabel = type === 'folder' ? t`New folder` : t`Link label`; - const placeholder = type === 'folder' ? t`Folder name` : t`Link label`; + const defaultLabel = t`New folder`; + const placeholder = t`Folder name`; const selectedItem = selectedNavigationMenuItemInEditMode ? items.find( (item) => - item.itemType === type && + item.itemType === NavigationMenuItemType.FOLDER && item.id === selectedNavigationMenuItemInEditMode, ) : undefined; @@ -57,11 +47,7 @@ export const CommandMenuFolderLinkInfo = ({ const itemName = selectedItem.name ?? defaultLabel; const handleChange = (text: string) => { - if (type === 'folder') { - updateFolderNameInDraft(itemId, text); - } else { - updateLinkInDraft(itemId, { name: text }); - } + updateFolderNameInDraft(itemId, text); }; const handleSave = () => { @@ -69,23 +55,17 @@ export const CommandMenuFolderLinkInfo = ({ const finalName = trimmed.length > 0 ? trimmed : defaultLabel; if (finalName !== itemName) { - if (type === 'folder') { - updateFolderNameInDraft(itemId, finalName); - } else { - updateLinkInDraft(itemId, { name: finalName }); - } + updateFolderNameInDraft(itemId, finalName); } }; - const { Icon, colorKey } = ICON_CONFIG[type]; - return ( - setShouldFocusTitleInput(false)} /> } - label={type === 'link' ? t`link` : undefined} /> ); }; diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuLinkInfo.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuLinkInfo.tsx new file mode 100644 index 0000000000..611dff7d8f --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuLinkInfo.tsx @@ -0,0 +1,94 @@ +import { useTheme } from '@emotion/react'; +import { useLingui } from '@lingui/react/macro'; +import { useRecoilValue } from 'recoil'; +import { IconLink } from 'twenty-ui/display'; + +import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout'; +import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState'; +import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer'; +import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft'; +import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; +import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; +import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors'; +import { TitleInput } from '@/ui/input/components/TitleInput'; +import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; + +export const CommandMenuLinkInfo = () => { + const theme = useTheme(); + const { t } = useLingui(); + const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState); + const [shouldFocusTitleInput, setShouldFocusTitleInput] = + useRecoilComponentState( + commandMenuShouldFocusTitleInputComponentState, + commandMenuPageInfo.instanceId, + ); + const selectedNavigationMenuItemInEditMode = useRecoilValue( + selectedNavigationMenuItemInEditModeState, + ); + const items = useWorkspaceSectionItems(); + const { updateLinkInDraft } = useUpdateLinkInDraft(); + + const defaultLabel = t`Link label`; + const placeholder = t`Link label`; + + const selectedItem = selectedNavigationMenuItemInEditMode + ? items.find( + (item) => + item.itemType === NavigationMenuItemType.LINK && + item.id === selectedNavigationMenuItemInEditMode, + ) + : undefined; + + if (!selectedItem) return null; + + const itemId = selectedItem.id; + const itemName = selectedItem.name ?? defaultLabel; + + const handleChange = (text: string) => { + updateLinkInDraft(itemId, { name: text }); + }; + + const handleSave = () => { + const trimmed = itemName.trim(); + const finalName = trimmed.length > 0 ? trimmed : defaultLabel; + + if (finalName !== itemName) { + updateLinkInDraft(itemId, { name: finalName }); + } + }; + + return ( + + + + } + title={ + setShouldFocusTitleInput(false)} + /> + } + label={t`link`} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuObjectViewRecordInfo.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuObjectViewRecordInfo.tsx index d2c3403d1f..e30ee6d7a7 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuObjectViewRecordInfo.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuObjectViewRecordInfo.tsx @@ -2,27 +2,35 @@ import { useLingui } from '@lingui/react/macro'; import { OverflowingTextWithTooltip } from 'twenty-ui/display'; import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout'; -import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData'; import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; +import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; +import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { ViewKey } from '@/views/types/ViewKey'; export const CommandMenuObjectViewRecordInfo = () => { const { t } = useLingui(); - const { processedItem, selectedItemLabel } = - useSelectedNavigationMenuItemEditData(); + const { selectedItem } = useSelectedNavigationMenuItemEditItem(); + const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel(); + + const processedItem = + selectedItem && selectedItem.itemType !== NavigationMenuItemType.FOLDER + ? selectedItem + : undefined; if (!processedItem || !selectedItemLabel) { return null; } const isViewOrRecord = - processedItem.itemType === 'view' || processedItem.itemType === 'record'; + processedItem.itemType === NavigationMenuItemType.VIEW || + processedItem.itemType === NavigationMenuItemType.RECORD; if (!isViewOrRecord) { return null; } const label = - processedItem.itemType === 'record' + processedItem.itemType === NavigationMenuItemType.RECORD ? t`record` : processedItem.viewKey === ViewKey.Index ? t`object` diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuPageInfo.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuPageInfo.tsx index 4309a354d8..a446b32850 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuPageInfo.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuPageInfo.tsx @@ -3,13 +3,15 @@ import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { OverflowingTextWithTooltip } from 'twenty-ui/display'; -import { CommandMenuFolderLinkInfo } from '@/command-menu/components/CommandMenuFolderLinkInfo'; +import { CommandMenuFolderInfo } from '@/command-menu/components/CommandMenuFolderInfo'; +import { CommandMenuLinkInfo } from '@/command-menu/components/CommandMenuLinkInfo'; import { CommandMenuMultipleRecordsInfo } from '@/command-menu/components/CommandMenuMultipleRecordsInfo'; import { CommandMenuObjectViewRecordInfo } from '@/command-menu/components/CommandMenuObjectViewRecordInfo'; import { CommandMenuPageLayoutInfo } from '@/command-menu/components/CommandMenuPageLayoutInfo'; import { CommandMenuRecordInfo } from '@/command-menu/components/CommandMenuRecordInfo'; import { CommandMenuWorkflowStepInfo } from '@/command-menu/components/CommandMenuWorkflowStepInfo'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; @@ -44,11 +46,18 @@ export const CommandMenuPageInfo = ({ pageChip }: CommandMenuPageInfoProps) => { if (isNavigationMenuItemEditPage && isDefined(selectedNavItem)) { const itemType = selectedNavItem.itemType; - if (itemType === 'folder' || itemType === 'link') { - return ; + if (itemType === NavigationMenuItemType.FOLDER) { + return ; } - if (itemType === 'view' || itemType === 'record') { + if (itemType === NavigationMenuItemType.LINK) { + return ; + } + + if ( + itemType === NavigationMenuItemType.VIEW || + itemType === NavigationMenuItemType.RECORD + ) { return ; } } diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions.ts new file mode 100644 index 0000000000..9c2d3c1b3a --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions.ts @@ -0,0 +1,8 @@ +export enum CommandMenuNavigationItemActions { + MOVE_UP = 'move-up', + MOVE_DOWN = 'move-down', + MOVE_TO_FOLDER = 'move-to-folder', + ADD_BEFORE = 'add-before', + ADD_AFTER = 'add-after', + REMOVE = 'remove', +} diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditFolderPickerSubView.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditFolderPickerSubView.tsx index 066e8dd6c8..56578e72bf 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditFolderPickerSubView.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditFolderPickerSubView.tsx @@ -20,7 +20,7 @@ export const CommandMenuEditFolderPickerSubView = ({ const { t } = useLingui(); const [searchValue, setSearchValue] = useState(''); const { foldersToShow, includeNoFolderOption, handleSelectFolder } = - useFolderPickerSelectionData(); + useFolderPickerSelectionData({ onCloseSubView: onBack }); const filteredFolders = filterBySearchQuery({ items: foldersToShow, diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx index bba1f081cc..526eba79f9 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx @@ -10,6 +10,7 @@ import { import { CommandGroup } from '@/command-menu/components/CommandGroup'; import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +import { CommandMenuNavigationItemActions } from '@/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; export type OrganizeActionsProps = { @@ -45,65 +46,77 @@ export const CommandMenuEditOrganizeActions = ({ return ( {showMoveToFolder && onMoveToFolder && ( - + )} {onAddBefore && ( - + )} {onAddAfter && ( - + )} - + diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOwnerSection.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOwnerSection.tsx index e7d3981a95..f5552237cd 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOwnerSection.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOwnerSection.tsx @@ -4,8 +4,8 @@ import { IconApps } from 'twenty-ui/display'; import { CommandGroup } from '@/command-menu/components/CommandGroup'; import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; -import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData'; -import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData'; +import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems'; +import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { useFindOneApplicationQuery } from '~/generated-metadata/graphql'; @@ -18,8 +18,8 @@ export const CommandMenuEditOwnerSection = ({ }: CommandMenuEditOwnerSectionProps) => { const { t } = useLingui(); - const { selectedItem } = useSelectedNavigationMenuItemEditData(); - const { currentDraft } = useNavigationMenuItemEditFolderData(); + const { selectedItem } = useSelectedNavigationMenuItemEditItem(); + const { currentDraft } = useDraftNavigationMenuItems(); const applicationIdFromDraft = selectedItem && currentDraft diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNavigationMenuItemEditPage.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNavigationMenuItemEditPage.tsx index 23e31b58f6..bb5a473bd7 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNavigationMenuItemEditPage.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNavigationMenuItemEditPage.tsx @@ -5,14 +5,16 @@ import { CommandMenuEditObjectViewBase } from '@/command-menu/pages/navigation-m import { CommandMenuEditOrganizeActions } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions'; import { CommandMenuEditOwnerSection } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuEditOwnerSection'; import { useNavigationMenuItemEditOrganizeActions } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditOrganizeActions'; -import { useNavigationMenuItemEditSubView } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView'; -import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData'; import { getOrganizeActionsSelectableItemIds } from '@/command-menu/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; +import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel'; +import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata'; import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; -import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; +import { useState } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; @@ -31,15 +33,14 @@ export const CommandMenuNavigationMenuItemEditPage = () => { const selectedNavigationMenuItemInEditMode = useRecoilValue( selectedNavigationMenuItemInEditModeState, ); - const { - selectedItemLabel, - selectedItem, - selectedItemObjectMetadata, - selectedItemType, - } = useSelectedNavigationMenuItemEditData(); + const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel(); + const { selectedItem } = useSelectedNavigationMenuItemEditItem(); + const { selectedItemObjectMetadata } = + useSelectedNavigationMenuItemEditItemObjectMetadata(); + const selectedItemType = selectedItem?.itemType ?? null; - const { editSubView, setFolderPicker, clearSubView } = - useNavigationMenuItemEditSubView(); + const [isFolderPickerOpen, setIsFolderPickerOpen] = useState(false); + const setFolderPicker = () => setIsFolderPickerOpen(true); const { canMoveUp, @@ -63,64 +64,20 @@ export const CommandMenuNavigationMenuItemEditPage = () => { ); } - if (editSubView === 'folder-picker') { - return ; - } - - if ( - selectedItemType === NAVIGATION_MENU_ITEM_TYPE.VIEW && - !selectedItemObjectMetadata - ) { - return null; - } - - if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.VIEW) { + if (isFolderPickerOpen) { return ( - setIsFolderPickerOpen(false)} /> ); } - if ( - isDefined(selectedItem) && - selectedItem.itemType === NAVIGATION_MENU_ITEM_TYPE.LINK - ) { - return ( - updateLinkInDraft(linkId, { link })} - onOpenFolderPicker={setFolderPicker} - canMoveUp={canMoveUp} - canMoveDown={canMoveDown} - onMoveUp={onMoveUp} - onMoveDown={onMoveDown} - onRemove={onRemove} - onAddBefore={onAddBefore} - onAddAfter={onAddAfter} - /> - ); - } - - if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK) { - return null; - } - - if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER) { - return ( - - { onAddBefore={onAddBefore} onAddAfter={onAddAfter} /> - - - ); + ); + case NavigationMenuItemType.LINK: + if ( + isDefined(selectedItem) && + selectedItem.itemType === NavigationMenuItemType.LINK + ) { + return ( + updateLinkInDraft(linkId, { link })} + onOpenFolderPicker={setFolderPicker} + canMoveUp={canMoveUp} + canMoveDown={canMoveDown} + onMoveUp={onMoveUp} + onMoveDown={onMoveDown} + onRemove={onRemove} + onAddBefore={onAddBefore} + onAddAfter={onAddAfter} + /> + ); + } + return null; + case NavigationMenuItemType.FOLDER: + return ( + + + + + ); + default: + return ( + + + + ); } - - return ( - - - - ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx index 14ef94cb43..3b1c8704d3 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx @@ -13,24 +13,25 @@ import { CommandMenuAddToNavDroppable } from '@/command-menu/components/CommandM import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag'; import { CommandMenuList } from '@/command-menu/components/CommandMenuList'; +import { useAddFolderToNavigationMenu } from '@/command-menu/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu'; +import { useAddLinkToNavigationMenu } from '@/command-menu/pages/navigation-menu-item/hooks/useAddLinkToNavigationMenu'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; type CommandMenuNewSidebarItemMainMenuProps = { onSelectObject: () => void; onSelectView: () => void; onSelectRecord: () => void; - onAddFolder: () => void; - onAddLink: () => void; }; export const CommandMenuNewSidebarItemMainMenu = ({ onSelectObject, onSelectView, onSelectRecord, - onAddFolder, - onAddLink, }: CommandMenuNewSidebarItemMainMenuProps) => { const { t } = useLingui(); + const { handleAddFolder } = useAddFolderToNavigationMenu(); + const { handleAddLink } = useAddLinkToNavigationMenu(); return ( @@ -77,29 +78,29 @@ export const CommandMenuNewSidebarItemMainMenu = ({ - + - + void; +}; + +export const CommandMenuNewSidebarItemObjectFlow = ({ + onBack, +}: CommandMenuNewSidebarItemObjectFlowProps) => { + const { t } = useLingui(); + const { closeCommandMenu } = useCommandMenu(); + const [objectSearchInput, setObjectSearchInput] = useState(''); + const [systemObjectSearchInput, setSystemObjectSearchInput] = useState(''); + const [isInSystemPicker, setIsInSystemPicker] = useState(false); + + const { currentDraft } = useDraftNavigationMenuItems(); + const { objectMetadataItems } = useObjectMetadataItems(); + const { addObjectToDraft } = useAddObjectToNavigationMenuDraft(); + const { activeNonSystemObjectMetadataItems } = + useFilteredObjectMetadataItems(); + const addMenuItemInsertionContext = useRecoilValue( + addMenuItemInsertionContextState, + ); + const setAddMenuItemInsertionContext = useSetRecoilState( + addMenuItemInsertionContextState, + ); + const { views, objectMetadataIdsWithIndexView } = + useNavigationMenuObjectMetadataFromDraft(currentDraft); + + const objectMetadataIdsWithDisplayableViews = new Set( + views + .filter((view) => view.key !== ViewKey.Index) + .map((view) => view.objectMetadataId), + ); + + const { availableObjectMetadataItems, availableSystemObjectMetadataItems } = + getAvailableObjectMetadataForNewSidebarItem({ + objectMetadataItems, + activeNonSystemObjectMetadataItems, + objectMetadataIdsWithIndexView, + objectMetadataIdsWithDisplayableViews, + }); + + const handleSelectObject = ( + objectMetadataItem: ObjectMetadataItem, + defaultViewId: string, + ) => { + addObjectToDraft( + objectMetadataItem.id, + defaultViewId, + currentDraft, + addMenuItemInsertionContext?.targetFolderId, + addMenuItemInsertionContext?.targetIndex, + ); + setAddMenuItemInsertionContext(null); + closeCommandMenu(); + }; + + const handleBackToObjectList = () => { + setIsInSystemPicker(false); + setSystemObjectSearchInput(''); + }; + + if (isInSystemPicker) { + return ( + + ); + } + + return ( + setIsInSystemPicker(true)} + isViewItem={false} + onChangeObject={handleSelectObject} + objectMenuItemVariant="add" + /> + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemPage.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemPage.tsx index 8bb8019ff0..bdd2ae8460 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemPage.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemPage.tsx @@ -1,252 +1,24 @@ -import { useLingui } from '@lingui/react/macro'; import { useState } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; -import { IconFolder, IconLink } from 'twenty-ui/display'; -import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { CommandMenuNewSidebarItemMainMenu } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu'; +import { CommandMenuNewSidebarItemObjectFlow } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemObjectFlow'; import { CommandMenuNewSidebarItemRecordSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordSubView'; -import { CommandMenuNewSidebarItemViewObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewObjectPickerSubView'; -import { CommandMenuNewSidebarItemViewPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView'; -import { CommandMenuNewSidebarItemViewSystemSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewSystemSubView'; -import { CommandMenuObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuObjectPickerSubView'; -import { CommandMenuSystemObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuSystemObjectPickerSubView'; -import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft'; -import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft'; -import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft'; -import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState'; -import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft'; -import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu'; -import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState'; -import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; -import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; -import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; -import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { ViewKey } from '@/views/types/ViewKey'; +import { CommandMenuNewSidebarItemViewFlow } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewFlow'; -type SelectedOption = - | 'object' - | 'record' - | 'system' - | 'view' - | 'view-system' - | null; +type SelectedOption = 'object' | 'record' | 'view' | null; export const CommandMenuNewSidebarItemPage = () => { - const { t } = useLingui(); - const { closeCommandMenu } = useCommandMenu(); const [selectedOption, setSelectedOption] = useState(null); - const [selectedObjectMetadataIdForView, setSelectedObjectMetadataIdForView] = - useState(null); - const [objectSearchInput, setObjectSearchInput] = useState(''); - const [systemObjectSearchInput, setSystemObjectSearchInput] = useState(''); - - const { objectMetadataItems } = useObjectMetadataItems(); - const { addObjectToDraft } = useAddObjectToNavigationMenuDraft(); - const { addFolderToDraft } = useAddFolderToNavigationMenuDraft(); - const { addLinkToDraft } = useAddLinkToNavigationMenuDraft(); - const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState(); - const navigationMenuItemsDraft = useRecoilValue( - navigationMenuItemsDraftState, - ); - const setSelectedNavigationMenuItemInEditMode = useSetRecoilState( - selectedNavigationMenuItemInEditModeState, - ); - const { openNavigationMenuItemInCommandMenu } = - useOpenNavigationMenuItemInCommandMenu(); - const { activeNonSystemObjectMetadataItems } = - useFilteredObjectMetadataItems(); - - const currentDraft = isDefined(navigationMenuItemsDraft) - ? navigationMenuItemsDraft - : workspaceNavigationMenuItems; - - const addMenuItemInsertionContext = useRecoilValue( - addMenuItemInsertionContextState, - ); - const setAddMenuItemInsertionContext = useSetRecoilState( - addMenuItemInsertionContextState, - ); - - const { views, objectMetadataIdsWithIndexView } = - useNavigationMenuObjectMetadataFromDraft(currentDraft); - - const objectMetadataIdsWithDisplayableViews = new Set( - views - .filter((view) => view.key !== ViewKey.Index) - .map((view) => view.objectMetadataId), - ); - - const availableObjectMetadataItems = [ - ...activeNonSystemObjectMetadataItems, - ].sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); - - const activeSystemObjectMetadataItems = objectMetadataItems - .filter((item) => item.isActive && item.isSystem) - .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); - const availableSystemObjectMetadataItems = - activeSystemObjectMetadataItems.filter((item) => - objectMetadataIdsWithIndexView.has(item.id), - ); - - const objectMetadataItemsWithViews = objectMetadataItems - .filter( - (item) => - item.isActive && objectMetadataIdsWithDisplayableViews.has(item.id), - ) - .filter((item) => !item.isSystem) - .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); - - const availableSystemObjectMetadataItemsForView = - activeSystemObjectMetadataItems.filter((item) => - objectMetadataIdsWithDisplayableViews.has(item.id), - ); - - const handleSelectObject = ( - objectMetadataItem: ObjectMetadataItem, - defaultViewId: string, - ) => { - addObjectToDraft( - objectMetadataItem.id, - defaultViewId, - currentDraft, - addMenuItemInsertionContext?.targetFolderId, - addMenuItemInsertionContext?.targetIndex, - ); - setAddMenuItemInsertionContext(null); - closeCommandMenu(); - }; const handleBackToMain = () => { setSelectedOption(null); - setSelectedObjectMetadataIdForView(null); - setObjectSearchInput(''); - setSystemObjectSearchInput(''); - }; - - const handleBackToObjectList = () => { - setSelectedOption('object'); - setSystemObjectSearchInput(''); - }; - - const handleBackToViewObjectList = () => { - const selectedObjectMetadataItem = isDefined( - selectedObjectMetadataIdForView, - ) - ? objectMetadataItems.find( - (item) => item.id === selectedObjectMetadataIdForView, - ) - : undefined; - const cameFromSystemObjects = selectedObjectMetadataItem?.isSystem ?? false; - - setSelectedObjectMetadataIdForView(null); - if (cameFromSystemObjects) { - setSelectedOption('view-system'); - } - }; - - const handleBackToViewObjectListFromSystem = () => { - setSelectedOption('view'); - setSystemObjectSearchInput(''); - }; - - const handleAddFolderAndOpenEdit = () => { - const newFolderId = addFolderToDraft( - t`New folder`, - currentDraft, - addMenuItemInsertionContext?.targetFolderId ?? null, - addMenuItemInsertionContext?.targetIndex, - ); - setAddMenuItemInsertionContext(null); - setSelectedNavigationMenuItemInEditMode(newFolderId); - openNavigationMenuItemInCommandMenu({ - pageTitle: t`Edit folder`, - pageIcon: IconFolder, - focusTitleInput: true, - }); - }; - - const handleAddLinkAndOpenEdit = () => { - const newLinkId = addLinkToDraft( - t`Link label`, - 'www.example.com', - currentDraft, - addMenuItemInsertionContext?.targetFolderId ?? null, - addMenuItemInsertionContext?.targetIndex, - ); - setAddMenuItemInsertionContext(null); - setSelectedNavigationMenuItemInEditMode(newLinkId); - openNavigationMenuItemInCommandMenu({ - pageTitle: t`Edit link`, - pageIcon: IconLink, - focusTitleInput: true, - }); }; switch (selectedOption) { case 'view': - if (isDefined(selectedObjectMetadataIdForView)) { - return ( - - ); - } - return ( - setSelectedOption('view-system')} - onSelectObject={(item) => setSelectedObjectMetadataIdForView(item.id)} - showSystemObjectsOption={ - availableSystemObjectMetadataItemsForView.length > 0 - } - /> - ); - case 'view-system': - return ( - { - setSelectedObjectMetadataIdForView(item.id); - setSelectedOption('view'); - }} - /> - ); + return ; case 'object': - return ( - setSelectedOption('system')} - isViewItem={false} - onChangeObject={handleSelectObject} - objectMenuItemVariant="add" - /> - ); - case 'system': - return ( - - ); + return ; case 'record': return ( @@ -254,11 +26,9 @@ export const CommandMenuNewSidebarItemPage = () => { default: return ( setSelectedOption('record')} onSelectObject={() => setSelectedOption('object')} onSelectView={() => setSelectedOption('view')} - onSelectRecord={() => setSelectedOption('record')} - onAddFolder={handleAddFolderAndOpenEdit} - onAddLink={handleAddLinkAndOpenEdit} /> ); } diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordItem.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordItem.tsx index 1c5078a54b..e7d377681b 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordItem.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordItem.tsx @@ -3,7 +3,8 @@ import { Avatar } from 'twenty-ui/display'; import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; -import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems'; import { useAddRecordToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft'; import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState'; import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload'; @@ -29,7 +30,7 @@ export const CommandMenuNewSidebarItemRecordItem = ({ }: CommandMenuNewSidebarItemRecordItemProps) => { const { closeCommandMenu } = useCommandMenu(); const { addRecordToDraft } = useAddRecordToNavigationMenuDraft(); - const { currentDraft } = useNavigationMenuItemEditFolderData(); + const { currentDraft } = useDraftNavigationMenuItems(); const addMenuItemInsertionContext = useRecoilValue( addMenuItemInsertionContextState, ); @@ -41,7 +42,7 @@ export const CommandMenuNewSidebarItemRecordItem = ({ (item) => item.nameSingular === record.objectNameSingular, ); const recordPayload: AddToNavigationDragPayload = { - type: 'record', + type: NavigationMenuItemType.RECORD, recordId: record.recordId, objectMetadataId: objectMetadataItem?.id ?? '', objectNameSingular: record.objectNameSingular, diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordSubView.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordSubView.tsx index 88b37e2429..8af7ee4358 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordSubView.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordSubView.tsx @@ -9,7 +9,7 @@ import { CommandMenuList } from '@/command-menu/components/CommandMenuList'; import { CommandMenuSubViewWithSearch } from '@/command-menu/components/CommandMenuSubViewWithSearch'; import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults'; import { CommandMenuNewSidebarItemRecordItem } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordItem'; -import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData'; +import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; @@ -31,7 +31,7 @@ export const CommandMenuNewSidebarItemRecordSubView = ({ onBack, }: CommandMenuNewSidebarItemRecordSubViewProps) => { const { t } = useLingui(); - const { currentDraft } = useNavigationMenuItemEditFolderData(); + const { currentDraft } = useDraftNavigationMenuItems(); const { objectMetadataItems } = useObjectMetadataItems(); const [recordSearchInput, setRecordSearchInput] = useState(''); const [deferredRecordSearchInput] = useDebounce(recordSearchInput, 300); diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewFlow.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewFlow.tsx new file mode 100644 index 0000000000..9e9547eb2e --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewFlow.tsx @@ -0,0 +1,111 @@ +import { useState } from 'react'; +import { isDefined } from 'twenty-shared/utils'; + +import { CommandMenuNewSidebarItemViewObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewObjectPickerSubView'; +import { CommandMenuNewSidebarItemViewPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView'; +import { CommandMenuNewSidebarItemViewSystemSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewSystemSubView'; +import { getAvailableObjectMetadataForNewSidebarItem } from '@/command-menu/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem'; +import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems'; +import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft'; +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { ViewKey } from '@/views/types/ViewKey'; + +type CommandMenuNewSidebarItemViewFlowProps = { + onBack: () => void; +}; + +export const CommandMenuNewSidebarItemViewFlow = ({ + onBack, +}: CommandMenuNewSidebarItemViewFlowProps) => { + const [selectedObjectMetadataIdForView, setSelectedObjectMetadataIdForView] = + useState(null); + const [objectSearchInput, setObjectSearchInput] = useState(''); + const [systemObjectSearchInput, setSystemObjectSearchInput] = useState(''); + const [isInSystemPicker, setIsInSystemPicker] = useState(false); + + const { currentDraft } = useDraftNavigationMenuItems(); + const { objectMetadataItems } = useObjectMetadataItems(); + const { activeNonSystemObjectMetadataItems } = + useFilteredObjectMetadataItems(); + const { views, objectMetadataIdsWithIndexView } = + useNavigationMenuObjectMetadataFromDraft(currentDraft); + + const objectMetadataIdsWithDisplayableViews = new Set( + views + .filter((view) => view.key !== ViewKey.Index) + .map((view) => view.objectMetadataId), + ); + + const { + objectMetadataItemsWithViews, + availableSystemObjectMetadataItemsForView, + } = getAvailableObjectMetadataForNewSidebarItem({ + objectMetadataItems, + activeNonSystemObjectMetadataItems, + objectMetadataIdsWithIndexView, + objectMetadataIdsWithDisplayableViews, + }); + + const handleBackFromViewPicker = () => { + const selectedObjectMetadataItem = isDefined( + selectedObjectMetadataIdForView, + ) + ? objectMetadataItems.find( + (item) => item.id === selectedObjectMetadataIdForView, + ) + : undefined; + const cameFromSystemObjects = selectedObjectMetadataItem?.isSystem ?? false; + + setSelectedObjectMetadataIdForView(null); + setIsInSystemPicker(cameFromSystemObjects); + }; + + const handleBackFromSystemPicker = () => { + setIsInSystemPicker(false); + }; + + const handleSelectObject = (objectId: string) => { + setSelectedObjectMetadataIdForView(objectId); + }; + + const handleSelectObjectFromSystem = (objectId: string) => { + setSelectedObjectMetadataIdForView(objectId); + setIsInSystemPicker(false); + }; + + if (isDefined(selectedObjectMetadataIdForView) && !isInSystemPicker) { + return ( + + ); + } + + if (isInSystemPicker) { + return ( + handleSelectObjectFromSystem(item.id)} + /> + ); + } + + return ( + setIsInSystemPicker(true)} + onSelectObject={(item) => handleSelectObject(item.id)} + showSystemObjectsOption={ + availableSystemObjectMetadataItemsForView.length > 0 + } + /> + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView.tsx index 8b24441685..27fe75c912 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView.tsx @@ -10,7 +10,8 @@ import { CommandMenuList } from '@/command-menu/components/CommandMenuList'; import { CommandMenuSubViewWithSearch } from '@/command-menu/components/CommandMenuSubViewWithSearch'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useFilteredPickerItems } from '@/command-menu/hooks/useFilteredPickerItems'; -import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData'; +import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { useAddViewToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft'; import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft'; import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState'; @@ -33,7 +34,7 @@ export const CommandMenuNewSidebarItemViewPickerSubView = ({ const [searchValue, setSearchValue] = useState(''); const { closeCommandMenu } = useCommandMenu(); const { addViewToDraft } = useAddViewToNavigationMenuDraft(); - const { currentDraft } = useNavigationMenuItemEditFolderData(); + const { currentDraft } = useDraftNavigationMenuItems(); const addMenuItemInsertionContext = useRecoilValue( addMenuItemInsertionContextState, ); @@ -114,7 +115,7 @@ export const CommandMenuNewSidebarItemViewPickerSubView = ({ onClick={() => handleSelectView(view)} dragIndex={index} payload={{ - type: 'view', + type: NavigationMenuItemType.VIEW, viewId: view.id, label: view.name, }} diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuObjectMenuItem.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuObjectMenuItem.tsx index ff63b53b9a..a966f1f82f 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuObjectMenuItem.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuObjectMenuItem.tsx @@ -6,6 +6,7 @@ import { useIcons } from 'twenty-ui/display'; import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag'; import { IconWithBackground } from '@/navigation-menu-item/components/IconWithBackground'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; @@ -55,7 +56,7 @@ export const CommandMenuObjectMenuItem = ({ onClick={handleClick} dragIndex={dragIndex} payload={{ - type: 'object' as const, + type: NavigationMenuItemType.OBJECT, objectMetadataId: objectMetadataItem.id, defaultViewId: defaultViewId ?? '', label: objectMetadataItem.labelPlural, diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts new file mode 100644 index 0000000000..9f495ee652 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu.ts @@ -0,0 +1,57 @@ +import { useLingui } from '@lingui/react/macro'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { IconFolder } from 'twenty-ui/display'; + +import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft'; +import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState'; +import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu'; +import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState'; +import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; +import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; + +export const useAddFolderToNavigationMenu = () => { + const { t } = useLingui(); + const { addFolderToDraft } = useAddFolderToNavigationMenuDraft(); + const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState(); + const navigationMenuItemsDraft = useRecoilValue( + navigationMenuItemsDraftState, + ); + const setSelectedNavigationMenuItemInEditMode = useSetRecoilState( + selectedNavigationMenuItemInEditModeState, + ); + const { openNavigationMenuItemInCommandMenu } = + useOpenNavigationMenuItemInCommandMenu(); + const addMenuItemInsertionContext = useRecoilValue( + addMenuItemInsertionContextState, + ); + const setAddMenuItemInsertionContext = useSetRecoilState( + addMenuItemInsertionContextState, + ); + + const currentDraft = isDefined(navigationMenuItemsDraft) + ? navigationMenuItemsDraft + : workspaceNavigationMenuItems; + + const handleAddFolder = () => { + const targetFolderId = addMenuItemInsertionContext?.targetFolderId ?? null; + const targetIndex = addMenuItemInsertionContext?.targetIndex; + + const itemId = addFolderToDraft( + t`New folder`, + currentDraft, + targetFolderId, + targetIndex, + ); + + setAddMenuItemInsertionContext(null); + setSelectedNavigationMenuItemInEditMode(itemId); + openNavigationMenuItemInCommandMenu({ + pageTitle: t`Edit folder`, + pageIcon: IconFolder, + focusTitleInput: true, + }); + }; + + return { handleAddFolder }; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useAddLinkToNavigationMenu.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useAddLinkToNavigationMenu.ts new file mode 100644 index 0000000000..69d3683e45 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useAddLinkToNavigationMenu.ts @@ -0,0 +1,58 @@ +import { useLingui } from '@lingui/react/macro'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { IconLink } from 'twenty-ui/display'; + +import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft'; +import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState'; +import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu'; +import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState'; +import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; +import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; + +export const useAddLinkToNavigationMenu = () => { + const { t } = useLingui(); + const { addLinkToDraft } = useAddLinkToNavigationMenuDraft(); + const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState(); + const navigationMenuItemsDraft = useRecoilValue( + navigationMenuItemsDraftState, + ); + const setSelectedNavigationMenuItemInEditMode = useSetRecoilState( + selectedNavigationMenuItemInEditModeState, + ); + const { openNavigationMenuItemInCommandMenu } = + useOpenNavigationMenuItemInCommandMenu(); + const addMenuItemInsertionContext = useRecoilValue( + addMenuItemInsertionContextState, + ); + const setAddMenuItemInsertionContext = useSetRecoilState( + addMenuItemInsertionContextState, + ); + + const currentDraft = isDefined(navigationMenuItemsDraft) + ? navigationMenuItemsDraft + : workspaceNavigationMenuItems; + + const handleAddLink = () => { + const targetFolderId = addMenuItemInsertionContext?.targetFolderId ?? null; + const targetIndex = addMenuItemInsertionContext?.targetIndex; + + const itemId = addLinkToDraft( + t`Link label`, + 'www.example.com', + currentDraft, + targetFolderId, + targetIndex, + ); + + setAddMenuItemInsertionContext(null); + setSelectedNavigationMenuItemInEditMode(itemId); + openNavigationMenuItemInCommandMenu({ + pageTitle: t`Edit link`, + pageIcon: IconLink, + focusTitleInput: true, + }); + }; + + return { handleAddLink }; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useFolderPickerSelectionData.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useFolderPickerSelectionData.ts index 47b521dfe3..4adde68355 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useFolderPickerSelectionData.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useFolderPickerSelectionData.ts @@ -2,12 +2,12 @@ import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; -import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData'; -import { useNavigationMenuItemEditSubView } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView'; -import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData'; +import { useDraftNavigationMenuItemsAllFolders } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItemsAllFolders'; +import { useDraftNavigationMenuItemsWorkspaceFolders } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItemsWorkspaceFolders'; +import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; import { useNavigationMenuItemMoveRemove } from '@/navigation-menu-item/hooks/useNavigationMenuItemMoveRemove'; import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; -import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; type FolderOption = { id: string; @@ -38,42 +38,47 @@ const excludeCurrentFolder = ( ? folders : folders.filter((folder) => folder.id !== currentFolderId); -export const useFolderPickerSelectionData = () => { +type UseFolderPickerSelectionDataParams = { + onCloseSubView: () => void; +}; + +export const useFolderPickerSelectionData = ({ + onCloseSubView, +}: UseFolderPickerSelectionDataParams) => { const { closeCommandMenu } = useCommandMenu(); - const { clearSubView } = useNavigationMenuItemEditSubView(); const { moveToFolder } = useNavigationMenuItemMoveRemove(); const selectedNavigationMenuItemInEditMode = useRecoilValue( selectedNavigationMenuItemInEditModeState, ); - const { selectedItem, selectedItemType } = - useSelectedNavigationMenuItemEditData(); - const { allFolders, workspaceFolders } = - useNavigationMenuItemEditFolderData(); + const { selectedItem } = useSelectedNavigationMenuItemEditItem(); + const selectedItemType = selectedItem?.itemType ?? null; + const { allFolders } = useDraftNavigationMenuItemsAllFolders(); + const { workspaceFolders } = useDraftNavigationMenuItemsWorkspaceFolders(); const selectedFolderId = - selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER + selectedItemType === NavigationMenuItemType.FOLDER ? selectedNavigationMenuItemInEditMode : null; const currentFolderId = - selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER + selectedItemType === NavigationMenuItemType.FOLDER ? (selectedItem?.id ?? null) : (selectedItem?.folderId ?? null); const descendantFolderIds = - selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER && + selectedItemType === NavigationMenuItemType.FOLDER && isDefined(selectedFolderId) ? getDescendantFolderIds(selectedFolderId, allFolders) : new Set(); const includeNoFolderOption = - (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER && + (selectedItemType === NavigationMenuItemType.FOLDER && isDefined(selectedFolderId)) || - (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK && + (selectedItemType === NavigationMenuItemType.LINK && isDefined(currentFolderId)); const folders = includeNoFolderOption && - selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER && + selectedItemType === NavigationMenuItemType.FOLDER && isDefined(selectedFolderId) ? allFolders.filter( (folder) => @@ -89,7 +94,7 @@ export const useFolderPickerSelectionData = () => { const handleSelectFolder = (folderId: string | null) => { if (isDefined(selectedNavigationMenuItemInEditMode)) { moveToFolder(selectedNavigationMenuItemInEditMode, folderId); - clearSubView(); + onCloseSubView(); closeCommandMenu(); } }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData.ts deleted file mode 100644 index dc731c1239..0000000000 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/hooks/useNavigationMenuItemsByFolder'; -import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState'; -import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; -import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder'; - -export const useNavigationMenuItemEditFolderData = () => { - const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState(); - const navigationMenuItemsDraft = useRecoilValue( - navigationMenuItemsDraftState, - ); - const { workspaceNavigationMenuItemsByFolder } = - useNavigationMenuItemsByFolder(); - - const currentDraft = navigationMenuItemsDraft ?? workspaceNavigationMenuItems; - - const workspaceFolders = workspaceNavigationMenuItemsByFolder.map( - (folder) => ({ - id: folder.id, - name: folder.folderName, - }), - ); - - const allFolders = - currentDraft?.filter(isNavigationMenuItemFolder).map((item) => ({ - id: item.id, - name: item.name ?? 'Folder', - folderId: item.folderId ?? undefined, - })) ?? []; - - return { - allFolders, - workspaceFolders, - currentDraft, - }; -}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView.ts deleted file mode 100644 index 440ae63e21..0000000000 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useState } from 'react'; - -export type EditSubView = 'folder-picker' | null; - -export const useNavigationMenuItemEditSubView = () => { - const [editSubView, setEditSubView] = useState(null); - - const setFolderPicker = () => setEditSubView('folder-picker'); - const clearSubView = () => setEditSubView(null); - - return { - editSubView, - setFolderPicker, - clearSubView, - }; -}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData.ts deleted file mode 100644 index 45a6520d88..0000000000 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; -import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; -import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; -import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; -import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; - -export const useSelectedNavigationMenuItemEditData = () => { - const selectedNavigationMenuItemInEditMode = useRecoilValue( - selectedNavigationMenuItemInEditModeState, - ); - const items = useWorkspaceSectionItems(); - const coreViews = useRecoilValue(coreViewsState); - const views = coreViews.map(convertCoreViewToView); - const { objectMetadataItems } = useObjectMetadataItems(); - - const selectedItem = selectedNavigationMenuItemInEditMode - ? items.find((item) => item.id === selectedNavigationMenuItemInEditMode) - : undefined; - - const selectedItemType = selectedItem?.itemType ?? null; - const selectedItemObjectMetadata = selectedItem - ? getObjectMetadataForNavigationMenuItem( - selectedItem, - objectMetadataItems, - views, - ) - : null; - const selectedItemLabel = selectedItem - ? selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER - ? (selectedItem.name ?? 'Folder') - : selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK - ? (selectedItem.name ?? 'Link') - : (selectedItemObjectMetadata?.labelPlural ?? '') - : null; - - const processedItem = - selectedItem && selectedItem.itemType !== NAVIGATION_MENU_ITEM_TYPE.FOLDER - ? selectedItem - : undefined; - - return { - selectedItem, - selectedItemType, - selectedItemObjectMetadata, - selectedItemLabel, - processedItem, - }; -}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem.ts new file mode 100644 index 0000000000..1ea7ef15b2 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem.ts @@ -0,0 +1,55 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +type GetAvailableObjectMetadataForNewSidebarItemProps = { + objectMetadataItems: ObjectMetadataItem[]; + activeNonSystemObjectMetadataItems: ObjectMetadataItem[]; + objectMetadataIdsWithIndexView: Set; + objectMetadataIdsWithDisplayableViews: Set; +}; + +type GetAvailableObjectMetadataForNewSidebarItemResult = { + availableObjectMetadataItems: ObjectMetadataItem[]; + availableSystemObjectMetadataItems: ObjectMetadataItem[]; + objectMetadataItemsWithViews: ObjectMetadataItem[]; + availableSystemObjectMetadataItemsForView: ObjectMetadataItem[]; +}; + +export const getAvailableObjectMetadataForNewSidebarItem = ({ + objectMetadataItems, + activeNonSystemObjectMetadataItems, + objectMetadataIdsWithIndexView, + objectMetadataIdsWithDisplayableViews, +}: GetAvailableObjectMetadataForNewSidebarItemProps): GetAvailableObjectMetadataForNewSidebarItemResult => { + const availableObjectMetadataItems = [ + ...activeNonSystemObjectMetadataItems, + ].sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); + + const activeSystemObjectMetadataItems = objectMetadataItems + .filter((item) => item.isActive && item.isSystem) + .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); + + const availableSystemObjectMetadataItems = + activeSystemObjectMetadataItems.filter((item) => + objectMetadataIdsWithIndexView.has(item.id), + ); + + const objectMetadataItemsWithViews = objectMetadataItems + .filter( + (item) => + item.isActive && objectMetadataIdsWithDisplayableViews.has(item.id), + ) + .filter((item) => !item.isSystem) + .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); + + const availableSystemObjectMetadataItemsForView = + activeSystemObjectMetadataItems.filter((item) => + objectMetadataIdsWithDisplayableViews.has(item.id), + ); + + return { + availableObjectMetadataItems, + availableSystemObjectMetadataItems, + objectMetadataItemsWithViews, + availableSystemObjectMetadataItemsForView, + }; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds.ts b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds.ts index b1f26f0a84..596de726f4 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds.ts @@ -1,10 +1,27 @@ +import { CommandMenuNavigationItemActions } from '@/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions'; + +const ORGANIZE_ACTIONS_WITHOUT_MOVE_TO_FOLDER: CommandMenuNavigationItemActions[] = + [ + CommandMenuNavigationItemActions.MOVE_UP, + CommandMenuNavigationItemActions.MOVE_DOWN, + CommandMenuNavigationItemActions.ADD_BEFORE, + CommandMenuNavigationItemActions.ADD_AFTER, + CommandMenuNavigationItemActions.REMOVE, + ]; + +const ORGANIZE_ACTIONS_WITH_MOVE_TO_FOLDER: CommandMenuNavigationItemActions[] = + [ + CommandMenuNavigationItemActions.MOVE_UP, + CommandMenuNavigationItemActions.MOVE_DOWN, + CommandMenuNavigationItemActions.MOVE_TO_FOLDER, + CommandMenuNavigationItemActions.ADD_BEFORE, + CommandMenuNavigationItemActions.ADD_AFTER, + CommandMenuNavigationItemActions.REMOVE, + ]; + export const getOrganizeActionsSelectableItemIds = ( includeMoveToFolder: boolean, -) => [ - 'move-up', - 'move-down', - ...(includeMoveToFolder ? ['move-to-folder'] : []), - 'add-before', - 'add-after', - 'remove', -]; +): CommandMenuNavigationItemActions[] => + includeMoveToFolder + ? ORGANIZE_ACTIONS_WITH_MOVE_TO_FOLDER + : ORGANIZE_ACTIONS_WITHOUT_MOVE_TO_FOLDER; 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 a90b5104de..6dcb62fa2f 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 @@ -3,8 +3,8 @@ import { Droppable } from '@hello-pangea/dnd'; import { useLingui } from '@lingui/react/macro'; import { useContext, useState } from 'react'; +import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants'; import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection'; -import { NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants'; import { createPortal } from 'react-dom'; import { useLocation, useNavigate } from 'react-router-dom'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; @@ -20,6 +20,7 @@ import { NavigationMenuItemDroppable } from '@/navigation-menu-item/components/N import { NavigationMenuItemFolderNavigationDrawerItemDropdown } from '@/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown'; import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; 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'; @@ -103,7 +104,9 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({ if (!isOpen) { const firstNonLinkItem = folder.navigationMenuItems.find( - (item) => item.itemType !== 'link' && isNonEmptyString(item.link), + (item) => + item.itemType !== NavigationMenuItemType.LINK && + isNonEmptyString(item.link), ); if (isDefined(firstNonLinkItem?.link)) { navigate(firstNonLinkItem.link); @@ -221,8 +224,8 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({ index={0} sectionId={ isWorkspaceFolder - ? NAVIGATION_SECTIONS.WORKSPACE - : NAVIGATION_SECTIONS.FAVORITES + ? NavigationSections.WORKSPACE + : NavigationSections.FAVORITES } > { return ( {orphanNavigationMenuItems.length > 0 ? ( @@ -53,7 +51,7 @@ export const CurrentWorkspaceMemberOrphanNavigationMenuItems = () => { key={navigationMenuItem.id} folderId={null} index={index} - sectionId={NAVIGATION_SECTIONS.FAVORITES} + sectionId={NavigationSections.FAVORITES} > { ) : ( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationItemDropTarget.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationItemDropTarget.tsx index 908a30ea1c..2f506513aa 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationItemDropTarget.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationItemDropTarget.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; import { type ReactNode, useContext } from 'react'; +import { type NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants'; import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext'; -import type { NavigationSectionId } from '@/navigation-menu-item/types/NavigationSectionId'; const StyledDropTarget = styled.div<{ $isDragOver: boolean; @@ -49,12 +49,10 @@ const StyledDropTarget = styled.div<{ `} `; -export type NavigationItemDropTargetSectionId = NavigationSectionId; - type NavigationItemDropTargetProps = { folderId: string | null; index: number; - sectionId: NavigationItemDropTargetSectionId; + sectionId: NavigationSections; children?: ReactNode; compact?: boolean; }; 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 204f3a071f..1e5c02f9c6 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 @@ -2,6 +2,7 @@ import { useTheme } from '@emotion/react'; import { Avatar, useIcons } from 'twenty-ui/display'; import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors'; import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon'; @@ -23,17 +24,19 @@ export const NavigationMenuItemIcon = ({ const placeholderColorSeed = navigationMenuItem.targetRecordId ?? undefined; - const isRecord = navigationMenuItem.itemType === 'record'; - const isLink = navigationMenuItem.itemType === 'link'; + const isRecord = + navigationMenuItem.itemType === NavigationMenuItemType.RECORD; + const isLink = navigationMenuItem.itemType === NavigationMenuItemType.LINK; const iconColors = getNavigationMenuItemIconColors(theme); const isObjectIndexView = - navigationMenuItem.itemType === 'view' && + navigationMenuItem.itemType === NavigationMenuItemType.VIEW && navigationMenuItem.viewKey === ViewKey.Index; const iconBackgroundColor = isRecord ? undefined : isLink ? iconColors.link - : navigationMenuItem.itemType === 'view' && !isObjectIndexView + : navigationMenuItem.itemType === NavigationMenuItemType.VIEW && + !isObjectIndexView ? iconColors.view : iconColors.object; 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 3e82852ec4..044eb62137 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 @@ -24,7 +24,7 @@ import { useWorkspaceSectionItems, } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState'; -import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems'; import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState'; @@ -91,7 +91,7 @@ export const WorkspaceNavigationMenuItems = () => { const { item, objectMetadataItem } = params; const id = item.id; setSelectedNavigationMenuItemInEditMode(id); - if (item.itemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER) { + if (item.itemType === NavigationMenuItemType.FOLDER) { setOpenNavigationMenuItemFolderIds((currentOpenFolders) => currentOpenFolders.includes(id) ? currentOpenFolders @@ -101,7 +101,7 @@ export const WorkspaceNavigationMenuItems = () => { pageTitle: t`Edit folder`, pageIcon: IconFolder, }); - } else if (item.itemType === NAVIGATION_MENU_ITEM_TYPE.LINK) { + } else if (item.itemType === NavigationMenuItemType.LINK) { openNavigationMenuItemInCommandMenu({ pageTitle: t`Edit link`, pageIcon: IconLink, 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 083edbde7d..1aa67d0e85 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 @@ -15,7 +15,8 @@ import { useIsMobile } from 'twenty-ui/utilities'; import { NavigationMenuItemDroppable } from '@/navigation-menu-item/components/NavigationMenuItemDroppable'; import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; import { WorkspaceNavigationMenuItemFolderDragClone } from '@/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone'; -import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; +import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext'; import { type NavigationMenuItemClickParams } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState'; @@ -113,7 +114,9 @@ export const WorkspaceNavigationMenuItemsFolder = ({ if (!isOpen) { const firstNonLinkItem = navigationMenuItems.find( - (item) => item.itemType !== 'link' && isNonEmptyString(item.link), + (item) => + item.itemType !== NavigationMenuItemType.LINK && + isNonEmptyString(item.link), ); if (isDefined(firstNonLinkItem?.link)) { navigate(firstNonLinkItem.link); @@ -139,7 +142,7 @@ export const WorkspaceNavigationMenuItemsFolder = ({ ( @@ -190,8 +193,10 @@ export const WorkspaceNavigationMenuItemsFolder = ({ > {navigationMenuItems.map((navigationMenuItem, index) => { const objectMetadataItem = - navigationMenuItem.itemType === 'view' || - navigationMenuItem.itemType === 'record' + navigationMenuItem.itemType === + NavigationMenuItemType.VIEW || + navigationMenuItem.itemType === + NavigationMenuItemType.RECORD ? getObjectMetadataForNavigationMenuItem( navigationMenuItem, objectMetadataItems, @@ -201,7 +206,8 @@ export const WorkspaceNavigationMenuItemsFolder = ({ const handleEditModeClick = isEditMode && isDefined(onNavigationMenuItemClick) && - (navigationMenuItem.itemType === 'link' || + (navigationMenuItem.itemType === + NavigationMenuItemType.LINK || isDefined(objectMetadataItem)) ? () => onNavigationMenuItemClick({ diff --git a/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationMenuItemDroppableIds.ts b/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationMenuItemDroppableIds.ts index 16d1824b44..700a471393 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationMenuItemDroppableIds.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationMenuItemDroppableIds.ts @@ -1,7 +1,6 @@ -export const NAVIGATION_MENU_ITEM_DROPPABLE_IDS = { - ORPHAN_NAVIGATION_MENU_ITEMS: 'orphan-navigation-menu-items', - WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS: - 'workspace-orphan-navigation-menu-items', - WORKSPACE_FOLDER_PREFIX: 'workspace-folder-', - WORKSPACE_FOLDER_HEADER_PREFIX: 'workspace-folder-header-', -} as const; +export enum NavigationMenuItemDroppableIds { + ORPHAN_NAVIGATION_MENU_ITEMS = 'orphan-navigation-menu-items', + WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS = 'workspace-orphan-navigation-menu-items', + WORKSPACE_FOLDER_PREFIX = 'workspace-folder-', + WORKSPACE_FOLDER_HEADER_PREFIX = 'workspace-folder-header-', +} 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 new file mode 100644 index 0000000000..5448cf3140 --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationMenuItemType.ts @@ -0,0 +1,7 @@ +export enum NavigationMenuItemType { + FOLDER = 'folder', + LINK = 'link', + OBJECT = 'object', + RECORD = 'record', + VIEW = 'view', +} diff --git a/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationSections.constants.ts b/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationSections.constants.ts index d072a986a0..dacae35550 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationSections.constants.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/constants/NavigationSections.constants.ts @@ -1,4 +1,4 @@ -export const NAVIGATION_SECTIONS = { - WORKSPACE: 'workspace', - FAVORITES: 'favorites', -} as const; +export enum NavigationSections { + WORKSPACE = 'workspace', + FAVORITES = 'favorites', +} diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft.ts index 33f51893eb..3f13c4147b 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft.ts @@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils'; import type { NavigationMenuItem } from '~/generated-metadata/graphql'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; -import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/add-to-navigation-draft.utils'; +import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition'; export const useAddFolderToNavigationMenuDraft = () => { const setNavigationMenuItemsDraft = useSetRecoilState( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft.ts index 70ae5a5933..87dfdd9951 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft.ts @@ -4,10 +4,8 @@ import { v4 } from 'uuid'; import type { NavigationMenuItem } from '~/generated-metadata/graphql'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; -import { - computeInsertIndexAndPosition, - normalizeUrl, -} from '@/navigation-menu-item/utils/add-to-navigation-draft.utils'; +import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition'; +import { normalizeUrl } from '@/navigation-menu-item/utils/normalizeUrl'; export const useAddLinkToNavigationMenuDraft = () => { const setNavigationMenuItemsDraft = useSetRecoilState( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft.ts index 160c58c174..9ab0959177 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft.ts @@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils'; import type { NavigationMenuItem } from '~/generated-metadata/graphql'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; -import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/add-to-navigation-draft.utils'; +import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition'; export const useAddObjectToNavigationMenuDraft = () => { const setNavigationMenuItemsDraft = useSetRecoilState( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft.ts index 94cd11b305..2bfd929fd8 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft.ts @@ -5,7 +5,7 @@ import type { NavigationMenuItem } from '~/generated-metadata/graphql'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/add-to-navigation-draft.utils'; +import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition'; type SearchRecord = { recordId: string; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft.ts index 9830657f26..54a4537543 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft.ts @@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils'; import type { NavigationMenuItem } from '~/generated-metadata/graphql'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; -import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/add-to-navigation-draft.utils'; +import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition'; export const useAddViewToNavigationMenuDraft = () => { const setNavigationMenuItemsDraft = useSetRecoilState( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItems.ts new file mode 100644 index 0000000000..0897f96e4f --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItems.ts @@ -0,0 +1,15 @@ +import { useRecoilValue } from 'recoil'; + +import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState'; +import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; + +export const useDraftNavigationMenuItems = () => { + const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState(); + const navigationMenuItemsDraft = useRecoilValue( + navigationMenuItemsDraftState, + ); + + const currentDraft = navigationMenuItemsDraft ?? workspaceNavigationMenuItems; + + return { currentDraft }; +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItemsAllFolders.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItemsAllFolders.ts new file mode 100644 index 0000000000..9a3637d5bf --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItemsAllFolders.ts @@ -0,0 +1,15 @@ +import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems'; +import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder'; + +export const useDraftNavigationMenuItemsAllFolders = () => { + const { currentDraft } = useDraftNavigationMenuItems(); + + const allFolders = + currentDraft?.filter(isNavigationMenuItemFolder).map((item) => ({ + id: item.id, + name: item.name ?? 'Folder', + folderId: item.folderId ?? undefined, + })) ?? []; + + return { allFolders }; +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItemsWorkspaceFolders.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItemsWorkspaceFolders.ts new file mode 100644 index 0000000000..c7a691d91e --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useDraftNavigationMenuItemsWorkspaceFolders.ts @@ -0,0 +1,15 @@ +import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/hooks/useNavigationMenuItemsByFolder'; + +export const useDraftNavigationMenuItemsWorkspaceFolders = () => { + const { workspaceNavigationMenuItemsByFolder } = + useNavigationMenuItemsByFolder(); + + const workspaceFolders = workspaceNavigationMenuItemsByFolder.map( + (folder) => ({ + id: folder.id, + name: folder.folderName, + }), + ); + + return { workspaceFolders }; +}; 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 e152c2c460..ae4f774061 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,6 +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 { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft'; import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft'; import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft'; @@ -77,7 +78,10 @@ export const useHandleAddToNavigationDrop = () => { ); const index = destination.index; - if (payload.type === 'folder' && folderId !== null) { + if ( + payload.type === NavigationMenuItemType.FOLDER && + folderId !== null + ) { return; } @@ -97,7 +101,7 @@ export const useHandleAddToNavigationDrop = () => { }; switch (payload.type) { - case 'folder': { + case NavigationMenuItemType.FOLDER: { const newFolderId = addFolderToDraft( payload.name, currentDraft, @@ -111,7 +115,7 @@ export const useHandleAddToNavigationDrop = () => { }); return; } - case 'link': { + case NavigationMenuItemType.LINK: { const newLinkId = addLinkToDraft( payload.name || t`Link label`, payload.link, @@ -126,7 +130,7 @@ export const useHandleAddToNavigationDrop = () => { }); return; } - case 'object': { + case NavigationMenuItemType.OBJECT: { const newItemId = addObjectToDraft( payload.objectMetadataId, payload.defaultViewId, @@ -145,7 +149,7 @@ export const useHandleAddToNavigationDrop = () => { }); return; } - case 'view': { + case NavigationMenuItemType.VIEW: { const newItemId = addViewToDraft( payload.viewId, currentDraft, @@ -160,7 +164,7 @@ export const useHandleAddToNavigationDrop = () => { }); return; } - case 'record': { + case NavigationMenuItemType.RECORD: { const newItemId = addRecordToDraft( { recordId: payload.recordId, diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop.ts index 3f8a567687..cd116f3efa 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop.ts @@ -1,7 +1,7 @@ import { type OnDragEndResponder } from '@hello-pangea/dnd'; import { useSetRecoilState } from 'recoil'; -import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; +import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; import { isWorkspaceDroppableId } from '@/navigation-menu-item/utils/isWorkspaceDroppableId'; import { useSortedNavigationMenuItems } from '@/navigation-menu-item/hooks/useSortedNavigationMenuItems'; import { useUpdateNavigationMenuItem } from '@/navigation-menu-item/hooks/useUpdateNavigationMenuItem'; @@ -63,12 +63,12 @@ export const useHandleNavigationMenuItemDragAndDrop = () => { const destinationFolderId = validateAndExtractFolderId({ droppableId: destination.droppableId, orphanDroppableId: - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.ORPHAN_NAVIGATION_MENU_ITEMS, + NavigationMenuItemDroppableIds.ORPHAN_NAVIGATION_MENU_ITEMS, }); const sourceFolderId = validateAndExtractFolderId({ droppableId: source.droppableId, orphanDroppableId: - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.ORPHAN_NAVIGATION_MENU_ITEMS, + NavigationMenuItemDroppableIds.ORPHAN_NAVIGATION_MENU_ITEMS, }); if ( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop.ts index 64e14d11f7..b1e3e3e586 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop.ts @@ -2,7 +2,7 @@ import { type OnDragEndResponder } from '@hello-pangea/dnd'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; -import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; +import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState'; import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState'; @@ -87,7 +87,7 @@ export const useHandleWorkspaceNavigationMenuItemDragAndDrop = () => { ); const isDropOnFolderHeader = destination.droppableId.startsWith( - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX, + NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX, ); if (isDropOnFolderHeader && isDefined(destinationFolderId)) { diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem.ts new file mode 100644 index 0000000000..4689de6c29 --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem.ts @@ -0,0 +1,17 @@ +import { useRecoilValue } from 'recoil'; + +import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems'; +import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState'; + +export const useSelectedNavigationMenuItemEditItem = () => { + const selectedNavigationMenuItemInEditMode = useRecoilValue( + selectedNavigationMenuItemInEditModeState, + ); + const items = useWorkspaceSectionItems(); + + const selectedItem = selectedNavigationMenuItemInEditMode + ? items.find((item) => item.id === selectedNavigationMenuItemInEditMode) + : undefined; + + return { selectedItem }; +}; 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 new file mode 100644 index 0000000000..be6a5d7e2b --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel.ts @@ -0,0 +1,19 @@ +import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; +import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; + +export const useSelectedNavigationMenuItemEditItemLabel = () => { + const { selectedItem } = useSelectedNavigationMenuItemEditItem(); + const { selectedItemObjectMetadata } = + useSelectedNavigationMenuItemEditItemObjectMetadata(); + + const selectedItemLabel = selectedItem + ? selectedItem.itemType === NavigationMenuItemType.FOLDER + ? (selectedItem.name ?? 'Folder') + : selectedItem.itemType === NavigationMenuItemType.LINK + ? (selectedItem.name ?? 'Link') + : (selectedItemObjectMetadata?.labelPlural ?? '') + : null; + + return { selectedItemLabel }; +}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts new file mode 100644 index 0000000000..aabaa5f98b --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts @@ -0,0 +1,24 @@ +import { useRecoilValue } from 'recoil'; + +import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; +import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { coreViewsState } from '@/views/states/coreViewState'; +import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; + +export const useSelectedNavigationMenuItemEditItemObjectMetadata = () => { + const { selectedItem } = useSelectedNavigationMenuItemEditItem(); + const coreViews = useRecoilValue(coreViewsState); + const views = coreViews.map(convertCoreViewToView); + const { objectMetadataItems } = useObjectMetadataItems(); + + const selectedItemObjectMetadata = selectedItem + ? getObjectMetadataForNavigationMenuItem( + selectedItem, + objectMetadataItems, + views, + ) + : null; + + return { selectedItemObjectMetadata }; +}; 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 abd8ee48e4..3110d2e8bc 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 @@ -3,7 +3,7 @@ import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder'; -import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; +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 { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -18,7 +18,7 @@ import { useSortedNavigationMenuItems } from './useSortedNavigationMenuItems'; export type FlatWorkspaceItem = | ProcessedNavigationMenuItem | (NavigationMenuItem & { - itemType: typeof NAVIGATION_MENU_ITEM_TYPE.FOLDER; + itemType: NavigationMenuItemType.FOLDER; }); export type NavigationMenuItemClickParams = { @@ -56,13 +56,13 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { FlatWorkspaceItem[] >((acc, item) => { if (isNavigationMenuItemFolder(item)) { - acc.push({ ...item, itemType: NAVIGATION_MENU_ITEM_TYPE.FOLDER }); + acc.push({ ...item, itemType: NavigationMenuItemType.FOLDER }); } else { const processedItem = processedObjectViewsById.get(item.id); if (!isDefined(processedItem)) { return acc; } - if (processedItem.itemType === NAVIGATION_MENU_ITEM_TYPE.LINK) { + if (processedItem.itemType === NavigationMenuItemType.LINK) { acc.push(processedItem); } else { const objectMetadataItem = getObjectMetadataForNavigationMenuItem( @@ -79,7 +79,7 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { }, []); return flatItems.flatMap((item) => - item.itemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER + item.itemType === NavigationMenuItemType.FOLDER ? [item, ...(folderChildrenById.get(item.id) ?? [])] : [item], ); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/types/NavigationSectionId.ts b/packages/twenty-front/src/modules/navigation-menu-item/types/NavigationSectionId.ts deleted file mode 100644 index ace7d5e197..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/types/NavigationSectionId.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { type NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants'; - -export type NavigationSectionId = - (typeof NAVIGATION_SECTIONS)[keyof typeof NAVIGATION_SECTIONS]; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/types/navigation-menu-item-type.ts b/packages/twenty-front/src/modules/navigation-menu-item/types/navigation-menu-item-type.ts deleted file mode 100644 index f63af35478..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/types/navigation-menu-item-type.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const NAVIGATION_MENU_ITEM_TYPE = { - FOLDER: 'folder', - LINK: 'link', - OBJECT: 'object', - RECORD: 'record', - VIEW: 'view', -} as const; - -export type NavigationMenuItemType = - (typeof NAVIGATION_MENU_ITEM_TYPE)[keyof typeof NAVIGATION_MENU_ITEM_TYPE]; 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 index db0913f5e3..fbe2cb33e0 100644 --- 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 @@ -2,7 +2,7 @@ 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/types/navigation-menu-item-type'; +import { type NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; export type ProcessedNavigationMenuItem = NavigationMenuItem & NavigationMenuItemDisplayFields & { diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/add-to-navigation-draft.utils.test.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/computeInsertIndexAndPosition.test.ts similarity index 77% rename from packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/add-to-navigation-draft.utils.test.ts rename to packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/computeInsertIndexAndPosition.test.ts index a195b29f56..a4e36eb9dd 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/add-to-navigation-draft.utils.test.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/computeInsertIndexAndPosition.test.ts @@ -1,17 +1,6 @@ -import { - computeInsertIndexAndPosition, - normalizeUrl, -} from '@/navigation-menu-item/utils/add-to-navigation-draft.utils'; +import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; -describe('normalizeUrl', () => { - it('should leave url unchanged when it has protocol, otherwise prepend https', () => { - expect(normalizeUrl('https://example.com')).toBe('https://example.com'); - expect(normalizeUrl('example.com')).toBe('https://example.com'); - expect(normalizeUrl(' example.com ')).toBe('https://example.com'); - }); -}); - describe('computeInsertIndexAndPosition', () => { it('should compute flatIndex and position for insert in folder', () => { const empty: NavigationMenuItem[] = []; 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 abdc5eccb5..a74f357fda 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,3 +1,4 @@ +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type View } from '@/views/types/View'; @@ -29,7 +30,7 @@ const mockViews: View[] = [ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for link item type', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: 'link' }, + { itemType: NavigationMenuItemType.LINK }, mockObjectMetadataItems, mockViews, ); @@ -38,7 +39,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return object metadata for view item when view and metadata exist', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: 'view', viewId: 'view-1' }, + { itemType: NavigationMenuItemType.VIEW, viewId: 'view-1' }, mockObjectMetadataItems, mockViews, ); @@ -48,7 +49,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for view item when view is not found', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: 'view', viewId: 'non-existent-view' }, + { itemType: NavigationMenuItemType.VIEW, viewId: 'non-existent-view' }, mockObjectMetadataItems, mockViews, ); @@ -60,7 +61,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { { id: 'orphan-view', objectMetadataId: 'non-existent-metadata' } as View, ]; const result = getObjectMetadataForNavigationMenuItem( - { itemType: 'view', viewId: 'orphan-view' }, + { itemType: NavigationMenuItemType.VIEW, viewId: 'orphan-view' }, mockObjectMetadataItems, viewsWithOrphanView, ); @@ -70,7 +71,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return object metadata for record item when metadata exists', () => { const result = getObjectMetadataForNavigationMenuItem( { - itemType: 'record', + itemType: NavigationMenuItemType.RECORD, targetObjectMetadataId: 'metadata-2', }, mockObjectMetadataItems, @@ -82,7 +83,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for record item when targetObjectMetadataId is undefined', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: 'record' }, + { itemType: NavigationMenuItemType.RECORD }, mockObjectMetadataItems, mockViews, ); @@ -92,7 +93,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for record item when metadata is not found', () => { const result = getObjectMetadataForNavigationMenuItem( { - itemType: 'record', + itemType: NavigationMenuItemType.RECORD, targetObjectMetadataId: 'non-existent-metadata', }, mockObjectMetadataItems, @@ -103,7 +104,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => { it('should return null for view item when viewId is undefined', () => { const result = getObjectMetadataForNavigationMenuItem( - { itemType: 'view' }, + { itemType: 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 35f4863a09..93fe33d938 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 { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem'; describe('isLocationMatchingNavigationMenuItem', () => { @@ -8,7 +8,7 @@ describe('isLocationMatchingNavigationMenuItem', () => { '/app/objects/people', '/app/objects/people?viewId=123', { - itemType: NAVIGATION_MENU_ITEM_TYPE.RECORD, + itemType: NavigationMenuItemType.RECORD, link: '/app/objects/people', }, ), @@ -18,7 +18,7 @@ describe('isLocationMatchingNavigationMenuItem', () => { '/app/objects/companies', '/app/objects/companies?viewId=123', { - itemType: NAVIGATION_MENU_ITEM_TYPE.VIEW, + itemType: NavigationMenuItemType.VIEW, link: '/app/objects/companies?viewId=123', }, ), @@ -31,7 +31,7 @@ describe('isLocationMatchingNavigationMenuItem', () => { '/app/objects/people', '/app/objects/people?viewId=123', { - itemType: NAVIGATION_MENU_ITEM_TYPE.RECORD, + itemType: NavigationMenuItemType.RECORD, link: '/app/objects/company', }, ), @@ -41,7 +41,7 @@ describe('isLocationMatchingNavigationMenuItem', () => { '/app/objects/companies', '/app/objects/companies?viewId=123', { - itemType: NAVIGATION_MENU_ITEM_TYPE.VIEW, + itemType: NavigationMenuItemType.VIEW, link: '/app/objects/companies?viewId=456', }, ), diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/normalizeUrl.test.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/normalizeUrl.test.ts new file mode 100644 index 0000000000..7d3d3fb699 --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/__tests__/normalizeUrl.test.ts @@ -0,0 +1,14 @@ +import { normalizeUrl } from '@/navigation-menu-item/utils/normalizeUrl'; + +describe('normalizeUrl', () => { + it('should leave url unchanged when it has protocol, otherwise prepend https', () => { + expect(normalizeUrl('https://example.com')).toBe('https://example.com'); + expect(normalizeUrl('example.com')).toBe('https://example.com'); + expect(normalizeUrl(' example.com ')).toBe('https://example.com'); + }); + + it('should return empty string for empty or whitespace input', () => { + expect(normalizeUrl('')).toBe(''); + expect(normalizeUrl(' ')).toBe(''); + }); +}); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/add-to-navigation-draft.utils.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/computeInsertIndexAndPosition.ts similarity index 82% rename from packages/twenty-front/src/modules/navigation-menu-item/utils/add-to-navigation-draft.utils.ts rename to packages/twenty-front/src/modules/navigation-menu-item/utils/computeInsertIndexAndPosition.ts index 5860f544bc..483efe2993 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/add-to-navigation-draft.utils.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/computeInsertIndexAndPosition.ts @@ -1,13 +1,6 @@ import { isDefined } from 'twenty-shared/utils'; import type { NavigationMenuItem } from '~/generated-metadata/graphql'; -export const normalizeUrl = (url: string) => { - const trimmed = url.trim(); - return trimmed.startsWith('http://') || trimmed.startsWith('https://') - ? trimmed - : `https://${trimmed}`; -}; - export const computeInsertIndexAndPosition = ( currentDraft: NavigationMenuItem[], targetFolderId: string | null, diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/getDropTargetIdFromDestination.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/getDropTargetIdFromDestination.ts index d992a1235c..88fe8c489d 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/getDropTargetIdFromDestination.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/getDropTargetIdFromDestination.ts @@ -1,4 +1,4 @@ -import { NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants'; +import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants'; import { isWorkspaceDroppableId } from '@/navigation-menu-item/utils/isWorkspaceDroppableId'; import { validateAndExtractWorkspaceFolderId } from '@/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId'; import type { DropResult } from '@hello-pangea/dnd'; @@ -11,5 +11,5 @@ export const getDropTargetIdFromDestination = ( } const folderId = validateAndExtractWorkspaceFolderId(destination.droppableId); const folderSegment = folderId ?? 'orphan'; - return `${NAVIGATION_SECTIONS.WORKSPACE}-${folderSegment}-${destination.index}`; + return `${NavigationSections.WORKSPACE}-${folderSegment}-${destination.index}`; }; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/getIconBackgroundColorForPayload.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/getIconBackgroundColorForPayload.ts index 20ce481c76..2bc17e4fba 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/getIconBackgroundColorForPayload.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/getIconBackgroundColorForPayload.ts @@ -1,5 +1,6 @@ import type { Theme } from '@emotion/react'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload'; import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors'; @@ -9,15 +10,15 @@ export const getIconBackgroundColorForPayload = ( ): string | undefined => { const colors = getNavigationMenuItemIconColors(theme); switch (payload.type) { - case 'object': + case NavigationMenuItemType.OBJECT: return colors.object; - case 'view': + case NavigationMenuItemType.VIEW: return colors.view; - case 'folder': + case NavigationMenuItemType.FOLDER: return colors.folder; - case 'link': + case NavigationMenuItemType.LINK: return colors.link; - case 'record': + case NavigationMenuItemType.RECORD: return undefined; 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 03e6844ee7..ddce6f258b 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,5 +1,6 @@ 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'; @@ -14,12 +15,12 @@ export const getObjectMetadataForNavigationMenuItem = ( objectMetadataItems: ObjectMetadataItem[], views: View[], ): ObjectMetadataItem | null => { - if (navigationMenuItem.itemType === 'link') { + if (navigationMenuItem.itemType === NavigationMenuItemType.LINK) { return null; } if ( - navigationMenuItem.itemType === 'view' && + navigationMenuItem.itemType === NavigationMenuItemType.VIEW && isDefined(navigationMenuItem.viewId) ) { const view = views.find((view) => view.id === navigationMenuItem.viewId); @@ -33,7 +34,7 @@ export const getObjectMetadataForNavigationMenuItem = ( } if ( - navigationMenuItem.itemType === 'record' && + navigationMenuItem.itemType === NavigationMenuItemType.RECORD && isDefined(navigationMenuItem.targetObjectMetadataId) ) { const objectMetadataItem = objectMetadataItems.find( 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 4f3711ceac..ca36240ee6 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,4 +1,4 @@ -import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; export const isLocationMatchingNavigationMenuItem = ( @@ -7,7 +7,7 @@ export const isLocationMatchingNavigationMenuItem = ( navigationMenuItem: Pick, ) => { const isViewItem = - navigationMenuItem.itemType === NAVIGATION_MENU_ITEM_TYPE.VIEW; + navigationMenuItem.itemType === NavigationMenuItemType.VIEW; return isViewItem ? navigationMenuItem.link === currentViewPath : navigationMenuItem.link === currentPath; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/isWorkspaceDroppableId.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/isWorkspaceDroppableId.ts index e6bc574ac1..a658550264 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/isWorkspaceDroppableId.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/isWorkspaceDroppableId.ts @@ -1,6 +1,6 @@ import { isDefined } from 'twenty-shared/utils'; -import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; +import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; export const isWorkspaceDroppableId = ( droppableId: string | null | undefined, @@ -10,12 +10,12 @@ export const isWorkspaceDroppableId = ( } return ( droppableId === - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS || + NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS || droppableId.startsWith( - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_PREFIX, + NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_PREFIX, ) || droppableId.startsWith( - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX, + NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX, ) ); }; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/normalizeUrl.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/normalizeUrl.ts new file mode 100644 index 0000000000..8f1fb1864b --- /dev/null +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/normalizeUrl.ts @@ -0,0 +1,11 @@ +export const normalizeUrl = (url: string) => { + const trimmedUrl = url.trim(); + + if (trimmedUrl === '') { + return trimmedUrl; + } + + return trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://') + ? trimmedUrl + : `https://${trimmedUrl}`; +}; 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 index 2a554fc10a..91a472c58b 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/sortNavigationMenuItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/sortNavigationMenuItems.ts @@ -7,7 +7,7 @@ import { getAppPath, isDefined } from 'twenty-shared/utils'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { getObjectMetadataNamePluralFromViewId } from '@/favorites/utils/getObjectMetadataNamePluralFromViewId'; -import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; import { computeNavigationMenuItemDisplayFields, @@ -15,8 +15,7 @@ import { } from './computeNavigationMenuItemDisplayFields'; import { isNavigationMenuItemLink } from './isNavigationMenuItemLink'; -export { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type'; -export type { NavigationMenuItemType } from '@/navigation-menu-item/types/navigation-menu-item-type'; +export { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; export type { ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item'; export const sortNavigationMenuItems = ( @@ -70,7 +69,7 @@ export const sortNavigationMenuItems = ( ...navigationMenuItem, ...displayFields, viewKey: view.key, - itemType: NAVIGATION_MENU_ITEM_TYPE.VIEW, + itemType: NavigationMenuItemType.VIEW, }; } @@ -94,7 +93,7 @@ export const sortNavigationMenuItems = ( return { ...navigationMenuItem, ...displayFields, - itemType: NAVIGATION_MENU_ITEM_TYPE.LINK, + itemType: NavigationMenuItemType.LINK, }; } @@ -131,7 +130,7 @@ export const sortNavigationMenuItems = ( ...navigationMenuItem, ...displayFields, link: hasLinkToShowPage ? displayFields.link : '', - itemType: NAVIGATION_MENU_ITEM_TYPE.RECORD, + itemType: NavigationMenuItemType.RECORD, }; }) .filter(isDefined) diff --git a/packages/twenty-front/src/modules/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId.ts b/packages/twenty-front/src/modules/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId.ts index f0fe66eeb0..c861d43834 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId.ts @@ -1,7 +1,8 @@ +import { isNonEmptyString } from '@sniptt/guards'; import { CustomError, isDefined } from 'twenty-shared/utils'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; -import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; +import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; export const matchesWorkspaceFolderId = ( item: NavigationMenuItem, @@ -15,21 +16,21 @@ export const validateAndExtractWorkspaceFolderId = ( ): string | null => { if ( droppableId === - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS + NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS ) { return null; } if ( droppableId.startsWith( - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX, + NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX, ) ) { const folderId = droppableId.replace( - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX, + NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX, '', ); - if (!folderId) + if (!isNonEmptyString(folderId)) throw new CustomError( `Invalid workspace folder header ID: ${droppableId}`, 'INVALID_WORKSPACE_FOLDER_HEADER_ID', @@ -39,14 +40,14 @@ export const validateAndExtractWorkspaceFolderId = ( if ( droppableId.startsWith( - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_PREFIX, + NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_PREFIX, ) ) { const folderId = droppableId.replace( - NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_PREFIX, + NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_PREFIX, '', ); - if (!folderId) + if (!isNonEmptyString(folderId)) throw new CustomError( `Invalid workspace folder ID: ${droppableId}`, 'INVALID_WORKSPACE_FOLDER_ID', 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 f5ce4b7e52..6186a01422 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx @@ -1,3 +1,4 @@ +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors'; import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState'; @@ -45,8 +46,9 @@ export const NavigationDrawerItemForObjectMetadataItem = ({ const currentPath = location.pathname; const currentPathWithSearch = `${location.pathname}${location.search}`; - const isRecord = navigationMenuItem?.itemType === 'record'; - const isView = navigationMenuItem?.itemType === 'view'; + const isRecord = + navigationMenuItem?.itemType === NavigationMenuItemType.RECORD; + const isView = navigationMenuItem?.itemType === NavigationMenuItemType.VIEW; const hasCustomLink = isRecord || isView; const navigationPath = hasCustomLink diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx index 11b8570d35..1f9cfde822 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx @@ -6,12 +6,12 @@ import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconLink, IconPlus } from 'twenty-ui/display'; -import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection'; -import { NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants'; - import { NavigationItemDropTarget } from '@/navigation-menu-item/components/NavigationItemDropTarget'; +import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; +import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection'; +import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants'; import { WorkspaceNavigationMenuItemsFolder } from '@/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder'; -import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; +import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext'; import { type FlatWorkspaceItem, @@ -85,15 +85,21 @@ export const NavigationDrawerSectionForWorkspaceItems = ({ }, new Map()); const folderCount = flatItems.filter( - (item) => item.itemType === 'folder', + (item) => item.itemType === NavigationMenuItemType.FOLDER, ).length; const filteredItems = flatItems.filter((item) => { const type = item.itemType; - if (type === 'folder' || type === 'link') { + if ( + type === NavigationMenuItemType.FOLDER || + type === NavigationMenuItemType.LINK + ) { return true; } - if (type === 'view' || type === 'record') { + if ( + type === NavigationMenuItemType.VIEW || + type === NavigationMenuItemType.RECORD + ) { const objectMetadataItem = getObjectMetadataForNavigationMenuItem( item as ProcessedNavigationMenuItem, objectMetadataItems, @@ -151,7 +157,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({ {isNavigationSectionOpen && ( @@ -171,7 +177,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({ key={item.id} folderId={null} index={index} - sectionId={NAVIGATION_SECTIONS.WORKSPACE} + sectionId={NavigationSections.WORKSPACE} > {isEditMode && onAddMenuItem && (