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:
-1
@@ -358,7 +358,6 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
|
||||
index: positionIndex,
|
||||
},
|
||||
objectMetadataId: null,
|
||||
isOverridden: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
|
||||
+54
-2
@@ -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"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user