Create a cancel action to leave the dashboard edit mode without saving (#14650)
Closes https://github.com/twentyhq/core-team-issues/issues/1523
This commit is contained in:
+28
-10
@@ -1,3 +1,4 @@
|
||||
import { CancelDashboardSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/components/CancelDashboardSingleRecordAction';
|
||||
import { EditDashboardSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/components/EditDashboardSingleRecordAction';
|
||||
import { DashboardSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys';
|
||||
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
|
||||
@@ -7,7 +8,7 @@ import { ActionType } from '@/action-menu/actions/types/ActionType';
|
||||
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconPencil } from 'twenty-ui/display';
|
||||
import { IconPencil, IconX } from 'twenty-ui/display';
|
||||
|
||||
export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
|
||||
config: {
|
||||
@@ -28,6 +29,23 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
|
||||
availableOn: [ActionViewType.SHOW_PAGE],
|
||||
component: <EditDashboardSingleRecordAction />,
|
||||
},
|
||||
[DashboardSingleRecordActionKeys.CANCEL_DASHBOARD_EDITION]: {
|
||||
key: DashboardSingleRecordActionKeys.CANCEL_DASHBOARD_EDITION,
|
||||
label: msg`Cancel Edition`,
|
||||
shortLabel: msg`Cancel`,
|
||||
isPinned: true,
|
||||
position: 2,
|
||||
Icon: IconX,
|
||||
type: ActionType.Standard,
|
||||
scope: ActionScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord }) =>
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord?.isRemote &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
isDefined(selectedRecord?.pageLayoutId),
|
||||
availableOn: [ActionViewType.SHOW_PAGE],
|
||||
component: <CancelDashboardSingleRecordAction />,
|
||||
},
|
||||
},
|
||||
actionKeys: [
|
||||
SingleRecordActionKeys.ADD_TO_FAVORITES,
|
||||
@@ -41,33 +59,33 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
|
||||
],
|
||||
propertiesToOverwrite: {
|
||||
[SingleRecordActionKeys.ADD_TO_FAVORITES]: {
|
||||
position: 2,
|
||||
},
|
||||
[SingleRecordActionKeys.REMOVE_FROM_FAVORITES]: {
|
||||
position: 3,
|
||||
},
|
||||
[SingleRecordActionKeys.DELETE]: {
|
||||
[SingleRecordActionKeys.REMOVE_FROM_FAVORITES]: {
|
||||
position: 4,
|
||||
},
|
||||
[SingleRecordActionKeys.DELETE]: {
|
||||
position: 5,
|
||||
label: msg`Delete dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW]: {
|
||||
position: 5,
|
||||
position: 6,
|
||||
label: msg`Export dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.DESTROY]: {
|
||||
position: 6,
|
||||
position: 7,
|
||||
label: msg`Permanently destroy dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.RESTORE]: {
|
||||
position: 7,
|
||||
position: 8,
|
||||
label: msg`Restore dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
|
||||
position: 8,
|
||||
position: 9,
|
||||
label: msg`Navigate to previous dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD]: {
|
||||
position: 9,
|
||||
position: 10,
|
||||
label: msg`Navigate to next dashboard`,
|
||||
},
|
||||
},
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { Action } from '@/action-menu/actions/components/Action';
|
||||
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useResetDraftPageLayoutToPersistedPageLayout } from '@/dashboards/hooks/useResetDraftPageLayoutToPersistedPageLayout';
|
||||
import { useSetIsDashboardInEditMode } from '@/dashboards/hooks/useSetDashboardInEditMode';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const CancelDashboardSingleRecordAction = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const pageLayoutId = selectedRecord?.pageLayoutId;
|
||||
|
||||
const { setIsDashboardInEditMode } =
|
||||
useSetIsDashboardInEditMode(pageLayoutId);
|
||||
|
||||
const { resetDraftPageLayoutToPersistedPageLayout } =
|
||||
useResetDraftPageLayoutToPersistedPageLayout(pageLayoutId);
|
||||
|
||||
const handleClick = () => {
|
||||
resetDraftPageLayoutToPersistedPageLayout();
|
||||
setIsDashboardInEditMode(false);
|
||||
};
|
||||
|
||||
return <Action onClick={handleClick} />;
|
||||
};
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
export enum DashboardSingleRecordActionKeys {
|
||||
EDIT_DASHBOARD = 'edit-dashboard-single-record',
|
||||
CANCEL_DASHBOARD_EDITION = 'cancel-dashboard-edition-single-record',
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
|
||||
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useResetDraftPageLayoutToPersistedPageLayout = (
|
||||
pageLayoutIdFromProps?: string,
|
||||
) => {
|
||||
const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
PageLayoutComponentInstanceContext,
|
||||
pageLayoutIdFromProps,
|
||||
);
|
||||
|
||||
const pageLayoutDraftState = useRecoilComponentCallbackState(
|
||||
pageLayoutDraftComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const pageLayoutPersistedState = useRecoilComponentCallbackState(
|
||||
pageLayoutPersistedComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const pageLayoutCurrentLayoutsState = useRecoilComponentCallbackState(
|
||||
pageLayoutCurrentLayoutsComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const resetDraftPageLayoutToPersistedPageLayout = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
() => {
|
||||
const pageLayoutPersisted = snapshot
|
||||
.getLoadable(pageLayoutPersistedState)
|
||||
.getValue();
|
||||
|
||||
if (isDefined(pageLayoutPersisted)) {
|
||||
set(pageLayoutDraftState, {
|
||||
id: pageLayoutPersisted.id,
|
||||
name: pageLayoutPersisted.name,
|
||||
type: pageLayoutPersisted.type,
|
||||
objectMetadataId: pageLayoutPersisted.objectMetadataId,
|
||||
tabs: pageLayoutPersisted.tabs,
|
||||
});
|
||||
|
||||
const tabLayouts = convertPageLayoutToTabLayouts(pageLayoutPersisted);
|
||||
set(pageLayoutCurrentLayoutsState, tabLayouts);
|
||||
}
|
||||
},
|
||||
[
|
||||
pageLayoutDraftState,
|
||||
pageLayoutPersistedState,
|
||||
pageLayoutCurrentLayoutsState,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
resetDraftPageLayoutToPersistedPageLayout,
|
||||
};
|
||||
};
|
||||
@@ -28,6 +28,7 @@ export const useSetIsDashboardInEditMode = (pageLayoutIdFromProps: string) => {
|
||||
set(forceRegisteredActionsByKeyState, (prev) => ({
|
||||
...prev,
|
||||
[DashboardSingleRecordActionKeys.EDIT_DASHBOARD]: !value,
|
||||
[DashboardSingleRecordActionKeys.CANCEL_DASHBOARD_EDITION]: value,
|
||||
}));
|
||||
},
|
||||
[forceRegisteredActionsByKeyState, isPageLayoutInEditModeState],
|
||||
|
||||
+3
-20
@@ -3,7 +3,7 @@ import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pag
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
|
||||
import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { type TabLayouts } from '@/page-layout/types/tab-layouts';
|
||||
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useQuery } from '@apollo/client';
|
||||
@@ -59,25 +59,8 @@ export const PageLayoutInitializationQueryEffect = ({
|
||||
tabs: layout.tabs,
|
||||
});
|
||||
|
||||
if (layout.tabs.length > 0) {
|
||||
const tabLayouts: TabLayouts = {};
|
||||
layout.tabs.forEach((tab) => {
|
||||
const layouts = tab.widgets.map((w) => ({
|
||||
i: w.id,
|
||||
x: w.gridPosition.column,
|
||||
y: w.gridPosition.row,
|
||||
w: w.gridPosition.columnSpan,
|
||||
h: w.gridPosition.rowSpan,
|
||||
}));
|
||||
tabLayouts[tab.id] = {
|
||||
desktop: layouts,
|
||||
mobile: layouts.map((l) => ({ ...l, w: 1, x: 0 })),
|
||||
};
|
||||
});
|
||||
set(pageLayoutCurrentLayoutsComponentCallbackState, tabLayouts);
|
||||
} else {
|
||||
set(pageLayoutCurrentLayoutsComponentCallbackState, {});
|
||||
}
|
||||
const tabLayouts = convertPageLayoutToTabLayouts(layout);
|
||||
set(pageLayoutCurrentLayoutsComponentCallbackState, tabLayouts);
|
||||
}
|
||||
},
|
||||
[
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
|
||||
import { PageLayoutType, WidgetType } from '~/generated/graphql';
|
||||
|
||||
describe('convertPageLayoutToTabLayouts', () => {
|
||||
it('should convert page layout to tab layouts', () => {
|
||||
const pageLayout: PageLayoutWithData = {
|
||||
id: 'page-layout-1',
|
||||
name: 'Page Layout 1',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: 'object-metadata-1',
|
||||
tabs: [
|
||||
{
|
||||
id: 'tab-1',
|
||||
title: 'Tab 1',
|
||||
position: 0,
|
||||
pageLayoutId: 'page-layout-1',
|
||||
widgets: [
|
||||
{
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Widget 1',
|
||||
type: WidgetType.GRAPH,
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 2 },
|
||||
objectMetadataId: 'object-metadata-1',
|
||||
createdAt: '2025-01-01T00:00:00.000Z',
|
||||
updatedAt: '2025-01-01T00:00:00.000Z',
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: 'widget-2',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Widget 2',
|
||||
type: WidgetType.GRAPH,
|
||||
gridPosition: { row: 2, column: 0, rowSpan: 2, columnSpan: 2 },
|
||||
objectMetadataId: 'object-metadata-1',
|
||||
createdAt: '2025-01-01T00:00:00.000Z',
|
||||
updatedAt: '2025-01-01T00:00:00.000Z',
|
||||
deletedAt: null,
|
||||
},
|
||||
],
|
||||
createdAt: '2025-01-01T00:00:00.000Z',
|
||||
updatedAt: '2025-01-01T00:00:00.000Z',
|
||||
deletedAt: null,
|
||||
},
|
||||
],
|
||||
createdAt: '2025-01-01T00:00:00.000Z',
|
||||
updatedAt: '2025-01-01T00:00:00.000Z',
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
const result = convertPageLayoutToTabLayouts(pageLayout);
|
||||
|
||||
expect(result).toEqual({
|
||||
'tab-1': {
|
||||
desktop: [
|
||||
{ i: 'widget-1', x: 0, y: 0, w: 2, h: 2 },
|
||||
{ i: 'widget-2', x: 0, y: 2, w: 2, h: 2 },
|
||||
],
|
||||
mobile: [
|
||||
{ i: 'widget-1', x: 0, y: 0, w: 1, h: 2 },
|
||||
{ i: 'widget-2', x: 0, y: 2, w: 1, h: 2 },
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { type TabLayouts } from '@/page-layout/types/tab-layouts';
|
||||
|
||||
export const convertPageLayoutToTabLayouts = (
|
||||
pageLayout: PageLayoutWithData,
|
||||
): TabLayouts => {
|
||||
if (pageLayout.tabs.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const tabLayouts: TabLayouts = {};
|
||||
|
||||
pageLayout.tabs.forEach((tab) => {
|
||||
const layouts = tab.widgets.map((widget) => ({
|
||||
i: widget.id,
|
||||
x: widget.gridPosition.column,
|
||||
y: widget.gridPosition.row,
|
||||
w: widget.gridPosition.columnSpan,
|
||||
h: widget.gridPosition.rowSpan,
|
||||
}));
|
||||
|
||||
tabLayouts[tab.id] = {
|
||||
desktop: layouts,
|
||||
mobile: layouts.map((layout) => ({ ...layout, w: 1, x: 0 })),
|
||||
};
|
||||
});
|
||||
|
||||
return tabLayouts;
|
||||
};
|
||||
Reference in New Issue
Block a user