import { useDuplicateFieldsWidgetForPageLayout } from '@/page-layout/hooks/useDuplicateFieldsWidgetForPageLayout'; import { useDuplicateRecordTableWidgetForPageLayout } from '@/page-layout/hooks/useDuplicateRecordTableWidgetForPageLayout'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab'; import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; import { generateDuplicatedTimestamps } from '@/page-layout/utils/generateDuplicatedTimestamps'; import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { useStore } from 'jotai'; import { useCallback } from 'react'; import { SidePanelPages } from 'twenty-shared/types'; import { appendCopySuffix, isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; export const useDuplicatePageLayoutTab = ({ pageLayoutId: pageLayoutIdFromProps, tabListInstanceId, }: { pageLayoutId: string; tabListInstanceId: string; }) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, ); const pageLayoutDraft = useAtomComponentStateCallbackState( pageLayoutDraftComponentState, pageLayoutId, ); const pageLayoutCurrentLayouts = useAtomComponentStateCallbackState( pageLayoutCurrentLayoutsComponentState, pageLayoutId, ); const store = useStore(); const setActiveTabId = useSetAtomComponentState( activeTabIdComponentState, tabListInstanceId, ); const setPageLayoutTabSettingsOpenTabId = useSetAtomComponentState( pageLayoutTabSettingsOpenTabIdComponentState, pageLayoutId, ); const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel(); const { closeSidePanelMenu } = useSidePanelMenu(); const { duplicateFieldsWidget } = useDuplicateFieldsWidgetForPageLayout({ pageLayoutId, }); const { duplicateRecordTableWidget } = useDuplicateRecordTableWidgetForPageLayout({ pageLayoutId, }); const duplicateTab = useCallback( (tabId: string): string => { const currentPageLayoutDraft = store.get(pageLayoutDraft); const allTabLayouts = store.get(pageLayoutCurrentLayouts); const sourceTab = currentPageLayoutDraft.tabs.find( (tab) => tab.id === tabId, ); if (!isDefined(sourceTab)) { throw new Error(`Tab with id ${tabId} not found`); } const newTabId = uuidv4(); const widgetOldIdNewIdMap = new Map(); const clonedWidgets: PageLayoutWidget[] = sourceTab.widgets.map( (widget) => { const newWidgetId = uuidv4(); widgetOldIdNewIdMap.set(widget.id, newWidgetId); const widgetViewCopyResult = duplicateFieldsWidget({ sourceWidget: widget, newWidgetId, }) ?? duplicateRecordTableWidget({ sourceWidget: widget, newWidgetId, }); const clonedConfiguration = isDefined(widgetViewCopyResult) ? { ...widget.configuration, viewId: widgetViewCopyResult.newViewId, } : widget.configuration; return { ...widget, id: newWidgetId, pageLayoutTabId: newTabId, configuration: clonedConfiguration, ...generateDuplicatedTimestamps(), }; }, ); const sortedTabs = sortTabsByPosition(currentPageLayoutDraft.tabs); const sourceIndex = sortedTabs.findIndex((t) => t.id === tabId); const newTabPosition = calculateNewPosition({ items: sortedTabs, destinationIndex: sourceIndex + 1, sourceIndex, }); const newTab: PageLayoutTab = { ...sourceTab, id: newTabId, title: appendCopySuffix(sourceTab.title), position: newTabPosition, widgets: clonedWidgets, ...generateDuplicatedTimestamps(), }; const sourceLayouts = allTabLayouts[tabId] ?? { desktop: [], mobile: [], }; const newLayouts = { desktop: sourceLayouts.desktop.map((layout) => ({ ...layout, i: widgetOldIdNewIdMap.get(layout.i) || layout.i, })), mobile: sourceLayouts.mobile.map((layout) => ({ ...layout, i: widgetOldIdNewIdMap.get(layout.i) || layout.i, })), }; store.set(pageLayoutCurrentLayouts, { ...allTabLayouts, [newTabId]: newLayouts, }); const prev = store.get(pageLayoutDraft); store.set(pageLayoutDraft, { ...prev, tabs: [...prev.tabs, newTab], }); closeSidePanelMenu(); setActiveTabId(newTabId); navigatePageLayoutSidePanel({ sidePanelPage: SidePanelPages.PageLayoutTabSettings, pageTitle: newTab.title, focusTitleInput: true, }); setPageLayoutTabSettingsOpenTabId(newTabId); return newTabId; }, [ closeSidePanelMenu, duplicateFieldsWidget, duplicateRecordTableWidget, navigatePageLayoutSidePanel, pageLayoutCurrentLayouts, pageLayoutDraft, setActiveTabId, setPageLayoutTabSettingsOpenTabId, store, ], ); return { duplicateTab }; };