diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 168aae6f81..f9f532d00b 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1125,6 +1125,7 @@ export type CreatePageLayoutInput = { }; export type CreatePageLayoutTabInput = { + layoutMode?: InputMaybe; pageLayoutId: Scalars['UUID']; position?: InputMaybe; title: Scalars['String']; @@ -1632,6 +1633,7 @@ export enum FeatureFlagKey { IS_NOTE_TARGET_MIGRATED = 'IS_NOTE_TARGET_MIGRATED', IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED', IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED', + IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED', IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED', IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED', IS_TASK_TARGET_MIGRATED = 'IS_TASK_TARGET_MIGRATED', @@ -5014,6 +5016,7 @@ export type UpdatePageLayoutInput = { export type UpdatePageLayoutTabInput = { icon?: InputMaybe; + layoutMode?: InputMaybe; position?: InputMaybe; title?: InputMaybe; }; @@ -5021,6 +5024,7 @@ export type UpdatePageLayoutTabInput = { export type UpdatePageLayoutTabWithWidgetsInput = { icon?: InputMaybe; id: Scalars['UUID']; + layoutMode?: InputMaybe; position: Scalars['Float']; title: Scalars['String']; widgets: Array; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx index e341b1572e..c150cf2b91 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx @@ -2,8 +2,10 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-c import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; +import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { isDefined } from 'twenty-shared/utils'; +import { PageLayoutType } from '~/generated-metadata/graphql'; export const CancelDashboardSingleRecordCommand = () => { const { selectedRecords } = useMountedEngineCommandContext(); @@ -15,13 +17,25 @@ export const CancelDashboardSingleRecordCommand = () => { const pageLayoutId = selectedRecord.pageLayoutId; + const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ + pageLayoutId: pageLayoutId ?? '', + layoutType: PageLayoutType.DASHBOARD, + targetRecordIdentifier: { + id: selectedRecord.id, + targetObjectNameSingular: '', + }, + }); + const { closeSidePanelMenu } = useSidePanelMenu(); const { setIsPageLayoutInEditMode } = useSetIsPageLayoutInEditMode(pageLayoutId); const { resetDraftPageLayoutToPersistedPageLayout } = - useResetDraftPageLayoutToPersistedPageLayout(pageLayoutId); + useResetDraftPageLayoutToPersistedPageLayout({ + pageLayoutId, + tabListInstanceId, + }); const handleExecute = () => { closeSidePanelMenu(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/CancelRecordPageLayoutSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/CancelRecordPageLayoutSingleRecordCommand.tsx index 60655c69ee..5706bb89fe 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/CancelRecordPageLayoutSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/CancelRecordPageLayoutSingleRecordCommand.tsx @@ -1,10 +1,13 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow'; import { useRecordPageLayoutIdFromRecordStoreOrThrow } from '@/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow'; import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; +import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { isDefined } from 'twenty-shared/utils'; +import { PageLayoutType } from '~/generated-metadata/graphql'; export const CancelRecordPageLayoutSingleRecordCommand = () => { const { objectMetadataItem } = useMountedEngineCommandContext(); @@ -15,17 +18,31 @@ export const CancelRecordPageLayoutSingleRecordCommand = () => { ); } + const recordId = useSelectedRecordIdOrThrow(); + const { pageLayoutId } = useRecordPageLayoutIdFromRecordStoreOrThrow({ targetObjectNameSingular: objectMetadataItem.nameSingular, }); + const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ + pageLayoutId, + layoutType: PageLayoutType.RECORD_PAGE, + targetRecordIdentifier: { + id: recordId, + targetObjectNameSingular: objectMetadataItem.nameSingular, + }, + }); + const { closeSidePanelMenu } = useSidePanelMenu(); const { setIsPageLayoutInEditMode } = useSetIsPageLayoutInEditMode(pageLayoutId); const { resetDraftPageLayoutToPersistedPageLayout } = - useResetDraftPageLayoutToPersistedPageLayout(pageLayoutId); + useResetDraftPageLayoutToPersistedPageLayout({ + pageLayoutId, + tabListInstanceId, + }); const handleExecute = () => { closeSidePanelMenu(); diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx index fbff05965a..0b5aa0e957 100644 --- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx @@ -1,10 +1,13 @@ import { Command } from '@/command-menu-item/display/components/Command'; import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow'; -import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; +import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; +import { isDefined } from 'twenty-shared/utils'; +import { PageLayoutType } from '~/generated-metadata/graphql'; export const CancelDashboardSingleRecordCommand = () => { const recordId = useSelectedRecordIdOrThrow(); @@ -13,13 +16,28 @@ export const CancelDashboardSingleRecordCommand = () => { const pageLayoutId = recordStore?.pageLayoutId; + if (!isDefined(pageLayoutId)) { + throw new Error( + 'CancelDashboardSingleRecordCommand requires a valid pageLayoutId from the record store.', + ); + } + + const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ + pageLayoutId, + layoutType: PageLayoutType.DASHBOARD, + targetRecordIdentifier: { id: recordId, targetObjectNameSingular: '' }, + }); + const { closeSidePanelMenu } = useSidePanelMenu(); const { setIsPageLayoutInEditMode } = useSetIsPageLayoutInEditMode(pageLayoutId); const { resetDraftPageLayoutToPersistedPageLayout } = - useResetDraftPageLayoutToPersistedPageLayout(pageLayoutId); + useResetDraftPageLayoutToPersistedPageLayout({ + pageLayoutId, + tabListInstanceId, + }); const handleClick = () => { closeSidePanelMenu(); diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx index 1c08a329f5..e765b17dc9 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx @@ -11,9 +11,10 @@ import { PAGE_LAYOUT_GRID_ITEM_DRAGGING_Z_INDEX } from '@/page-layout/constants/ import { PAGE_LAYOUT_GRID_ITEM_Z_INDEX } from '@/page-layout/constants/PageLayoutGridItemZIndex'; import { PAGE_LAYOUT_GRID_MARGIN } from '@/page-layout/constants/PageLayoutGridMargin'; import { PAGE_LAYOUT_GRID_ROW_HEIGHT } from '@/page-layout/constants/PageLayoutGridRowHeight'; +import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; import { usePageLayoutHandleLayoutChange } from '@/page-layout/hooks/usePageLayoutHandleLayoutChange'; import { usePageLayoutTabWithVisibleWidgetsOrThrow } from '@/page-layout/hooks/usePageLayoutTabWithVisibleWidgetsOrThrow'; -import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; +import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { pageLayoutCurrentBreakpointComponentState } from '@/page-layout/states/pageLayoutCurrentBreakpointComponentState'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState'; @@ -24,20 +25,22 @@ import { filterPendingPlaceholderFromLayouts } from '@/page-layout/utils/filterP import { prepareGridLayoutItemsWithPlaceholders } from '@/page-layout/utils/prepareGridLayoutItemsWithPlaceholders'; import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder'; import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; +import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; +import { css } from '@linaria/core'; import { styled } from '@linaria/react'; import { useMemo, useRef } from 'react'; import { - Responsive, - WidthProvider, type Layout, type Layouts, + Responsive, type ResponsiveProps, + WidthProvider, } from 'react-grid-layout'; import { isDefined } from 'twenty-shared/utils'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { css } from '@linaria/core'; const disabledTransitionsClass = css` .react-grid-layout { @@ -99,6 +102,14 @@ type PageLayoutGridLayoutProps = { }; export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => { + const pageLayoutId = useAvailableComponentInstanceIdOrThrow( + PageLayoutComponentInstanceContext, + ); + + const tabListInstanceId = useAvailableComponentInstanceIdOrThrow( + TabListComponentInstanceContext, + ); + const setPageLayoutCurrentBreakpoint = useSetAtomComponentState( pageLayoutCurrentBreakpointComponentState, ); @@ -111,7 +122,10 @@ export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => { pageLayoutResizingWidgetIdComponentState, ); - const { handleLayoutChange } = usePageLayoutHandleLayoutChange(); + const { handleLayoutChange } = usePageLayoutHandleLayoutChange({ + pageLayoutId, + tabListInstanceId, + }); const handleLayoutChangeWithoutPendingPlaceholder = ( currentLayout: Layout[], diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx index 736c0eaf43..6dcc50b2b5 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx @@ -1,186 +1,15 @@ -import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { PageLayoutLeftPanel } from '@/page-layout/components/PageLayoutLeftPanel'; -import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList'; -import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabListEffect'; -import { PAGE_LAYOUT_LEFT_PANEL_CONTAINER_WIDTH } from '@/page-layout/constants/PageLayoutLeftPanelContainerWidth'; -import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab'; -import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout'; -import { useReorderPageLayoutTabs } from '@/page-layout/hooks/useReorderPageLayoutTabs'; -import { PageLayoutMainContent } from '@/page-layout/PageLayoutMainContent'; -import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; -import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; -import { getScrollWrapperInstanceIdFromPageLayoutId } from '@/page-layout/utils/getScrollWrapperInstanceIdFromPageLayoutId'; -import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; -import { getTabsByDisplayMode } from '@/page-layout/utils/getTabsByDisplayMode'; -import { getTabsWithVisibleWidgets } from '@/page-layout/utils/getTabsWithVisibleWidgets'; -import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures'; -import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; -import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; -import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; -import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; +import { PageLayoutTabsRenderer } from '@/page-layout/components/PageLayoutTabsRenderer'; +import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/pageLayoutIsInitializedComponentState'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; -import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; -import { styled } from '@linaria/react'; -import { t } from '@lingui/core/macro'; -import { SidePanelPages } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { useIsMobile } from 'twenty-ui/utilities'; - -const StyledContainer = styled.div<{ hasPinnedTab: boolean }>` - display: grid; - grid-template-columns: ${({ hasPinnedTab }) => - hasPinnedTab ? `${PAGE_LAYOUT_LEFT_PANEL_CONTAINER_WIDTH}px 1fr` : '1fr'}; - grid-template-rows: minmax(0, 1fr); - height: 100%; - width: 100%; -`; - -const StyledTabsAndDashboardContainer = styled.div` - display: flex; - flex-direction: column; - overflow: hidden; -`; - -const StyledPageLayoutTabListContainer = styled.div` - padding-left: ${themeCssVariables.spacing[2]}; -`; - -const StyledScrollWrapperContainer = styled.div` - flex: 1; - min-height: 0; -`; export const PageLayoutRendererContent = () => { - const { currentPageLayout } = useCurrentPageLayout(); - - const { isInSidePanel, layoutType, targetRecordIdentifier } = - useLayoutRenderingContext(); - - const isPageLayoutInEditMode = useIsPageLayoutInEditMode(); - - const activeTabId = useAtomComponentStateValue(activeTabIdComponentState); - - const { createPageLayoutTab } = useCreatePageLayoutTab(currentPageLayout?.id); - const { reorderTabs } = useReorderPageLayoutTabs(currentPageLayout?.id ?? ''); - const setPageLayoutTabSettingsOpenTabId = useSetAtomComponentState( - pageLayoutTabSettingsOpenTabIdComponentState, - ); - const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel(); - - const isMobile = useIsMobile(); - - const metadataStore = useAtomFamilyStateValue( - metadataStoreState, - 'objectMetadataItems', + const pageLayoutIsInitialized = useAtomComponentStateValue( + pageLayoutIsInitializedComponentState, ); - const isSystemObject = - (metadataStore.current as ObjectMetadataItem[]).find( - (item) => - item.nameSingular === targetRecordIdentifier?.targetObjectNameSingular, - )?.isSystem ?? false; - - if (!isDefined(currentPageLayout)) { + if (!pageLayoutIsInitialized) { return null; } - const handleAddTab = - isPageLayoutInEditMode && - shouldEnableTabEditingFeatures(currentPageLayout.type) - ? () => { - const newTabId = createPageLayoutTab(t`Untitled`); - setPageLayoutTabSettingsOpenTabId(newTabId); - navigatePageLayoutSidePanel({ - sidePanelPage: SidePanelPages.PageLayoutTabSettings, - focusTitleInput: true, - }); - } - : undefined; - - const canEnableTabEditing = - isPageLayoutInEditMode && - shouldEnableTabEditingFeatures(currentPageLayout.type); - - const tabsWithVisibleWidgets = getTabsWithVisibleWidgets({ - tabs: currentPageLayout.tabs, - isMobile, - isInSidePanel, - isEditMode: isPageLayoutInEditMode, - }); - - const SYSTEM_OBJECT_TABS = ['Home', 'Timeline', 'Overview', 'Flow']; - - const tabsForCurrentObject = isSystemObject - ? tabsWithVisibleWidgets.filter((tab) => - SYSTEM_OBJECT_TABS.includes(tab.title), - ) - : tabsWithVisibleWidgets; - - const { tabsToRenderInTabList, pinnedLeftTab } = getTabsByDisplayMode({ - tabs: tabsForCurrentObject, - pageLayoutType: currentPageLayout.type, - isMobile, - isInSidePanel, - }); - - const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ - pageLayoutId: currentPageLayout.id, - layoutType, - targetRecordIdentifier, - }); - - const sortedTabs = sortTabsByPosition(tabsToRenderInTabList); - - const activeTabExistsInCurrentPageLayout = currentPageLayout.tabs.some( - (tab) => tab.id === activeTabId, - ); - - return ( - - {isDefined(pinnedLeftTab) && ( - - )} - - - - {(sortedTabs.length > 1 || isPageLayoutInEditMode) && ( - - - - )} - - - - {isDefined(activeTabId) && activeTabExistsInCurrentPageLayout && ( - - )} - - - - - ); + return ; }; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabList.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabList.tsx index 3932cfd74f..8ebe034beb 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabList.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabList.tsx @@ -1,4 +1,3 @@ -import { styled } from '@linaria/react'; import { DragDropContext, type DropResult, @@ -7,6 +6,7 @@ import { type OnDragUpdateResponder, type ResponderProvided, } from '@hello-pangea/dnd'; +import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; import { useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -25,30 +25,34 @@ import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/con import { type TabListProps } from '@/ui/layout/tab-list/types/TabListProps'; import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimension'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; -import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState'; +import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; -import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; import { PAGE_LAYOUT_TAB_LIST_DROPPABLE_IDS } from '@/page-layout/components/PageLayoutTabListDroppableIds'; import { PageLayoutTabListReorderableOverflowDropdown } from '@/page-layout/components/PageLayoutTabListReorderableOverflowDropdown'; -import { TabListDropdown } from '@/ui/layout/tab-list/components/TabListDropdown'; import { PageLayoutTabListVisibleTabs } from '@/page-layout/components/PageLayoutTabListVisibleTabs'; import { STANDARD_PAGE_LAYOUT_TAB_TITLE_TRANSLATIONS } from '@/page-layout/constants/StandardPageLayoutTabTitleTranslations'; import { useIsCurrentObjectCustom } from '@/page-layout/hooks/useIsCurrentObjectCustom'; -import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; +import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { pageLayoutTabListCurrentDragDroppableIdComponentState } from '@/page-layout/states/pageLayoutTabListCurrentDragDroppableIdComponentState'; import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab'; import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures'; +import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; +import { TabListDropdown } from '@/ui/layout/tab-list/components/TabListDropdown'; import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab-list/components/TabListFromUrlOptionalEffect'; import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { SidePanelPages } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { type PageLayoutType } from '~/generated-metadata/graphql'; import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { + FeatureFlagKey, + type PageLayoutType, +} from '~/generated-metadata/graphql'; const StyledContainer = styled.div` box-sizing: border-box; @@ -109,6 +113,10 @@ export const PageLayoutTabList = ({ const shouldTranslateTabTitles = !isCustom; + const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, + ); + const tabsWithIcons: SingleTabProps[] = tabs.map((tab) => ({ id: tab.id, // TODO: drop once the configuration of all record page layouts has been migrated to the backend. @@ -260,7 +268,10 @@ export const PageLayoutTabList = ({ (tabId: string) => { const shouldOpenSettings = isPageLayoutInEditMode && - shouldEnableTabEditingFeatures(pageLayoutType); + shouldEnableTabEditingFeatures( + pageLayoutType, + isRecordPageGlobalEditionEnabled, + ); if (shouldOpenSettings && activeTabId === tabId) { openTabSettings(tabId); @@ -275,6 +286,7 @@ export const PageLayoutTabList = ({ }, [ isPageLayoutInEditMode, + isRecordPageGlobalEditionEnabled, pageLayoutType, activeTabId, isTabSettingsOpen, @@ -287,7 +299,10 @@ export const PageLayoutTabList = ({ (tabId: string) => { const shouldOpenSettings = isPageLayoutInEditMode && - shouldEnableTabEditingFeatures(pageLayoutType); + shouldEnableTabEditingFeatures( + pageLayoutType, + isRecordPageGlobalEditionEnabled, + ); if (shouldOpenSettings && activeTabId === tabId) { openTabSettings(tabId); @@ -303,6 +318,7 @@ export const PageLayoutTabList = ({ }, [ isPageLayoutInEditMode, + isRecordPageGlobalEditionEnabled, pageLayoutType, activeTabId, isTabSettingsOpen, diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableOverflowDropdown.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableOverflowDropdown.tsx index 825c9d231d..488f5a4f44 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableOverflowDropdown.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableOverflowDropdown.tsx @@ -1,4 +1,3 @@ -import { styled } from '@linaria/react'; import { Draggable, type DraggableProvided, @@ -6,17 +5,18 @@ import { type DraggableStateSnapshot, Droppable, } from '@hello-pangea/dnd'; +import { styled } from '@linaria/react'; -import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; import { PAGE_LAYOUT_TAB_LIST_DROPPABLE_IDS } from '@/page-layout/components/PageLayoutTabListDroppableIds'; import { PageLayoutTabListDroppableMoreButton } from '@/page-layout/components/PageLayoutTabListDroppableMoreButton'; import { PageLayoutTabMenuItemSelectAvatar } from '@/page-layout/components/PageLayoutTabMenuItemSelectAvatar'; import { PageLayoutTabRenderClone } from '@/page-layout/components/PageLayoutTabRenderClone'; -import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; +import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { isPageLayoutTabDraggingComponentState } from '@/page-layout/states/isPageLayoutTabDraggingComponentState'; import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures'; +import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -26,10 +26,14 @@ import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useContext } from 'react'; import { SidePanelPages } from 'twenty-shared/types'; -import { type PageLayoutType } from '~/generated-metadata/graphql'; import { ThemeContext } from 'twenty-ui/theme-constants'; +import { + FeatureFlagKey, + type PageLayoutType, +} from '~/generated-metadata/graphql'; const StyledOverflowDropdownListDraggableWrapper = styled.div` cursor: grab; @@ -75,8 +79,16 @@ export const PageLayoutTabListReorderableOverflowDropdown = ({ const isPageLayoutInEditMode = useIsPageLayoutInEditMode(); + const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, + ); + const shouldShowEditButton = - isPageLayoutInEditMode && shouldEnableTabEditingFeatures(pageLayoutType); + isPageLayoutInEditMode && + shouldEnableTabEditingFeatures( + pageLayoutType, + isRecordPageGlobalEditionEnabled, + ); const isPageLayoutTabDragging = useAtomComponentStateValue( isPageLayoutTabDraggingComponentState, diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx new file mode 100644 index 0000000000..029db1518d --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx @@ -0,0 +1,208 @@ +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { PageLayoutLeftPanel } from '@/page-layout/components/PageLayoutLeftPanel'; +import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList'; +import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabListEffect'; +import { PAGE_LAYOUT_LEFT_PANEL_CONTAINER_WIDTH } from '@/page-layout/constants/PageLayoutLeftPanelContainerWidth'; +import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab'; +import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; +import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode'; +import { useReorderRecordPageLayoutTabs } from '@/page-layout/hooks/useReorderRecordPageLayoutTabs'; +import { PageLayoutMainContent } from '@/page-layout/PageLayoutMainContent'; +import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; +import { getScrollWrapperInstanceIdFromPageLayoutId } from '@/page-layout/utils/getScrollWrapperInstanceIdFromPageLayoutId'; +import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; +import { getTabsByDisplayMode } from '@/page-layout/utils/getTabsByDisplayMode'; +import { getTabsWithVisibleWidgets } from '@/page-layout/utils/getTabsWithVisibleWidgets'; +import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures'; +import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; +import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; +import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { styled } from '@linaria/react'; +import { t } from '@lingui/core/macro'; +import { SidePanelPages } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { useIsMobile } from 'twenty-ui/utilities'; +import { FeatureFlagKey } from '~/generated-metadata/graphql'; + +const StyledContainer = styled.div<{ hasPinnedTab: boolean }>` + display: grid; + grid-template-columns: ${({ hasPinnedTab }) => + hasPinnedTab ? `${PAGE_LAYOUT_LEFT_PANEL_CONTAINER_WIDTH}px 1fr` : '1fr'}; + grid-template-rows: minmax(0, 1fr); + height: 100%; + width: 100%; +`; + +const StyledTabsAndDashboardContainer = styled.div` + display: flex; + flex-direction: column; + overflow: hidden; +`; + +const StyledPageLayoutTabListContainer = styled.div` + padding-left: ${themeCssVariables.spacing[2]}; +`; + +const StyledScrollWrapperContainer = styled.div` + flex: 1; + min-height: 0; +`; + +export const PageLayoutTabsRenderer = () => { + const { currentPageLayout } = useCurrentPageLayoutOrThrow(); + + const { isInSidePanel, layoutType, targetRecordIdentifier } = + useLayoutRenderingContext(); + + const isPageLayoutInEditMode = useIsPageLayoutInEditMode(); + + const activeTabId = useAtomComponentStateValue(activeTabIdComponentState); + + const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ + pageLayoutId: currentPageLayout.id, + layoutType, + targetRecordIdentifier, + }); + + const { createPageLayoutTab } = useCreatePageLayoutTab({ + pageLayoutId: currentPageLayout.id, + tabListInstanceId, + }); + const { reorderRecordPageTabs } = useReorderRecordPageLayoutTabs( + currentPageLayout.id, + ); + const setPageLayoutTabSettingsOpenTabId = useSetAtomComponentState( + pageLayoutTabSettingsOpenTabIdComponentState, + ); + const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel(); + + const isMobile = useIsMobile(); + + const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, + ); + + const metadataStore = useAtomFamilyStateValue( + metadataStoreState, + 'objectMetadataItems', + ); + + const isSystemObject = + (metadataStore.current as ObjectMetadataItem[]).find( + (item) => + item.nameSingular === targetRecordIdentifier?.targetObjectNameSingular, + )?.isSystem ?? false; + + const handleAddTab = + isPageLayoutInEditMode && + shouldEnableTabEditingFeatures( + currentPageLayout.type, + isRecordPageGlobalEditionEnabled, + ) + ? () => { + const newTabId = createPageLayoutTab(t`Untitled`); + setPageLayoutTabSettingsOpenTabId(newTabId); + navigatePageLayoutSidePanel({ + sidePanelPage: SidePanelPages.PageLayoutTabSettings, + focusTitleInput: true, + }); + } + : undefined; + + const canEnableTabEditing = + isPageLayoutInEditMode && + shouldEnableTabEditingFeatures( + currentPageLayout.type, + isRecordPageGlobalEditionEnabled, + ); + + const tabsWithVisibleWidgets = getTabsWithVisibleWidgets({ + tabs: currentPageLayout.tabs, + isMobile, + isInSidePanel, + isEditMode: isPageLayoutInEditMode, + }); + + const SYSTEM_OBJECT_TABS = ['Home', 'Timeline', 'Overview', 'Flow']; + + const tabsForCurrentObject = isSystemObject + ? tabsWithVisibleWidgets.filter((tab) => + SYSTEM_OBJECT_TABS.includes(tab.title), + ) + : tabsWithVisibleWidgets; + + const { tabsToRenderInTabList, pinnedLeftTab } = getTabsByDisplayMode({ + tabs: tabsForCurrentObject, + pageLayoutType: currentPageLayout.type, + isMobile, + isInSidePanel, + }); + + const sortedTabs = sortTabsByPosition(tabsToRenderInTabList); + + const activeTabExistsInCurrentPageLayout = currentPageLayout.tabs.some( + (tab) => tab.id === activeTabId, + ); + + return ( + + {isDefined(pinnedLeftTab) && ( + + )} + + + + {(sortedTabs.length > 1 || isPageLayoutInEditMode) && ( + + + reorderRecordPageTabs( + result, + provided, + isDefined(pinnedLeftTab), + ) + : undefined + } + pageLayoutType={currentPageLayout.type} + /> + + )} + + + + {isDefined(activeTabId) && activeTabExistsInCurrentPageLayout && ( + + )} + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutGraphWidget.test.tsx b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutGraphWidget.test.tsx index c85d413bc5..fa6ce26cb7 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutGraphWidget.test.tsx +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutGraphWidget.test.tsx @@ -2,6 +2,7 @@ import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePag import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { type GraphWidgetFieldSelection } from '@/page-layout/types/GraphWidgetFieldSelection'; +import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; @@ -48,9 +49,12 @@ describe('useCreatePageLayoutGraphWidget', () => { pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, ); - const createWidget = useCreatePageLayoutGraphWidget( - PAGE_LAYOUT_TEST_INSTANCE_ID, - ); + const createWidget = useCreatePageLayoutGraphWidget({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }); return { setActiveTabId, setPageLayoutDraft, @@ -128,9 +132,12 @@ describe('useCreatePageLayoutGraphWidget', () => { pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, ); - const createWidget = useCreatePageLayoutGraphWidget( - PAGE_LAYOUT_TEST_INSTANCE_ID, - ); + const createWidget = useCreatePageLayoutGraphWidget({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }); return { setPageLayoutDraft, setActiveTabId, @@ -235,9 +242,12 @@ describe('useCreatePageLayoutGraphWidget', () => { it('should throw an error when activeTabId is null', () => { const { result } = renderHook( () => { - const createWidget = useCreatePageLayoutGraphWidget( - PAGE_LAYOUT_TEST_INSTANCE_ID, - ); + const createWidget = useCreatePageLayoutGraphWidget({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }); return { createWidget }; }, { diff --git a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.tsx b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.tsx index 9b664fdfc4..019e84b7b5 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.tsx +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.tsx @@ -1,12 +1,15 @@ import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; +import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { act, renderHook } from '@testing-library/react'; -import { useSetAtom } from 'jotai'; -import { PageLayoutType } from '~/generated-metadata/graphql'; +import { + PageLayoutTabLayoutMode, + PageLayoutType, +} from '~/generated-metadata/graphql'; import { PAGE_LAYOUT_TEST_INSTANCE_ID, PageLayoutTestWrapper, @@ -27,7 +30,12 @@ describe('useCreatePageLayoutTab', () => { const { result } = renderHook( () => ({ - createTab: useCreatePageLayoutTab(PAGE_LAYOUT_TEST_INSTANCE_ID), + createTab: useCreatePageLayoutTab({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), pageLayoutDraft: useAtomComponentStateValue( pageLayoutDraftComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -36,11 +44,6 @@ describe('useCreatePageLayoutTab', () => { pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, ), - activeTabId: useSetAtom( - activeTabIdComponentState.atomFamily({ - instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`, - }), - ), }), { wrapper: PageLayoutTestWrapper, @@ -55,6 +58,9 @@ describe('useCreatePageLayoutTab', () => { expect(result.current.pageLayoutDraft.tabs[0].id).toBe('mock-uuid'); expect(result.current.pageLayoutDraft.tabs[0].title).toBe('Tab 1'); expect(result.current.pageLayoutDraft.tabs[0].position).toBe(0); + expect(result.current.pageLayoutDraft.tabs[0].layoutMode).toBe( + PageLayoutTabLayoutMode.GRID, + ); expect(result.current.pageLayoutDraft.tabs[0].widgets).toEqual([]); expect(result.current.pageLayoutCurrentLayouts['mock-uuid']).toEqual({ @@ -69,7 +75,12 @@ describe('useCreatePageLayoutTab', () => { const { result } = renderHook( () => ({ - createTab: useCreatePageLayoutTab(PAGE_LAYOUT_TEST_INSTANCE_ID), + createTab: useCreatePageLayoutTab({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), pageLayoutDraft: useAtomComponentStateValue( pageLayoutDraftComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -97,7 +108,12 @@ describe('useCreatePageLayoutTab', () => { const { result } = renderHook( () => ({ - createTab: useCreatePageLayoutTab(PAGE_LAYOUT_TEST_INSTANCE_ID), + createTab: useCreatePageLayoutTab({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), pageLayoutDraft: useAtomComponentStateValue( pageLayoutDraftComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -125,6 +141,53 @@ describe('useCreatePageLayoutTab', () => { expect(result.current.pageLayoutDraft.tabs[1].title).toBe('Tab 2'); }); + it('should default layoutMode to VERTICAL_LIST for record page layouts', () => { + const uuidModule = require('uuid'); + uuidModule.v4.mockReturnValue('mock-uuid'); + + const { result } = renderHook( + () => { + const setPageLayoutDraft = useSetAtomComponentState( + pageLayoutDraftComponentState, + PAGE_LAYOUT_TEST_INSTANCE_ID, + ); + const pageLayoutDraft = useAtomComponentStateValue( + pageLayoutDraftComponentState, + PAGE_LAYOUT_TEST_INSTANCE_ID, + ); + const createTab = useCreatePageLayoutTab({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }); + return { setPageLayoutDraft, pageLayoutDraft, createTab }; + }, + { + wrapper: PageLayoutTestWrapper, + }, + ); + + act(() => { + result.current.setPageLayoutDraft({ + id: 'test-layout', + name: 'Test Layout', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + tabs: [], + }); + }); + + act(() => { + result.current.createTab.createPageLayoutTab(); + }); + + expect(result.current.pageLayoutDraft.tabs).toHaveLength(1); + expect(result.current.pageLayoutDraft.tabs[0].layoutMode).toBe( + PageLayoutTabLayoutMode.VERTICAL_LIST, + ); + }); + it('should create isolated layouts for multiple tabs', () => { const uuidModule = require('uuid'); uuidModule.v4 @@ -133,7 +196,12 @@ describe('useCreatePageLayoutTab', () => { const { result } = renderHook( () => ({ - createTab: useCreatePageLayoutTab(PAGE_LAYOUT_TEST_INSTANCE_ID), + createTab: useCreatePageLayoutTab({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), pageLayoutCurrentLayouts: useAtomComponentStateValue( pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -179,7 +247,12 @@ describe('useCreatePageLayoutTab', () => { `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`, ); return { - createTab: useCreatePageLayoutTab(PAGE_LAYOUT_TEST_INSTANCE_ID), + createTab: useCreatePageLayoutTab({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), activeTabId: activeTabId, }; }, @@ -211,7 +284,12 @@ describe('useCreatePageLayoutTab', () => { pageLayoutDraftComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, ); - const createTab = useCreatePageLayoutTab(PAGE_LAYOUT_TEST_INSTANCE_ID); + const createTab = useCreatePageLayoutTab({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }); return { setPageLayoutDraft, pageLayoutDraft, createTab }; }, { diff --git a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.tsx b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.tsx index 015822ee5e..810fbf8f7f 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.tsx +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.tsx @@ -1,9 +1,10 @@ +import { usePageLayoutHandleLayoutChange } from '@/page-layout/hooks/usePageLayoutHandleLayoutChange'; +import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; +import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { act, renderHook } from '@testing-library/react'; import { useSetAtom } from 'jotai'; -import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; -import { usePageLayoutHandleLayoutChange } from '@/page-layout/hooks/usePageLayoutHandleLayoutChange'; import { PAGE_LAYOUT_TEST_INSTANCE_ID, PageLayoutTestWrapper, @@ -13,7 +14,12 @@ describe('usePageLayoutHandleLayoutChange', () => { it('should update layouts for specific tab only', () => { const { result } = renderHook( () => ({ - handler: usePageLayoutHandleLayoutChange(PAGE_LAYOUT_TEST_INSTANCE_ID), + handler: usePageLayoutHandleLayoutChange({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), layouts: useAtomComponentStateValue( pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -56,7 +62,12 @@ describe('usePageLayoutHandleLayoutChange', () => { it('should isolate layouts between different tabs', () => { const { result } = renderHook( () => ({ - handler: usePageLayoutHandleLayoutChange(PAGE_LAYOUT_TEST_INSTANCE_ID), + handler: usePageLayoutHandleLayoutChange({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), layouts: useAtomComponentStateValue( pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -91,7 +102,12 @@ describe('usePageLayoutHandleLayoutChange', () => { it('should not update layouts when activeTabId is null', () => { const { result } = renderHook( () => ({ - handler: usePageLayoutHandleLayoutChange(PAGE_LAYOUT_TEST_INSTANCE_ID), + handler: usePageLayoutHandleLayoutChange({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), layouts: useAtomComponentStateValue( pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutFrontComponentWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutFrontComponentWidget.ts index 69a5c8365c..23d8f6c462 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutFrontComponentWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutFrontComponentWidget.ts @@ -7,7 +7,6 @@ import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab'; import { createDefaultFrontComponentWidget } from '@/page-layout/utils/createDefaultFrontComponentWidget'; import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -19,9 +18,13 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; import { WidgetType } from '~/generated-metadata/graphql'; -export const useCreatePageLayoutFrontComponentWidget = ( - pageLayoutIdFromProps?: string, -) => { +export const useCreatePageLayoutFrontComponentWidget = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId: string; + tabListInstanceId: string; +}) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, @@ -29,7 +32,7 @@ export const useCreatePageLayoutFrontComponentWidget = ( const activeTabId = useAtomComponentStateValue( activeTabIdComponentState, - getTabListInstanceIdFromPageLayoutId(pageLayoutId), + tabListInstanceId, ); const pageLayoutCurrentLayoutsState = useAtomComponentStateCallbackState( diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts index 5c6e16fd04..8e0344a911 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts @@ -1,4 +1,3 @@ -import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; import { useDateTimeFormat } from '@/localization/hooks/useDateTimeFormat'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; @@ -9,10 +8,10 @@ import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab'; import { createDefaultGraphWidget } from '@/page-layout/utils/createDefaultGraphWidget'; import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts'; import { getWidgetSize } from '@/page-layout/utils/getWidgetSize'; import { getWidgetTitle } from '@/page-layout/utils/getWidgetTitle'; +import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; @@ -26,9 +25,13 @@ import { WidgetType, } from '~/generated-metadata/graphql'; -export const useCreatePageLayoutGraphWidget = ( - pageLayoutIdFromProps?: string, -) => { +export const useCreatePageLayoutGraphWidget = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId: string; + tabListInstanceId: string; +}) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, @@ -37,8 +40,6 @@ export const useCreatePageLayoutGraphWidget = ( const store = useStore(); const { timeZone, calendarStartDay } = useDateTimeFormat(); - const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId); - const pageLayoutDraftState = useAtomComponentStateCallbackState( pageLayoutDraftComponentState, pageLayoutId, diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts index 58672455ce..cca0a67c03 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts @@ -7,7 +7,6 @@ import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab'; import { createDefaultIframeWidget } from '@/page-layout/utils/createDefaultIframeWidget'; import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -19,9 +18,13 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; import { WidgetType } from '~/generated-metadata/graphql'; -export const useCreatePageLayoutIframeWidget = ( - pageLayoutIdFromProps?: string, -) => { +export const useCreatePageLayoutIframeWidget = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId: string; + tabListInstanceId: string; +}) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, @@ -29,7 +32,7 @@ export const useCreatePageLayoutIframeWidget = ( const activeTabId = useAtomComponentStateValue( activeTabIdComponentState, - getTabListInstanceIdFromPageLayoutId(pageLayoutId), + tabListInstanceId, ); const pageLayoutCurrentLayoutsState = useAtomComponentStateCallbackState( diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutStandaloneRichTextWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutStandaloneRichTextWidget.ts index 6744485813..7d3a66bb49 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutStandaloneRichTextWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutStandaloneRichTextWidget.ts @@ -6,7 +6,6 @@ import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLa import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab'; import { createDefaultStandaloneRichTextWidget } from '@/page-layout/utils/createDefaultStandaloneRichTextWidget'; import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -21,16 +20,19 @@ import { WidgetType, } from '~/generated-metadata/graphql'; -export const useCreatePageLayoutStandaloneRichTextWidget = ( - pageLayoutIdFromProps?: string, -) => { +export const useCreatePageLayoutStandaloneRichTextWidget = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId: string; + tabListInstanceId: string; +}) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, ); const store = useStore(); - const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId); const pageLayoutCurrentLayoutsState = useAtomComponentStateCallbackState( pageLayoutCurrentLayoutsComponentState, diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts index 4385d0dd6e..94a531dd32 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts @@ -2,8 +2,8 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/context import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab'; +import { getDefaultTabLayoutMode } from '@/page-layout/utils/getDefaultTabLayoutMode'; import { getEmptyTabLayout } from '@/page-layout/utils/getEmptyTabLayout'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; @@ -12,7 +12,13 @@ import { useStore } from 'jotai'; import { useCallback } from 'react'; import { v4 as uuidv4 } from 'uuid'; -export const useCreatePageLayoutTab = (pageLayoutIdFromProps?: string) => { +export const useCreatePageLayoutTab = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId: string; + tabListInstanceId: string; +}) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, @@ -28,7 +34,6 @@ export const useCreatePageLayoutTab = (pageLayoutIdFromProps?: string) => { pageLayoutId, ); - const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId); const setActiveTabId = useSetAtomComponentState( activeTabIdComponentState, tabListInstanceId, @@ -52,6 +57,7 @@ export const useCreatePageLayoutTab = (pageLayoutIdFromProps?: string) => { title: title || `Tab ${tabsLength + 1}`, position: maxPosition + 1, pageLayoutId: pageLayoutId, + layoutMode: getDefaultTabLayoutMode(pageLayoutDraft.type), widgets: [], isOverridden: false, createdAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutTab.ts index 78f7c512e9..1146b22fcf 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutTab.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutTab.ts @@ -1,7 +1,6 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { removeTabLayouts } from '@/page-layout/utils/removeTabLayouts'; import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; @@ -10,7 +9,13 @@ import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/h import { useStore } from 'jotai'; import { useCallback } from 'react'; -export const useDeletePageLayoutTab = (pageLayoutIdFromProps?: string) => { +export const useDeletePageLayoutTab = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId: string; + tabListInstanceId: string; +}) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, @@ -27,7 +32,6 @@ export const useDeletePageLayoutTab = (pageLayoutIdFromProps?: string) => { ); const store = useStore(); - const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId); const activeTabIdAtom = activeTabIdComponentState.atomFamily({ instanceId: tabListInstanceId, diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useDuplicatePageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/hooks/useDuplicatePageLayoutTab.ts index 912e211c27..7f46da03d6 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useDuplicatePageLayoutTab.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useDuplicatePageLayoutTab.ts @@ -1,14 +1,12 @@ -import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; -import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; -import { SidePanelPages } from 'twenty-shared/types'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab'; import { generateDuplicatedTimestamps } from '@/page-layout/utils/generateDuplicatedTimestamps'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; +import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -16,10 +14,17 @@ import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/h import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { useStore } from 'jotai'; import { useCallback } from 'react'; +import { SidePanelPages } from 'twenty-shared/types'; import { appendCopySuffix, isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; -export const useDuplicatePageLayoutTab = (pageLayoutIdFromProps?: string) => { +export const useDuplicatePageLayoutTab = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId: string; + tabListInstanceId: string; +}) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, @@ -37,7 +42,6 @@ export const useDuplicatePageLayoutTab = (pageLayoutIdFromProps?: string) => { const store = useStore(); - const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId); const setActiveTabId = useSetAtomComponentState( activeTabIdComponentState, tabListInstanceId, @@ -128,14 +132,14 @@ export const useDuplicatePageLayoutTab = (pageLayoutIdFromProps?: string) => { setActiveTabId(newTabId); - setPageLayoutTabSettingsOpenTabId(newTabId); - navigatePageLayoutSidePanel({ sidePanelPage: SidePanelPages.PageLayoutTabSettings, pageTitle: newTab.title, focusTitleInput: true, }); + setPageLayoutTabSettingsOpenTabId(newTabId); + return newTabId; }, [ diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useMovePageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/hooks/useMovePageLayoutTab.ts index 0404978af5..b0b4b78fef 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useMovePageLayoutTab.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useMovePageLayoutTab.ts @@ -1,7 +1,6 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; -import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useStore } from 'jotai'; @@ -25,23 +24,25 @@ export const useMovePageLayoutTab = (pageLayoutIdFromProps?: string) => { store.set(pageLayoutDraftState, (prev) => { const sorted = sortTabsByPosition(prev.tabs); const index = sorted.findIndex((t) => t.id === tabId); - if (index <= 0) return prev; + if (index <= 0) { + return prev; + } - const items = sorted.filter((t) => t.id !== tabId); - const destinationIndex = index - 1; - const sourceIndex = index; - - const newPosition = calculateNewPosition({ - destinationIndex, - sourceIndex, - items, - }); + const neighborId = sorted[index - 1].id; + const currentPosition = sorted[index].position; + const neighborPosition = sorted[index - 1].position; return { ...prev, - tabs: prev.tabs.map((t) => - t.id === tabId ? { ...t, position: newPosition } : t, - ), + tabs: prev.tabs.map((t) => { + if (t.id === tabId) { + return { ...t, position: neighborPosition }; + } + if (t.id === neighborId) { + return { ...t, position: currentPosition }; + } + return t; + }), }; }); }, @@ -53,23 +54,25 @@ export const useMovePageLayoutTab = (pageLayoutIdFromProps?: string) => { store.set(pageLayoutDraftState, (prev) => { const sorted = sortTabsByPosition(prev.tabs); const index = sorted.findIndex((t) => t.id === tabId); - if (index < 0 || index >= sorted.length - 1) return prev; + if (index < 0 || index >= sorted.length - 1) { + return prev; + } - const items = sorted.filter((t) => t.id !== tabId); - const destinationIndex = index + 1; - const sourceIndex = index; - - const newPosition = calculateNewPosition({ - destinationIndex, - sourceIndex, - items, - }); + const neighborId = sorted[index + 1].id; + const currentPosition = sorted[index].position; + const neighborPosition = sorted[index + 1].position; return { ...prev, - tabs: prev.tabs.map((t) => - t.id === tabId ? { ...t, position: newPosition } : t, - ), + tabs: prev.tabs.map((t) => { + if (t.id === tabId) { + return { ...t, position: neighborPosition }; + } + if (t.id === neighborId) { + return { ...t, position: currentPosition }; + } + return t; + }), }; }); }, diff --git a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts index 1a77c3f27b..2c99212763 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts @@ -1,26 +1,28 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; +import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; +import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; +import { convertLayoutsToWidgets } from '@/page-layout/utils/convertLayoutsToWidgets'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useStore } from 'jotai'; -import { type Layout, type Layouts } from 'react-grid-layout'; import { useCallback } from 'react'; +import { type Layout, type Layouts } from 'react-grid-layout'; import { isDefined } from 'twenty-shared/utils'; -import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; -import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; -import { convertLayoutsToWidgets } from '@/page-layout/utils/convertLayoutsToWidgets'; -export const usePageLayoutHandleLayoutChange = ( - pageLayoutIdFromProps?: string, -) => { +export const usePageLayoutHandleLayoutChange = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId: string; + tabListInstanceId: string; +}) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, ); const store = useStore(); - const tabListInstanceId = getTabListInstanceIdFromPageLayoutId(pageLayoutId); const pageLayoutCurrentLayoutsState = useAtomComponentStateCallbackState( pageLayoutCurrentLayoutsComponentState, diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useReorderPageLayoutTabs.ts b/packages/twenty-front/src/modules/page-layout/hooks/useReorderPageLayoutTabs.ts index f1c79f90fa..5c39de81a8 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useReorderPageLayoutTabs.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useReorderPageLayoutTabs.ts @@ -2,7 +2,6 @@ import { PAGE_LAYOUT_TAB_LIST_DROPPABLE_IDS } from '@/page-layout/components/Pag import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout'; import { usePageLayoutDraftState } from '@/page-layout/hooks/usePageLayoutDraftState'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; -import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { type DropResult } from '@hello-pangea/dnd'; import { useCallback } from 'react'; @@ -41,53 +40,40 @@ export const useReorderPageLayoutTabs = (pageLayoutIdFromProps?: string) => { return false; } - if ( + const isDropOnMoreButton = destination.droppableId === - PAGE_LAYOUT_TAB_LIST_DROPPABLE_IDS.MORE_BUTTON - ) { - const maxPosition = - sortedTabs.length > 0 - ? Math.max(...sortedTabs.map((tab) => tab.position)) - : 0; + PAGE_LAYOUT_TAB_LIST_DROPPABLE_IDS.MORE_BUTTON; - setPageLayoutDraft((prev) => ({ - ...prev, - tabs: prev.tabs.map((tab) => - tab.id === draggableId - ? { ...tab, position: maxPosition + 1 } - : tab, - ), - })); - - return true; - } - - const tabsWithoutDragged = sortedTabs.filter( - (tab) => tab.id !== draggableId, - ); + const orderedIds = sortedTabs + .map((tab) => tab.id) + .filter((id) => id !== draggableId); const movingBetweenDroppables = source.droppableId !== destination.droppableId; - const destinationIndexAdjusted = - movingBetweenDroppables && destination.index > source.index + const insertIndex = isDropOnMoreButton + ? orderedIds.length + : movingBetweenDroppables && destination.index > source.index ? destination.index - 1 : destination.index; - const newPosition = calculateNewPosition({ - destinationIndex: destinationIndexAdjusted, - sourceIndex: source.index, - items: tabsWithoutDragged, - }); + orderedIds.splice(insertIndex, 0, draggableId); + + const newPositionById = new Map( + orderedIds.map((id, index) => [id, index]), + ); setPageLayoutDraft((prev) => ({ ...prev, - tabs: prev.tabs.map((tab) => - tab.id === draggableId ? { ...tab, position: newPosition } : tab, - ), + tabs: prev.tabs.map((tab) => { + const newPosition = newPositionById.get(tab.id); + return isDefined(newPosition) + ? { ...tab, position: newPosition } + : tab; + }), })); - return false; + return isDropOnMoreButton; }, [currentPageLayout, setPageLayoutDraft], ); diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useReorderRecordPageLayoutTabs.ts b/packages/twenty-front/src/modules/page-layout/hooks/useReorderRecordPageLayoutTabs.ts new file mode 100644 index 0000000000..fd3baed44e --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/hooks/useReorderRecordPageLayoutTabs.ts @@ -0,0 +1,45 @@ +import { useReorderPageLayoutTabs } from '@/page-layout/hooks/useReorderPageLayoutTabs'; +import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd'; +import { useCallback } from 'react'; +import { isDefined } from 'twenty-shared/utils'; + +export const useReorderRecordPageLayoutTabs = ( + pageLayoutIdFromProps?: string, +) => { + const { reorderTabs } = useReorderPageLayoutTabs(pageLayoutIdFromProps); + + const reorderRecordPageTabs = useCallback( + ( + result: DropResult, + provided: ResponderProvided, + hasPinnedTab: boolean, + ): boolean => { + if (!hasPinnedTab) { + return reorderTabs(result); + } + + const { source, destination } = result; + + if (!isDefined(destination)) { + return reorderTabs(result); + } + + const adjustedResult: DropResult = { + ...result, + source: { + ...source, + index: source.index + 1, + }, + destination: { + ...destination, + index: destination.index + 1, + }, + }; + + return reorderTabs(adjustedResult); + }, + [reorderTabs], + ); + + return { reorderRecordPageTabs }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout.ts b/packages/twenty-front/src/modules/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout.ts index 66be40b944..ccfb394c27 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout.ts @@ -10,7 +10,6 @@ import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pag import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState'; import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; -import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; @@ -18,17 +17,19 @@ import { useStore } from 'jotai'; import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; -export const useResetDraftPageLayoutToPersistedPageLayout = ( - pageLayoutIdFromProps?: string, -) => { +export const useResetDraftPageLayoutToPersistedPageLayout = ({ + pageLayoutId: pageLayoutIdFromProps, + tabListInstanceId, +}: { + pageLayoutId?: string; + tabListInstanceId: string; +}) => { const componentInstanceId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, ); const store = useStore(); - const tabListComponentInstanceId = - getTabListInstanceIdFromPageLayoutId(componentInstanceId); const pageLayoutDraftState = useAtomComponentStateCallbackState( pageLayoutDraftComponentState, @@ -46,7 +47,7 @@ export const useResetDraftPageLayoutToPersistedPageLayout = ( ); const activeTabId = activeTabIdComponentState.atomFamily({ - instanceId: tabListComponentInstanceId, + instanceId: tabListInstanceId, }); const fieldsWidgetGroupsDraftState = useAtomComponentStateCallbackState( diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useSetAsPinnedTab.ts b/packages/twenty-front/src/modules/page-layout/hooks/useSetAsPinnedTab.ts new file mode 100644 index 0000000000..873f6486c1 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/hooks/useSetAsPinnedTab.ts @@ -0,0 +1,38 @@ +import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; +import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; +import { useStore } from 'jotai'; +import { useCallback } from 'react'; + +export const useSetAsPinnedTab = (pageLayoutIdFromProps?: string) => { + const pageLayoutId = useAvailableComponentInstanceIdOrThrow( + PageLayoutComponentInstanceContext, + pageLayoutIdFromProps, + ); + + const pageLayoutDraftState = useAtomComponentStateCallbackState( + pageLayoutDraftComponentState, + pageLayoutId, + ); + + const store = useStore(); + + const setAsPinnedTab = useCallback( + (tabId: string) => { + store.set(pageLayoutDraftState, (prev) => { + const minPosition = Math.min(...prev.tabs.map((t) => t.position)); + + return { + ...prev, + tabs: prev.tabs.map((t) => + t.id === tabId ? { ...t, position: minPosition - 1 } : t, + ), + }; + }); + }, + [pageLayoutDraftState, store], + ); + + return { setAsPinnedTab }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/getTabsByDisplayMode.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/getTabsByDisplayMode.test.ts index fcd2810bb7..e41db139c4 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/getTabsByDisplayMode.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/getTabsByDisplayMode.test.ts @@ -443,4 +443,24 @@ describe('getTabsByDisplayMode', () => { expect(resultBothTrue.pinnedLeftTab).toBeUndefined(); }); }); + + describe('pinned tab selection by position', () => { + it('should pin the tab with the lowest position regardless of array order', () => { + const tab1 = { ...createMockTab('tab-1'), position: 2 }; + const tab2 = { ...createMockTab('tab-2'), position: 0 }; + const tab3 = { ...createMockTab('tab-3'), position: 1 }; + + const result = getTabsByDisplayMode({ + tabs: [tab1, tab2, tab3], + pageLayoutType: PageLayoutType.RECORD_PAGE, + isMobile: false, + isInSidePanel: false, + }); + + expect(result.pinnedLeftTab?.id).toBe('tab-2'); + expect(result.tabsToRenderInTabList).toHaveLength(2); + expect(result.tabsToRenderInTabList[0].id).toBe('tab-3'); + expect(result.tabsToRenderInTabList[1].id).toBe('tab-1'); + }); + }); }); diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts index c0eec0fe3f..52bb8d6bf1 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts @@ -7,29 +7,54 @@ describe('shouldEnableTabEditingFeatures', () => { expect(result).toBe(true); }); - it('should return false for RECORD_PAGE layout type', () => { + it('should return false for RECORD_PAGE layout type without flag', () => { const result = shouldEnableTabEditingFeatures(PageLayoutType.RECORD_PAGE); expect(result).toBe(false); }); + it('should return true for RECORD_PAGE layout type with flag enabled', () => { + const result = shouldEnableTabEditingFeatures( + PageLayoutType.RECORD_PAGE, + true, + ); + expect(result).toBe(true); + }); + + it('should return false for RECORD_PAGE layout type with flag disabled', () => { + const result = shouldEnableTabEditingFeatures( + PageLayoutType.RECORD_PAGE, + false, + ); + expect(result).toBe(false); + }); + it('should return false for RECORD_INDEX layout type', () => { const result = shouldEnableTabEditingFeatures(PageLayoutType.RECORD_INDEX); expect(result).toBe(false); }); + it('should return false for RECORD_INDEX layout type even with flag', () => { + const result = shouldEnableTabEditingFeatures( + PageLayoutType.RECORD_INDEX, + true, + ); + expect(result).toBe(false); + }); + describe('behavior validation', () => { - it('should enable tab editing features only for dashboards', () => { - // Dashboards should allow adding tabs and opening settings on click + it('should enable tab editing features only for dashboards and record pages with flag', () => { expect(shouldEnableTabEditingFeatures(PageLayoutType.DASHBOARD)).toBe( true, ); - // Record pages should NOT allow adding tabs or opening settings on click expect(shouldEnableTabEditingFeatures(PageLayoutType.RECORD_PAGE)).toBe( false, ); - // Record index pages should NOT allow adding tabs or opening settings on click + expect( + shouldEnableTabEditingFeatures(PageLayoutType.RECORD_PAGE, true), + ).toBe(true); + expect(shouldEnableTabEditingFeatures(PageLayoutType.RECORD_INDEX)).toBe( false, ); diff --git a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts index 48feaaad48..8cc8512742 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts @@ -49,6 +49,7 @@ export const convertPageLayoutDraftToUpdateInput = ( id: tab.id, title: tab.title, position: tab.position, + layoutMode: tab.layoutMode, widgets: tab.widgets .filter((widget) => !isDynamicRelationWidget(widget)) .map((widget, widgetIndex) => ({ diff --git a/packages/twenty-front/src/modules/page-layout/utils/getDefaultTabLayoutMode.ts b/packages/twenty-front/src/modules/page-layout/utils/getDefaultTabLayoutMode.ts new file mode 100644 index 0000000000..bf7e0c2866 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/utils/getDefaultTabLayoutMode.ts @@ -0,0 +1,14 @@ +import { + PageLayoutTabLayoutMode, + PageLayoutType, +} from '~/generated-metadata/graphql'; + +export const getDefaultTabLayoutMode = ( + pageLayoutType: PageLayoutType, +): PageLayoutTabLayoutMode => { + if (pageLayoutType === PageLayoutType.RECORD_PAGE) { + return PageLayoutTabLayoutMode.VERTICAL_LIST; + } + + return PageLayoutTabLayoutMode.GRID; +}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/getTabsByDisplayMode.ts b/packages/twenty-front/src/modules/page-layout/utils/getTabsByDisplayMode.ts index f49c5ec6aa..c64a315fb5 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/getTabsByDisplayMode.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/getTabsByDisplayMode.ts @@ -1,4 +1,5 @@ import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab'; +import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; import { PageLayoutType } from '~/generated-metadata/graphql'; type GetTabsByDisplayModeParams = { @@ -32,8 +33,10 @@ export const getTabsByDisplayMode = ({ }; } - const tabsToRenderInTabList = tabs.slice(1); - const pinnedLeftTab = tabs[0]; + const sortedTabs = sortTabsByPosition(tabs); + + const tabsToRenderInTabList = sortedTabs.slice(1); + const pinnedLeftTab = sortedTabs[0]; return { tabsToRenderInTabList, diff --git a/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts b/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts index 5da943dedd..dca137ddff 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts @@ -2,6 +2,18 @@ import { PageLayoutType } from '~/generated-metadata/graphql'; export const shouldEnableTabEditingFeatures = ( pageLayoutType: PageLayoutType, + isRecordPageGlobalEditionEnabled?: boolean, ): boolean => { - return pageLayoutType === PageLayoutType.DASHBOARD; + if (pageLayoutType === PageLayoutType.DASHBOARD) { + return true; + } + + if ( + pageLayoutType === PageLayoutType.RECORD_PAGE && + isRecordPageGlobalEditionEnabled + ) { + return true; + } + + return false; }; diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelDashboardPageLayoutInfo.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelDashboardPageLayoutInfo.tsx deleted file mode 100644 index 2d025a3881..0000000000 --- a/packages/twenty-front/src/modules/side-panel/components/SidePanelDashboardPageLayoutInfo.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { SidePanelPageLayoutInfoContent } from '@/side-panel/components/SidePanelPageLayoutInfoContent'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; -import { isDefined } from 'twenty-shared/utils'; - -export const SidePanelDashboardPageLayoutInfo = () => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); - - if (!isDefined(pageLayoutId)) { - return null; - } - - return ; -}; diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelPageLayoutInfo.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelPageLayoutInfo.tsx index 0450c08f21..6ea20fa946 100644 --- a/packages/twenty-front/src/modules/side-panel/components/SidePanelPageLayoutInfo.tsx +++ b/packages/twenty-front/src/modules/side-panel/components/SidePanelPageLayoutInfo.tsx @@ -1,30 +1,13 @@ -import { SidePanelDashboardPageLayoutInfo } from '@/side-panel/components/SidePanelDashboardPageLayoutInfo'; -import { SidePanelRecordPageLayoutInfo } from '@/side-panel/components/SidePanelRecordPageLayoutInfo'; -import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { SidePanelPageLayoutInfoContent } from '@/side-panel/components/SidePanelPageLayoutInfoContent'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { isDefined } from 'twenty-shared/utils'; export const SidePanelPageLayoutInfo = () => { - const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue( - contextStoreCurrentObjectMetadataItemIdComponentState, - ); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); - if (!isDefined(contextStoreCurrentObjectMetadataItemId)) { - throw new Error('Object metadata ID is not defined'); + if (!isDefined(pageLayoutId)) { + return null; } - const { objectMetadataItem } = useObjectMetadataItemById({ - objectId: contextStoreCurrentObjectMetadataItemId, - }); - - const isDashboardContext = - objectMetadataItem.nameSingular === CoreObjectNameSingular.Dashboard; - - if (isDashboardContext) { - return ; - } - - return ; + return ; }; diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelRecordPageLayoutInfo.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelRecordPageLayoutInfo.tsx deleted file mode 100644 index fc1b15ba53..0000000000 --- a/packages/twenty-front/src/modules/side-panel/components/SidePanelRecordPageLayoutInfo.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { SidePanelPageLayoutInfoContent } from '@/side-panel/components/SidePanelPageLayoutInfoContent'; -import { usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord'; - -export const SidePanelRecordPageLayoutInfo = () => { - const { pageLayoutId } = - usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord(); - - return ; -}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/ChartFiltersSettings.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/ChartFiltersSettings.tsx index 98457eaa9c..29587ba95b 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/ChartFiltersSettings.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/ChartFiltersSettings.tsx @@ -1,5 +1,5 @@ import { ChartFiltersSettingsInitializeStateEffect } from '@/side-panel/pages/page-layout/components/ChartFiltersSettingsInitializeStateEffect'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { type ChartWidget } from '@/side-panel/pages/page-layout/types/ChartWidget'; import { type ChartWidgetConfiguration } from '@/side-panel/pages/page-layout/types/ChartWidgetConfiguration'; @@ -41,7 +41,7 @@ export const ChartFiltersSettings = ({ objectMetadataItemId: objectMetadataItem.id, }); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { updateCurrentWidgetConfig } = useUpdateCurrentWidgetConfig(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/ChartSettings.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/ChartSettings.tsx index f43209d8dc..a1ab7f6137 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/ChartSettings.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/ChartSettings.tsx @@ -7,7 +7,7 @@ import { ChartTypeSelectionSection } from '@/side-panel/pages/page-layout/compon import { CHART_SETTINGS_HEADINGS } from '@/side-panel/pages/page-layout/constants/ChartSettingsHeadings'; import { GRAPH_TYPE_INFORMATION } from '@/side-panel/pages/page-layout/constants/GraphTypeInformation'; import { useChartSettingsValues } from '@/side-panel/pages/page-layout/hooks/useChartSettingsValues'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useGetConfigToUpdateAfterGraphTypeChange } from '@/side-panel/pages/page-layout/hooks/useUpdateGraphTypeConfig'; import { CHART_CONFIGURATION_SETTING_IDS } from '@/side-panel/pages/page-layout/types/ChartConfigurationSettingIds'; @@ -34,7 +34,7 @@ const StyledSidePanelContainer = styled.div` export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => { const { updateSidePanelPageInfo } = useUpdateSidePanelPageInfo(); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { updateCurrentWidgetConfig } = useUpdateCurrentWidgetConfig(pageLayoutId); const { objectMetadataItems } = useObjectMetadataItems(); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelChartFilterSubPage.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelChartFilterSubPage.tsx index fe2ccd3bf2..942ea42f66 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelChartFilterSubPage.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelChartFilterSubPage.tsx @@ -2,14 +2,14 @@ import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMeta import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; import { ChartFiltersSettings } from '@/side-panel/pages/page-layout/components/ChartFiltersSettings'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { isChartWidget } from '@/side-panel/pages/page-layout/utils/isChartWidget'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { isDefined } from 'twenty-shared/utils'; export const SidePanelChartFilterSubPage = () => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const pageLayoutDraft = useAtomComponentStateValue( pageLayoutDraftComponentState, diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelFieldsLayoutSubPage.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelFieldsLayoutSubPage.tsx index a61b679ef5..6310410ddd 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelFieldsLayoutSubPage.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelFieldsLayoutSubPage.tsx @@ -1,7 +1,7 @@ import { useTemporaryFieldsConfiguration } from '@/page-layout/hooks/useTemporaryFieldsConfiguration'; import { FieldsConfigurationEditor } from '@/page-layout/widgets/fields/components/FieldsConfigurationEditor'; import { FieldsWidgetGroupsDraftInitializationEffect } from '@/page-layout/widgets/fields/components/FieldsWidgetGroupsDraftInitializationEffect'; -import { usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { styled } from '@linaria/react'; import { isDefined } from 'twenty-shared/utils'; @@ -21,8 +21,7 @@ const StyledFieldsLayoutContainer = styled.div` `; export const SidePanelFieldsLayoutSubPage = () => { - const { pageLayoutId } = - usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const temporaryFieldsConfiguration = useTemporaryFieldsConfiguration(); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutChartSettings.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutChartSettings.tsx index 7c13c2865e..e857dcc6a8 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutChartSettings.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutChartSettings.tsx @@ -3,7 +3,7 @@ import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pa import { WidgetComponentInstanceContext } from '@/page-layout/widgets/states/contexts/WidgetComponentInstanceContext'; import { ChartSettings } from '@/side-panel/pages/page-layout/components/ChartSettings'; import { WidgetSettingsFooter } from '@/side-panel/pages/page-layout/components/WidgetSettingsFooter'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { isChartWidget } from '@/side-panel/pages/page-layout/utils/isChartWidget'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { styled } from '@linaria/react'; @@ -16,7 +16,7 @@ const StyledContainer = styled.div` `; export const SidePanelPageLayoutChartSettings = () => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const pageLayoutDraft = useAtomComponentStateValue( pageLayoutDraftComponentState, diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutFieldsSettings.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutFieldsSettings.tsx index 0b91fd838d..e73a224819 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutFieldsSettings.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutFieldsSettings.tsx @@ -6,7 +6,7 @@ import { SidePanelList } from '@/side-panel/components/SidePanelList'; import { useSidePanelSubPageHistory } from '@/side-panel/hooks/useSidePanelSubPageHistory'; import { NewFieldDefaultVisibilityToggle } from '@/side-panel/pages/page-layout/components/NewFieldDefaultVisibilityToggle'; import { WidgetSettingsFooter } from '@/side-panel/pages/page-layout/components/WidgetSettingsFooter'; -import { usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { SidePanelSubPages } from '@/side-panel/types/SidePanelSubPages'; @@ -34,7 +34,7 @@ export const SidePanelPageLayoutFieldsSettings = () => { const { t } = useLingui(); const { navigateToSidePanelSubPage } = useSidePanelSubPageHistory(); const { pageLayoutId, objectNameSingular } = - usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord(); + usePageLayoutIdFromContextStore(); const { updateCurrentWidgetConfig } = useUpdateCurrentWidgetConfig(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutIframeSettings.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutIframeSettings.tsx index 9d598b6917..df55353333 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutIframeSettings.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutIframeSettings.tsx @@ -1,5 +1,5 @@ import { WidgetSettingsFooter } from '@/side-panel/pages/page-layout/components/WidgetSettingsFooter'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget'; @@ -26,7 +26,7 @@ const StyledContainer = styled.div` `; export const SidePanelPageLayoutIframeSettings = () => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettings.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettings.tsx index 4cadb83a85..e6b300226f 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettings.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettings.tsx @@ -1,129 +1,18 @@ -import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; -import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; -import { SidePanelList } from '@/side-panel/components/SidePanelList'; -import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; -import { TAB_SETTINGS_SELECTABLE_ITEM_IDS } from '@/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; -import { useDeletePageLayoutTab } from '@/page-layout/hooks/useDeletePageLayoutTab'; -import { useDuplicatePageLayoutTab } from '@/page-layout/hooks/useDuplicatePageLayoutTab'; -import { useMovePageLayoutTab } from '@/page-layout/hooks/useMovePageLayoutTab'; -import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; -import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; -import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; -import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; -import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { t } from '@lingui/core/macro'; +import { SidePanelPageLayoutTabSettingsContent } from '@/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettingsContent'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { isDefined } from 'twenty-shared/utils'; -import { - IconChevronLeft, - IconChevronRight, - IconCopyPlus, - IconTrash, -} from 'twenty-ui/display'; export const SidePanelPageLayoutTabSettings = () => { - const { closeSidePanelMenu } = useSidePanelMenu(); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId, recordId } = usePageLayoutIdFromContextStore(); - const pageLayoutDraft = useAtomComponentStateValue( - pageLayoutDraftComponentState, - pageLayoutId, - ); - - const [pageLayoutTabSettingsOpenTabId, setPageLayoutTabSettingsOpenTabId] = - useAtomComponentState( - pageLayoutTabSettingsOpenTabIdComponentState, - pageLayoutId, - ); - - const { moveLeft, moveRight } = useMovePageLayoutTab(pageLayoutId); - const { deleteTab } = useDeletePageLayoutTab(pageLayoutId); - const { duplicateTab } = useDuplicatePageLayoutTab(pageLayoutId); - - if (!isDefined(pageLayoutTabSettingsOpenTabId)) { + if (!isDefined(pageLayoutId)) { return null; } - const tabsSorted = sortTabsByPosition(pageLayoutDraft.tabs); - const currentIndex = tabsSorted.findIndex( - (t) => t.id === pageLayoutTabSettingsOpenTabId, - ); - if (currentIndex < 0) return null; - const tab = tabsSorted[currentIndex]; - const canMoveLeft = currentIndex > 0; - const canMoveRight = currentIndex < tabsSorted.length - 1; - const canDelete = tabsSorted.length > 1; - - const handleDelete = () => { - deleteTab(tab.id); - setPageLayoutTabSettingsOpenTabId(null); - closeSidePanelMenu(); - }; - - const selectableItemIds = [ - ...(canMoveLeft ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_LEFT] : []), - ...(canMoveRight ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_RIGHT] : []), - TAB_SETTINGS_SELECTABLE_ITEM_IDS.DUPLICATE, - ...(canDelete ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.DELETE] : []), - ]; - return ( - <> - - - {canMoveLeft && ( - moveLeft(tab.id)} - > - moveLeft(tab.id)} - /> - - )} - {canMoveRight && ( - moveRight(tab.id)} - > - moveRight(tab.id)} - /> - - )} - duplicateTab(tab.id)} - > - duplicateTab(tab.id)} - /> - - {canDelete && ( - - - - )} - - - + ); }; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettingsContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettingsContent.tsx new file mode 100644 index 0000000000..3ed2383451 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettingsContent.tsx @@ -0,0 +1,171 @@ +import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +import { useDeletePageLayoutTab } from '@/page-layout/hooks/useDeletePageLayoutTab'; +import { useDuplicatePageLayoutTab } from '@/page-layout/hooks/useDuplicatePageLayoutTab'; +import { useMovePageLayoutTab } from '@/page-layout/hooks/useMovePageLayoutTab'; +import { useSetAsPinnedTab } from '@/page-layout/hooks/useSetAsPinnedTab'; +import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; +import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; +import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; +import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; +import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; +import { SidePanelList } from '@/side-panel/components/SidePanelList'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; +import { TAB_SETTINGS_SELECTABLE_ITEM_IDS } from '@/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds'; +import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { t } from '@lingui/core/macro'; +import { isDefined } from 'twenty-shared/utils'; +import { + IconChevronLeft, + IconChevronRight, + IconCopyPlus, + IconPinned, + IconTrash, +} from 'twenty-ui/display'; +import { PageLayoutType } from '~/generated-metadata/graphql'; + +type SidePanelPageLayoutTabSettingsContentProps = { + pageLayoutId: string; + recordId: string; +}; + +export const SidePanelPageLayoutTabSettingsContent = ({ + pageLayoutId, + recordId, +}: SidePanelPageLayoutTabSettingsContentProps) => { + const { closeSidePanelMenu } = useSidePanelMenu(); + + const pageLayoutDraft = useAtomComponentStateValue( + pageLayoutDraftComponentState, + pageLayoutId, + ); + + const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ + pageLayoutId, + layoutType: pageLayoutDraft.type, + targetRecordIdentifier: { id: recordId, targetObjectNameSingular: '' }, + }); + + const [pageLayoutTabSettingsOpenTabId, setPageLayoutTabSettingsOpenTabId] = + useAtomComponentState( + pageLayoutTabSettingsOpenTabIdComponentState, + pageLayoutId, + ); + + const { moveLeft, moveRight } = useMovePageLayoutTab(pageLayoutId); + const { deleteTab } = useDeletePageLayoutTab({ + pageLayoutId, + tabListInstanceId, + }); + const { duplicateTab } = useDuplicatePageLayoutTab({ + pageLayoutId, + tabListInstanceId, + }); + const { setAsPinnedTab } = useSetAsPinnedTab(pageLayoutId); + + if (!isDefined(pageLayoutTabSettingsOpenTabId)) { + return null; + } + + const tabsSorted = sortTabsByPosition(pageLayoutDraft.tabs); + const currentIndex = tabsSorted.findIndex( + (t) => t.id === pageLayoutTabSettingsOpenTabId, + ); + if (currentIndex < 0) return null; + const tab = tabsSorted[currentIndex]; + const isRecordPage = pageLayoutDraft.type === PageLayoutType.RECORD_PAGE; + const hasPinnedTab = isRecordPage && tabsSorted.length > 1; + const canMoveLeft = hasPinnedTab ? currentIndex > 1 : currentIndex > 0; + const canMoveRight = currentIndex < tabsSorted.length - 1; + const canDelete = tabsSorted.length > 1; + const isAlreadyPinned = currentIndex === 0; + const canSetAsPinned = + isRecordPage && !isAlreadyPinned && tabsSorted.length > 1; + + const handleDelete = () => { + deleteTab(tab.id); + setPageLayoutTabSettingsOpenTabId(null); + closeSidePanelMenu(); + }; + + const selectableItemIds = [ + ...(canMoveLeft ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_LEFT] : []), + ...(canMoveRight ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_RIGHT] : []), + ...(canSetAsPinned ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.SET_AS_PINNED] : []), + TAB_SETTINGS_SELECTABLE_ITEM_IDS.DUPLICATE, + ...(canDelete ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.DELETE] : []), + ]; + + return ( + <> + + + {canMoveLeft && ( + moveLeft(tab.id)} + > + moveLeft(tab.id)} + /> + + )} + {canMoveRight && ( + moveRight(tab.id)} + > + moveRight(tab.id)} + /> + + )} + {canSetAsPinned && ( + setAsPinnedTab(tab.id)} + > + setAsPinnedTab(tab.id)} + /> + + )} + duplicateTab(tab.id)} + > + duplicateTab(tab.id)} + /> + + {canDelete && ( + + + + )} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutWidgetTypeSelect.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutWidgetTypeSelect.tsx index ffacf81828..32fb4a2819 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutWidgetTypeSelect.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutWidgetTypeSelect.tsx @@ -1,11 +1,4 @@ -import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; -import { SidePanelList } from '@/side-panel/components/SidePanelList'; -import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; -import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; -import { getFrontComponentWidgetTypeSelectItemId } from '@/side-panel/pages/page-layout/utils/getFrontComponentWidgetTypeSelectItemId'; -import { isExistingWidgetMissingOrDifferentType } from '@/side-panel/pages/page-layout/utils/isExistingWidgetMissingOrDifferentType'; import { FIND_MANY_FRONT_COMPONENTS } from '@/front-components/graphql/queries/findManyFrontComponents'; import { useCreatePageLayoutFrontComponentWidget } from '@/page-layout/hooks/useCreatePageLayoutFrontComponentWidget'; import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget'; @@ -15,6 +8,14 @@ import { useOpportunityDefaultChartConfig } from '@/page-layout/hooks/useOpportu import { useRemovePageLayoutWidgetAndPreservePosition } from '@/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; +import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; +import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; +import { SidePanelList } from '@/side-panel/components/SidePanelList'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; +import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; +import { getFrontComponentWidgetTypeSelectItemId } from '@/side-panel/pages/page-layout/utils/getFrontComponentWidgetTypeSelectItemId'; +import { isExistingWidgetMissingOrDifferentType } from '@/side-panel/pages/page-layout/utils/isExistingWidgetMissingOrDifferentType'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; @@ -36,7 +37,7 @@ import { } from '~/generated-metadata/graphql'; export const SidePanelPageLayoutWidgetTypeSelect = () => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId, recordId } = usePageLayoutIdFromContextStore(); const { closeSidePanelMenu } = useSidePanelMenu(); @@ -44,17 +45,38 @@ export const SidePanelPageLayoutWidgetTypeSelect = () => { const { buildBarChartFieldSelection } = useOpportunityDefaultChartConfig(); - const { createPageLayoutGraphWidget } = - useCreatePageLayoutGraphWidget(pageLayoutId); + const pageLayoutDraft = useAtomComponentStateValue( + pageLayoutDraftComponentState, + pageLayoutId, + ); - const { createPageLayoutIframeWidget } = - useCreatePageLayoutIframeWidget(pageLayoutId); + const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ + pageLayoutId, + layoutType: pageLayoutDraft.type, + targetRecordIdentifier: { id: recordId, targetObjectNameSingular: '' }, + }); + + const { createPageLayoutGraphWidget } = useCreatePageLayoutGraphWidget({ + pageLayoutId, + tabListInstanceId, + }); + + const { createPageLayoutIframeWidget } = useCreatePageLayoutIframeWidget({ + pageLayoutId, + tabListInstanceId, + }); const { createPageLayoutStandaloneRichTextWidget } = - useCreatePageLayoutStandaloneRichTextWidget(pageLayoutId); + useCreatePageLayoutStandaloneRichTextWidget({ + pageLayoutId, + tabListInstanceId, + }); const { createPageLayoutFrontComponentWidget } = - useCreatePageLayoutFrontComponentWidget(pageLayoutId); + useCreatePageLayoutFrontComponentWidget({ + pageLayoutId, + tabListInstanceId, + }); const { removePageLayoutWidgetAndPreservePosition } = useRemovePageLayoutWidgetAndPreservePosition(pageLayoutId); @@ -84,11 +106,6 @@ export const SidePanelPageLayoutWidgetTypeSelect = () => { pageLayoutId, ); - const pageLayoutDraft = useAtomComponentStateValue( - pageLayoutDraftComponentState, - pageLayoutId, - ); - const existingWidget = isDefined(pageLayoutEditingWidgetId) ? pageLayoutDraft.tabs .flatMap((tab) => tab.widgets) diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx index 8763a9a9ae..5c66cdcf64 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx @@ -6,7 +6,7 @@ import { CommandMenuItemToggle } from '@/command-menu/components/CommandMenuItem import { SIDE_PANEL_SELECTABLE_LIST_ID } from '@/side-panel/constants/SidePanelSelectableListId'; import { useSidePanelSubPageHistory } from '@/side-panel/hooks/useSidePanelSubPageHistory'; import { useChartSettingsValues } from '@/side-panel/pages/page-layout/hooks/useChartSettingsValues'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateChartSettingInput } from '@/side-panel/pages/page-layout/hooks/useUpdateChartSettingInput'; import { useUpdateChartSettingTextInput } from '@/side-panel/pages/page-layout/hooks/useUpdateChartSettingTextInput'; import { useUpdateChartSettingToggle } from '@/side-panel/pages/page-layout/hooks/useUpdateChartSettingToggle'; @@ -35,7 +35,7 @@ export const ChartSettingItem = ({ objectMetadataId, configuration, }: ChartSettingItemProps) => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown(); const { openDropdown } = useOpenDropdown(); const { setSelectedItemId } = useSelectableList( diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem.tsx index 4faf23ce3e..f1198ada5f 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem.tsx @@ -1,4 +1,4 @@ -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; @@ -30,7 +30,7 @@ export const ChartAggregateOperationSelectableListItem = ({ label: string; currentFieldMetadataId: string; }) => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const { updateCurrentWidgetConfig } = useUpdateCurrentWidgetConfig(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx index eec18f2733..5c52e3c3fc 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx @@ -1,7 +1,7 @@ import { ChartAggregateOperationSelectableListItem } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem'; import { ChartRatioAggregateOperationSelectableListItem } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem'; import { ChartRatioOptionValueSelectionDropdownContent } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -35,7 +35,7 @@ export const ChartAggregateOperationSelectionDropdownContent = ({ const [searchQuery, setSearchQuery] = useState(''); const [isOptionValueMenuOpen, setIsOptionValueMenuOpen] = useState(false); const { objectMetadataItems } = useObjectMetadataItems(); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const configuration = widgetInEditMode?.configuration; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAxisNameSelectionDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAxisNameSelectionDropdownContent.tsx index 86c72f0aea..f0090e1d02 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAxisNameSelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartAxisNameSelectionDropdownContent.tsx @@ -1,4 +1,4 @@ -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { getChartAxisNameDisplayOptions } from '@/side-panel/pages/page-layout/utils/getChartAxisNameDisplayOptions'; @@ -15,7 +15,7 @@ import { MenuItemSelect } from 'twenty-ui/navigation'; import { AxisNameDisplay } from '~/generated-metadata/graphql'; export const ChartAxisNameSelectionDropdownContent = () => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const configuration = widgetInEditMode?.configuration; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartColorSelectionDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartColorSelectionDropdownContent.tsx index 76ed4cd8c1..4e7f55a45b 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartColorSelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartColorSelectionDropdownContent.tsx @@ -1,6 +1,6 @@ import { ChartColorGradientOption } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartColorGradientOption'; import { ChartColorPaletteOption } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartColorPaletteOption'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { type ChartConfiguration } from '@/side-panel/pages/page-layout/types/ChartConfiguration'; @@ -28,7 +28,7 @@ type ColorOption = { export const ChartColorSelectionDropdownContent = () => { const [searchQuery, setSearchQuery] = useState(''); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const dropdownId = useAvailableComponentInstanceIdOrThrow( diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx index 5965c8e2f9..53dc4cceaa 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx @@ -1,4 +1,4 @@ -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useResetChartDraftFiltersSettings } from '@/side-panel/pages/page-layout/hooks/useResetChartDraftFiltersSettings'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; @@ -32,7 +32,7 @@ export const ChartDataSourceDropdownContent = () => { useState(false); const { objectMetadataItems } = useObjectMetadataItems(); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDateGranularitySelectionDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDateGranularitySelectionDropdownContent.tsx index 7c511f9cca..ed61f6b37a 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDateGranularitySelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDateGranularitySelectionDropdownContent.tsx @@ -1,4 +1,4 @@ -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { type ChartConfiguration } from '@/side-panel/pages/page-layout/types/ChartConfiguration'; @@ -75,7 +75,7 @@ const isChartConfiguration = ( export const ChartDateGranularitySelectionDropdownContent = ({ axis, }: ChartDateGranularitySelectionDropdownContentProps) => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const configuration = widgetInEditMode?.configuration; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent.tsx index 649ff92eb2..aaaed54117 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent.tsx @@ -1,5 +1,5 @@ import { ChartAggregateOperationSelectionDropdownContent } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -25,7 +25,7 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => { const [searchQuery, setSearchQuery] = useState(''); const { objectMetadataItems } = useObjectMetadataItems(); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const configuration = widgetInEditMode?.configuration; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase.tsx index 11ed7cc0b1..aaecbfa8d4 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase.tsx @@ -1,6 +1,6 @@ import { ChartGroupByFieldSelectionCompositeFieldView } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionCompositeFieldView'; import { ChartGroupByFieldSelectionRelationFieldView } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionRelationFieldView'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { type ChartConfiguration } from '@/side-panel/pages/page-layout/types/ChartConfiguration'; @@ -51,7 +51,7 @@ export const ChartGroupByFieldSelectionDropdownContentBase = < const { objectMetadataItems } = useObjectMetadataItems(); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartManualSortSubMenuContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartManualSortSubMenuContent.tsx index 3a1e18c598..179b0c62da 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartManualSortSubMenuContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartManualSortSubMenuContent.tsx @@ -1,6 +1,6 @@ import { type DropResult } from '@hello-pangea/dnd'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; @@ -32,7 +32,7 @@ export const ChartManualSortSubMenuContent = ({ axis, onBack, }: ChartManualSortSubMenuContentProps) => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const { updateCurrentWidgetConfig } = useUpdateCurrentWidgetConfig(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem.tsx index 292b2a325d..7537dd3105 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem.tsx @@ -1,4 +1,4 @@ -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio'; @@ -17,7 +17,7 @@ export const ChartRatioAggregateOperationSelectableListItem = ({ label: string; onSelect: () => void; }) => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const dropdownId = useAvailableComponentInstanceIdOrThrow( diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionBooleanSelectableListItem.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionBooleanSelectableListItem.tsx index 38fb1e9ee8..d3c91eb3df 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionBooleanSelectableListItem.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionBooleanSelectableListItem.tsx @@ -1,4 +1,4 @@ -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; @@ -21,7 +21,7 @@ export const ChartRatioOptionBooleanSelectableListItem = ({ label: string; currentFieldMetadataId: string; }) => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const { updateCurrentWidgetConfig } = useUpdateCurrentWidgetConfig(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionSelectSelectableListItem.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionSelectSelectableListItem.tsx index 8de708e809..2e1ce0480a 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionSelectSelectableListItem.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionSelectSelectableListItem.tsx @@ -1,4 +1,4 @@ -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; @@ -23,7 +23,7 @@ export const ChartRatioOptionSelectSelectableListItem = ({ color: ThemeColor | undefined; currentFieldMetadataId: string; }) => { - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const { updateCurrentWidgetConfig } = useUpdateCurrentWidgetConfig(pageLayoutId); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx index 5e0a058484..60564f1ad4 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx @@ -1,6 +1,6 @@ import { ChartRatioOptionBooleanSelectableListItem } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionBooleanSelectableListItem'; import { ChartRatioOptionSelectSelectableListItem } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartRatioOptionSelectSelectableListItem'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -35,7 +35,7 @@ export const ChartRatioOptionValueSelectionDropdownContent = ({ }) => { const [searchQuery, setSearchQuery] = useState(''); const { objectMetadataItems } = useObjectMetadataItems(); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const dropdownId = useAvailableComponentInstanceIdOrThrow( diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent.tsx index f19096011f..aea2ad8f0a 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { ChartManualSortSubMenuContent } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartManualSortSubMenuContent'; import { AGGREGATE_SORT_BY_OPTIONS } from '@/side-panel/pages/page-layout/constants/AggregateSortByOptions'; import { useGraphGroupBySortOptionLabels } from '@/side-panel/pages/page-layout/hooks/useGraphGroupBySortOptionLabels'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { filterSortOptionsByFieldType } from '@/side-panel/pages/page-layout/utils/filterSortOptionsByFieldType'; @@ -28,7 +28,7 @@ import { export const ChartSortByGroupByFieldDropdownContent = () => { const [isSubMenuOpen, setIsSubMenuOpen] = useState(false); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const { objectMetadataItems } = useObjectMetadataItems(); const { updateCurrentWidgetConfig } = diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartSortBySelectionDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartSortBySelectionDropdownContent.tsx index 147cf301ff..3a2bd740b1 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartSortBySelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartSortBySelectionDropdownContent.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { ChartManualSortSubMenuContent } from '@/side-panel/pages/page-layout/components/dropdown-content/ChartManualSortSubMenuContent'; import { X_SORT_BY_OPTIONS } from '@/side-panel/pages/page-layout/constants/XSortByOptions'; import { useGraphXSortOptionLabels } from '@/side-panel/pages/page-layout/hooks/useGraphXSortOptionLabels'; -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { filterSortOptionsByFieldType } from '@/side-panel/pages/page-layout/utils/filterSortOptionsByFieldType'; @@ -30,7 +30,7 @@ import { export const ChartSortBySelectionDropdownContent = () => { const [isSubMenuOpen, setIsSubMenuOpen] = useState(false); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const { objectMetadataItems } = useObjectMetadataItems(); const { updateCurrentWidgetConfig } = diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts index fd8c55b67a..d1354cd609 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts @@ -1,6 +1,7 @@ export const TAB_SETTINGS_SELECTABLE_ITEM_IDS = { MOVE_LEFT: 'tab-move-left', MOVE_RIGHT: 'tab-move-right', + SET_AS_PINNED: 'tab-set-as-pinned', DUPLICATE: 'tab-duplicate', DELETE: 'tab-delete', } as const; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord.ts deleted file mode 100644 index b7e2ffcb5a..0000000000 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { currentPageLayoutIdState } from '@/page-layout/states/currentPageLayoutIdState'; -import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; - -export const usePageLayoutIdFromContextStoreTargetedRecord = () => { - const contextStoreTargetedRecordsRule = useAtomComponentStateValue( - contextStoreTargetedRecordsRuleComponentState, - ); - - if ( - !( - contextStoreTargetedRecordsRule.mode === 'selection' && - contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 - ) - ) { - throw new Error('Only one recordStore should be selected'); - } - - const recordId: string = contextStoreTargetedRecordsRule.selectedRecordIds[0]; - - const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId); - - const currentPageLayoutId = useAtomStateValue(currentPageLayoutIdState); - - return { - pageLayoutId: recordStore?.pageLayoutId ?? currentPageLayoutId, - }; -}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord.ts deleted file mode 100644 index f90027d2b6..0000000000 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; -import { useRecordPageLayoutIdFromRecordStoreOrThrow } from '@/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { isDefined } from 'twenty-shared/utils'; - -export const usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord = - () => { - const contextStoreTargetedRecordsRule = useAtomComponentStateValue( - contextStoreTargetedRecordsRuleComponentState, - ); - - const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue( - contextStoreCurrentObjectMetadataItemIdComponentState, - ); - - if (!isDefined(contextStoreCurrentObjectMetadataItemId)) { - throw new Error('Object metadata ID is not defined'); - } - - const { objectMetadataItem } = useObjectMetadataItemById({ - objectId: contextStoreCurrentObjectMetadataItemId ?? undefined, - }); - - if ( - !( - contextStoreTargetedRecordsRule.mode === 'selection' && - contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 - ) - ) { - throw new Error('Only one record should be selected'); - } - - const { pageLayoutId } = useRecordPageLayoutIdFromRecordStoreOrThrow({ - targetObjectNameSingular: objectMetadataItem.nameSingular, - }); - - return { - pageLayoutId, - objectNameSingular: objectMetadataItem.nameSingular, - }; - }; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore.ts new file mode 100644 index 0000000000..29cbf402c5 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore.ts @@ -0,0 +1,67 @@ +import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; +import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { currentPageLayoutIdState } from '@/page-layout/states/currentPageLayoutIdState'; +import { recordPageLayoutByObjectMetadataIdFamilySelector } from '@/page-layout/states/selectors/recordPageLayoutByObjectMetadataIdFamilySelector'; +import { getDefaultRecordPageLayoutId } from '@/page-layout/utils/getDefaultRecordPageLayoutId'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; +import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +export const usePageLayoutIdFromContextStore = () => { + const contextStoreTargetedRecordsRule = useAtomComponentStateValue( + contextStoreTargetedRecordsRuleComponentState, + ); + + const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue( + contextStoreCurrentObjectMetadataItemIdComponentState, + ); + + if (!isDefined(contextStoreCurrentObjectMetadataItemId)) { + throw new Error('Object metadata ID is not defined'); + } + + const { objectMetadataItem } = useObjectMetadataItemById({ + objectId: contextStoreCurrentObjectMetadataItemId, + }); + + if ( + !( + contextStoreTargetedRecordsRule.mode === 'selection' && + contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 + ) + ) { + throw new Error('Only one record should be selected'); + } + + const recordId: string = contextStoreTargetedRecordsRule.selectedRecordIds[0]; + + const isDashboardContext = + objectMetadataItem.nameSingular === CoreObjectNameSingular.Dashboard; + + const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId); + const currentPageLayoutId = useAtomStateValue(currentPageLayoutIdState); + + const recordPageLayout = useAtomFamilySelectorValue( + recordPageLayoutByObjectMetadataIdFamilySelector, + { objectMetadataId: objectMetadataItem.id }, + ); + + const pageLayoutId = isDashboardContext + ? (recordStore?.pageLayoutId ?? currentPageLayoutId) + : isDefined(recordPageLayout) + ? recordPageLayout.id + : getDefaultRecordPageLayoutId({ + targetObjectNameSingular: objectMetadataItem.nameSingular, + }); + + return { + pageLayoutId, + recordId, + objectNameSingular: objectMetadataItem.nameSingular, + }; +}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useResetChartDraftFiltersSettings.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useResetChartDraftFiltersSettings.ts index 58065852de..be1e83c594 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useResetChartDraftFiltersSettings.ts +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useResetChartDraftFiltersSettings.ts @@ -1,4 +1,4 @@ -import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; +import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; import { getChartFiltersSettingsInstanceId } from '@/side-panel/pages/page-layout/utils/getChartFiltersSettingsInstanceId'; import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; @@ -11,7 +11,7 @@ import { useStore } from 'jotai'; export const useResetChartDraftFiltersSettings = () => { const store = useStore(); - const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); + const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); diff --git a/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql b/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql index eb63cc733b..f49b58e4da 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql +++ b/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql @@ -1641,6 +1641,7 @@ enum FeatureFlagKey { IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED IS_DRAFT_EMAIL_ENABLED IS_RICH_TEXT_V1_MIGRATED + IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED } type SSOIdentityProvider { @@ -3445,12 +3446,14 @@ input CreatePageLayoutTabInput { title: String! position: Float pageLayoutId: UUID! + layoutMode: PageLayoutTabLayoutMode = GRID } input UpdatePageLayoutTabInput { title: String position: Float icon: String + layoutMode: PageLayoutTabLayoutMode } input CreatePageLayoutInput { @@ -3477,6 +3480,7 @@ input UpdatePageLayoutTabWithWidgetsInput { title: String! position: Float! icon: String + layoutMode: PageLayoutTabLayoutMode = GRID widgets: [UpdatePageLayoutWidgetWithIdInput!]! } diff --git a/packages/twenty-sdk/src/clients/generated/metadata/schema.ts b/packages/twenty-sdk/src/clients/generated/metadata/schema.ts index f2568c8217..98c3b52ac4 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/schema.ts +++ b/packages/twenty-sdk/src/clients/generated/metadata/schema.ts @@ -1338,7 +1338,7 @@ export interface FeatureFlag { __typename: 'FeatureFlag' } -export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_APPLICATION_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_DASHBOARD_V2_ENABLED' | 'IS_ATTACHMENT_MIGRATED' | 'IS_NOTE_TARGET_MIGRATED' | 'IS_TASK_TARGET_MIGRATED' | 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_NAVIGATION_MENU_ITEM_ENABLED' | 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED' | 'IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' +export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_APPLICATION_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_DASHBOARD_V2_ENABLED' | 'IS_ATTACHMENT_MIGRATED' | 'IS_NOTE_TARGET_MIGRATED' | 'IS_TASK_TARGET_MIGRATED' | 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_NAVIGATION_MENU_ITEM_ENABLED' | 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED' | 'IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' export interface SSOIdentityProvider { id: Scalars['UUID'] @@ -5981,9 +5981,9 @@ export interface GridPositionInput {row: Scalars['Float'],column: Scalars['Float export interface UpdatePageLayoutWidgetInput {title?: (Scalars['String'] | null),type?: (WidgetType | null),objectMetadataId?: (Scalars['UUID'] | null),gridPosition?: (GridPositionInput | null),position?: (Scalars['JSON'] | null),configuration?: (Scalars['JSON'] | null),conditionalDisplay?: (Scalars['JSON'] | null)} -export interface CreatePageLayoutTabInput {title: Scalars['String'],position?: (Scalars['Float'] | null),pageLayoutId: Scalars['UUID']} +export interface CreatePageLayoutTabInput {title: Scalars['String'],position?: (Scalars['Float'] | null),pageLayoutId: Scalars['UUID'],layoutMode?: (PageLayoutTabLayoutMode | null)} -export interface UpdatePageLayoutTabInput {title?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),icon?: (Scalars['String'] | null)} +export interface UpdatePageLayoutTabInput {title?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),icon?: (Scalars['String'] | null),layoutMode?: (PageLayoutTabLayoutMode | null)} export interface CreatePageLayoutInput {name: Scalars['String'],type?: (PageLayoutType | null),objectMetadataId?: (Scalars['UUID'] | null)} @@ -5991,7 +5991,7 @@ export interface UpdatePageLayoutInput {name?: (Scalars['String'] | null),type?: export interface UpdatePageLayoutWithTabsInput {name: Scalars['String'],type: PageLayoutType,objectMetadataId?: (Scalars['UUID'] | null),tabs: UpdatePageLayoutTabWithWidgetsInput[]} -export interface UpdatePageLayoutTabWithWidgetsInput {id: Scalars['UUID'],title: Scalars['String'],position: Scalars['Float'],icon?: (Scalars['String'] | null),widgets: UpdatePageLayoutWidgetWithIdInput[]} +export interface UpdatePageLayoutTabWithWidgetsInput {id: Scalars['UUID'],title: Scalars['String'],position: Scalars['Float'],icon?: (Scalars['String'] | null),layoutMode?: (PageLayoutTabLayoutMode | null),widgets: UpdatePageLayoutWidgetWithIdInput[]} export interface UpdatePageLayoutWidgetWithIdInput {id: Scalars['UUID'],pageLayoutTabId: Scalars['UUID'],title: Scalars['String'],type: WidgetType,objectMetadataId?: (Scalars['UUID'] | null),gridPosition: GridPositionInput,position?: (Scalars['JSON'] | null),configuration?: (Scalars['JSON'] | null),conditionalDisplay?: (Scalars['JSON'] | null)} @@ -8685,7 +8685,8 @@ export const enumFeatureFlagKey = { IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED: 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED' as const, IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED: 'IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED' as const, IS_DRAFT_EMAIL_ENABLED: 'IS_DRAFT_EMAIL_ENABLED' as const, - IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const + IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const, + IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const } export const enumRelationType = { diff --git a/packages/twenty-sdk/src/clients/generated/metadata/types.ts b/packages/twenty-sdk/src/clients/generated/metadata/types.ts index ab4b012503..7bbf480605 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/types.ts +++ b/packages/twenty-sdk/src/clients/generated/metadata/types.ts @@ -8824,6 +8824,9 @@ export default { "pageLayoutId": [ 3 ], + "layoutMode": [ + 79 + ], "__typename": [ 1 ] @@ -8838,6 +8841,9 @@ export default { "icon": [ 1 ], + "layoutMode": [ + 79 + ], "__typename": [ 1 ] @@ -8900,6 +8906,9 @@ export default { "icon": [ 1 ], + "layoutMode": [ + 79 + ], "widgets": [ 360 ], diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant.ts index 1c27e1fd3c..c088b13057 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant.ts @@ -4,4 +4,5 @@ export const FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES = [ 'title', 'position', 'icon', + 'layoutMode', ] as const satisfies MetadataEntityPropertyName<'pageLayoutTab'>[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util.ts index 3634bf4ecb..0f5f88ecf9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util.ts @@ -56,7 +56,8 @@ export const fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate = ({ widgetIds: [], widgetUniversalIdentifiers: [], icon: null, - layoutMode: PageLayoutTabLayoutMode.GRID, + layoutMode: + createPageLayoutTabInput.layoutMode ?? PageLayoutTabLayoutMode.GRID, overrides: null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/create-page-layout-tab.input.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/create-page-layout-tab.input.ts index 6be51db393..7f9c96cef9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/create-page-layout-tab.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/create-page-layout-tab.input.ts @@ -1,12 +1,14 @@ import { Field, Float, InputType } from '@nestjs/graphql'; import { + IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, } from 'class-validator'; +import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; @@ -26,4 +28,12 @@ export class CreatePageLayoutTabInput { @IsUUID() @IsNotEmpty() pageLayoutId: string; + + @Field(() => PageLayoutTabLayoutMode, { + nullable: true, + defaultValue: PageLayoutTabLayoutMode.GRID, + }) + @IsEnum(PageLayoutTabLayoutMode) + @IsOptional() + layoutMode?: PageLayoutTabLayoutMode; } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab-with-widgets.input.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab-with-widgets.input.ts index 9032551225..f80ae4c338 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab-with-widgets.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab-with-widgets.input.ts @@ -3,6 +3,7 @@ import { Field, Float, InputType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { IsArray, + IsEnum, IsNotEmpty, IsNumber, IsOptional, @@ -10,6 +11,7 @@ import { IsUUID, ValidateNested, } from 'class-validator'; +import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { UpdatePageLayoutWidgetWithIdInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/update-page-layout-widget-with-id.input'; @@ -35,6 +37,14 @@ export class UpdatePageLayoutTabWithWidgetsInput { @IsOptional() icon?: string | null; + @Field(() => PageLayoutTabLayoutMode, { + nullable: true, + defaultValue: PageLayoutTabLayoutMode.GRID, + }) + @IsEnum(PageLayoutTabLayoutMode) + @IsOptional() + layoutMode?: PageLayoutTabLayoutMode; + @Field(() => [UpdatePageLayoutWidgetWithIdInput]) @IsArray() @ValidateNested({ each: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab.input.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab.input.ts index f9819744a5..733a3c0bc0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab.input.ts @@ -1,6 +1,7 @@ import { Field, Float, InputType } from '@nestjs/graphql'; -import { IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; +import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; @InputType() export class UpdatePageLayoutTabInput { @@ -18,4 +19,9 @@ export class UpdatePageLayoutTabInput { @IsString() @IsOptional() icon?: string | null; + + @Field(() => PageLayoutTabLayoutMode, { nullable: true }) + @IsEnum(PageLayoutTabLayoutMode) + @IsOptional() + layoutMode?: PageLayoutTabLayoutMode; } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts index 147c148602..48d8d954cb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts @@ -294,7 +294,7 @@ export class PageLayoutUpdateService { widgetIds: [], widgetUniversalIdentifiers: [], icon: null, - layoutMode: PageLayoutTabLayoutMode.GRID, + layoutMode: tabInput.layoutMode ?? PageLayoutTabLayoutMode.GRID, overrides: null, }; }, @@ -311,6 +311,7 @@ export class PageLayoutUpdateService { ...existingTab, title: tabInput.title, position: tabInput.position, + layoutMode: tabInput.layoutMode ?? existingTab.layoutMode, updatedAt: now.toISOString(), }; }, @@ -327,6 +328,7 @@ export class PageLayoutUpdateService { ...existingTab, title: tabInput.title, position: tabInput.position, + layoutMode: tabInput.layoutMode ?? existingTab.layoutMode, deletedAt: null, updatedAt: now.toISOString(), }; diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index 77c18e1533..23bbaf17d3 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -243,6 +243,7 @@ describe('WorkspaceEntityManager', () => { IS_NAVIGATION_MENU_ITEM_ENABLED: false, IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED: false, IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false, + IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: false, IS_MARKETPLACE_ENABLED: false, IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED: false, IS_DRAFT_EMAIL_ENABLED: false, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts index 9c267733ae..38e1da9d1f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts @@ -1,5 +1,5 @@ -import { type QueryRunner } from 'typeorm'; import { FeatureFlagKey } from 'twenty-shared/types'; +import { type QueryRunner } from 'typeorm'; const tableName = 'featureFlag'; @@ -105,6 +105,11 @@ export const seedFeatureFlags = async ({ workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, + workspaceId: workspaceId, + value: true, + }, ]) .execute(); }; diff --git a/packages/twenty-shared/src/types/FeatureFlagKey.ts b/packages/twenty-shared/src/types/FeatureFlagKey.ts index f064a7f138..9a21153eb2 100644 --- a/packages/twenty-shared/src/types/FeatureFlagKey.ts +++ b/packages/twenty-shared/src/types/FeatureFlagKey.ts @@ -19,4 +19,5 @@ export enum FeatureFlagKey { IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED = 'IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED', IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED', IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED', + IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED', }