From 3f28fde03adab524918db013912c9bac0a4e0a58 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Thu, 15 Jan 2026 19:17:02 +0530 Subject: [PATCH] [Dashboards] fix widget type switching when navigating back in command menu (#17166) before - https://github.com/user-attachments/assets/ab1e1719-f636-4d49-8c3f-cbc6b5e1f61f after - https://github.com/user-attachments/assets/4bebe9c5-d8eb-49ca-9265-70e571408465 --- .../CommandMenuPageLayoutWidgetTypeSelect.tsx | 60 +++++++++-- .../isExistingWidgetMissingOrDifferentType.ts | 8 ++ ...movePageLayoutWidgetAndPreservePosition.ts | 99 +++++++++++++++++++ 3 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isExistingWidgetMissingOrDifferentType.ts create mode 100644 packages/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect.tsx index 81b1af9c30..1a2e607606 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect.tsx @@ -5,13 +5,17 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu'; import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; +import { isExistingWidgetMissingOrDifferentType } from '@/command-menu/pages/page-layout/utils/isExistingWidgetMissingOrDifferentType'; import { useCompanyDefaultChartConfig } from '@/page-layout/hooks/useCompanyDefaultChartConfig'; import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget'; import { useCreatePageLayoutIframeWidget } from '@/page-layout/hooks/useCreatePageLayoutIframeWidget'; import { useCreatePageLayoutStandaloneRichTextWidget } from '@/page-layout/hooks/useCreatePageLayoutStandaloneRichTextWidget'; +import { useRemovePageLayoutWidgetAndPreservePosition } from '@/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition'; +import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { t } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { @@ -19,6 +23,7 @@ import { IconChartPie, IconFrame, } from 'twenty-ui/display'; +import { WidgetType } from '~/generated/graphql'; export const CommandMenuPageLayoutWidgetTypeSelect = () => { const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); @@ -38,19 +43,39 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => { const { createPageLayoutStandaloneRichTextWidget } = useCreatePageLayoutStandaloneRichTextWidget(pageLayoutId); + const { removePageLayoutWidgetAndPreservePosition } = + useRemovePageLayoutWidgetAndPreservePosition(pageLayoutId); + const [pageLayoutEditingWidgetId, setPageLayoutEditingWidgetId] = useRecoilComponentState( pageLayoutEditingWidgetIdComponentState, pageLayoutId, ); - const handleNavigateToGraphTypeSelect = () => { - if (!isDefined(pageLayoutEditingWidgetId)) { - const fieldSelection = buildBarChartFieldSelection(); - const newWidget = createPageLayoutGraphWidget({ - fieldSelection, - }); + const draftPageLayout = useRecoilComponentValue( + pageLayoutDraftComponentState, + pageLayoutId, + ); + const existingWidget = isDefined(pageLayoutEditingWidgetId) + ? draftPageLayout.tabs + .flatMap((tab) => tab.widgets) + .find((widget) => widget.id === pageLayoutEditingWidgetId) + : undefined; + + const handleNavigateToGraphTypeSelect = () => { + if ( + isExistingWidgetMissingOrDifferentType( + existingWidget?.type, + WidgetType.GRAPH, + ) + ) { + if (isDefined(pageLayoutEditingWidgetId)) { + removePageLayoutWidgetAndPreservePosition(pageLayoutEditingWidgetId); + } + + const fieldSelection = buildBarChartFieldSelection(); + const newWidget = createPageLayoutGraphWidget({ fieldSelection }); setPageLayoutEditingWidgetId(newWidget.id); } @@ -61,9 +86,17 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => { }; const handleNavigateToIframeSettings = () => { - if (!isDefined(pageLayoutEditingWidgetId)) { - const newWidget = createPageLayoutIframeWidget(t`Untitled iFrame`, null); + if ( + isExistingWidgetMissingOrDifferentType( + existingWidget?.type, + WidgetType.IFRAME, + ) + ) { + if (isDefined(pageLayoutEditingWidgetId)) { + removePageLayoutWidgetAndPreservePosition(pageLayoutEditingWidgetId); + } + const newWidget = createPageLayoutIframeWidget(t`Untitled iFrame`, null); setPageLayoutEditingWidgetId(newWidget.id); } @@ -74,7 +107,16 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => { }; const handleNavigateToRichTextSettings = () => { - if (!isDefined(pageLayoutEditingWidgetId)) { + if ( + isExistingWidgetMissingOrDifferentType( + existingWidget?.type, + WidgetType.STANDALONE_RICH_TEXT, + ) + ) { + if (isDefined(pageLayoutEditingWidgetId)) { + removePageLayoutWidgetAndPreservePosition(pageLayoutEditingWidgetId); + } + const newWidget = createPageLayoutStandaloneRichTextWidget({ blocknote: '', markdown: null, diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isExistingWidgetMissingOrDifferentType.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isExistingWidgetMissingOrDifferentType.ts new file mode 100644 index 0000000000..68ecfd170d --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isExistingWidgetMissingOrDifferentType.ts @@ -0,0 +1,8 @@ +import { type WidgetType } from '~/generated/graphql'; + +export const isExistingWidgetMissingOrDifferentType = ( + existingWidgetType: WidgetType | undefined, + targetType: WidgetType, +): boolean => { + return existingWidgetType === undefined || existingWidgetType !== targetType; +}; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts b/packages/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts new file mode 100644 index 0000000000..104b542a64 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts @@ -0,0 +1,99 @@ +import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; +import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; +import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; +import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState'; +import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; +import { removeWidgetFromTab } from '@/page-layout/utils/removeWidgetFromTab'; +import { removeWidgetLayoutFromTab } from '@/page-layout/utils/removeWidgetLayoutFromTab'; +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 useRemovePageLayoutWidgetAndPreservePosition = ( + pageLayoutIdFromProps?: string, +) => { + const pageLayoutId = useAvailableComponentInstanceIdOrThrow( + PageLayoutComponentInstanceContext, + pageLayoutIdFromProps, + ); + + const pageLayoutDraftState = useRecoilComponentCallbackState( + pageLayoutDraftComponentState, + pageLayoutId, + ); + + const pageLayoutCurrentLayoutsState = useRecoilComponentCallbackState( + pageLayoutCurrentLayoutsComponentState, + pageLayoutId, + ); + + const pageLayoutDraggedAreaState = useRecoilComponentCallbackState( + pageLayoutDraggedAreaComponentState, + pageLayoutId, + ); + + const pageLayoutEditingWidgetIdState = useRecoilComponentCallbackState( + pageLayoutEditingWidgetIdComponentState, + pageLayoutId, + ); + + const removePageLayoutWidgetAndPreservePosition = useRecoilCallback( + ({ snapshot, set }) => + (widgetId: string) => { + const pageLayoutDraft = snapshot + .getLoadable(pageLayoutDraftState) + .getValue(); + const allTabLayouts = snapshot + .getLoadable(pageLayoutCurrentLayoutsState) + .getValue(); + + const tabWithWidget = pageLayoutDraft.tabs.find((tab) => + tab.widgets.some((w) => w.id === widgetId), + ); + const tabId = tabWithWidget?.id; + + if (!isDefined(tabId)) { + return; + } + + const tabLayouts = allTabLayouts[tabId]; + const widgetLayout = tabLayouts?.desktop?.find( + (layout) => layout.i === widgetId, + ); + + if (!isDefined(widgetLayout)) { + return; + } + + set(pageLayoutDraggedAreaState, { + x: widgetLayout.x, + y: widgetLayout.y, + w: widgetLayout.w, + h: widgetLayout.h, + }); + + const updatedLayouts = removeWidgetLayoutFromTab( + allTabLayouts, + tabId, + widgetId, + ); + set(pageLayoutCurrentLayoutsState, updatedLayouts); + + set(pageLayoutDraftState, (prev) => ({ + ...prev, + tabs: removeWidgetFromTab(prev.tabs, tabId, widgetId), + })); + + set(pageLayoutEditingWidgetIdState, null); + }, + [ + pageLayoutCurrentLayoutsState, + pageLayoutDraftState, + pageLayoutDraggedAreaState, + pageLayoutEditingWidgetIdState, + ], + ); + + return { removePageLayoutWidgetAndPreservePosition }; +};