Never display broken relations in record page layouts (#17738)
We generate Field widgets for relations on-the-fly, when a record page layout is first requested by the user. When the user changed their data model and then returned to a record page layout, the Field widgets weren't updated. **This PR ensures Field widgets are recomputed when relations change.** Future subjects: - Now that we will fetch the configuration from the backend and start storing updates, we will have to think about how we deal with these generated relation Field widgets. ## Before https://github.com/user-attachments/assets/99d53b19-b231-435f-b14f-4473ba269ad2 ## After https://github.com/user-attachments/assets/357d956d-8b3b-448c-a983-82569a7dd0a0 --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
committed by
GitHub
parent
df516a904b
commit
5b701f5ba4
+2
-68
@@ -1,22 +1,4 @@
|
||||
import { FIND_ONE_PAGE_LAYOUT } from '@/dashboards/graphql/queries/findOnePageLayout';
|
||||
import { DEFAULT_COMPANY_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultCompanyRecordPageLayout';
|
||||
import { DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultCompanyRecordPageLayoutId';
|
||||
import { DEFAULT_NOTE_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultNoteRecordPageLayout';
|
||||
import { DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultNoteRecordPageLayoutId';
|
||||
import { DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultOpportunityRecordPageLayout';
|
||||
import { DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultOpportunityRecordPageLayoutId';
|
||||
import { DEFAULT_PERSON_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultPersonRecordPageLayout';
|
||||
import { DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultPersonRecordPageLayoutId';
|
||||
import { DEFAULT_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultRecordPageLayout';
|
||||
import { DEFAULT_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultRecordPageLayoutId';
|
||||
import { DEFAULT_TASK_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultTaskRecordPageLayout';
|
||||
import { DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultTaskRecordPageLayoutId';
|
||||
import { DEFAULT_WORKFLOW_PAGE_LAYOUT } from '@/page-layout/constants/DefaultWorkflowPageLayout';
|
||||
import { DEFAULT_WORKFLOW_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowPageLayoutId';
|
||||
import { DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT } from '@/page-layout/constants/DefaultWorkflowRunPageLayout';
|
||||
import { DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowRunPageLayoutId';
|
||||
import { DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT } from '@/page-layout/constants/DefaultWorkflowVersionPageLayout';
|
||||
import { DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowVersionPageLayoutId';
|
||||
import { useBasePageLayout } from '@/page-layout/hooks/useBasePageLayout';
|
||||
import { usePageLayoutWithRelationWidgets } from '@/page-layout/hooks/usePageLayoutWithRelationWidgets';
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
@@ -24,40 +6,14 @@ import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/page
|
||||
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
|
||||
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
const getDefaultLayoutById = (layoutId: string): PageLayout => {
|
||||
switch (layoutId) {
|
||||
case DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_COMPANY_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_PERSON_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_NOTE_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_TASK_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_WORKFLOW_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_WORKFLOW_PAGE_LAYOUT;
|
||||
case DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT;
|
||||
case DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT;
|
||||
case DEFAULT_RECORD_PAGE_LAYOUT_ID:
|
||||
default:
|
||||
return DEFAULT_RECORD_PAGE_LAYOUT;
|
||||
}
|
||||
};
|
||||
|
||||
type PageLayoutInitializationQueryEffectProps = {
|
||||
pageLayoutId: string;
|
||||
onInitialized?: (pageLayout: PageLayout) => void;
|
||||
@@ -71,29 +27,7 @@ export const PageLayoutInitializationQueryEffect = ({
|
||||
pageLayoutIsInitializedComponentState,
|
||||
);
|
||||
|
||||
const isDefaultLayout =
|
||||
pageLayoutId === DEFAULT_RECORD_PAGE_LAYOUT_ID ||
|
||||
pageLayoutId === DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID ||
|
||||
pageLayoutId === DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID ||
|
||||
pageLayoutId === DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID ||
|
||||
pageLayoutId === DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID ||
|
||||
pageLayoutId === DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID ||
|
||||
pageLayoutId === DEFAULT_WORKFLOW_PAGE_LAYOUT_ID ||
|
||||
pageLayoutId === DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID ||
|
||||
pageLayoutId === DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID;
|
||||
|
||||
const { data } = useQuery(FIND_ONE_PAGE_LAYOUT, {
|
||||
variables: {
|
||||
id: pageLayoutId,
|
||||
},
|
||||
skip: isDefaultLayout,
|
||||
});
|
||||
|
||||
const basePageLayout: PageLayout | undefined = isDefaultLayout
|
||||
? getDefaultLayoutById(pageLayoutId)
|
||||
: data?.getPageLayout
|
||||
? transformPageLayout(data.getPageLayout)
|
||||
: undefined;
|
||||
const basePageLayout = useBasePageLayout(pageLayoutId);
|
||||
|
||||
const pageLayout = usePageLayoutWithRelationWidgets(basePageLayout);
|
||||
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
|
||||
import { useBasePageLayout } from '@/page-layout/hooks/useBasePageLayout';
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/pageLayoutIsInitializedComponentState';
|
||||
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
|
||||
import { injectRelationWidgetsIntoLayout } from '@/page-layout/utils/injectRelationWidgetsIntoLayout';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PageLayoutType } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
type PageLayoutRelationWidgetsSyncEffectProps = {
|
||||
pageLayoutId: string;
|
||||
};
|
||||
|
||||
export const PageLayoutRelationWidgetsSyncEffect = ({
|
||||
pageLayoutId,
|
||||
}: PageLayoutRelationWidgetsSyncEffectProps) => {
|
||||
const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext();
|
||||
|
||||
const isInitialized = useRecoilComponentValue(
|
||||
pageLayoutIsInitializedComponentState,
|
||||
);
|
||||
|
||||
const basePageLayout = useBasePageLayout(pageLayoutId);
|
||||
|
||||
const { boxedRelationFieldMetadataItems } = useFieldListFieldMetadataItems({
|
||||
objectNameSingular: targetRecordIdentifier?.targetObjectNameSingular ?? '',
|
||||
});
|
||||
|
||||
const pageLayoutPersistedComponentCallbackState =
|
||||
useRecoilComponentCallbackState(pageLayoutPersistedComponentState);
|
||||
|
||||
const pageLayoutDraftComponentCallbackState = useRecoilComponentCallbackState(
|
||||
pageLayoutDraftComponentState,
|
||||
);
|
||||
|
||||
const pageLayoutCurrentLayoutsComponentCallbackState =
|
||||
useRecoilComponentCallbackState(pageLayoutCurrentLayoutsComponentState);
|
||||
|
||||
const syncPageLayoutWithRelationWidgets = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(layout: PageLayout) => {
|
||||
const currentPersisted = getSnapshotValue(
|
||||
snapshot,
|
||||
pageLayoutPersistedComponentCallbackState,
|
||||
);
|
||||
|
||||
if (!isDeeplyEqual(layout, currentPersisted)) {
|
||||
set(pageLayoutPersistedComponentCallbackState, layout);
|
||||
set(pageLayoutDraftComponentCallbackState, {
|
||||
id: layout.id,
|
||||
name: layout.name,
|
||||
type: layout.type,
|
||||
objectMetadataId: layout.objectMetadataId,
|
||||
tabs: layout.tabs,
|
||||
});
|
||||
|
||||
const tabLayouts = convertPageLayoutToTabLayouts(layout);
|
||||
set(pageLayoutCurrentLayoutsComponentCallbackState, tabLayouts);
|
||||
}
|
||||
},
|
||||
[
|
||||
pageLayoutCurrentLayoutsComponentCallbackState,
|
||||
pageLayoutDraftComponentCallbackState,
|
||||
pageLayoutPersistedComponentCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDefined(basePageLayout)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isRecordPage = layoutType === PageLayoutType.RECORD_PAGE;
|
||||
if (!isRecordPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const layoutWithRelationWidgets = injectRelationWidgetsIntoLayout(
|
||||
basePageLayout,
|
||||
boxedRelationFieldMetadataItems,
|
||||
);
|
||||
|
||||
syncPageLayoutWithRelationWidgets(layoutWithRelationWidgets);
|
||||
}, [
|
||||
basePageLayout,
|
||||
boxedRelationFieldMetadataItems,
|
||||
isInitialized,
|
||||
layoutType,
|
||||
syncPageLayoutWithRelationWidgets,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PageLayoutInitializationQueryEffect } from '@/page-layout/components/PageLayoutInitializationQueryEffect';
|
||||
import { PageLayoutRelationWidgetsSyncEffect } from '@/page-layout/components/PageLayoutRelationWidgetsSyncEffect';
|
||||
import { PageLayoutRendererContent } from '@/page-layout/components/PageLayoutRendererContent';
|
||||
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
@@ -42,6 +43,7 @@ export const PageLayoutRenderer = ({
|
||||
pageLayoutId={pageLayoutId}
|
||||
onInitialized={onInitialized}
|
||||
/>
|
||||
<PageLayoutRelationWidgetsSyncEffect pageLayoutId={pageLayoutId} />
|
||||
<PageLayoutRendererContent />
|
||||
</TabListComponentInstanceContext.Provider>
|
||||
</PageLayoutComponentInstanceContext.Provider>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { FIND_ONE_PAGE_LAYOUT } from '@/dashboards/graphql/queries/findOnePageLayout';
|
||||
import { DEFAULT_COMPANY_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultCompanyRecordPageLayout';
|
||||
import { DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultCompanyRecordPageLayoutId';
|
||||
import { DEFAULT_NOTE_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultNoteRecordPageLayout';
|
||||
import { DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultNoteRecordPageLayoutId';
|
||||
import { DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultOpportunityRecordPageLayout';
|
||||
import { DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultOpportunityRecordPageLayoutId';
|
||||
import { DEFAULT_PERSON_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultPersonRecordPageLayout';
|
||||
import { DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultPersonRecordPageLayoutId';
|
||||
import { DEFAULT_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultRecordPageLayout';
|
||||
import { DEFAULT_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultRecordPageLayoutId';
|
||||
import { DEFAULT_TASK_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultTaskRecordPageLayout';
|
||||
import { DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultTaskRecordPageLayoutId';
|
||||
import { DEFAULT_WORKFLOW_PAGE_LAYOUT } from '@/page-layout/constants/DefaultWorkflowPageLayout';
|
||||
import { DEFAULT_WORKFLOW_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowPageLayoutId';
|
||||
import { DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT } from '@/page-layout/constants/DefaultWorkflowRunPageLayout';
|
||||
import { DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowRunPageLayoutId';
|
||||
import { DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT } from '@/page-layout/constants/DefaultWorkflowVersionPageLayout';
|
||||
import { DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowVersionPageLayoutId';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
|
||||
import { useQuery } from '@apollo/client';
|
||||
|
||||
const getDefaultLayoutById = (layoutId: string): PageLayout => {
|
||||
switch (layoutId) {
|
||||
case DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_COMPANY_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_PERSON_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_NOTE_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_TASK_RECORD_PAGE_LAYOUT;
|
||||
case DEFAULT_WORKFLOW_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_WORKFLOW_PAGE_LAYOUT;
|
||||
case DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT;
|
||||
case DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID:
|
||||
return DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT;
|
||||
case DEFAULT_RECORD_PAGE_LAYOUT_ID:
|
||||
default:
|
||||
return DEFAULT_RECORD_PAGE_LAYOUT;
|
||||
}
|
||||
};
|
||||
|
||||
const isDefaultLayoutId = (layoutId: string): boolean =>
|
||||
layoutId === DEFAULT_RECORD_PAGE_LAYOUT_ID ||
|
||||
layoutId === DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID ||
|
||||
layoutId === DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID ||
|
||||
layoutId === DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID ||
|
||||
layoutId === DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID ||
|
||||
layoutId === DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID ||
|
||||
layoutId === DEFAULT_WORKFLOW_PAGE_LAYOUT_ID ||
|
||||
layoutId === DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID ||
|
||||
layoutId === DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID;
|
||||
|
||||
export const useBasePageLayout = (
|
||||
pageLayoutId: string,
|
||||
): PageLayout | undefined => {
|
||||
const isDefaultLayout = isDefaultLayoutId(pageLayoutId);
|
||||
|
||||
const { data } = useQuery(FIND_ONE_PAGE_LAYOUT, {
|
||||
variables: {
|
||||
id: pageLayoutId,
|
||||
},
|
||||
skip: isDefaultLayout,
|
||||
});
|
||||
|
||||
const basePageLayout: PageLayout | undefined = isDefaultLayout
|
||||
? getDefaultLayoutById(pageLayoutId)
|
||||
: data?.getPageLayout
|
||||
? transformPageLayout(data.getPageLayout)
|
||||
: undefined;
|
||||
|
||||
return basePageLayout;
|
||||
};
|
||||
+2
-115
@@ -1,122 +1,9 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { injectRelationWidgetsIntoLayout } from '@/page-layout/utils/injectRelationWidgetsIntoLayout';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
PageLayoutType,
|
||||
WidgetConfigurationType,
|
||||
WidgetType,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const createRelationFieldWidget = (
|
||||
field: FieldMetadataItem,
|
||||
tabId: string,
|
||||
): PageLayoutWidget => ({
|
||||
__typename: 'PageLayoutWidget' as const,
|
||||
id: `dynamic-relation-widget-${field.id}-${field.label}`,
|
||||
pageLayoutTabId: tabId,
|
||||
title: field.label,
|
||||
type: WidgetType.FIELD,
|
||||
objectMetadataId: null,
|
||||
gridPosition: {
|
||||
__typename: 'GridPosition' as const,
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 12,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'FieldConfiguration' as const,
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.id,
|
||||
layout: 'CARD' as const,
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
});
|
||||
|
||||
const createRelationFieldWidgets = (
|
||||
relationFields: FieldMetadataItem[],
|
||||
tabId: string,
|
||||
): PageLayoutWidget[] => {
|
||||
return relationFields.map((field) => createRelationFieldWidget(field, tabId));
|
||||
};
|
||||
|
||||
const injectRelationWidgetsIntoLayout = (
|
||||
layout: PageLayout,
|
||||
boxedRelationFieldMetadataItems: FieldMetadataItem[],
|
||||
): PageLayout => {
|
||||
if (boxedRelationFieldMetadataItems.length === 0) {
|
||||
return layout;
|
||||
}
|
||||
|
||||
const firstTab = layout.tabs[0];
|
||||
if (!isDefined(firstTab)) {
|
||||
return layout;
|
||||
}
|
||||
|
||||
const relationWidgets = createRelationFieldWidgets(
|
||||
boxedRelationFieldMetadataItems,
|
||||
firstTab.id,
|
||||
);
|
||||
|
||||
return {
|
||||
...layout,
|
||||
tabs: layout.tabs.map((tab) => {
|
||||
if (tab.id === firstTab.id) {
|
||||
const firstFieldsWidgetIndex = tab.widgets.findIndex(
|
||||
(widget) => widget.type === WidgetType.FIELDS,
|
||||
);
|
||||
|
||||
if (firstFieldsWidgetIndex === -1) {
|
||||
return {
|
||||
...tab,
|
||||
widgets: [...tab.widgets, ...relationWidgets],
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: This note widget repositioning logic is temporary and will be deleted soon.
|
||||
// We need this to ensure the note editor is displayed before record relations,
|
||||
// matching the behavior of the old show page.
|
||||
const noteWidgetIndex = tab.widgets.findIndex(
|
||||
(widget) => widget.type === WidgetType.NOTES,
|
||||
);
|
||||
|
||||
const widgetsBeforeRelation = tab.widgets.slice(
|
||||
0,
|
||||
firstFieldsWidgetIndex + 1,
|
||||
);
|
||||
const widgetsAfterRelation =
|
||||
noteWidgetIndex === -1
|
||||
? tab.widgets.slice(firstFieldsWidgetIndex + 1)
|
||||
: [
|
||||
...tab.widgets.slice(
|
||||
firstFieldsWidgetIndex + 1,
|
||||
noteWidgetIndex,
|
||||
),
|
||||
...tab.widgets.slice(noteWidgetIndex + 1),
|
||||
];
|
||||
|
||||
const noteWidget =
|
||||
noteWidgetIndex !== -1 ? [tab.widgets[noteWidgetIndex]] : [];
|
||||
|
||||
return {
|
||||
...tab,
|
||||
widgets: [
|
||||
...widgetsBeforeRelation,
|
||||
...relationWidgets,
|
||||
...noteWidget,
|
||||
...widgetsAfterRelation,
|
||||
],
|
||||
};
|
||||
}
|
||||
return tab;
|
||||
}),
|
||||
};
|
||||
};
|
||||
import { PageLayoutType } from '~/generated/graphql';
|
||||
|
||||
export const usePageLayoutWithRelationWidgets = (
|
||||
basePageLayout: PageLayout | undefined,
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WidgetConfigurationType, WidgetType } from '~/generated/graphql';
|
||||
|
||||
const getRelationFieldWidgetToInsert = (
|
||||
field: FieldMetadataItem,
|
||||
tabId: string,
|
||||
): PageLayoutWidget => ({
|
||||
__typename: 'PageLayoutWidget' as const,
|
||||
id: `dynamic-relation-widget-${field.id}-${field.label}`,
|
||||
pageLayoutTabId: tabId,
|
||||
title: field.label,
|
||||
type: WidgetType.FIELD,
|
||||
objectMetadataId: null,
|
||||
gridPosition: {
|
||||
__typename: 'GridPosition' as const,
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 12,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'FieldConfiguration' as const,
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.id,
|
||||
layout: 'CARD' as const,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
deletedAt: null,
|
||||
});
|
||||
|
||||
const getRelationFieldWidgetsToInsert = (
|
||||
relationFields: FieldMetadataItem[],
|
||||
tabId: string,
|
||||
): PageLayoutWidget[] => {
|
||||
return relationFields.map((field) =>
|
||||
getRelationFieldWidgetToInsert(field, tabId),
|
||||
);
|
||||
};
|
||||
|
||||
export const injectRelationWidgetsIntoLayout = (
|
||||
layout: PageLayout,
|
||||
boxedRelationFieldMetadataItems: FieldMetadataItem[],
|
||||
): PageLayout => {
|
||||
if (boxedRelationFieldMetadataItems.length === 0) {
|
||||
return layout;
|
||||
}
|
||||
|
||||
const firstTab = layout.tabs[0];
|
||||
if (!isDefined(firstTab)) {
|
||||
return layout;
|
||||
}
|
||||
|
||||
const relationWidgets = getRelationFieldWidgetsToInsert(
|
||||
boxedRelationFieldMetadataItems,
|
||||
firstTab.id,
|
||||
);
|
||||
|
||||
return {
|
||||
...layout,
|
||||
tabs: layout.tabs.map((tab) => {
|
||||
if (tab.id === firstTab.id) {
|
||||
const firstFieldsWidgetIndex = tab.widgets.findIndex(
|
||||
(widget) => widget.type === WidgetType.FIELDS,
|
||||
);
|
||||
|
||||
if (firstFieldsWidgetIndex === -1) {
|
||||
return {
|
||||
...tab,
|
||||
widgets: [...tab.widgets, ...relationWidgets],
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: This note widget repositioning logic is temporary and will be deleted soon.
|
||||
// We need this to ensure the note editor is displayed before record relations,
|
||||
// matching the behavior of the old show page.
|
||||
const noteWidgetIndex = tab.widgets.findIndex(
|
||||
(widget) => widget.type === WidgetType.NOTES,
|
||||
);
|
||||
|
||||
const widgetsBeforeRelation = tab.widgets.slice(
|
||||
0,
|
||||
firstFieldsWidgetIndex + 1,
|
||||
);
|
||||
const widgetsAfterRelation =
|
||||
noteWidgetIndex === -1
|
||||
? tab.widgets.slice(firstFieldsWidgetIndex + 1)
|
||||
: [
|
||||
...tab.widgets.slice(
|
||||
firstFieldsWidgetIndex + 1,
|
||||
noteWidgetIndex,
|
||||
),
|
||||
...tab.widgets.slice(noteWidgetIndex + 1),
|
||||
];
|
||||
|
||||
const noteWidget =
|
||||
noteWidgetIndex !== -1 ? [tab.widgets[noteWidgetIndex]] : [];
|
||||
|
||||
return {
|
||||
...tab,
|
||||
widgets: [
|
||||
...widgetsBeforeRelation,
|
||||
...relationWidgets,
|
||||
...noteWidget,
|
||||
...widgetsAfterRelation,
|
||||
],
|
||||
};
|
||||
}
|
||||
return tab;
|
||||
}),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user