Reset Tab Page Layout (#19453)

## Context
- Add "Reset to default" for page layout tabs and widgets — backend
mutation resets overrides, reactivates deactivated children, and deletes
custom children
- Fix override write bug where mutating an overridable property (e.g.
widget title) on a standard-app entity incorrectly overwrote the base
column instead of writing to the overrides JSONB —
PageLayoutUpdateService now uses resolveFlatEntityOverridableProperties
for accurate diffing and routes properties through
sanitizeOverridableEntityInput
- Deprecate isOverridden - We need more time to think about this
feature. Currently this adds too much complexity for a very small
benefit



https://github.com/user-attachments/assets/a84546c8-1e15-4d9e-a489-0825cf8b8ed2
This commit is contained in:
Weiko
2026-04-08 18:43:57 +02:00
committed by GitHub
parent 2267c56f15
commit 238018dc7b
94 changed files with 1000 additions and 619 deletions
@@ -358,7 +358,6 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
index: positionIndex,
},
objectMetadataId: null,
isOverridden: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deletedAt: null,
@@ -1,7 +1,9 @@
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useDeletePageLayoutTab } from '@/page-layout/hooks/useDeletePageLayoutTab';
import { useDuplicatePageLayoutTab } from '@/page-layout/hooks/useDuplicatePageLayoutTab';
import { useMovePageLayoutTab } from '@/page-layout/hooks/useMovePageLayoutTab';
import { useResetPageLayoutTabToDefault } from '@/page-layout/hooks/useResetPageLayoutTabToDefault';
import { useSetAsPinnedTab } from '@/page-layout/hooks/useSetAsPinnedTab';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
@@ -11,16 +13,20 @@ 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 { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
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 { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import {
IconChevronLeft,
IconChevronRight,
IconCopyPlus,
IconPinned,
IconRefreshDot,
IconTrash,
} from 'twenty-ui/display';
import { PageLayoutType } from '~/generated-metadata/graphql';
@@ -30,12 +36,17 @@ type SidePanelPageLayoutTabSettingsContentProps = {
recordId: string;
};
const RESET_TAB_TO_DEFAULT_MODAL_ID = 'reset-tab-to-default-modal';
export const SidePanelPageLayoutTabSettingsContent = ({
pageLayoutId,
recordId,
}: SidePanelPageLayoutTabSettingsContentProps) => {
const { t } = useLingui();
const { closeSidePanelMenu } = useSidePanelMenu();
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const pageLayoutDraft = useAtomComponentStateValue(
pageLayoutDraftComponentState,
pageLayoutId,
@@ -63,6 +74,9 @@ export const SidePanelPageLayoutTabSettingsContent = ({
tabListInstanceId,
});
const { setAsPinnedTab } = useSetAsPinnedTab(pageLayoutId);
const { resetPageLayoutTabToDefault } =
useResetPageLayoutTabToDefault(pageLayoutId);
const { openModal } = useModal();
if (!isDefined(pageLayoutTabSettingsOpenTabId)) {
return null;
@@ -70,7 +84,7 @@ export const SidePanelPageLayoutTabSettingsContent = ({
const tabsSorted = sortTabsByPosition(pageLayoutDraft.tabs);
const currentIndex = tabsSorted.findIndex(
(t) => t.id === pageLayoutTabSettingsOpenTabId,
(tabItem) => tabItem.id === pageLayoutTabSettingsOpenTabId,
);
if (currentIndex < 0) return null;
const tab = tabsSorted[currentIndex];
@@ -83,17 +97,34 @@ export const SidePanelPageLayoutTabSettingsContent = ({
const canSetAsPinned =
isRecordPage && !isAlreadyPinned && tabsSorted.length > 1;
const isCustomTab =
isDefined(currentWorkspace?.workspaceCustomApplication) &&
tab.applicationId === currentWorkspace.workspaceCustomApplication.id;
const canShowResetToDefault = !isCustomTab;
const handleDelete = () => {
deleteTab(tab.id);
setPageLayoutTabSettingsOpenTabId(null);
closeSidePanelMenu();
};
const handleResetToDefault = () => {
openModal(RESET_TAB_TO_DEFAULT_MODAL_ID);
};
const handleConfirmReset = () => {
resetPageLayoutTabToDefault(tab.id);
};
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,
...(canShowResetToDefault
? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.RESET_TO_DEFAULT]
: []),
...(canDelete ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.DELETE] : []),
];
@@ -151,6 +182,19 @@ export const SidePanelPageLayoutTabSettingsContent = ({
onClick={() => duplicateTab(tab.id)}
/>
</SelectableListItem>
{canShowResetToDefault && (
<SelectableListItem
itemId={TAB_SETTINGS_SELECTABLE_ITEM_IDS.RESET_TO_DEFAULT}
onEnter={handleResetToDefault}
>
<CommandMenuItem
id={TAB_SETTINGS_SELECTABLE_ITEM_IDS.RESET_TO_DEFAULT}
Icon={IconRefreshDot}
label={t`Reset to default`}
onClick={handleResetToDefault}
/>
</SelectableListItem>
)}
{canDelete && (
<SelectableListItem
itemId={TAB_SETTINGS_SELECTABLE_ITEM_IDS.DELETE}
@@ -166,6 +210,14 @@ export const SidePanelPageLayoutTabSettingsContent = ({
)}
</SidePanelGroup>
</SidePanelList>
<ConfirmationModal
modalInstanceId={RESET_TAB_TO_DEFAULT_MODAL_ID}
title={t`Reset to default`}
subtitle={t`This will cancel all modifications done on the tab and its widgets. Edit mode will be canceled and the page will refresh. This action cannot be undone.`}
onConfirmClick={handleConfirmReset}
confirmButtonText={t`Reset`}
confirmButtonAccent="danger"
/>
</>
);
};
@@ -3,5 +3,6 @@ export const TAB_SETTINGS_SELECTABLE_ITEM_IDS = {
MOVE_RIGHT: 'tab-move-right',
SET_AS_PINNED: 'tab-set-as-pinned',
DUPLICATE: 'tab-duplicate',
RESET_TO_DEFAULT: 'tab-reset-to-default',
DELETE: 'tab-delete',
} as const;