Fix record page layout BE (#17758)

## Context
- Add missing conditional display
- Set default tab as null for most of the page layout, letting the FE
handle that
- Fix duplicate "Note" widget in both task and note pages
- Simplify typing, removing redundant layoutName
This commit is contained in:
Weiko
2026-02-06 19:34:35 +01:00
committed by GitHub
parent 30cbf0e40e
commit cc86b9acae
25 changed files with 278 additions and 144 deletions
@@ -7,6 +7,7 @@ export type SeederFlatPageLayoutWidget = Pick<
| 'title'
| 'type'
| 'gridPosition'
| 'position'
| 'configuration'
| 'objectMetadataId'
>;
@@ -1,6 +1,7 @@
import { CalendarStartDay } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { PageLayoutTabLayoutMode } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -69,6 +70,13 @@ export const getPageLayoutWidgetDataSeedsV2 = (
title: 'Revenue Forecast',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 7, rowSpan: 8, columnSpan: 5 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 7,
rowSpan: 8,
columnSpan: 5,
},
configuration: {
configurationType: WidgetConfigurationType.LINE_CHART,
aggregateFieldMetadataId: opportunityAmountFieldId,
@@ -98,6 +106,13 @@ export const getPageLayoutWidgetDataSeedsV2 = (
title: 'New Customers Over Time',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 3, rowSpan: 6, columnSpan: 5 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 3,
rowSpan: 6,
columnSpan: 5,
},
configuration: {
configurationType: WidgetConfigurationType.LINE_CHART,
aggregateFieldMetadataId: companyIdFieldId,
@@ -127,6 +142,13 @@ export const getPageLayoutWidgetDataSeedsV2 = (
title: 'Revenue Distribution',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 4, rowSpan: 2, columnSpan: 3 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 4,
rowSpan: 2,
columnSpan: 3,
},
configuration: {
configurationType: WidgetConfigurationType.PIE_CHART,
aggregateFieldMetadataId: companyArrFieldId,
@@ -155,6 +177,13 @@ export const getPageLayoutWidgetDataSeedsV2 = (
title: 'Average ARR',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 7, rowSpan: 6, columnSpan: 5 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 7,
rowSpan: 6,
columnSpan: 5,
},
configuration: {
configurationType: WidgetConfigurationType.GAUGE_CHART,
aggregateFieldMetadataId: companyArrFieldId,
@@ -181,6 +210,13 @@ export const getPageLayoutWidgetDataSeedsV2 = (
title: 'Companies by LinkedIn (Field Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 6, column: 0, rowSpan: 4, columnSpan: 6 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 6,
column: 0,
rowSpan: 4,
columnSpan: 6,
},
configuration: {
configurationType: WidgetConfigurationType.PIE_CHART,
aggregateFieldMetadataId: companyIdFieldId,
@@ -210,6 +246,13 @@ export const getPageLayoutWidgetDataSeedsV2 = (
title: 'Contact Roles',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 6 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 4,
columnSpan: 6,
},
configuration: {
configurationType: WidgetConfigurationType.PIE_CHART,
aggregateFieldMetadataId: personIdFieldId,
@@ -1,6 +1,7 @@
import { CalendarStartDay } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { PageLayoutTabLayoutMode } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -87,6 +88,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Total Pipeline Value',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 3 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 2,
columnSpan: 3,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId: opportunityAmountFieldId,
@@ -111,6 +119,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Rocket Count (Object Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 3, rowSpan: 4, columnSpan: 4 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 3,
rowSpan: 4,
columnSpan: 4,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId: rocketIdFieldId,
@@ -137,6 +152,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Pipeline Value by Close Date (Stacked by Stage)',
type: WidgetType.GRAPH,
gridPosition: { row: 4, column: 0, rowSpan: 8, columnSpan: 6 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 4,
column: 0,
rowSpan: 8,
columnSpan: 6,
},
configuration: {
configurationType: WidgetConfigurationType.BAR_CHART,
aggregateFieldMetadataId: opportunityAmountFieldId,
@@ -169,6 +191,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Rockets by Created Date (Object Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 5, columnSpan: 5 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 5,
columnSpan: 5,
},
configuration: {
configurationType: WidgetConfigurationType.BAR_CHART,
aggregateFieldMetadataId: rocketIdFieldId,
@@ -198,6 +227,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Opportunity Count',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 5, rowSpan: 5, columnSpan: 7 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 5,
rowSpan: 5,
columnSpan: 7,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId: opportunityIdFieldId,
@@ -224,6 +260,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Total Customers',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 3 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 2,
columnSpan: 3,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId: companyIdFieldId,
@@ -250,6 +293,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Companies by Size (Stacked by City)',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 8, rowSpan: 10, columnSpan: 8 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 8,
rowSpan: 10,
columnSpan: 8,
},
configuration: {
configurationType: WidgetConfigurationType.BAR_CHART,
aggregateFieldMetadataId: companyIdFieldId,
@@ -285,6 +335,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Annual Recurring Revenue',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 4 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 4,
columnSpan: 4,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId: companyArrFieldId,
@@ -309,6 +366,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'LinkedIn Profiles Count (Field Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 2, column: 0, rowSpan: 4, columnSpan: 3 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 2,
column: 0,
rowSpan: 4,
columnSpan: 3,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId: companyLinkedinLinkFieldId,
@@ -332,6 +396,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Team Size',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 5, columnSpan: 6 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 5,
columnSpan: 6,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId: personIdFieldId,
@@ -356,6 +427,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Geographic Distribution',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 6, rowSpan: 5, columnSpan: 6 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 6,
rowSpan: 5,
columnSpan: 6,
},
configuration: {
configurationType: WidgetConfigurationType.BAR_CHART,
aggregateFieldMetadataId: personIdFieldId,
@@ -387,6 +465,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Open Tasks',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 6, rowSpan: 6, columnSpan: 6 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 6,
rowSpan: 6,
columnSpan: 6,
},
configuration: {
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
aggregateFieldMetadataId: taskIdFieldId,
@@ -409,6 +494,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Front Component',
type: WidgetType.FRONT_COMPONENT,
gridPosition: { row: 2, column: 7, rowSpan: 2, columnSpan: 5 },
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 2,
column: 7,
rowSpan: 2,
columnSpan: 5,
},
configuration: {
configurationType: WidgetConfigurationType.FRONT_COMPONENT,
frontComponentId: '6cdf2607-4b28-40e6-8c53-cc06799ddc88',
@@ -38,6 +38,7 @@ export const seedPageLayoutWidgets = async ({
...widget,
workspaceId,
gridPosition: widget.gridPosition,
position: widget.position,
configuration: widget.configuration,
universalIdentifier: v4(),
applicationId: workspaceCustomApplicationId,
@@ -54,6 +55,7 @@ export const seedPageLayoutWidgets = async ({
'title',
'type',
'gridPosition',
'position',
'configuration',
'objectMetadataId',
'workspaceId',
@@ -1,6 +1,7 @@
import {
PageLayoutTabLayoutMode,
type PageLayoutWidgetCanvasPosition,
type PageLayoutWidgetConditionalDisplay,
type PageLayoutWidgetGridPosition,
type PageLayoutWidgetVerticalListPosition,
} from 'twenty-shared/types';
@@ -8,6 +9,14 @@ import {
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
export const CONDITIONAL_DISPLAY_DEVICE_MOBILE = {
and: [{ '===': [{ var: 'device' }, 'MOBILE'] }],
} as const satisfies PageLayoutWidgetConditionalDisplay;
export const CONDITIONAL_DISPLAY_DEVICE_DESKTOP = {
and: [{ '===': [{ var: 'device' }, 'DESKTOP'] }],
} as const satisfies PageLayoutWidgetConditionalDisplay;
export const GRID_POSITIONS = {
FULL_WIDTH: {
row: 0,
@@ -69,61 +78,61 @@ export const CANVAS_LAYOUT_POSITIONS = {
export const TAB_PROPS = {
home: {
title: 'Home',
position: 100,
position: 10,
icon: 'IconHome',
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
},
timeline: {
title: 'Timeline',
position: 200,
position: 20,
icon: 'IconTimelineEvent',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
tasks: {
title: 'Tasks',
position: 300,
position: 30,
icon: 'IconCheckbox',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
notes: {
title: 'Notes',
position: 400,
position: 40,
icon: 'IconNotes',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
files: {
title: 'Files',
position: 500,
position: 50,
icon: 'IconPaperclip',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
emails: {
title: 'Emails',
position: 600,
position: 60,
icon: 'IconMail',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
calendar: {
title: 'Calendar',
position: 700,
position: 70,
icon: 'IconCalendarEvent',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
note: {
title: 'Note',
position: 150,
position: 15,
icon: 'IconNotes',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
flow: {
title: 'Flow',
position: 100,
position: 10,
icon: 'IconSettings',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
flowSecondary: {
title: 'Flow',
position: 200,
position: 20,
icon: 'IconSettings',
layoutMode: PageLayoutTabLayoutMode.CANVAS,
},
@@ -172,12 +181,18 @@ export const WIDGET_PROPS = {
gridPosition: GRID_POSITIONS.HALF_HEIGHT,
position: CANVAS_LAYOUT_POSITIONS.DEFAULT,
},
richText: {
noteRichText: {
title: 'Note',
type: WidgetType.FIELD_RICH_TEXT,
gridPosition: GRID_POSITIONS.RICH_TEXT,
position: CANVAS_LAYOUT_POSITIONS.DEFAULT,
},
taskRichText: {
title: 'Task',
type: WidgetType.FIELD_RICH_TEXT,
gridPosition: GRID_POSITIONS.RICH_TEXT,
position: CANVAS_LAYOUT_POSITIONS.DEFAULT,
},
workflow: {
title: 'Flow',
type: WidgetType.WORKFLOW,
@@ -9,7 +9,7 @@ import {
STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG,
STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config';
import { type StandardRecordPageLayouts } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types';
import { type StandardRecordPageLayouts } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type';
export const STANDARD_PAGE_LAYOUTS = {
myFirstDashboard: STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG,
@@ -1,53 +0,0 @@
import {
type PageLayoutTabLayoutMode,
type PageLayoutWidgetPosition,
} from 'twenty-shared/types';
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
export type StandardPageLayoutWidgetDefinition = {
universalIdentifier: string;
title?: string;
type?: WidgetType;
gridPosition?: GridPosition;
position?: PageLayoutWidgetPosition;
};
export type StandardPageLayoutTabDefinition = {
universalIdentifier: string;
title: string;
position: number;
icon: string | null;
layoutMode: PageLayoutTabLayoutMode;
widgets: Record<string, StandardPageLayoutWidgetDefinition>;
};
export type StandardRecordPageWidgetDefinition = {
universalIdentifier: string;
title: string;
type: WidgetType;
gridPosition: GridPosition;
position?: PageLayoutWidgetPosition;
};
export type StandardRecordPageTabDefinition = {
universalIdentifier: string;
title: string;
position: number;
icon: string | null;
layoutMode: PageLayoutTabLayoutMode;
widgets: Record<string, StandardRecordPageWidgetDefinition>;
};
export type StandardRecordPageLayoutDefinition = {
universalIdentifier: string;
objectUniversalIdentifier: string | null;
defaultTabUniversalIdentifier: string | null;
tabs: Record<string, StandardRecordPageTabDefinition>;
};
export type StandardRecordPageLayouts = Record<
string,
StandardRecordPageLayoutDefinition
>;
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard page layout metadata related entity ids 1`] = `
{
@@ -114,7 +114,7 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
"fields": {
"id": "00000000-0000-0000-0000-000000000058",
},
"richText": {
"noteRichText": {
"id": "00000000-0000-0000-0000-000000000059",
},
},
@@ -122,7 +122,7 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
"note": {
"id": "00000000-0000-0000-0000-000000000060",
"widgets": {
"richText": {
"noteRichText": {
"id": "00000000-0000-0000-0000-000000000061",
},
},
@@ -276,7 +276,7 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
"fields": {
"id": "00000000-0000-0000-0000-000000000068",
},
"richText": {
"taskRichText": {
"id": "00000000-0000-0000-0000-000000000069",
},
},
@@ -284,7 +284,7 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
"note": {
"id": "00000000-0000-0000-0000-000000000070",
"widgets": {
"richText": {
"taskRichText": {
"id": "00000000-0000-0000-0000-000000000071",
},
},
@@ -7,4 +7,12 @@ export { STANDARD_TASK_PAGE_LAYOUT_CONFIG } from './standard-task-page-layout.co
export { STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG } from './standard-workflow-page-layout.config';
export { STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG } from './standard-workflow-run-page-layout.config';
export { STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG } from './standard-workflow-version-page-layout.config';
export { type StandardPageLayoutConfig } from './standard-page-layout-config.type';
export {
type StandardPageLayoutConfig,
type StandardPageLayoutTabConfig,
type StandardPageLayoutWidgetConfig,
type StandardRecordPageLayoutConfig,
type StandardRecordPageLayouts,
type StandardRecordPageTabConfig,
type StandardRecordPageWidgetConfig,
} from './standard-page-layout-config.type';
@@ -84,11 +84,10 @@ const COMPANY_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_COMPANY_PAGE_LAYOUT_CONFIG = {
layoutName: 'companyRecordPage',
name: 'Default Company Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier: STANDARD_OBJECTS.company.universalIdentifier,
universalIdentifier: '20202020-a101-4001-8001-c0aba11c0001',
defaultTabUniversalIdentifier: COMPANY_PAGE_TABS.home.universalIdentifier,
defaultTabUniversalIdentifier: null,
tabs: COMPANY_PAGE_TABS,
} as const satisfies StandardPageLayoutConfig;
@@ -43,11 +43,10 @@ const DASHBOARD_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG = {
layoutName: 'myFirstDashboard',
name: 'My First Dashboard',
type: PageLayoutType.DASHBOARD,
objectUniversalIdentifier: null,
universalIdentifier: '20202020-d001-4d01-8d01-da5ab0a00001',
defaultTabUniversalIdentifier: DASHBOARD_PAGE_TABS.tab1.universalIdentifier,
defaultTabUniversalIdentifier: null,
tabs: DASHBOARD_PAGE_TABS,
} as const satisfies StandardPageLayoutConfig;
@@ -2,6 +2,8 @@ import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import {
CONDITIONAL_DISPLAY_DEVICE_DESKTOP,
CONDITIONAL_DISPLAY_DEVICE_MOBILE,
TAB_PROPS,
WIDGET_PROPS,
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
@@ -19,12 +21,13 @@ const NOTE_PAGE_TABS = {
universalIdentifier: '20202020-ac04-4004-8004-a0be5a11a411',
...WIDGET_PROPS.fields,
},
richText: {
noteRichText: {
universalIdentifier: '20202020-ac04-4004-8004-a0be5a11a412',
title: WIDGET_PROPS.richText.title,
type: WIDGET_PROPS.richText.type,
gridPosition: WIDGET_PROPS.richText.gridPosition,
title: WIDGET_PROPS.noteRichText.title,
type: WIDGET_PROPS.noteRichText.type,
gridPosition: WIDGET_PROPS.noteRichText.gridPosition,
position: { layoutMode: TAB_PROPS.home.layoutMode, index: 1 },
conditionalDisplay: CONDITIONAL_DISPLAY_DEVICE_MOBILE,
},
},
},
@@ -32,9 +35,10 @@ const NOTE_PAGE_TABS = {
universalIdentifier: '20202020-ab04-4004-8004-a0be5a11a402',
...TAB_PROPS.note,
widgets: {
richText: {
noteRichText: {
universalIdentifier: '20202020-ac04-4004-8004-a0be5a11a421',
...WIDGET_PROPS.richText,
...WIDGET_PROPS.noteRichText,
conditionalDisplay: CONDITIONAL_DISPLAY_DEVICE_DESKTOP,
},
},
},
@@ -61,11 +65,10 @@ const NOTE_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_NOTE_PAGE_LAYOUT_CONFIG = {
layoutName: 'noteRecordPage',
name: 'Default Note Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier: STANDARD_OBJECTS.note.universalIdentifier,
universalIdentifier: '20202020-a104-4004-8004-a0be5a11a004',
defaultTabUniversalIdentifier: NOTE_PAGE_TABS.home.universalIdentifier,
defaultTabUniversalIdentifier: null,
tabs: NOTE_PAGE_TABS,
} as const satisfies StandardPageLayoutConfig;
@@ -84,11 +84,10 @@ const OPPORTUNITY_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG = {
layoutName: 'opportunityRecordPage',
name: 'Default Opportunity Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier,
universalIdentifier: '20202020-a103-4003-8003-0aa0b1ca1003',
defaultTabUniversalIdentifier: OPPORTUNITY_PAGE_TABS.home.universalIdentifier,
defaultTabUniversalIdentifier: null,
tabs: OPPORTUNITY_PAGE_TABS,
} as const satisfies StandardPageLayoutConfig;
@@ -1,5 +1,6 @@
import {
type PageLayoutTabLayoutMode,
type PageLayoutWidgetConditionalDisplay,
type PageLayoutWidgetPosition,
} from 'twenty-shared/types';
@@ -13,6 +14,7 @@ export type StandardPageLayoutWidgetConfig = {
type?: WidgetType;
gridPosition?: GridPosition;
position?: PageLayoutWidgetPosition;
conditionalDisplay?: PageLayoutWidgetConditionalDisplay | null;
};
export type StandardPageLayoutTabConfig = {
@@ -25,7 +27,6 @@ export type StandardPageLayoutTabConfig = {
};
export type StandardPageLayoutConfig = {
layoutName: string;
name: string;
type: PageLayoutType;
objectUniversalIdentifier: string | null;
@@ -33,3 +34,33 @@ export type StandardPageLayoutConfig = {
defaultTabUniversalIdentifier: string | null;
tabs: Record<string, StandardPageLayoutTabConfig>;
};
export type StandardRecordPageWidgetConfig = {
universalIdentifier: string;
title: string;
type: WidgetType;
gridPosition: GridPosition;
position?: PageLayoutWidgetPosition;
conditionalDisplay?: PageLayoutWidgetConditionalDisplay | null;
};
export type StandardRecordPageTabConfig = {
universalIdentifier: string;
title: string;
position: number;
icon: string | null;
layoutMode: PageLayoutTabLayoutMode;
widgets: Record<string, StandardRecordPageWidgetConfig>;
};
export type StandardRecordPageLayoutConfig = {
universalIdentifier: string;
objectUniversalIdentifier: string | null;
defaultTabUniversalIdentifier: string | null;
tabs: Record<string, StandardRecordPageTabConfig>;
};
export type StandardRecordPageLayouts = Record<
string,
StandardRecordPageLayoutConfig
>;
@@ -84,11 +84,10 @@ const PERSON_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_PERSON_PAGE_LAYOUT_CONFIG = {
layoutName: 'personRecordPage',
name: 'Default Person Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier: STANDARD_OBJECTS.person.universalIdentifier,
universalIdentifier: '20202020-a102-4002-8002-ae0a1ea11002',
defaultTabUniversalIdentifier: PERSON_PAGE_TABS.home.universalIdentifier,
defaultTabUniversalIdentifier: null,
tabs: PERSON_PAGE_TABS,
} as const satisfies StandardPageLayoutConfig;
@@ -2,6 +2,8 @@ import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import {
CONDITIONAL_DISPLAY_DEVICE_DESKTOP,
CONDITIONAL_DISPLAY_DEVICE_MOBILE,
TAB_PROPS,
WIDGET_PROPS,
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
@@ -19,12 +21,13 @@ const TASK_PAGE_TABS = {
universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5511',
...WIDGET_PROPS.fields,
},
richText: {
taskRichText: {
universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5512',
title: WIDGET_PROPS.richText.title,
type: WIDGET_PROPS.richText.type,
gridPosition: WIDGET_PROPS.richText.gridPosition,
title: WIDGET_PROPS.taskRichText.title,
type: WIDGET_PROPS.taskRichText.type,
gridPosition: WIDGET_PROPS.taskRichText.gridPosition,
position: { layoutMode: TAB_PROPS.home.layoutMode, index: 1 },
conditionalDisplay: CONDITIONAL_DISPLAY_DEVICE_MOBILE,
},
},
},
@@ -32,9 +35,10 @@ const TASK_PAGE_TABS = {
universalIdentifier: '20202020-ab05-4005-8005-ba5ca11a5502',
...TAB_PROPS.note,
widgets: {
richText: {
taskRichText: {
universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5521',
...WIDGET_PROPS.richText,
...WIDGET_PROPS.taskRichText,
conditionalDisplay: CONDITIONAL_DISPLAY_DEVICE_DESKTOP,
},
},
},
@@ -61,11 +65,10 @@ const TASK_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_TASK_PAGE_LAYOUT_CONFIG = {
layoutName: 'taskRecordPage',
name: 'Default Task Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier: STANDARD_OBJECTS.task.universalIdentifier,
universalIdentifier: '20202020-a105-4005-8005-ba5ca11a1005',
defaultTabUniversalIdentifier: TASK_PAGE_TABS.home.universalIdentifier,
defaultTabUniversalIdentifier: null,
tabs: TASK_PAGE_TABS,
} as const satisfies StandardPageLayoutConfig;
@@ -24,11 +24,10 @@ const WORKFLOW_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG = {
layoutName: 'workflowRecordPage',
name: 'Default Workflow Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier: STANDARD_OBJECTS.workflow.universalIdentifier,
universalIdentifier: '20202020-a106-4006-8006-a0bcf10aa006',
defaultTabUniversalIdentifier: WORKFLOW_PAGE_TABS.flow.universalIdentifier,
defaultTabUniversalIdentifier: null,
tabs: WORKFLOW_PAGE_TABS,
} as const satisfies StandardPageLayoutConfig;
@@ -34,7 +34,6 @@ const WORKFLOW_RUN_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG = {
layoutName: 'workflowRunRecordPage',
name: 'Default Workflow Run Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier: STANDARD_OBJECTS.workflowRun.universalIdentifier,
@@ -34,7 +34,6 @@ const WORKFLOW_VERSION_PAGE_TABS = {
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
export const STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG = {
layoutName: 'workflowVersionRecordPage',
name: 'Default Workflow Version Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier:
@@ -6,7 +6,7 @@ import {
STANDARD_PAGE_LAYOUTS,
STANDARD_RECORD_PAGE_LAYOUTS,
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
import { type StandardPageLayoutTabDefinition } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types';
import { type StandardPageLayoutTabConfig } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type';
import {
type CreateStandardPageLayoutTabArgs,
createStandardPageLayoutTabFlatMetadata,
@@ -39,7 +39,7 @@ export const buildStandardFlatPageLayoutTabMetadataMaps = ({
const layout = STANDARD_PAGE_LAYOUTS[
layoutName as keyof typeof STANDARD_PAGE_LAYOUTS
] as { tabs: Record<string, StandardPageLayoutTabDefinition> };
] as { tabs: Record<string, StandardPageLayoutTabConfig> };
for (const tabTitle of Object.keys(layout.tabs)) {
const tab = layout.tabs[tabTitle];
@@ -3,9 +3,9 @@ import { isDefined } from 'twenty-shared/utils';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
import { type StandardPageLayoutTabDefinition } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
import { type StandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util';
import { type StandardPageLayoutTabConfig } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config';
export type CreateStandardPageLayoutTabContext = {
layoutName: string;
@@ -38,7 +38,7 @@ export const createStandardPageLayoutTabFlatMetadata = ({
universalIdentifier: string;
tabs: Record<
string,
StandardPageLayoutTabDefinition & {
StandardPageLayoutTabConfig & {
widgets: Record<string, { universalIdentifier: string }>;
}
>;
@@ -5,7 +5,7 @@ import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { STANDARD_RECORD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
import { type StandardRecordPageLayoutDefinition } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types';
import { type StandardRecordPageLayoutConfig } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type';
import { computeMyFirstDashboardWidgets } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/compute-my-first-dashboard-widgets.util';
import {
type CreateStandardPageLayoutWidgetArgs,
@@ -31,6 +31,9 @@ const RECORD_PAGE_LAYOUT_WIDGET_TYPES = [
WidgetType.EMAILS,
WidgetType.CALENDAR,
WidgetType.FIELD_RICH_TEXT,
WidgetType.WORKFLOW,
WidgetType.WORKFLOW_VERSION,
WidgetType.WORKFLOW_RUN,
];
const computeRecordPageWidgets = ({
@@ -45,7 +48,7 @@ const computeRecordPageWidgets = ({
for (const layoutName of Object.keys(STANDARD_RECORD_PAGE_LAYOUTS)) {
const layout = STANDARD_RECORD_PAGE_LAYOUTS[
layoutName as keyof typeof STANDARD_RECORD_PAGE_LAYOUTS
] as StandardRecordPageLayoutDefinition;
] as StandardRecordPageLayoutConfig;
let layoutObjectMetadataId: string | null = null;
@@ -99,6 +102,7 @@ const computeRecordPageWidgets = ({
configurationType: WidgetConfigurationType.FIELDS,
},
objectMetadataId,
conditionalDisplay: widget.conditionalDisplay ?? null,
},
}),
);
@@ -220,6 +220,7 @@ const createWelcomeRichText = ({
},
},
objectMetadataId: null,
conditionalDisplay: null,
},
});
@@ -266,6 +267,7 @@ const createDealsByCompany = ({
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
},
objectMetadataId: opportunityObjectId,
conditionalDisplay: null,
},
});
};
@@ -319,6 +321,7 @@ const createPipelineValueByStage = ({
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
},
objectMetadataId: opportunityObjectId,
conditionalDisplay: null,
},
});
};
@@ -367,6 +370,7 @@ const createRevenueTimeline = ({
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
},
objectMetadataId: opportunityObjectId,
conditionalDisplay: null,
},
});
};
@@ -422,6 +426,7 @@ const createOpportunitiesByOwner = ({
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
},
objectMetadataId: opportunityObjectId,
conditionalDisplay: null,
},
});
};
@@ -453,6 +458,7 @@ const createStockMarketIframe = ({
url: 'https://www.tradingview.com/embed-widget/hotlists/?locale=en',
},
objectMetadataId: null,
conditionalDisplay: null,
},
});
@@ -504,6 +510,7 @@ const createDealsCreatedThisMonth = ({
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
},
objectMetadataId: opportunityObjectId,
conditionalDisplay: null,
},
});
};
@@ -556,6 +563,7 @@ const createDealValueCreatedThisMonth = ({
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
},
objectMetadataId: opportunityObjectId,
conditionalDisplay: null,
},
});
};
@@ -1,4 +1,7 @@
import { type PageLayoutWidgetPosition } from 'twenty-shared/types';
import {
type PageLayoutWidgetConditionalDisplay,
type PageLayoutWidgetPosition,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
@@ -6,13 +9,13 @@ import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
import {
type StandardPageLayoutTabDefinition,
type StandardPageLayoutWidgetDefinition,
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
import { type StandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util';
import { type StandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util';
import {
type StandardPageLayoutTabConfig,
type StandardPageLayoutWidgetConfig,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config';
export type CreateStandardPageLayoutWidgetContext = {
layoutName: string;
@@ -24,6 +27,7 @@ export type CreateStandardPageLayoutWidgetContext = {
position: PageLayoutWidgetPosition | null;
configuration: AllPageLayoutWidgetConfiguration;
objectMetadataId: string | null;
conditionalDisplay: PageLayoutWidgetConditionalDisplay | null;
};
export type CreateStandardPageLayoutWidgetArgs = {
@@ -46,6 +50,7 @@ export const createStandardPageLayoutWidgetFlatMetadata = ({
position,
configuration,
objectMetadataId,
conditionalDisplay,
},
workspaceId,
twentyStandardApplicationId,
@@ -61,13 +66,13 @@ export const createStandardPageLayoutWidgetFlatMetadata = ({
] as {
tabs: Record<
string,
StandardPageLayoutTabDefinition & {
StandardPageLayoutTabConfig & {
universalIdentifier: string;
}
>;
};
const tabDefinition = layout.tabs[tabTitle];
const widgetDef: StandardPageLayoutWidgetDefinition =
const widgetDef: StandardPageLayoutWidgetConfig =
tabDefinition.widgets[widgetName];
if (!isDefined(widgetDef)) {
@@ -98,6 +103,6 @@ export const createStandardPageLayoutWidgetFlatMetadata = ({
createdAt: now,
updatedAt: now,
deletedAt: null,
conditionalDisplay: null,
conditionalDisplay: conditionalDisplay ?? null,
};
};
@@ -1,45 +1,24 @@
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
import {
STANDARD_COMPANY_PAGE_LAYOUT_CONFIG,
STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG,
STANDARD_NOTE_PAGE_LAYOUT_CONFIG,
STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG,
STANDARD_PERSON_PAGE_LAYOUT_CONFIG,
STANDARD_TASK_PAGE_LAYOUT_CONFIG,
STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG,
STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG,
STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG,
type StandardPageLayoutConfig,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config';
import { type StandardPageLayoutConfig } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config';
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
import {
type CreateStandardPageLayoutArgs,
createStandardPageLayoutFlatMetadata,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/create-standard-page-layout-flat-metadata.util';
export const STANDARD_PAGE_LAYOUT_CONFIGS = [
STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG,
STANDARD_COMPANY_PAGE_LAYOUT_CONFIG,
STANDARD_PERSON_PAGE_LAYOUT_CONFIG,
STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG,
STANDARD_NOTE_PAGE_LAYOUT_CONFIG,
STANDARD_TASK_PAGE_LAYOUT_CONFIG,
STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG,
STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG,
STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG,
] as const;
type BuilderArgs = Omit<CreateStandardPageLayoutArgs, 'context'>;
type BuilderFn = (args: BuilderArgs) => FlatPageLayout;
const createBuilderFromConfig = (
layoutName: string,
config: StandardPageLayoutConfig,
): BuilderFn => {
return (args: BuilderArgs) =>
createStandardPageLayoutFlatMetadata({
...args,
context: {
layoutName: config.layoutName,
layoutName,
name: config.name,
type: config.type,
objectUniversalIdentifier: config.objectUniversalIdentifier,
@@ -50,9 +29,9 @@ const createBuilderFromConfig = (
const createBuilders = (): Record<string, BuilderFn> => {
return Object.fromEntries(
STANDARD_PAGE_LAYOUT_CONFIGS.map((config) => [
config.layoutName,
createBuilderFromConfig(config),
Object.entries(STANDARD_PAGE_LAYOUTS).map(([layoutName, config]) => [
layoutName,
createBuilderFromConfig(layoutName, config),
]),
);
};