Skip to content

Commit f6d133f

Browse files
Scaffold all company cards as widgets (#15149)
This PR doesn't support mobile and side-panel modes. The fields tab will be displayed in these cases. ## Demo https://github.com/user-attachments/assets/0b2e699c-2b8e-4212-8c75-00d7d2e25237 --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
1 parent 357a12f commit f6d133f

File tree

20 files changed

+363
-40
lines changed

20 files changed

+363
-40
lines changed

packages/twenty-front/src/generated-metadata/graphql.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4477,9 +4477,15 @@ export type Webhook = {
44774477
export type WidgetConfiguration = BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | NumberChartConfiguration | PieChartConfiguration;
44784478

44794479
export enum WidgetType {
4480+
CALENDAR = 'CALENDAR',
4481+
EMAILS = 'EMAILS',
44804482
FIELDS = 'FIELDS',
4483+
FILES = 'FILES',
44814484
GRAPH = 'GRAPH',
44824485
IFRAME = 'IFRAME',
4486+
NOTES = 'NOTES',
4487+
TASKS = 'TASKS',
4488+
TIMELINE = 'TIMELINE',
44834489
VIEW = 'VIEW'
44844490
}
44854491

packages/twenty-front/src/generated/graphql.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4305,9 +4305,15 @@ export type Webhook = {
43054305
export type WidgetConfiguration = BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | NumberChartConfiguration | PieChartConfiguration;
43064306

43074307
export enum WidgetType {
4308+
CALENDAR = 'CALENDAR',
4309+
EMAILS = 'EMAILS',
43084310
FIELDS = 'FIELDS',
4311+
FILES = 'FILES',
43094312
GRAPH = 'GRAPH',
43104313
IFRAME = 'IFRAME',
4314+
NOTES = 'NOTES',
4315+
TASKS = 'TASKS',
4316+
TIMELINE = 'TIMELINE',
43114317
VIEW = 'VIEW'
43124318
}
43134319

packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { pageLayoutCurrentBreakpointComponentState } from '@/page-layout/states/
1313
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
1414
import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder';
1515
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
16-
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
1716
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
1817
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
1918
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
@@ -63,7 +62,11 @@ const StyledVerticalListContainer = styled.div`
6362
gap: ${({ theme }) => theme.spacing(2)};
6463
`;
6564

66-
export const PageLayoutGridLayout = () => {
65+
type PageLayoutGridLayoutProps = {
66+
tabId: string;
67+
};
68+
69+
export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => {
6770
const isRecordPageEnabled = useIsFeatureEnabled(
6871
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_ENABLED,
6972
);
@@ -84,19 +87,11 @@ export const PageLayoutGridLayout = () => {
8487
pageLayoutCurrentLayoutsComponentState,
8588
);
8689

87-
const activeTabId = useRecoilComponentValue(activeTabIdComponentState);
88-
8990
const { currentPageLayout } = useCurrentPageLayout();
9091

91-
const activeTab = currentPageLayout?.tabs.find(
92-
(tab) => tab.id === activeTabId,
93-
);
92+
const activeTab = currentPageLayout?.tabs.find((tab) => tab.id === tabId);
9493

95-
if (
96-
!isDefined(activeTabId) ||
97-
!isDefined(currentPageLayout) ||
98-
!isDefined(activeTab)
99-
) {
94+
if (!isDefined(currentPageLayout) || !isDefined(activeTab)) {
10095
return null;
10196
}
10297

@@ -107,7 +102,7 @@ export const PageLayoutGridLayout = () => {
107102

108103
const layouts = isLayoutEmpty
109104
? EMPTY_LAYOUT
110-
: (pageLayoutCurrentLayouts[activeTabId] ?? EMPTY_LAYOUT);
105+
: (pageLayoutCurrentLayouts[tabId] ?? EMPTY_LAYOUT);
111106

112107
const Widgets = isLayoutEmpty ? (
113108
<div key="empty-placeholder" data-select-disable="true">
Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1+
import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
12
import { PageLayoutGridLayout } from '@/page-layout/components/PageLayoutGridLayout';
23
import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab';
34
import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout';
45
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
56
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
7+
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
8+
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
9+
import { ShowPageContainer } from '@/ui/layout/page/components/ShowPageContainer';
10+
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
611
import { TabList } from '@/ui/layout/tab-list/components/TabList';
12+
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
713
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
814
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
915
import styled from '@emotion/styled';
1016
import { isDefined } from 'twenty-shared/utils';
1117

12-
const StyledContainer = styled.div`
18+
const StyledTabsAndDashboardContainer = styled.div`
1319
display: flex;
1420
flex-direction: column;
1521
height: 100%;
@@ -18,6 +24,16 @@ const StyledContainer = styled.div`
1824
background: ${({ theme }) => theme.background.primary};
1925
`;
2026

27+
const StyledShowPageRightContainer = styled.div`
28+
display: flex;
29+
flex-direction: column;
30+
height: 100%;
31+
justify-content: start;
32+
width: 100%;
33+
height: 100%;
34+
overflow: auto;
35+
`;
36+
2137
const StyledTabList = styled(TabList)`
2238
padding-left: ${({ theme }) => theme.spacing(2)};
2339
`;
@@ -29,10 +45,15 @@ const StyledScrollWrapper = styled(ScrollWrapper)`
2945
export const PageLayoutRendererContent = () => {
3046
const { currentPageLayout } = useCurrentPageLayout();
3147

48+
const targetRecordIdentifier = useTargetRecord();
49+
const { isInRightDrawer } = useLayoutRenderingContext();
50+
3251
const isPageLayoutInEditMode = useRecoilComponentValue(
3352
isPageLayoutInEditModeComponentState,
3453
);
3554

55+
const activeTabId = useRecoilComponentValue(activeTabIdComponentState);
56+
3657
const { createPageLayoutTab } = useCreatePageLayoutTab(currentPageLayout?.id);
3758

3859
const handleAddTab = isPageLayoutInEditMode ? createPageLayoutTab : undefined;
@@ -41,22 +62,47 @@ export const PageLayoutRendererContent = () => {
4162
return null;
4263
}
4364

65+
const tabsToRenderInTabList = currentPageLayout.tabs.filter(
66+
(tab) => tab.selfDisplayMode !== 'pinned-left',
67+
);
68+
const pinnedLeftTab = currentPageLayout.tabs.find(
69+
(tab) => tab.selfDisplayMode === 'pinned-left',
70+
);
71+
4472
return (
45-
<StyledContainer>
46-
<StyledTabList
47-
tabs={currentPageLayout.tabs}
48-
behaveAsLinks={false}
49-
componentInstanceId={getTabListInstanceIdFromPageLayoutId(
50-
currentPageLayout.id,
51-
)}
52-
onAddTab={handleAddTab}
53-
/>
54-
<StyledScrollWrapper
55-
componentInstanceId={`scroll-wrapper-page-layout-${currentPageLayout.id}`}
56-
defaultEnableXScroll={false}
57-
>
58-
<PageLayoutGridLayout />
59-
</StyledScrollWrapper>
60-
</StyledContainer>
73+
<ShowPageContainer>
74+
{isDefined(pinnedLeftTab) && (
75+
<ShowPageLeftContainer forceMobile={false}>
76+
<SummaryCard
77+
objectNameSingular={targetRecordIdentifier.targetObjectNameSingular}
78+
objectRecordId={targetRecordIdentifier.id}
79+
isInRightDrawer={isInRightDrawer}
80+
/>
81+
82+
<PageLayoutGridLayout tabId={pinnedLeftTab.id} />
83+
</ShowPageLeftContainer>
84+
)}
85+
86+
<StyledShowPageRightContainer>
87+
<StyledTabsAndDashboardContainer>
88+
<StyledTabList
89+
tabs={tabsToRenderInTabList}
90+
behaveAsLinks={false}
91+
componentInstanceId={getTabListInstanceIdFromPageLayoutId(
92+
currentPageLayout.id,
93+
)}
94+
onAddTab={handleAddTab}
95+
/>
96+
<StyledScrollWrapper
97+
componentInstanceId={`scroll-wrapper-page-layout-${currentPageLayout.id}`}
98+
defaultEnableXScroll={false}
99+
>
100+
{isDefined(activeTabId) && (
101+
<PageLayoutGridLayout tabId={activeTabId} />
102+
)}
103+
</StyledScrollWrapper>
104+
</StyledTabsAndDashboardContainer>
105+
</StyledShowPageRightContainer>
106+
</ShowPageContainer>
61107
);
62108
};

packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import { MemoryRouter } from 'react-router-dom';
1010

1111
import { FIND_ONE_PAGE_LAYOUT } from '@/dashboards/graphql/queries/findOnePageLayout';
1212
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
13+
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
1314
import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer';
1415
import { generateGroupByQuery } from '@/page-layout/widgets/graph/utils/generateGroupByQuery';
16+
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
1517
import {
1618
GraphOrderBy,
1719
GraphType,
@@ -270,7 +272,18 @@ const meta: Meta<typeof PageLayoutRenderer> = {
270272
<MemoryRouter>
271273
<JestMetadataAndApolloMocksWrapper>
272274
<CoreClientProviderWrapper>
273-
<Story />
275+
<LayoutRenderingProvider
276+
value={{
277+
isInRightDrawer: false,
278+
layoutType: PageLayoutType.DASHBOARD,
279+
targetRecordIdentifier: {
280+
targetObjectNameSingular: CoreObjectNameSingular.Dashboard,
281+
id: mixedGraphsPageLayoutMocks.id,
282+
},
283+
}}
284+
>
285+
<Story />
286+
</LayoutRenderingProvider>
274287
</CoreClientProviderWrapper>
275288
</JestMetadataAndApolloMocksWrapper>
276289
</MemoryRouter>

packages/twenty-front/src/modules/page-layout/constants/DefaultPageLayout.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = {
2424
title: 'Fields',
2525
position: 100,
2626
layoutMode: 'vertical-list',
27+
selfDisplayMode: 'pinned-left',
2728
pageLayoutId: DEFAULT_PAGE_LAYOUT_ID,
2829
createdAt: new Date().toISOString(),
2930
updatedAt: new Date().toISOString(),
@@ -67,7 +68,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = {
6768
id: 'default-widget-timeline',
6869
pageLayoutTabId: 'default-tab-timeline',
6970
title: 'Timeline',
70-
type: WidgetType.VIEW,
71+
type: WidgetType.TIMELINE,
7172
objectMetadataId: null,
7273
gridPosition: {
7374
__typename: 'GridPosition',
@@ -100,7 +101,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = {
100101
id: 'default-widget-tasks',
101102
pageLayoutTabId: 'default-tab-tasks',
102103
title: 'Tasks',
103-
type: WidgetType.VIEW,
104+
type: WidgetType.TASKS,
104105
objectMetadataId: null,
105106
gridPosition: {
106107
__typename: 'GridPosition',
@@ -133,7 +134,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = {
133134
id: 'default-widget-notes',
134135
pageLayoutTabId: 'default-tab-notes',
135136
title: 'Notes',
136-
type: WidgetType.VIEW,
137+
type: WidgetType.NOTES,
137138
objectMetadataId: null,
138139
gridPosition: {
139140
__typename: 'GridPosition',
@@ -166,7 +167,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = {
166167
id: 'default-widget-files',
167168
pageLayoutTabId: 'default-tab-files',
168169
title: 'Files',
169-
type: WidgetType.VIEW,
170+
type: WidgetType.FILES,
170171
objectMetadataId: null,
171172
gridPosition: {
172173
__typename: 'GridPosition',
@@ -199,7 +200,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = {
199200
id: 'default-widget-emails',
200201
pageLayoutTabId: 'default-tab-emails',
201202
title: 'Emails',
202-
type: WidgetType.VIEW,
203+
type: WidgetType.EMAILS,
203204
objectMetadataId: null,
204205
gridPosition: {
205206
__typename: 'GridPosition',
@@ -232,7 +233,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = {
232233
id: 'default-widget-calendar',
233234
pageLayoutTabId: 'default-tab-calendar',
234235
title: 'Calendar',
235-
type: WidgetType.VIEW,
236+
type: WidgetType.CALENDAR,
236237
objectMetadataId: null,
237238
gridPosition: {
238239
__typename: 'GridPosition',

packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ export type PageLayoutTab = Omit<PageLayoutTabGenerated, 'widgets'> & {
1313
* Only available behind IS_RECORD_PAGE_LAYOUT_ENABLED for now.
1414
*/
1515
layoutMode?: 'grid' | 'vertical-list';
16+
selfDisplayMode?: 'pinned-left';
1617
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { CalendarEventsCard } from '@/activities/calendar/components/CalendarEventsCard';
2+
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
3+
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
4+
import styled from '@emotion/styled';
5+
import { type PageLayoutWidget } from '~/generated/graphql';
6+
7+
const StyledContainer = styled.div`
8+
display: flex;
9+
flex-direction: column;
10+
width: 100%;
11+
`;
12+
13+
type CalendarWidgetProps = {
14+
widget: PageLayoutWidget;
15+
};
16+
17+
export const CalendarWidget = ({ widget: _widget }: CalendarWidgetProps) => {
18+
const { isInRightDrawer } = useLayoutRenderingContext();
19+
20+
return (
21+
<RightDrawerProvider value={{ isInRightDrawer }}>
22+
<StyledContainer>
23+
<CalendarEventsCard />
24+
</StyledContainer>
25+
</RightDrawerProvider>
26+
);
27+
};

packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import { CalendarWidget } from '@/page-layout/widgets/calendar/components/CalendarWidget';
2+
import { EmailWidget } from '@/page-layout/widgets/emails/components/EmailWidget';
13
import { FieldsWidget } from '@/page-layout/widgets/fields/components/FieldsWidget';
4+
import { FileWidget } from '@/page-layout/widgets/files/components/FileWidget';
25
import { GraphWidgetRenderer } from '@/page-layout/widgets/graph/components/GraphWidgetRenderer';
36
import { IframeWidget } from '@/page-layout/widgets/iframe/components/IframeWidget';
7+
import { NoteWidget } from '@/page-layout/widgets/notes/components/NoteWidget';
8+
import { TaskWidget } from '@/page-layout/widgets/tasks/components/TaskWidget';
9+
import { TimelineWidget } from '@/page-layout/widgets/timeline/components/TimelineWidget';
410
import { type PageLayoutWidget, WidgetType } from '~/generated/graphql';
511

612
type WidgetContentRendererProps = {
@@ -20,6 +26,24 @@ export const WidgetContentRenderer = ({
2026
case WidgetType.FIELDS:
2127
return <FieldsWidget widget={widget} />;
2228

29+
case WidgetType.TIMELINE:
30+
return <TimelineWidget widget={widget} />;
31+
32+
case WidgetType.TASKS:
33+
return <TaskWidget widget={widget} />;
34+
35+
case WidgetType.NOTES:
36+
return <NoteWidget widget={widget} />;
37+
38+
case WidgetType.FILES:
39+
return <FileWidget widget={widget} />;
40+
41+
case WidgetType.EMAILS:
42+
return <EmailWidget widget={widget} />;
43+
44+
case WidgetType.CALENDAR:
45+
return <CalendarWidget widget={widget} />;
46+
2347
default:
2448
return null;
2549
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { EmailsCard } from '@/activities/emails/components/EmailsCard';
2+
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
3+
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
4+
import styled from '@emotion/styled';
5+
import { type PageLayoutWidget } from '~/generated/graphql';
6+
7+
const StyledContainer = styled.div`
8+
display: flex;
9+
flex-direction: column;
10+
width: 100%;
11+
`;
12+
13+
type EmailWidgetProps = {
14+
widget: PageLayoutWidget;
15+
};
16+
17+
export const EmailWidget = ({ widget: _widget }: EmailWidgetProps) => {
18+
const { isInRightDrawer } = useLayoutRenderingContext();
19+
20+
return (
21+
<RightDrawerProvider value={{ isInRightDrawer }}>
22+
<StyledContainer>
23+
<EmailsCard />
24+
</StyledContainer>
25+
</RightDrawerProvider>
26+
);
27+
};

0 commit comments

Comments
 (0)