diff --git a/packages/twenty-front/src/modules/object-record/record-merge/components/MergePreviewTab.tsx b/packages/twenty-front/src/modules/object-record/record-merge/components/MergePreviewTab.tsx index 46dab38975..33ada923ac 100644 --- a/packages/twenty-front/src/modules/object-record/record-merge/components/MergePreviewTab.tsx +++ b/packages/twenty-front/src/modules/object-record/record-merge/components/MergePreviewTab.tsx @@ -1,10 +1,8 @@ import { usePerformMergePreview } from '@/object-record/record-merge/hooks/usePerformMergePreview'; -import { SummaryCard } from '@/object-record/record-show/components/SummaryCard'; -import { CardType } from '@/object-record/record-show/types/CardType'; -import { getCardComponent } from '@/object-record/record-show/utils/getCardComponent'; +import { PageLayoutSingleTabRenderer } from '@/page-layout/components/PageLayoutSingleTabRenderer'; +import { usePageLayoutIdForRecord } from '@/page-layout/hooks/usePageLayoutIdForRecord'; import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; import { isDefined } from 'twenty-shared/utils'; -import { Section } from 'twenty-ui/layout'; import { PageLayoutType } from '~/generated-metadata/graphql'; type MergePreviewTabProps = { @@ -18,7 +16,16 @@ export const MergePreviewTab = ({ objectNameSingular, }); - if (!isDefined(mergePreviewRecord) || isGeneratingPreview) { + const { pageLayoutId } = usePageLayoutIdForRecord({ + id: mergePreviewRecord?.id ?? '', + targetObjectNameSingular: objectNameSingular, + }); + + if ( + !isDefined(mergePreviewRecord) || + isGeneratingPreview || + !isDefined(pageLayoutId) + ) { return null; } @@ -35,17 +42,7 @@ export const MergePreviewTab = ({ isInSidePanel: true, }} > -
- - - {getCardComponent(CardType.FieldCard, { - showDuplicatesSection: false, - })} -
+ ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordTab.tsx b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordTab.tsx index 8ba8ce3d82..0bcea251a9 100644 --- a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordTab.tsx +++ b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordTab.tsx @@ -1,12 +1,11 @@ -import { SummaryCard } from '@/object-record/record-show/components/SummaryCard'; -import { CardType } from '@/object-record/record-show/types/CardType'; -import { getCardComponent } from '@/object-record/record-show/utils/getCardComponent'; +import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect'; +import { PageLayoutSingleTabRenderer } from '@/page-layout/components/PageLayoutSingleTabRenderer'; +import { usePageLayoutIdForRecord } from '@/page-layout/hooks/usePageLayoutIdForRecord'; import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { Section } from 'twenty-ui/layout'; +import { isDefined } from 'twenty-shared/utils'; import { PageLayoutType } from '~/generated-metadata/graphql'; type MergeRecordTabProps = { - isInSidePanel?: boolean; objectNameSingular: string; recordId: string; }; @@ -15,6 +14,11 @@ export const MergeRecordTab = ({ objectNameSingular, recordId, }: MergeRecordTabProps) => { + const { pageLayoutId } = usePageLayoutIdForRecord({ + id: recordId, + targetObjectNameSingular: objectNameSingular, + }); + return ( -
- - - {getCardComponent(CardType.FieldCard, { - showDuplicatesSection: false, - })} -
+ + {isDefined(pageLayoutId) && ( + + )}
); }; diff --git a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordsContainer.tsx b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordsContainer.tsx index b5c0a8a6c8..748214db2b 100644 --- a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordsContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordsContainer.tsx @@ -8,11 +8,11 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { SidePanelPageComponentInstanceContext } from '@/side-panel/states/contexts/SidePanelPageComponentInstanceContext'; +import { useMergeRecordsContainerTabs } from '@/object-record/record-merge/hooks/useMergeRecordsContainerTabs'; import { useMergeRecordsSelectedRecords } from '@/object-record/record-merge/hooks/useMergeRecordsSelectedRecords'; import { MergeRecordsTabId } from '@/object-record/record-merge/types/MergeRecordsTabId'; +import { SidePanelPageComponentInstanceContext } from '@/side-panel/states/contexts/SidePanelPageComponentInstanceContext'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; -import { useMergeRecordsContainerTabs } from '@/object-record/record-merge/hooks/useMergeRecordsContainerTabs'; import { MergePreviewTab } from './MergePreviewTab'; import { MergeRecordTab } from './MergeRecordTab'; import { MergeRecordsFooter } from './MergeRecordsFooter'; @@ -36,7 +36,6 @@ const StyledContentContainer = styled.div` background: ${themeCssVariables.background.primary}; flex: 1; overflow-y: auto; - padding-bottom: ${themeCssVariables.spacing[16]}; `; type MergeRecordsContainerProps = { diff --git a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeSettingsTab.tsx b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeSettingsTab.tsx index ac9f271296..745317ed09 100644 --- a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeSettingsTab.tsx +++ b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeSettingsTab.tsx @@ -1,15 +1,15 @@ -import { t } from '@lingui/core/macro'; import { useMergeRecordsSelectedRecords } from '@/object-record/record-merge/hooks/useMergeRecordsSelectedRecords'; import { useMergeRecordsSettings } from '@/object-record/record-merge/hooks/useMergeRecordsSettings'; -import { Select } from '@/ui/input/components/Select'; -import { styled } from '@linaria/react'; -import { Section } from 'twenty-ui/layout'; import { getPositionNumberIcon } from '@/object-record/record-merge/utils/getPositionNumberIcon'; import { getPositionWordLabel } from '@/object-record/record-merge/utils/getPositionWordLabel'; +import { Select } from '@/ui/input/components/Select'; +import { styled } from '@linaria/react'; +import { t } from '@lingui/core/macro'; +import { Section } from 'twenty-ui/layout'; import { themeCssVariables } from 'twenty-ui/theme-constants'; const StyledSectionContainer = styled.div` - margin: ${themeCssVariables.spacing[4]}; + padding: ${themeCssVariables.spacing[2]}; width: auto; `; diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx deleted file mode 100644 index 1257799bcc..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { RecordFieldList } from '@/object-record/record-field-list/components/RecordFieldList'; -import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext'; -import { type FieldCardConfiguration } from '@/object-record/record-show/types/CardConfiguration'; -import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; - -type FieldsCardProps = { - configuration?: FieldCardConfiguration; -}; - -export const FieldsCard = ({ configuration }: FieldsCardProps) => { - const targetRecord = useTargetRecord(); - const { isInSidePanel } = useLayoutRenderingContext(); - - const instanceId = `fields-card-${targetRecord.id}${isInSidePanel ? '-side-panel' : ''}`; - - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts b/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts deleted file mode 100644 index ad5655f789..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { type CardType } from '@/object-record/record-show/types/CardType'; - -// Card configuration types - each card type can define its own configuration -export type FieldCardConfiguration = { - showDuplicatesSection?: boolean; -}; - -// For cards that don't need configuration, use undefined -export type EmptyCardConfiguration = undefined; - -// Type mapping from CardType to its specific configuration type -// This creates precise typing: each CardType is linked to exactly one configuration type -export type CardTypeToConfiguration = { - [CardType.FieldCard]: FieldCardConfiguration; - [CardType.TimelineCard]: EmptyCardConfiguration; - [CardType.TaskCard]: EmptyCardConfiguration; - [CardType.NoteCard]: EmptyCardConfiguration; - [CardType.FileCard]: EmptyCardConfiguration; - [CardType.EmailCard]: EmptyCardConfiguration; - [CardType.CalendarCard]: EmptyCardConfiguration; - [CardType.FieldRichTextCard]: EmptyCardConfiguration; - [CardType.WorkflowCard]: EmptyCardConfiguration; - [CardType.WorkflowVersionCard]: EmptyCardConfiguration; - [CardType.WorkflowRunCard]: EmptyCardConfiguration; - [CardType.DashboardCard]: EmptyCardConfiguration; -}; - -// Union type for all card configurations (for general use) -export type CardConfiguration = FieldCardConfiguration | EmptyCardConfiguration; diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts b/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts deleted file mode 100644 index a80aa4497d..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts +++ /dev/null @@ -1,14 +0,0 @@ -export enum CardType { - FieldCard = 'FieldCard', - TimelineCard = 'TimelineCard', - TaskCard = 'TaskCard', - NoteCard = 'NoteCard', - FileCard = 'FileCard', - EmailCard = 'EmailCard', - CalendarCard = 'CalendarCard', - WorkflowCard = 'WorkflowCard', - WorkflowVersionCard = 'WorkflowVersionCard', - WorkflowRunCard = 'WorkflowRunCard', - FieldRichTextCard = 'FieldRichTextCard', - DashboardCard = 'DashboardCard', -} diff --git a/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx b/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx deleted file mode 100644 index f7ec0f487d..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { CalendarEventsCard } from '@/activities/calendar/components/CalendarEventsCard'; -import { EmailsCard } from '@/activities/emails/components/EmailsCard'; -import { FilesCard } from '@/activities/files/components/FilesCard'; -import { NotesCard } from '@/activities/notes/components/NotesCard'; -import { TasksCard } from '@/activities/tasks/components/TasksCard'; -import { TimelineCard } from '@/activities/timeline-activities/components/TimelineCard'; -import { FieldsCard } from '@/object-record/record-show/components/FieldsCard'; -import { - type CardConfiguration, - type CardTypeToConfiguration, - type FieldCardConfiguration, -} from '@/object-record/record-show/types/CardConfiguration'; -import { CardType } from '@/object-record/record-show/types/CardType'; -import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { FieldRichTextCard } from '@/ui/layout/show-page/components/FieldRichTextCard'; -import { WorkflowCard } from '@/workflow/workflow-diagram/components/WorkflowCard'; -import { WorkflowRunCard } from '@/workflow/workflow-diagram/components/WorkflowRunCard'; -import { WorkflowVersionCard } from '@/workflow/workflow-diagram/components/WorkflowVersionCard'; -import { assertUnreachable } from 'twenty-shared/utils'; - -const CardRenderer = ({ - Component, - configuration, -}: { - Component: React.ComponentType<{ configuration?: T }> | React.ComponentType; - configuration?: T; -}) => { - const { targetRecordIdentifier } = useLayoutRenderingContext(); - - if (!targetRecordIdentifier) { - return null; - } - - // TypeScript can't infer if Component accepts configuration prop or not - // So we cast to the more permissive type and let the component ignore unused props - const ComponentWithConfig = Component as React.ComponentType<{ - configuration?: T; - }>; - - return ; -}; - -// Generic function with precise type mapping from CardType to Configuration -// TypeScript will enforce that the correct configuration type is passed for each card type -export const getCardComponent = ( - cardType: T, - configuration?: CardTypeToConfiguration[T], -): JSX.Element | null => { - switch (cardType) { - case CardType.TimelineCard: - return ; - - case CardType.FieldCard: - return ( - - ); - - case CardType.FieldRichTextCard: - return ; - - case CardType.TaskCard: - return ; - - case CardType.NoteCard: - return ; - - case CardType.FileCard: - return ; - - case CardType.EmailCard: - return ; - - case CardType.CalendarCard: - return ; - - case CardType.WorkflowCard: - return ; - - case CardType.WorkflowVersionCard: - return ; - - case CardType.WorkflowRunCard: - return ; - - case CardType.DashboardCard: - throw new Error('Dashboard are handled separately currently'); - - default: - assertUnreachable(cardType); - } -}; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx new file mode 100644 index 0000000000..412a94d31c --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx @@ -0,0 +1,115 @@ +import { SummaryCard } from '@/object-record/record-show/components/SummaryCard'; +import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent'; +import { PageLayoutEditModeProvider } from '@/page-layout/components/PageLayoutEditModeProvider'; +import { PageLayoutInitializationQueryEffect } from '@/page-layout/components/PageLayoutInitializationQueryEffect'; +import { PageLayoutRecordPageCustomizationSessionRegistrationEffect } from '@/page-layout/components/PageLayoutRecordPageCustomizationSessionRegistrationEffect'; +import { PageLayoutRelationWidgetsSyncEffect } from '@/page-layout/components/PageLayoutRelationWidgetsSyncEffect'; +import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; +import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; +import { usePageLayoutTabWithVisibleWidgetsOrThrow } from '@/page-layout/hooks/usePageLayoutTabWithVisibleWidgetsOrThrow'; +import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; +import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/pageLayoutIsInitializedComponentState'; +import { getTabLayoutMode } from '@/page-layout/utils/getTabLayoutMode'; +import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; +import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; +import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; +import { FeatureFlagKey } from '~/generated-metadata/graphql'; + +type PageLayoutSingleTabRendererProps = { + pageLayoutId: string; +}; + +const PageLayoutSingleTabRendererContent = () => { + const pageLayoutIsInitialized = useAtomComponentStateValue( + pageLayoutIsInitializedComponentState, + ); + + if (!pageLayoutIsInitialized) { + return null; + } + + return ; +}; + +const PageLayoutSingleTabRendererInner = () => { + const { currentPageLayout } = useCurrentPageLayoutOrThrow(); + const targetRecordIdentifier = useTargetRecord(); + const { isInSidePanel } = useLayoutRenderingContext(); + + const sortedTabs = sortTabsByPosition(currentPageLayout.tabs); + const firstTab = sortedTabs[0]; + + const firstTabWithVisibleWidgets = usePageLayoutTabWithVisibleWidgetsOrThrow( + firstTab.id, + ); + + const layoutMode = getTabLayoutMode({ + tab: firstTabWithVisibleWidgets, + pageLayoutType: currentPageLayout.type, + }); + + return ( + <> + + + + + + + ); +}; + +export const PageLayoutSingleTabRenderer = ({ + pageLayoutId, +}: PageLayoutSingleTabRendererProps) => { + const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext(); + + const featureFlags = useFeatureFlagsMap(); + const isRecordPageLayoutEditingEnabled = + featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED]; + + const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ + pageLayoutId, + layoutType, + targetRecordIdentifier, + }); + + return ( + + + + + + {!isRecordPageLayoutEditingEnabled && ( + + )} + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/tab-list/types/LayoutCard.ts b/packages/twenty-front/src/modules/ui/layout/tab-list/types/LayoutCard.ts deleted file mode 100644 index ffda093ce9..0000000000 --- a/packages/twenty-front/src/modules/ui/layout/tab-list/types/LayoutCard.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type CardConfiguration } from '@/object-record/record-show/types/CardConfiguration'; -import { type CardType } from '@/object-record/record-show/types/CardType'; - -export type LayoutCard = { - type: CardType; - configuration?: CardConfiguration; -}; diff --git a/packages/twenty-front/src/modules/ui/layout/tab-list/types/SingleTabProps.ts b/packages/twenty-front/src/modules/ui/layout/tab-list/types/SingleTabProps.ts index 13e2873189..416e53ae67 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab-list/types/SingleTabProps.ts +++ b/packages/twenty-front/src/modules/ui/layout/tab-list/types/SingleTabProps.ts @@ -1,4 +1,3 @@ -import { type LayoutCard } from '@/ui/layout/tab-list/types/LayoutCard'; import { type IconComponent } from 'twenty-ui/display'; export type SingleTabProps = { @@ -8,6 +7,5 @@ export type SingleTabProps = { hide?: boolean; disabled?: boolean; pill?: string | React.ReactElement; - cards?: LayoutCard[]; logo?: string; };