Various bug fixes Record page layouts (#19719)
Fixes: - Can't add multiple widgets in a row - Ensure newly created is always focused
This commit is contained in:
committed by
GitHub
parent
5e83ad43de
commit
8cb803cedf
@@ -1,15 +1,48 @@
|
||||
import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { widgetCreationTargetTabIdComponentState } from '@/page-layout/states/widgetCreationTargetTabIdComponentState';
|
||||
import { widgetInsertionContextComponentState } from '@/page-layout/states/widgetInsertionContextComponentState';
|
||||
import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { SidePanelPages } from 'twenty-shared/types';
|
||||
|
||||
export const useNavigateToMoreWidgets = () => {
|
||||
const { tabId } = usePageLayoutContentContext();
|
||||
|
||||
const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel();
|
||||
|
||||
const pageLayoutEditingWidgetIdState = useAtomComponentStateCallbackState(
|
||||
pageLayoutEditingWidgetIdComponentState,
|
||||
);
|
||||
|
||||
const widgetCreationTargetTabIdState = useAtomComponentStateCallbackState(
|
||||
widgetCreationTargetTabIdComponentState,
|
||||
);
|
||||
|
||||
const widgetInsertionContextState = useAtomComponentStateCallbackState(
|
||||
widgetInsertionContextComponentState,
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const navigateToMoreWidgets = useCallback(() => {
|
||||
store.set(pageLayoutEditingWidgetIdState, null);
|
||||
store.set(widgetInsertionContextState, null);
|
||||
store.set(widgetCreationTargetTabIdState, tabId);
|
||||
|
||||
navigatePageLayoutSidePanel({
|
||||
sidePanelPage: SidePanelPages.PageLayoutRecordPageWidgetTypeSelect,
|
||||
});
|
||||
}, [navigatePageLayoutSidePanel]);
|
||||
}, [
|
||||
navigatePageLayoutSidePanel,
|
||||
pageLayoutEditingWidgetIdState,
|
||||
store,
|
||||
tabId,
|
||||
widgetCreationTargetTabIdState,
|
||||
widgetInsertionContextState,
|
||||
]);
|
||||
|
||||
return { navigateToMoreWidgets };
|
||||
};
|
||||
|
||||
@@ -7,10 +7,12 @@ import { type PageLayoutAddTabStrategy } from '@/page-layout/types/PageLayoutAdd
|
||||
import { isReactivatableTab } from '@/page-layout/utils/isReactivatableTab';
|
||||
import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures';
|
||||
import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { SidePanelPages } from 'twenty-shared/types';
|
||||
import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql';
|
||||
|
||||
@@ -40,8 +42,17 @@ export const usePageLayoutAddTabStrategy = ({
|
||||
|
||||
const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel();
|
||||
|
||||
const { isInSidePanel } = useLayoutRenderingContext();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onCreate = useCallback(() => {
|
||||
const newTabId = createPageLayoutTab(t`Untitled`);
|
||||
|
||||
if (!isInSidePanel) {
|
||||
navigate(`#${newTabId}`);
|
||||
}
|
||||
|
||||
setPageLayoutTabSettingsOpenTabId(newTabId);
|
||||
navigatePageLayoutSidePanel({
|
||||
sidePanelPage: SidePanelPages.PageLayoutTabSettings,
|
||||
@@ -49,6 +60,8 @@ export const usePageLayoutAddTabStrategy = ({
|
||||
});
|
||||
}, [
|
||||
createPageLayoutTab,
|
||||
isInSidePanel,
|
||||
navigate,
|
||||
setPageLayoutTabSettingsOpenTabId,
|
||||
navigatePageLayoutSidePanel,
|
||||
]);
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
import { PageLayoutComponentInstanceContext } from './contexts/PageLayoutComponentInstanceContext';
|
||||
|
||||
export const widgetCreationTargetTabIdComponentState = createAtomComponentState<
|
||||
string | null
|
||||
>({
|
||||
key: 'widgetCreationTargetTabIdComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: PageLayoutComponentInstanceContext,
|
||||
});
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { createDefaultFieldWidget } from '@/page-layout/utils/createDefaultFieldWidget';
|
||||
import {
|
||||
FieldDisplayMode,
|
||||
PageLayoutTabLayoutMode,
|
||||
WidgetConfigurationType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
describe('createDefaultFieldWidget', () => {
|
||||
it('should return a FIELD widget with CARD display mode by default', () => {
|
||||
const widget = createDefaultFieldWidget({
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Company Name',
|
||||
fieldMetadataId: 'field-1',
|
||||
objectMetadataId: 'object-1',
|
||||
positionIndex: 0,
|
||||
});
|
||||
|
||||
expect(widget).toMatchObject({
|
||||
__typename: 'PageLayoutWidget',
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Company Name',
|
||||
isActive: true,
|
||||
type: WidgetType.FIELD,
|
||||
configuration: {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: 'field-1',
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
position: {
|
||||
__typename: 'PageLayoutWidgetVerticalListPosition',
|
||||
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
||||
index: 0,
|
||||
},
|
||||
objectMetadataId: 'object-1',
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the provided fieldDisplayMode', () => {
|
||||
const widget = createDefaultFieldWidget({
|
||||
id: 'widget-2',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Description',
|
||||
fieldMetadataId: 'field-2',
|
||||
fieldDisplayMode: FieldDisplayMode.EDITOR,
|
||||
objectMetadataId: 'object-1',
|
||||
positionIndex: 1,
|
||||
});
|
||||
|
||||
expect(widget.configuration).toMatchObject({
|
||||
fieldDisplayMode: FieldDisplayMode.EDITOR,
|
||||
});
|
||||
expect(widget.position).toMatchObject({ index: 1 });
|
||||
});
|
||||
});
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { createDefaultFieldsWidget } from '@/page-layout/utils/createDefaultFieldsWidget';
|
||||
import {
|
||||
PageLayoutTabLayoutMode,
|
||||
WidgetConfigurationType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
describe('createDefaultFieldsWidget', () => {
|
||||
it('should return a FIELDS widget with correct properties', () => {
|
||||
const widget = createDefaultFieldsWidget({
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
viewId: 'view-1',
|
||||
objectMetadataId: 'object-1',
|
||||
positionIndex: 3,
|
||||
});
|
||||
|
||||
expect(widget).toMatchObject({
|
||||
__typename: 'PageLayoutWidget',
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Fields',
|
||||
isActive: true,
|
||||
type: WidgetType.FIELDS,
|
||||
configuration: {
|
||||
__typename: 'FieldsConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELDS,
|
||||
viewId: 'view-1',
|
||||
},
|
||||
gridPosition: {
|
||||
__typename: 'GridPosition',
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 12,
|
||||
},
|
||||
position: {
|
||||
__typename: 'PageLayoutWidgetVerticalListPosition',
|
||||
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
||||
index: 3,
|
||||
},
|
||||
objectMetadataId: 'object-1',
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { createDefaultFrontComponentWidget } from '@/page-layout/utils/createDefaultFrontComponentWidget';
|
||||
import {
|
||||
type GridPosition,
|
||||
PageLayoutTabLayoutMode,
|
||||
WidgetConfigurationType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
describe('createDefaultFrontComponentWidget', () => {
|
||||
it('should return a FRONT_COMPONENT widget with grid position mapped to position', () => {
|
||||
const gridPosition: GridPosition = {
|
||||
__typename: 'GridPosition',
|
||||
row: 2,
|
||||
column: 3,
|
||||
rowSpan: 4,
|
||||
columnSpan: 6,
|
||||
};
|
||||
|
||||
const widget = createDefaultFrontComponentWidget(
|
||||
'widget-1',
|
||||
'tab-1',
|
||||
'My Component',
|
||||
'front-comp-1',
|
||||
gridPosition,
|
||||
);
|
||||
|
||||
expect(widget).toMatchObject({
|
||||
__typename: 'PageLayoutWidget',
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'My Component',
|
||||
isActive: true,
|
||||
type: WidgetType.FRONT_COMPONENT,
|
||||
configuration: {
|
||||
__typename: 'FrontComponentConfiguration',
|
||||
configurationType: WidgetConfigurationType.FRONT_COMPONENT,
|
||||
frontComponentId: 'front-comp-1',
|
||||
},
|
||||
gridPosition,
|
||||
position: {
|
||||
__typename: 'PageLayoutWidgetGridPosition',
|
||||
layoutMode: PageLayoutTabLayoutMode.GRID,
|
||||
row: 2,
|
||||
column: 3,
|
||||
rowSpan: 4,
|
||||
columnSpan: 6,
|
||||
},
|
||||
objectMetadataId: null,
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { isReactivatableTab } from '@/page-layout/utils/isReactivatableTab';
|
||||
|
||||
const makeTab = (overrides: Partial<PageLayoutTab> = {}): PageLayoutTab =>
|
||||
({
|
||||
id: 'tab-1',
|
||||
applicationId: 'app-1',
|
||||
title: 'Tab',
|
||||
isActive: true,
|
||||
position: 0,
|
||||
pageLayoutId: '',
|
||||
widgets: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
...overrides,
|
||||
}) as unknown as PageLayoutTab;
|
||||
|
||||
describe('isReactivatableTab', () => {
|
||||
it('should return true when tab is inactive and applicationId matches', () => {
|
||||
const tab = makeTab({ isActive: false, applicationId: 'app-1' });
|
||||
|
||||
expect(isReactivatableTab({ tab, objectApplicationId: 'app-1' })).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false when tab is active', () => {
|
||||
const tab = makeTab({ isActive: true, applicationId: 'app-1' });
|
||||
|
||||
expect(isReactivatableTab({ tab, objectApplicationId: 'app-1' })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false when applicationId does not match', () => {
|
||||
const tab = makeTab({ isActive: false, applicationId: 'app-1' });
|
||||
|
||||
expect(isReactivatableTab({ tab, objectApplicationId: 'app-2' })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false when objectApplicationId is undefined', () => {
|
||||
const tab = makeTab({ isActive: false, applicationId: 'app-1' });
|
||||
|
||||
expect(isReactivatableTab({ tab, objectApplicationId: undefined })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
+1
@@ -36,6 +36,7 @@ const StyledHeader = styled.div`
|
||||
`;
|
||||
|
||||
const StyledMenuItemList = styled.div`
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
margin-top: ${themeCssVariables.spacing[2]};
|
||||
|
||||
+12
-39
@@ -4,12 +4,12 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { useInsertCreatedWidgetAtContext } from '@/page-layout/hooks/useInsertCreatedWidgetAtContext';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { widgetCreationTargetTabIdComponentState } from '@/page-layout/states/widgetCreationTargetTabIdComponentState';
|
||||
import { widgetInsertionContextComponentState } from '@/page-layout/states/widgetInsertionContextComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
|
||||
import { createDefaultFieldWidget } from '@/page-layout/utils/createDefaultFieldWidget';
|
||||
import { createDefaultFieldsWidget } from '@/page-layout/utils/createDefaultFieldsWidget';
|
||||
import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
|
||||
import { isVerticalListPosition } from '@/page-layout/utils/isVerticalListPosition';
|
||||
import { removeWidgetFromTab } from '@/page-layout/utils/removeWidgetFromTab';
|
||||
import { useFieldWidgetEligibleFields } from '@/page-layout/widgets/field/hooks/useFieldWidgetEligibleFields';
|
||||
@@ -20,8 +20,8 @@ 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 { resolveWidgetTypeSelectTargetTabId } from '@/side-panel/pages/page-layout/utils/resolveWidgetTypeSelectTargetTabId';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
@@ -41,11 +41,8 @@ import {
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
|
||||
const {
|
||||
pageLayoutId,
|
||||
recordId,
|
||||
objectNameSingular: targetObjectNameSingular,
|
||||
} = usePageLayoutIdFromContextStore();
|
||||
const { pageLayoutId, objectNameSingular: targetObjectNameSingular } =
|
||||
usePageLayoutIdFromContextStore();
|
||||
|
||||
const { closeSidePanelMenu } = useSidePanelMenu();
|
||||
|
||||
@@ -72,15 +69,9 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({
|
||||
const widgetCreationTargetTabId = useAtomComponentStateValue(
|
||||
widgetCreationTargetTabIdComponentState,
|
||||
pageLayoutId,
|
||||
layoutType: pageLayoutDraft.type,
|
||||
targetRecordIdentifier: { id: recordId, targetObjectNameSingular: '' },
|
||||
});
|
||||
|
||||
const activeTabId = useAtomComponentStateValue(
|
||||
activeTabIdComponentState,
|
||||
tabListInstanceId,
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
@@ -96,13 +87,11 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
|
||||
targetObjectNameSingular,
|
||||
);
|
||||
|
||||
const editingWidgetTab = isDefined(pageLayoutEditingWidgetId)
|
||||
? pageLayoutDraft.tabs.find((tab) =>
|
||||
tab.widgets.some((widget) => widget.id === pageLayoutEditingWidgetId),
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const tabId = editingWidgetTab?.id ?? activeTabId;
|
||||
const tabId = resolveWidgetTypeSelectTargetTabId({
|
||||
pageLayoutEditingWidgetId,
|
||||
tabs: pageLayoutDraft.tabs,
|
||||
widgetCreationTargetTabId,
|
||||
});
|
||||
|
||||
const isReplaceMode =
|
||||
isDefined(pageLayoutEditingWidgetId) && !isDefined(widgetInsertionContext);
|
||||
@@ -126,11 +115,7 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
|
||||
}, [existingWidget]);
|
||||
|
||||
const removeExistingWidgetIfReplacing = useCallback(() => {
|
||||
if (
|
||||
!isReplaceMode ||
|
||||
!isDefined(pageLayoutEditingWidgetId) ||
|
||||
!isDefined(tabId)
|
||||
) {
|
||||
if (!isReplaceMode || !isDefined(pageLayoutEditingWidgetId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,10 +145,6 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
|
||||
);
|
||||
|
||||
const handleCreateFieldsWidget = useCallback(() => {
|
||||
if (!isDefined(tabId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const replacePositionIndex = getExistingWidgetPositionIndex();
|
||||
|
||||
const viewId = uuidv4();
|
||||
@@ -210,10 +191,6 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
|
||||
]);
|
||||
|
||||
const handleCreateFieldWidget = useCallback(() => {
|
||||
if (!isDefined(tabId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const replacePositionIndex = getExistingWidgetPositionIndex();
|
||||
removeExistingWidgetIfReplacing();
|
||||
|
||||
@@ -287,10 +264,6 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
|
||||
|
||||
const handleCreateFrontComponentWidget = useCallback(
|
||||
(frontComponent: FrontComponent) => {
|
||||
if (!isDefined(tabId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const replacePositionIndex = getExistingWidgetPositionIndex();
|
||||
removeExistingWidgetIfReplacing();
|
||||
|
||||
|
||||
+7
-1
@@ -14,6 +14,7 @@ import { RegularTabSettingsContent } from '@/side-panel/pages/page-layout/compon
|
||||
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
PageLayoutTabLayoutMode,
|
||||
@@ -31,6 +32,8 @@ export const SidePanelPageLayoutTabSettingsContent = ({
|
||||
}: SidePanelPageLayoutTabSettingsContentProps) => {
|
||||
const { closeSidePanelMenu } = useSidePanelMenu();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
|
||||
const pageLayoutDraft = useAtomComponentStateValue(
|
||||
@@ -129,7 +132,10 @@ export const SidePanelPageLayoutTabSettingsContent = ({
|
||||
onMoveLeft={() => moveLeft(tab.id)}
|
||||
onMoveRight={() => moveRight(tab.id)}
|
||||
onSetAsPinned={() => setAsPinnedTab(tab.id)}
|
||||
onDuplicate={() => duplicateTab(tab.id)}
|
||||
onDuplicate={() => {
|
||||
const newTabId = duplicateTab(tab.id);
|
||||
navigate(`#${newTabId}`);
|
||||
}}
|
||||
onResetToDefault={handleResetToDefault}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { resolveWidgetTypeSelectTargetTabId } from '@/side-panel/pages/page-layout/utils/resolveWidgetTypeSelectTargetTabId';
|
||||
import { PageLayoutTabLayoutMode } from '~/generated-metadata/graphql';
|
||||
|
||||
const makeTab = (id: string, widgetIds: string[] = []): PageLayoutTab =>
|
||||
({
|
||||
id,
|
||||
applicationId: '',
|
||||
title: id,
|
||||
isActive: true,
|
||||
position: 0,
|
||||
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
||||
pageLayoutId: '',
|
||||
widgets: widgetIds.map((wId) => ({ id: wId })),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
}) as unknown as PageLayoutTab;
|
||||
|
||||
describe('resolveWidgetTypeSelectTargetTabId', () => {
|
||||
it('should return the tab containing the editing widget', () => {
|
||||
const tabs = [
|
||||
makeTab('tab-1', ['widget-a']),
|
||||
makeTab('tab-2', ['widget-b']),
|
||||
];
|
||||
|
||||
const result = resolveWidgetTypeSelectTargetTabId({
|
||||
pageLayoutEditingWidgetId: 'widget-b',
|
||||
tabs,
|
||||
widgetCreationTargetTabId: null,
|
||||
});
|
||||
|
||||
expect(result).toBe('tab-2');
|
||||
});
|
||||
|
||||
it('should throw when the editing widget is not found in any tab', () => {
|
||||
const tabs = [makeTab('tab-1', ['widget-a'])];
|
||||
|
||||
expect(() =>
|
||||
resolveWidgetTypeSelectTargetTabId({
|
||||
pageLayoutEditingWidgetId: 'non-existent',
|
||||
tabs,
|
||||
widgetCreationTargetTabId: null,
|
||||
}),
|
||||
).toThrow('Cannot find tab containing editing widget non-existent');
|
||||
});
|
||||
|
||||
it('should return widgetCreationTargetTabId when no editing widget is set', () => {
|
||||
const tabs = [makeTab('tab-1', ['widget-a'])];
|
||||
|
||||
const result = resolveWidgetTypeSelectTargetTabId({
|
||||
pageLayoutEditingWidgetId: null,
|
||||
tabs,
|
||||
widgetCreationTargetTabId: 'tab-1',
|
||||
});
|
||||
|
||||
expect(result).toBe('tab-1');
|
||||
});
|
||||
|
||||
it('should throw when both pageLayoutEditingWidgetId and widgetCreationTargetTabId are null', () => {
|
||||
const tabs = [makeTab('tab-1')];
|
||||
|
||||
expect(() =>
|
||||
resolveWidgetTypeSelectTargetTabId({
|
||||
pageLayoutEditingWidgetId: null,
|
||||
tabs,
|
||||
widgetCreationTargetTabId: null,
|
||||
}),
|
||||
).toThrow(
|
||||
'widgetCreationTargetTabId must be set when navigating to widget type select without an editing widget',
|
||||
);
|
||||
});
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const resolveWidgetTypeSelectTargetTabId = ({
|
||||
pageLayoutEditingWidgetId,
|
||||
tabs,
|
||||
widgetCreationTargetTabId,
|
||||
}: {
|
||||
pageLayoutEditingWidgetId: string | null;
|
||||
tabs: PageLayoutTab[];
|
||||
widgetCreationTargetTabId: string | null;
|
||||
}): string => {
|
||||
if (isDefined(pageLayoutEditingWidgetId)) {
|
||||
const editingWidgetTab = tabs.find((tab) =>
|
||||
tab.widgets.some((widget) => widget.id === pageLayoutEditingWidgetId),
|
||||
);
|
||||
|
||||
if (!isDefined(editingWidgetTab)) {
|
||||
throw new Error(
|
||||
`Cannot find tab containing editing widget ${pageLayoutEditingWidgetId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return editingWidgetTab.id;
|
||||
}
|
||||
|
||||
if (!isDefined(widgetCreationTargetTabId)) {
|
||||
throw new Error(
|
||||
'widgetCreationTargetTabId must be set when navigating to widget type select without an editing widget',
|
||||
);
|
||||
}
|
||||
|
||||
return widgetCreationTargetTabId;
|
||||
};
|
||||
Reference in New Issue
Block a user