From 5f0d24798a2d4147e29f1aa76c12b5edcacb0b85 Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Fri, 31 Oct 2025 18:45:58 +0100 Subject: [PATCH] Support workflows in record page layouts (#15471) https://github.com/user-attachments/assets/275a02a3-7054-45d1-9423-75bb5223a8ba --- .../src/generated-metadata/graphql.ts | 5 +- .../twenty-front/src/generated/graphql.ts | 5 +- .../components/PageLayoutDispatcher.tsx | 10 +- .../components/PageLayoutCanvasViewer.tsx | 33 ++++++ .../components/PageLayoutContent.tsx | 7 ++ .../components/PageLayoutGridLayout.tsx | 112 ++++++++---------- .../PageLayoutInitializationQueryEffect.tsx | 17 ++- .../PageLayoutVerticalListEditor.tsx | 5 +- .../PageLayoutVerticalListViewer.tsx | 8 +- .../constants/DefaultWorkflowPageLayout.ts | 53 +++++++++ .../constants/DefaultWorkflowPageLayoutId.ts | 1 + .../constants/DefaultWorkflowRunPageLayout.ts | 86 ++++++++++++++ .../DefaultWorkflowRunPageLayoutId.ts | 2 + .../DefaultWorkflowVersionPageLayout.ts | 86 ++++++++++++++ .../DefaultWorkflowVersionPageLayoutId.ts | 2 + .../hooks/useRecordPageLayoutId.ts | 7 ++ .../page-layout/types/PageLayoutTab.ts | 3 +- .../types/PageLayoutTabLayoutMode.ts | 1 + .../__tests__/getTabsByDisplayMode.test.ts | 39 ++++-- .../page-layout/utils/getTabsByDisplayMode.ts | 7 ++ .../components/WidgetContentRenderer.tsx | 12 ++ .../widgets/components/WidgetPlaceholder.tsx | 4 +- .../widgets/components/WidgetRenderer.tsx | 49 ++++---- .../__stories__/WidgetRenderer.stories.tsx | 49 ++++++-- .../widget-card/components/WidgetCard.tsx | 35 ++++-- .../components/WidgetCardContent.tsx | 25 ++-- .../__stories__/WidgetCard.stories.tsx | 33 ++++-- .../widget-card/types/WidgetCardContext.ts | 1 - .../workflow/components/WorkflowRunWidget.tsx | 5 + .../components/WorkflowVersionWidget.tsx | 5 + .../workflow/components/WorkflowWidget.tsx | 5 + .../components/WorkflowRunVisualizer.tsx | 11 +- .../1761574442000-addWorkflowWidgetTypes.ts | 45 +++++++ .../page-layout/enums/widget-type.enum.ts | 3 + 34 files changed, 607 insertions(+), 164 deletions(-) create mode 100644 packages/twenty-front/src/modules/page-layout/components/PageLayoutCanvasViewer.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowPageLayout.ts create mode 100644 packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowPageLayoutId.ts create mode 100644 packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowRunPageLayout.ts create mode 100644 packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowRunPageLayoutId.ts create mode 100644 packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowVersionPageLayout.ts create mode 100644 packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowVersionPageLayoutId.ts create mode 100644 packages/twenty-front/src/modules/page-layout/types/PageLayoutTabLayoutMode.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/widget-card/types/WidgetCardContext.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowRunWidget.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowVersionWidget.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowWidget.tsx create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1761574442000-addWorkflowWidgetTypes.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f353d5e006..20ffe6d004 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4591,7 +4591,10 @@ export enum WidgetType { RICH_TEXT = 'RICH_TEXT', TASKS = 'TASKS', TIMELINE = 'TIMELINE', - VIEW = 'VIEW' + VIEW = 'VIEW', + WORKFLOW = 'WORKFLOW', + WORKFLOW_RUN = 'WORKFLOW_RUN', + WORKFLOW_VERSION = 'WORKFLOW_VERSION' } export type WorkerQueueMetrics = { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 707357bea3..e61bfe1c0e 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -4419,7 +4419,10 @@ export enum WidgetType { RICH_TEXT = 'RICH_TEXT', TASKS = 'TASKS', TIMELINE = 'TIMELINE', - VIEW = 'VIEW' + VIEW = 'VIEW', + WORKFLOW = 'WORKFLOW', + WORKFLOW_RUN = 'WORKFLOW_RUN', + WORKFLOW_VERSION = 'WORKFLOW_VERSION' } export type WorkerQueueMetrics = { diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutDispatcher.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutDispatcher.tsx index 69d6899e76..bc477a1397 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutDispatcher.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutDispatcher.tsx @@ -18,18 +18,10 @@ export const PageLayoutDispatcher = ({ FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_ENABLED, ); - const isStandardObjectUnsupportingRecordPageLayouts = - targetRecordIdentifier.targetObjectNameSingular === - CoreObjectNameSingular.Workflow || - targetRecordIdentifier.targetObjectNameSingular === - CoreObjectNameSingular.WorkflowRun || - targetRecordIdentifier.targetObjectNameSingular === - CoreObjectNameSingular.WorkflowVersion; - if ( targetRecordIdentifier.targetObjectNameSingular === CoreObjectNameSingular.Dashboard || - (isRecordPageEnabled && !isStandardObjectUnsupportingRecordPageLayouts) + isRecordPageEnabled ) { return ( { + const widget = widgets.at(0); + + if (!isDefined(widget)) { + throw new Error('No widget found in canvas layout'); + } + + return ( + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx index ca40888029..49ecde4f6e 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx @@ -1,3 +1,4 @@ +import { PageLayoutCanvasViewer } from '@/page-layout/components/PageLayoutCanvasViewer'; import { PageLayoutGridLayout } from '@/page-layout/components/PageLayoutGridLayout'; import { PageLayoutVerticalListEditor } from '@/page-layout/components/PageLayoutVerticalListEditor'; import { PageLayoutVerticalListViewer } from '@/page-layout/components/PageLayoutVerticalListViewer'; @@ -42,9 +43,15 @@ export const PageLayoutContent = ({ tabId }: PageLayoutContentProps) => { return null; } + const isCanvasLayout = + isRecordPageEnabled && activeTab.layoutMode === 'canvas'; const isVerticalList = isRecordPageEnabled && activeTab.layoutMode === 'vertical-list'; + if (isCanvasLayout) { + return ; + } + if (isVerticalList) { return ( diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx index 4c1355e519..213de6689d 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx @@ -16,7 +16,6 @@ import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceh import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; import { useRef } from 'react'; import { @@ -25,7 +24,6 @@ import { type ResponsiveProps, } from 'react-grid-layout'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from '~/generated/graphql'; const StyledGridContainer = styled.div` background: ${({ theme }) => theme.background.primary}; @@ -61,21 +59,11 @@ const ResponsiveGridLayout = WidthProvider( Responsive, ) as React.ComponentType; -const StyledVerticalListContainer = styled.div` - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.spacing(2)}; -`; - type PageLayoutGridLayoutProps = { tabId: string; }; export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => { - const isRecordPageEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_ENABLED, - ); - const setPageLayoutCurrentBreakpoint = useSetRecoilComponentState( pageLayoutCurrentBreakpointComponentState, ); @@ -113,18 +101,6 @@ export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => { ? EMPTY_LAYOUT : (pageLayoutCurrentLayouts[tabId] ?? EMPTY_LAYOUT); - const Widgets = isLayoutEmpty ? ( -
- -
- ) : ( - activeTabWidgets?.map((widget) => ( -
- -
- )) - ); - return ( <> @@ -137,47 +113,53 @@ export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => { )} - {isRecordPageEnabled && - !isPageLayoutInEditMode && - activeTab.layoutMode === 'vertical-list' ? ( - {Widgets} - ) : ( - - ) : undefined - } - resizeHandles={['se']} - onDragStart={(_layout, _oldItem, newItem) => { - setDraggingWidgetId(newItem.i); - }} - onDragStop={() => { - setDraggingWidgetId(null); - }} - onLayoutChange={handleLayoutChange} - onBreakpointChange={(newBreakpoint) => - setPageLayoutCurrentBreakpoint( - newBreakpoint as PageLayoutBreakpoint, - ) - } - > - {Widgets} - - )} + : undefined + } + resizeHandles={['se']} + onDragStart={(_layout, _oldItem, newItem) => { + setDraggingWidgetId(newItem.i); + }} + onDragStop={() => { + setDraggingWidgetId(null); + }} + onLayoutChange={handleLayoutChange} + onBreakpointChange={(newBreakpoint) => + setPageLayoutCurrentBreakpoint( + newBreakpoint as PageLayoutBreakpoint, + ) + } + > + {isLayoutEmpty ? ( +
+ +
+ ) : ( + activeTabWidgets?.map((widget) => ( +
+ +
+ )) + )} +
); diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx index e590e78eb7..d9bf2738e3 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx @@ -11,6 +11,12 @@ import { DEFAULT_RECORD_PAGE_LAYOUT } from '@/page-layout/constants/DefaultRecor 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 { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState'; @@ -37,6 +43,12 @@ const getDefaultLayoutById = (layoutId: string): PageLayout => { 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; @@ -60,7 +72,10 @@ export const PageLayoutInitializationQueryEffect = ({ 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_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: { diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListEditor.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListEditor.tsx index 4ddd8e55c7..88f72d52ff 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListEditor.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListEditor.tsx @@ -9,7 +9,7 @@ import { type DropResult, } from '@hello-pangea/dnd'; import { useId } from 'react'; -import { type PageLayoutWidget } from '~/generated/graphql'; +import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql'; const StyledVerticalListContainer = styled.div` display: flex; @@ -69,7 +69,8 @@ export const PageLayoutVerticalListEditor = ({
diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListViewer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListViewer.tsx index 2b2a4d5a93..f3da18ae92 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListViewer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListViewer.tsx @@ -1,6 +1,6 @@ import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; import styled from '@emotion/styled'; -import { type PageLayoutWidget } from '~/generated/graphql'; +import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql'; const StyledVerticalListContainer = styled.div` display: flex; @@ -19,7 +19,11 @@ export const PageLayoutVerticalListViewer = ({ {widgets.map((widget) => (
- +
))}
diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowPageLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowPageLayout.ts new file mode 100644 index 0000000000..27983d6a8f --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowPageLayout.ts @@ -0,0 +1,53 @@ +import { DEFAULT_WORKFLOW_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowPageLayoutId'; +import { type PageLayout } from '@/page-layout/types/PageLayout'; +import { PageLayoutType, WidgetType } from '~/generated/graphql'; + +/** + * Default Workflow PageLayout. + * Specialized layout for workflow visualization with a single Flow tab. + */ +export const DEFAULT_WORKFLOW_PAGE_LAYOUT: PageLayout = { + __typename: 'PageLayout', + id: DEFAULT_WORKFLOW_PAGE_LAYOUT_ID, + name: 'Default Workflow Layout', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + tabs: [ + // Flow tab (position 100) + { + __typename: 'PageLayoutTab', + id: 'workflow-tab-flow', + title: 'Flow', + position: 100, + layoutMode: 'canvas', + pageLayoutId: DEFAULT_WORKFLOW_PAGE_LAYOUT_ID, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + widgets: [ + { + __typename: 'PageLayoutWidget', + id: 'workflow-widget-flow', + pageLayoutTabId: 'workflow-tab-flow', + title: 'Flow', + type: WidgetType.WORKFLOW, + objectMetadataId: null, + gridPosition: { + __typename: 'GridPosition', + row: 0, + column: 0, + rowSpan: 12, + columnSpan: 12, + }, + configuration: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + }, + ], + }, + ], +}; diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowPageLayoutId.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowPageLayoutId.ts new file mode 100644 index 0000000000..8d3144bf97 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowPageLayoutId.ts @@ -0,0 +1 @@ +export const DEFAULT_WORKFLOW_PAGE_LAYOUT_ID = 'default-workflow-page-layout'; diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowRunPageLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowRunPageLayout.ts new file mode 100644 index 0000000000..13a9a82ff4 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowRunPageLayout.ts @@ -0,0 +1,86 @@ +import { DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowRunPageLayoutId'; +import { type PageLayout } from '@/page-layout/types/PageLayout'; +import { PageLayoutType, WidgetType } from '~/generated/graphql'; + +/** + * Default WorkflowRun PageLayout. + * Specialized layout for workflow run visualization with Fields and Flow tabs. + */ +export const DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT: PageLayout = { + __typename: 'PageLayout', + id: DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID, + name: 'Default Workflow Run Layout', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + tabs: [ + // Fields tab (position 100) + { + __typename: 'PageLayoutTab', + id: 'workflow-run-tab-fields', + title: 'Fields', + position: 100, + layoutMode: 'vertical-list', + pageLayoutId: DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + widgets: [ + { + __typename: 'PageLayoutWidget', + id: 'workflow-run-widget-fields', + pageLayoutTabId: 'workflow-run-tab-fields', + title: 'Fields', + type: WidgetType.FIELDS, + objectMetadataId: null, + gridPosition: { + __typename: 'GridPosition', + row: 0, + column: 0, + rowSpan: 12, + columnSpan: 12, + }, + configuration: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + }, + ], + }, + // Flow tab (position 200) + { + __typename: 'PageLayoutTab', + id: 'workflow-run-tab-flow', + title: 'Flow', + position: 200, + layoutMode: 'canvas', + pageLayoutId: DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + widgets: [ + { + __typename: 'PageLayoutWidget', + id: 'workflow-run-widget-flow', + pageLayoutTabId: 'workflow-run-tab-flow', + title: 'Flow', + type: WidgetType.WORKFLOW_RUN, + objectMetadataId: null, + gridPosition: { + __typename: 'GridPosition', + row: 0, + column: 0, + rowSpan: 12, + columnSpan: 12, + }, + configuration: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + }, + ], + }, + ], +}; diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowRunPageLayoutId.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowRunPageLayoutId.ts new file mode 100644 index 0000000000..0687022672 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowRunPageLayoutId.ts @@ -0,0 +1,2 @@ +export const DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID = + 'default-workflow-run-page-layout'; diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowVersionPageLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowVersionPageLayout.ts new file mode 100644 index 0000000000..b64388f4e8 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowVersionPageLayout.ts @@ -0,0 +1,86 @@ +import { DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowVersionPageLayoutId'; +import { type PageLayout } from '@/page-layout/types/PageLayout'; +import { PageLayoutType, WidgetType } from '~/generated/graphql'; + +/** + * Default WorkflowVersion PageLayout. + * Specialized layout for workflow version visualization with Fields and Flow tabs. + */ +export const DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT: PageLayout = { + __typename: 'PageLayout', + id: DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID, + name: 'Default Workflow Version Layout', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + tabs: [ + // Fields tab (position 100) + { + __typename: 'PageLayoutTab', + id: 'workflow-version-tab-fields', + title: 'Fields', + position: 100, + layoutMode: 'vertical-list', + pageLayoutId: DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + widgets: [ + { + __typename: 'PageLayoutWidget', + id: 'workflow-version-widget-fields', + pageLayoutTabId: 'workflow-version-tab-fields', + title: 'Fields', + type: WidgetType.FIELDS, + objectMetadataId: null, + gridPosition: { + __typename: 'GridPosition', + row: 0, + column: 0, + rowSpan: 12, + columnSpan: 12, + }, + configuration: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + }, + ], + }, + // Flow tab (position 200) + { + __typename: 'PageLayoutTab', + id: 'workflow-version-tab-flow', + title: 'Flow', + position: 200, + layoutMode: 'canvas', + pageLayoutId: DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + widgets: [ + { + __typename: 'PageLayoutWidget', + id: 'workflow-version-widget-flow', + pageLayoutTabId: 'workflow-version-tab-flow', + title: 'Flow', + type: WidgetType.WORKFLOW_VERSION, + objectMetadataId: null, + gridPosition: { + __typename: 'GridPosition', + row: 0, + column: 0, + rowSpan: 12, + columnSpan: 12, + }, + configuration: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + }, + ], + }, + ], +}; diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowVersionPageLayoutId.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowVersionPageLayoutId.ts new file mode 100644 index 0000000000..8dd0b6b070 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultWorkflowVersionPageLayoutId.ts @@ -0,0 +1,2 @@ +export const DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID = + 'default-workflow-version-page-layout'; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutId.ts b/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutId.ts index 83dce97e73..3f4891affa 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutId.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutId.ts @@ -7,6 +7,9 @@ import { DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constan import { DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultPersonRecordPageLayoutId'; import { DEFAULT_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultRecordPageLayoutId'; import { DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultTaskRecordPageLayoutId'; +import { DEFAULT_WORKFLOW_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowPageLayoutId'; +import { DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowRunPageLayoutId'; +import { DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowVersionPageLayoutId'; import { type TargetRecordIdentifier } from '@/ui/layout/contexts/TargetRecordIdentifier'; import { isDefined } from 'twenty-shared/utils'; @@ -17,6 +20,10 @@ const OBJECT_NAME_TO_DEFAULT_LAYOUT_ID: Record = { DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID, [CoreObjectNameSingular.Note]: DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID, [CoreObjectNameSingular.Task]: DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID, + [CoreObjectNameSingular.Workflow]: DEFAULT_WORKFLOW_PAGE_LAYOUT_ID, + [CoreObjectNameSingular.WorkflowVersion]: + DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID, + [CoreObjectNameSingular.WorkflowRun]: DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID, }; export const useRecordPageLayoutId = ({ diff --git a/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts index 31cb1d4ab5..04d38b601f 100644 --- a/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts +++ b/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts @@ -1,3 +1,4 @@ +import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode'; import { type ModifiedProperties, type Nullable } from 'twenty-shared/types'; import { type PageLayoutTab as PageLayoutTabGenerated, @@ -12,5 +13,5 @@ export type PageLayoutTab = Omit & { /** * Only available behind IS_RECORD_PAGE_LAYOUT_ENABLED for now. */ - layoutMode?: 'grid' | 'vertical-list'; + layoutMode?: PageLayoutTabLayoutMode; }; diff --git a/packages/twenty-front/src/modules/page-layout/types/PageLayoutTabLayoutMode.ts b/packages/twenty-front/src/modules/page-layout/types/PageLayoutTabLayoutMode.ts new file mode 100644 index 0000000000..7aac8d6b6a --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/types/PageLayoutTabLayoutMode.ts @@ -0,0 +1 @@ +export type PageLayoutTabLayoutMode = 'grid' | 'vertical-list' | 'canvas'; diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/getTabsByDisplayMode.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/getTabsByDisplayMode.test.ts index ff7a2de432..e5786f98e4 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/getTabsByDisplayMode.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/getTabsByDisplayMode.test.ts @@ -204,9 +204,9 @@ describe('getTabsByDisplayMode', () => { isInRightDrawer: false, }); - expect(result.pinnedLeftTab).toBeDefined(); - expect(result.pinnedLeftTab?.id).toBe('tab-1'); - expect(result.tabsToRenderInTabList).toEqual([]); + expect(result.pinnedLeftTab).toBeUndefined(); + expect(result.tabsToRenderInTabList).toEqual(tabs); + expect(result.tabsToRenderInTabList).toHaveLength(1); }); it('should handle empty tabs array', () => { @@ -257,8 +257,8 @@ describe('getTabsByDisplayMode', () => { expect(resultMobile.tabsToRenderInTabList).toEqual(tabs); expect(resultMobile.pinnedLeftTab).toBeUndefined(); - expect(resultDesktop.tabsToRenderInTabList).toEqual([]); - expect(resultDesktop.pinnedLeftTab).toEqual(tabs[0]); + expect(resultDesktop.tabsToRenderInTabList).toEqual(tabs); + expect(resultDesktop.pinnedLeftTab).toBeUndefined(); }); it('should handle single tab with pinned-left display mode', () => { @@ -279,8 +279,8 @@ describe('getTabsByDisplayMode', () => { expect(resultMobile.tabsToRenderInTabList).toEqual(tabs); expect(resultMobile.pinnedLeftTab).toBeUndefined(); - expect(resultDesktop.tabsToRenderInTabList).toEqual([]); - expect(resultDesktop.pinnedLeftTab).toEqual(tabs[0]); + expect(resultDesktop.tabsToRenderInTabList).toEqual(tabs); + expect(resultDesktop.pinnedLeftTab).toBeUndefined(); }); it('should not mutate the original page layout', () => { @@ -311,7 +311,8 @@ describe('getTabsByDisplayMode', () => { isInRightDrawer: false, }); - expect(result.pinnedLeftTab?.layoutMode).toBe('grid'); + expect(result.pinnedLeftTab).toBeUndefined(); + expect(result.tabsToRenderInTabList[0]?.layoutMode).toBe('grid'); }); }); @@ -355,6 +356,28 @@ describe('getTabsByDisplayMode', () => { expect(mobileResult.pinnedLeftTab).toBeUndefined(); expect(desktopResult.pinnedLeftTab).toBeDefined(); }); + + it('should show same results for mobile vs desktop when single tab', () => { + const tabs = [createMockTab('tab-1')]; + const pageLayout = createMockPageLayout(tabs); + + const mobileResult = getTabsByDisplayMode({ + pageLayout, + isMobile: true, + isInRightDrawer: false, + }); + const desktopResult = getTabsByDisplayMode({ + pageLayout, + isMobile: false, + isInRightDrawer: false, + }); + + expect(mobileResult.tabsToRenderInTabList).toEqual(tabs); + expect(desktopResult.tabsToRenderInTabList).toEqual(tabs); + + expect(mobileResult.pinnedLeftTab).toBeUndefined(); + expect(desktopResult.pinnedLeftTab).toBeUndefined(); + }); }); describe('when both isMobile and isInRightDrawer are true', () => { diff --git a/packages/twenty-front/src/modules/page-layout/utils/getTabsByDisplayMode.ts b/packages/twenty-front/src/modules/page-layout/utils/getTabsByDisplayMode.ts index 4587365a4a..123ed6cf35 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/getTabsByDisplayMode.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/getTabsByDisplayMode.ts @@ -24,6 +24,13 @@ export const getTabsByDisplayMode = ({ }; } + if (pageLayout.tabs.length === 1) { + return { + tabsToRenderInTabList: pageLayout.tabs, + pinnedLeftTab: undefined, + }; + } + const tabsToRenderInTabList = pageLayout.tabs.slice(1); const pinnedLeftTab = pageLayout.tabs[0]; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx index 33e39902d0..49be52a072 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx @@ -8,6 +8,9 @@ import { NoteWidget } from '@/page-layout/widgets/notes/components/NoteWidget'; import { RichTextWidget } from '@/page-layout/widgets/rich-text/components/RichTextWidget'; import { TaskWidget } from '@/page-layout/widgets/tasks/components/TaskWidget'; import { TimelineWidget } from '@/page-layout/widgets/timeline/components/TimelineWidget'; +import { WorkflowRunWidget } from '@/page-layout/widgets/workflow/components/WorkflowRunWidget'; +import { WorkflowVersionWidget } from '@/page-layout/widgets/workflow/components/WorkflowVersionWidget'; +import { WorkflowWidget } from '@/page-layout/widgets/workflow/components/WorkflowWidget'; import { type PageLayoutWidget, WidgetType, @@ -51,6 +54,15 @@ export const WidgetContentRenderer = ({ case WidgetType.CALENDAR: return ; + case WidgetType.WORKFLOW: + return ; + + case WidgetType.WORKFLOW_VERSION: + return ; + + case WidgetType.WORKFLOW_RUN: + return ; + default: return null; } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx index 32d1777a75..1698d9f253 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx @@ -17,6 +17,7 @@ import { AnimatedPlaceholderEmptyTitle, EMPTY_PLACEHOLDER_TRANSITION_PROPS, } from 'twenty-ui/layout'; +import { PageLayoutType } from '~/generated/graphql'; export const WidgetPlaceholder = () => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( @@ -44,7 +45,8 @@ export const WidgetPlaceholder = () => { return ( diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx index 64423a1d2a..711ba289c5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx @@ -3,27 +3,29 @@ import { useEditPageLayoutWidget } from '@/page-layout/hooks/useEditPageLayoutWi import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState'; import { pageLayoutDraggingWidgetIdComponentState } from '@/page-layout/states/pageLayoutDraggingWidgetIdComponentState'; import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; +import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode'; import { PageLayoutWidgetForbiddenDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetForbiddenDisplay'; import { WidgetContentRenderer } from '@/page-layout/widgets/components/WidgetContentRenderer'; import { useWidgetPermissions } from '@/page-layout/widgets/hooks/useWidgetPermissions'; import { WidgetCard } from '@/page-layout/widgets/widget-card/components/WidgetCard'; import { WidgetCardContent } from '@/page-layout/widgets/widget-card/components/WidgetCardContent'; import { WidgetCardHeader } from '@/page-layout/widgets/widget-card/components/WidgetCardHeader'; -import { type WidgetCardContext } from '@/page-layout/widgets/widget-card/types/WidgetCardContext'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useTheme } from '@emotion/react'; import { type MouseEvent } from 'react'; import { IconLock } from 'twenty-ui/display'; -import { type PageLayoutWidget } from '~/generated/graphql'; +import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql'; type WidgetRendererProps = { widget: PageLayoutWidget; - widgetCardContext?: WidgetCardContext; + pageLayoutType: PageLayoutType; + layoutMode: PageLayoutTabLayoutMode; }; export const WidgetRenderer = ({ widget, - widgetCardContext = 'dashboard', + pageLayoutType, + layoutMode, }: WidgetRendererProps) => { const theme = useTheme(); const { deletePageLayoutWidget } = useDeletePageLayoutWidget(); @@ -63,25 +65,32 @@ export const WidgetRenderer = ({ - - ) - } - /> - + {layoutMode !== 'canvas' && ( + + ) + } + /> + )} + + {hasAccess && } - {!hasAccess && widgetCardContext === 'dashboard' && ( + {!hasAccess && pageLayoutType === PageLayoutType.DASHBOARD && ( (
- +
), }; @@ -217,7 +222,11 @@ export const WithGaugeChart: Story = { }, render: (args) => (
- +
), }; @@ -255,7 +264,11 @@ export const WithBarChart: Story = { }, render: (args) => (
- +
), }; @@ -297,7 +310,11 @@ export const SmallWidget: Story = { }, render: (args) => (
- +
), }; @@ -342,7 +359,11 @@ export const MediumWidget: Story = { }, render: (args) => (
- +
), }; @@ -387,7 +408,11 @@ export const LargeWidget: Story = { }, render: (args) => (
- +
), }; @@ -429,7 +454,11 @@ export const WideWidget: Story = { }, render: (args) => (
- +
), }; @@ -474,7 +503,11 @@ export const TallWidget: Story = { }, render: (args) => (
- +
), }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx index 3684fb467b..5efd670583 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx @@ -1,15 +1,16 @@ import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState'; +import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { type ReactNode } from 'react'; -import { assertUnreachable, isDefined } from 'twenty-shared/utils'; - -import { type WidgetCardContext } from '../types/WidgetCardContext'; +import { isDefined } from 'twenty-shared/utils'; +import { PageLayoutType } from '~/generated/graphql'; export type WidgetCardProps = { children?: ReactNode; - widgetCardContext: WidgetCardContext; + pageLayoutType: PageLayoutType; + layoutMode: PageLayoutTabLayoutMode; onClick?: () => void; isEditing: boolean; isDragging: boolean; @@ -18,7 +19,8 @@ export type WidgetCardProps = { const StyledWidgetCard = styled.div<{ onClick?: () => void; - widgetCardContext: WidgetCardContext; + pageLayoutType: PageLayoutType; + layoutMode: PageLayoutTabLayoutMode; isPageLayoutInEditMode: boolean; isEditing: boolean; isDragging: boolean; @@ -32,14 +34,21 @@ const StyledWidgetCard = styled.div<{ ${({ theme, - widgetCardContext, + pageLayoutType, + layoutMode, isPageLayoutInEditMode, isEditing, isDragging, onClick, }) => { - switch (widgetCardContext) { - case 'dashboard': + if (layoutMode === 'canvas') { + return css` + height: 100%; + `; + } + + switch (pageLayoutType) { + case PageLayoutType.DASHBOARD: return css` background: ${theme.background.secondary}; border: 1px solid ${theme.border.color.light}; @@ -79,7 +88,7 @@ const StyledWidgetCard = styled.div<{ `} `; - case 'recordPage': + case PageLayoutType.RECORD_PAGE: return css` background: ${theme.background.primary}; border: 1px solid transparent; @@ -120,14 +129,15 @@ const StyledWidgetCard = styled.div<{ `; default: - return assertUnreachable(widgetCardContext); + return ''; } }} `; export const WidgetCard = ({ children, - widgetCardContext, + pageLayoutType, + layoutMode, onClick, isEditing, isDragging, @@ -140,7 +150,8 @@ export const WidgetCard = ({ return ( ` box-sizing: border-box; padding: ${({ theme }) => theme.spacing(2)}; - ${({ theme, widgetCardContext }) => { - switch (widgetCardContext) { - case 'recordPage': + ${({ theme, pageLayoutType, layoutMode }) => { + if (layoutMode === 'canvas') { + return css` + padding: 0; + `; + } + + switch (pageLayoutType) { + case 'RECORD_PAGE': return css` border: 1px solid ${theme.border.color.medium}; border-radius: ${theme.border.radius.md}; `; - case 'dashboard': - return ''; - default: - return assertUnreachable(widgetCardContext); + return ''; } }} `; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/__stories__/WidgetCard.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/__stories__/WidgetCard.stories.tsx index 565f6f0895..579c735a23 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/__stories__/WidgetCard.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/__stories__/WidgetCard.stories.tsx @@ -9,8 +9,9 @@ import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPag import { WidgetCard } from '@/page-layout/widgets/widget-card/components/WidgetCard'; import { WidgetCardContent } from '@/page-layout/widgets/widget-card/components/WidgetCardContent'; import { WidgetCardHeader } from '@/page-layout/widgets/widget-card/components/WidgetCardHeader'; -import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { PageLayoutType } from '~/generated/graphql'; +import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; const StyledContainer = styled.div` height: 200px; @@ -70,13 +71,15 @@ export const Default: Story = { pseudo: { hover: false }, }, args: { - widgetCardContext: 'dashboard', + pageLayoutType: PageLayoutType.DASHBOARD, + layoutMode: 'grid', isEditing: false, isDragging: false, }, render: (args) => ( @@ -85,7 +88,10 @@ export const Default: Story = { onRemove={() => {}} title="Widget name" /> - + Widget @@ -117,13 +123,13 @@ export const Catalog: CatalogStory = { 'Dashboard - Restriction', ], props: (contextName: string) => { - const widgetCardContext = contextName.includes('Record') - ? 'recordPage' - : 'dashboard'; + const pageLayoutType = contextName.includes('Record') + ? PageLayoutType.RECORD_PAGE + : PageLayoutType.DASHBOARD; const hasRestriction = contextName.includes('Restriction'); return { - widgetCardContext, + pageLayoutType, contextVariant: contextName, hasRestriction, }; @@ -162,7 +168,8 @@ export const Catalog: CatalogStory = { }, render: (args: any) => { const isReadMode = args.state === 'Read Mode'; - const widgetCardContext = args.widgetCardContext || 'dashboard'; + const pageLayoutType = args.pageLayoutType || PageLayoutType.DASHBOARD; + const layoutMode = args.layoutMode || 'grid'; const hasRestriction = args.hasRestriction || false; return ( @@ -172,7 +179,8 @@ export const Catalog: CatalogStory = { className={args.className} isDragging={args.isDragging ?? false} isEditing={args.isEditing ?? false} - widgetCardContext={widgetCardContext} + pageLayoutType={pageLayoutType} + layoutMode={layoutMode} > = { onRemove={!isReadMode ? () => {} : undefined} title="Widget name" /> - + Widget
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/types/WidgetCardContext.ts b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/types/WidgetCardContext.ts deleted file mode 100644 index a5d206d983..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/types/WidgetCardContext.ts +++ /dev/null @@ -1 +0,0 @@ -export type WidgetCardContext = 'dashboard' | 'recordPage'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowRunWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowRunWidget.tsx new file mode 100644 index 0000000000..4ffcc9ff93 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowRunWidget.tsx @@ -0,0 +1,5 @@ +import { WorkflowRunCard } from '@/workflow/workflow-diagram/components/WorkflowRunCard'; + +export const WorkflowRunWidget = () => { + return ; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowVersionWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowVersionWidget.tsx new file mode 100644 index 0000000000..9c8d2b6d07 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowVersionWidget.tsx @@ -0,0 +1,5 @@ +import { WorkflowVersionCard } from '@/workflow/workflow-diagram/components/WorkflowVersionCard'; + +export const WorkflowVersionWidget = () => { + return ; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowWidget.tsx new file mode 100644 index 0000000000..ecba8f0315 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/workflow/components/WorkflowWidget.tsx @@ -0,0 +1,5 @@ +import { WorkflowCard } from '@/workflow/workflow-diagram/components/WorkflowCard'; + +export const WorkflowWidget = () => { + return ; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizer.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizer.tsx index f6f5f6aacf..a670d84079 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizer.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizer.tsx @@ -2,13 +2,8 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas'; import { workflowDiagramStatusComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramStatusComponentState'; -import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; -const StyledContainer = styled.div` - height: 100%; -`; - export const WorkflowRunVisualizer = ({ workflowRunId, }: { @@ -26,9 +21,5 @@ export const WorkflowRunVisualizer = ({ return null; } - return ( - - - - ); + return ; }; diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1761574442000-addWorkflowWidgetTypes.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1761574442000-addWorkflowWidgetTypes.ts new file mode 100644 index 0000000000..7a5c1c2691 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1761574442000-addWorkflowWidgetTypes.ts @@ -0,0 +1,45 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class AddWorkflowWidgetTypes1761574442000 implements MigrationInterface { + name = 'AddWorkflowWidgetTypes1761574442000'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TYPE "core"."pageLayoutWidget_type_enum" RENAME TO "pageLayoutWidget_type_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "core"."pageLayoutWidget_type_enum" AS ENUM('VIEW', 'IFRAME', 'FIELDS', 'GRAPH', 'TIMELINE', 'TASKS', 'NOTES', 'FILES', 'EMAILS', 'CALENDAR', 'RICH_TEXT', 'WORKFLOW', 'WORKFLOW_VERSION', 'WORKFLOW_RUN')`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" TYPE "core"."pageLayoutWidget_type_enum" USING "type"::"text"::"core"."pageLayoutWidget_type_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" SET DEFAULT 'VIEW'`, + ); + await queryRunner.query( + `DROP TYPE "core"."pageLayoutWidget_type_enum_old"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "core"."pageLayoutWidget_type_enum_old" AS ENUM('VIEW', 'IFRAME', 'FIELDS', 'GRAPH', 'TIMELINE', 'TASKS', 'NOTES', 'FILES', 'EMAILS', 'CALENDAR', 'RICH_TEXT')`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" TYPE "core"."pageLayoutWidget_type_enum_old" USING "type"::"text"::"core"."pageLayoutWidget_type_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" SET DEFAULT 'VIEW'`, + ); + await queryRunner.query(`DROP TYPE "core"."pageLayoutWidget_type_enum"`); + await queryRunner.query( + `ALTER TYPE "core"."pageLayoutWidget_type_enum_old" RENAME TO "pageLayoutWidget_type_enum"`, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts b/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts index 3b801571a9..bbc50b566c 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts @@ -10,4 +10,7 @@ export enum WidgetType { EMAILS = 'EMAILS', CALENDAR = 'CALENDAR', RICH_TEXT = 'RICH_TEXT', + WORKFLOW = 'WORKFLOW', + WORKFLOW_VERSION = 'WORKFLOW_VERSION', + WORKFLOW_RUN = 'WORKFLOW_RUN', }