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 810fbf8f7f1..6c9cad932e4 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 @@ -129,4 +129,50 @@ describe('usePageLayoutHandleLayoutChange', () => { expect(Object.keys(result.current.layouts)).toHaveLength(0); }); + + it('should not rewrite tab layouts when new layout is identical', () => { + const { result } = renderHook( + () => ({ + handler: usePageLayoutHandleLayoutChange({ + pageLayoutId: PAGE_LAYOUT_TEST_INSTANCE_ID, + tabListInstanceId: getTabListInstanceIdFromPageLayoutId( + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + }), + layouts: useAtomComponentStateValue( + pageLayoutCurrentLayoutsComponentState, + PAGE_LAYOUT_TEST_INSTANCE_ID, + ), + setActiveTabId: useSetAtom( + activeTabIdComponentState.atomFamily({ + instanceId: `${PAGE_LAYOUT_TEST_INSTANCE_ID}-tab-list`, + }), + ), + }), + { + wrapper: PageLayoutTestWrapper, + }, + ); + + act(() => { + result.current.setActiveTabId('tab-1'); + }); + + const newLayouts = { + desktop: [{ i: 'widget-1', x: 1, y: 0, w: 2, h: 2 }], + mobile: [{ i: 'widget-1', x: 0, y: 0, w: 1, h: 2 }], + }; + + act(() => { + result.current.handler.handleLayoutChange([], newLayouts); + }); + + const firstStoredLayouts = result.current.layouts['tab-1']; + + act(() => { + result.current.handler.handleLayoutChange([], newLayouts); + }); + + expect(result.current.layouts['tab-1']).toBe(firstStoredLayouts); + }); }); 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 2c992127635..ce4aaa49f90 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts @@ -10,6 +10,10 @@ import { useCallback } from 'react'; import { type Layout, type Layouts } from 'react-grid-layout'; import { isDefined } from 'twenty-shared/utils'; +const areLayoutsEqual = (firstLayouts: Layouts, secondLayouts: Layouts) => { + return JSON.stringify(firstLayouts) === JSON.stringify(secondLayouts); +}; + export const usePageLayoutHandleLayoutChange = ({ pageLayoutId: pageLayoutIdFromProps, tabListInstanceId, @@ -45,11 +49,18 @@ export const usePageLayoutHandleLayoutChange = ({ if (!isDefined(activeTabId)) return; const currentTabLayouts = store.get(pageLayoutCurrentLayoutsState); + const activeTabLayouts = currentTabLayouts[activeTabId]; - store.set(pageLayoutCurrentLayoutsState, { - ...currentTabLayouts, - [activeTabId]: structuredClone(allLayouts), - }); + const hasLayoutChanged = + !isDefined(activeTabLayouts) || + !areLayoutsEqual(activeTabLayouts, allLayouts); + + if (hasLayoutChanged) { + store.set(pageLayoutCurrentLayoutsState, { + ...currentTabLayouts, + [activeTabId]: structuredClone(allLayouts), + }); + } const pageLayoutDraft = store.get(pageLayoutDraftState);