Support workflows in record page layouts (#15471)

https://github.com/user-attachments/assets/275a02a3-7054-45d1-9423-75bb5223a8ba
This commit is contained in:
Baptiste Devessier
2025-10-31 18:45:58 +01:00
committed by GitHub
parent 09c704c8bd
commit 5f0d24798a
34 changed files with 607 additions and 164 deletions
@@ -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 <CalendarWidget widget={widget} />;
case WidgetType.WORKFLOW:
return <WorkflowWidget />;
case WidgetType.WORKFLOW_VERSION:
return <WorkflowVersionWidget />;
case WidgetType.WORKFLOW_RUN:
return <WorkflowRunWidget />;
default:
return null;
}
@@ -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 (
<WidgetCard
onClick={handleClick}
widgetCardContext="dashboard"
pageLayoutType={PageLayoutType.DASHBOARD}
layoutMode="grid"
isEditing={false}
isDragging={false}
>
@@ -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 = ({
<WidgetCard
onClick={isPageLayoutInEditMode ? handleClick : undefined}
isDragging={isDragging}
widgetCardContext={widgetCardContext}
pageLayoutType={pageLayoutType}
layoutMode={layoutMode}
isEditing={isEditing}
>
<WidgetCardHeader
isInEditMode={isPageLayoutInEditMode}
title={widget.title}
onRemove={handleRemove}
forbiddenDisplay={
!hasAccess && (
<PageLayoutWidgetForbiddenDisplay
widgetId={widget.id}
restriction={restriction}
/>
)
}
/>
<WidgetCardContent widgetCardContext={widgetCardContext}>
{layoutMode !== 'canvas' && (
<WidgetCardHeader
isInEditMode={isPageLayoutInEditMode}
title={widget.title}
onRemove={handleRemove}
forbiddenDisplay={
!hasAccess && (
<PageLayoutWidgetForbiddenDisplay
widgetId={widget.id}
restriction={restriction}
/>
)
}
/>
)}
<WidgetCardContent
pageLayoutType={pageLayoutType}
layoutMode={layoutMode}
>
{hasAccess && <WidgetContentRenderer widget={widget} />}
{!hasAccess && widgetCardContext === 'dashboard' && (
{!hasAccess && pageLayoutType === PageLayoutType.DASHBOARD && (
<IconLock
color={theme.font.color.tertiary}
stroke={theme.icon.stroke.sm}
@@ -23,6 +23,7 @@ import {
import {
AggregateOperations,
AxisNameDisplay,
PageLayoutType,
type PageLayoutWidget,
} from '~/generated/graphql';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
@@ -182,7 +183,11 @@ export const WithNumberChart: Story = {
},
render: (args) => (
<div style={{ width: '300px', height: '100px' }}>
<WidgetRenderer widget={args.widget} />
<WidgetRenderer
widget={args.widget}
layoutMode="grid"
pageLayoutType={PageLayoutType.DASHBOARD}
/>
</div>
),
};
@@ -217,7 +222,11 @@ export const WithGaugeChart: Story = {
},
render: (args) => (
<div style={{ width: '300px', height: '400px' }}>
<WidgetRenderer widget={args.widget} />
<WidgetRenderer
widget={args.widget}
layoutMode="grid"
pageLayoutType={PageLayoutType.DASHBOARD}
/>
</div>
),
};
@@ -255,7 +264,11 @@ export const WithBarChart: Story = {
},
render: (args) => (
<div style={{ width: '300px', height: '500px' }}>
<WidgetRenderer widget={args.widget} />
<WidgetRenderer
widget={args.widget}
layoutMode="grid"
pageLayoutType={PageLayoutType.DASHBOARD}
/>
</div>
),
};
@@ -297,7 +310,11 @@ export const SmallWidget: Story = {
},
render: (args) => (
<div style={{ width: '300px', height: '100px' }}>
<WidgetRenderer widget={args.widget} />
<WidgetRenderer
widget={args.widget}
layoutMode="grid"
pageLayoutType={PageLayoutType.DASHBOARD}
/>
</div>
),
};
@@ -342,7 +359,11 @@ export const MediumWidget: Story = {
},
render: (args) => (
<div style={{ width: '400px', height: '250px' }}>
<WidgetRenderer widget={args.widget} />
<WidgetRenderer
widget={args.widget}
layoutMode="grid"
pageLayoutType={PageLayoutType.DASHBOARD}
/>
</div>
),
};
@@ -387,7 +408,11 @@ export const LargeWidget: Story = {
},
render: (args) => (
<div style={{ width: '600px', height: '400px' }}>
<WidgetRenderer widget={args.widget} />
<WidgetRenderer
widget={args.widget}
layoutMode="grid"
pageLayoutType={PageLayoutType.DASHBOARD}
/>
</div>
),
};
@@ -429,7 +454,11 @@ export const WideWidget: Story = {
},
render: (args) => (
<div style={{ width: '800px', height: '200px' }}>
<WidgetRenderer widget={args.widget} />
<WidgetRenderer
widget={args.widget}
layoutMode="grid"
pageLayoutType={PageLayoutType.DASHBOARD}
/>
</div>
),
};
@@ -474,7 +503,11 @@ export const TallWidget: Story = {
},
render: (args) => (
<div style={{ width: '300px', height: '500px' }}>
<WidgetRenderer widget={args.widget} />
<WidgetRenderer
widget={args.widget}
layoutMode="grid"
pageLayoutType={PageLayoutType.DASHBOARD}
/>
</div>
),
};