From f6d133f2851d0dd30d523a4312dd332195afd4fc Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Sat, 18 Oct 2025 22:26:27 +0200 Subject: [PATCH] Scaffold all company cards as widgets (#15149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/generated-metadata/graphql.ts | 6 ++ .../twenty-front/src/generated/graphql.ts | 6 ++ .../components/PageLayoutGridLayout.tsx | 21 ++--- .../components/PageLayoutRendererContent.tsx | 80 +++++++++++++++---- .../PageLayoutRenderer.stories.tsx | 15 +++- .../constants/DefaultPageLayout.ts | 13 +-- .../page-layout/types/PageLayoutTab.ts | 1 + .../calendar/components/CalendarWidget.tsx | 27 +++++++ .../components/WidgetContentRenderer.tsx | 24 ++++++ .../widgets/emails/components/EmailWidget.tsx | 27 +++++++ .../fields/components/FieldsWidget.tsx | 1 + .../widgets/files/components/FileWidget.tsx | 27 +++++++ .../widgets/notes/components/NoteWidget.tsx | 27 +++++++ .../widgets/tasks/components/TaskWidget.tsx | 27 +++++++ .../timeline/components/TimelineWidget.tsx | 27 +++++++ .../page/components/ShowPageContainer.tsx | 4 +- .../common/1760628085765-addNewWidgetTypes.ts | 45 +++++++++++ .../open-api/utils/components.utils.ts | 13 ++- .../page-layout/enums/widget-type.enum.ts | 6 ++ ...idget-configuration-test-data.constants.ts | 6 ++ 20 files changed, 363 insertions(+), 40 deletions(-) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/calendar/components/CalendarWidget.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/emails/components/EmailWidget.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/files/components/FileWidget.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/notes/components/NoteWidget.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/tasks/components/TaskWidget.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/timeline/components/TimelineWidget.tsx create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1760628085765-addNewWidgetTypes.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 87120e6c94..3f98c114fa 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4477,9 +4477,15 @@ export type Webhook = { export type WidgetConfiguration = BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | NumberChartConfiguration | PieChartConfiguration; export enum WidgetType { + CALENDAR = 'CALENDAR', + EMAILS = 'EMAILS', FIELDS = 'FIELDS', + FILES = 'FILES', GRAPH = 'GRAPH', IFRAME = 'IFRAME', + NOTES = 'NOTES', + TASKS = 'TASKS', + TIMELINE = 'TIMELINE', VIEW = 'VIEW' } diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 17c3725c13..db53d8b620 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -4305,9 +4305,15 @@ export type Webhook = { export type WidgetConfiguration = BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | NumberChartConfiguration | PieChartConfiguration; export enum WidgetType { + CALENDAR = 'CALENDAR', + EMAILS = 'EMAILS', FIELDS = 'FIELDS', + FILES = 'FILES', GRAPH = 'GRAPH', IFRAME = 'IFRAME', + NOTES = 'NOTES', + TASKS = 'TASKS', + TIMELINE = 'TIMELINE', VIEW = 'VIEW' } 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 2515e2090c..5977090c39 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutGridLayout.tsx @@ -13,7 +13,6 @@ import { pageLayoutCurrentBreakpointComponentState } from '@/page-layout/states/ import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder'; import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; 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'; @@ -63,7 +62,11 @@ const StyledVerticalListContainer = styled.div` gap: ${({ theme }) => theme.spacing(2)}; `; -export const PageLayoutGridLayout = () => { +type PageLayoutGridLayoutProps = { + tabId: string; +}; + +export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => { const isRecordPageEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_ENABLED, ); @@ -84,19 +87,11 @@ export const PageLayoutGridLayout = () => { pageLayoutCurrentLayoutsComponentState, ); - const activeTabId = useRecoilComponentValue(activeTabIdComponentState); - const { currentPageLayout } = useCurrentPageLayout(); - const activeTab = currentPageLayout?.tabs.find( - (tab) => tab.id === activeTabId, - ); + const activeTab = currentPageLayout?.tabs.find((tab) => tab.id === tabId); - if ( - !isDefined(activeTabId) || - !isDefined(currentPageLayout) || - !isDefined(activeTab) - ) { + if (!isDefined(currentPageLayout) || !isDefined(activeTab)) { return null; } @@ -107,7 +102,7 @@ export const PageLayoutGridLayout = () => { const layouts = isLayoutEmpty ? EMPTY_LAYOUT - : (pageLayoutCurrentLayouts[activeTabId] ?? EMPTY_LAYOUT); + : (pageLayoutCurrentLayouts[tabId] ?? EMPTY_LAYOUT); const Widgets = isLayoutEmpty ? (
diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx index 72273e7fda..d081d833ad 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx @@ -1,15 +1,21 @@ +import { SummaryCard } from '@/object-record/record-show/components/SummaryCard'; import { PageLayoutGridLayout } from '@/page-layout/components/PageLayoutGridLayout'; import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab'; import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout'; import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState'; import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; +import { ShowPageContainer } from '@/ui/layout/page/components/ShowPageContainer'; +import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer'; import { TabList } from '@/ui/layout/tab-list/components/TabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; -const StyledContainer = styled.div` +const StyledTabsAndDashboardContainer = styled.div` display: flex; flex-direction: column; height: 100%; @@ -18,6 +24,16 @@ const StyledContainer = styled.div` background: ${({ theme }) => theme.background.primary}; `; +const StyledShowPageRightContainer = styled.div` + display: flex; + flex-direction: column; + height: 100%; + justify-content: start; + width: 100%; + height: 100%; + overflow: auto; +`; + const StyledTabList = styled(TabList)` padding-left: ${({ theme }) => theme.spacing(2)}; `; @@ -29,10 +45,15 @@ const StyledScrollWrapper = styled(ScrollWrapper)` export const PageLayoutRendererContent = () => { const { currentPageLayout } = useCurrentPageLayout(); + const targetRecordIdentifier = useTargetRecord(); + const { isInRightDrawer } = useLayoutRenderingContext(); + const isPageLayoutInEditMode = useRecoilComponentValue( isPageLayoutInEditModeComponentState, ); + const activeTabId = useRecoilComponentValue(activeTabIdComponentState); + const { createPageLayoutTab } = useCreatePageLayoutTab(currentPageLayout?.id); const handleAddTab = isPageLayoutInEditMode ? createPageLayoutTab : undefined; @@ -41,22 +62,47 @@ export const PageLayoutRendererContent = () => { return null; } + const tabsToRenderInTabList = currentPageLayout.tabs.filter( + (tab) => tab.selfDisplayMode !== 'pinned-left', + ); + const pinnedLeftTab = currentPageLayout.tabs.find( + (tab) => tab.selfDisplayMode === 'pinned-left', + ); + return ( - - - - - - + + {isDefined(pinnedLeftTab) && ( + + + + + + )} + + + + + + {isDefined(activeTabId) && ( + + )} + + + + ); }; diff --git a/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx b/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx index 6e6ecd03c3..0b610998bf 100644 --- a/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx @@ -10,8 +10,10 @@ import { MemoryRouter } from 'react-router-dom'; import { FIND_ONE_PAGE_LAYOUT } from '@/dashboards/graphql/queries/findOnePageLayout'; import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer'; import { generateGroupByQuery } from '@/page-layout/widgets/graph/utils/generateGroupByQuery'; +import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; import { GraphOrderBy, GraphType, @@ -270,7 +272,18 @@ const meta: Meta = { - + + + diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultPageLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultPageLayout.ts index 9ae322cb43..865a3d4884 100644 --- a/packages/twenty-front/src/modules/page-layout/constants/DefaultPageLayout.ts +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultPageLayout.ts @@ -24,6 +24,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = { title: 'Fields', position: 100, layoutMode: 'vertical-list', + selfDisplayMode: 'pinned-left', pageLayoutId: DEFAULT_PAGE_LAYOUT_ID, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -67,7 +68,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = { id: 'default-widget-timeline', pageLayoutTabId: 'default-tab-timeline', title: 'Timeline', - type: WidgetType.VIEW, + type: WidgetType.TIMELINE, objectMetadataId: null, gridPosition: { __typename: 'GridPosition', @@ -100,7 +101,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = { id: 'default-widget-tasks', pageLayoutTabId: 'default-tab-tasks', title: 'Tasks', - type: WidgetType.VIEW, + type: WidgetType.TASKS, objectMetadataId: null, gridPosition: { __typename: 'GridPosition', @@ -133,7 +134,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = { id: 'default-widget-notes', pageLayoutTabId: 'default-tab-notes', title: 'Notes', - type: WidgetType.VIEW, + type: WidgetType.NOTES, objectMetadataId: null, gridPosition: { __typename: 'GridPosition', @@ -166,7 +167,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = { id: 'default-widget-files', pageLayoutTabId: 'default-tab-files', title: 'Files', - type: WidgetType.VIEW, + type: WidgetType.FILES, objectMetadataId: null, gridPosition: { __typename: 'GridPosition', @@ -199,7 +200,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = { id: 'default-widget-emails', pageLayoutTabId: 'default-tab-emails', title: 'Emails', - type: WidgetType.VIEW, + type: WidgetType.EMAILS, objectMetadataId: null, gridPosition: { __typename: 'GridPosition', @@ -232,7 +233,7 @@ export const DEFAULT_PAGE_LAYOUT: PageLayout = { id: 'default-widget-calendar', pageLayoutTabId: 'default-tab-calendar', title: 'Calendar', - type: WidgetType.VIEW, + type: WidgetType.CALENDAR, objectMetadataId: null, gridPosition: { __typename: 'GridPosition', 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..437ea661bd 100644 --- a/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts +++ b/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts @@ -13,4 +13,5 @@ export type PageLayoutTab = Omit & { * Only available behind IS_RECORD_PAGE_LAYOUT_ENABLED for now. */ layoutMode?: 'grid' | 'vertical-list'; + selfDisplayMode?: 'pinned-left'; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/calendar/components/CalendarWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/calendar/components/CalendarWidget.tsx new file mode 100644 index 0000000000..14ac302aa9 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/calendar/components/CalendarWidget.tsx @@ -0,0 +1,27 @@ +import { CalendarEventsCard } from '@/activities/calendar/components/CalendarEventsCard'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; +import styled from '@emotion/styled'; +import { type PageLayoutWidget } from '~/generated/graphql'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +type CalendarWidgetProps = { + widget: PageLayoutWidget; +}; + +export const CalendarWidget = ({ widget: _widget }: CalendarWidgetProps) => { + const { isInRightDrawer } = useLayoutRenderingContext(); + + return ( + + + + + + ); +}; 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 e543059114..16038dd6df 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 @@ -1,6 +1,12 @@ +import { CalendarWidget } from '@/page-layout/widgets/calendar/components/CalendarWidget'; +import { EmailWidget } from '@/page-layout/widgets/emails/components/EmailWidget'; import { FieldsWidget } from '@/page-layout/widgets/fields/components/FieldsWidget'; +import { FileWidget } from '@/page-layout/widgets/files/components/FileWidget'; import { GraphWidgetRenderer } from '@/page-layout/widgets/graph/components/GraphWidgetRenderer'; import { IframeWidget } from '@/page-layout/widgets/iframe/components/IframeWidget'; +import { NoteWidget } from '@/page-layout/widgets/notes/components/NoteWidget'; +import { TaskWidget } from '@/page-layout/widgets/tasks/components/TaskWidget'; +import { TimelineWidget } from '@/page-layout/widgets/timeline/components/TimelineWidget'; import { type PageLayoutWidget, WidgetType } from '~/generated/graphql'; type WidgetContentRendererProps = { @@ -20,6 +26,24 @@ export const WidgetContentRenderer = ({ case WidgetType.FIELDS: return ; + case WidgetType.TIMELINE: + return ; + + case WidgetType.TASKS: + return ; + + case WidgetType.NOTES: + return ; + + case WidgetType.FILES: + return ; + + case WidgetType.EMAILS: + return ; + + case WidgetType.CALENDAR: + return ; + default: return null; } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/emails/components/EmailWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/emails/components/EmailWidget.tsx new file mode 100644 index 0000000000..936131d868 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/emails/components/EmailWidget.tsx @@ -0,0 +1,27 @@ +import { EmailsCard } from '@/activities/emails/components/EmailsCard'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; +import styled from '@emotion/styled'; +import { type PageLayoutWidget } from '~/generated/graphql'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +type EmailWidgetProps = { + widget: PageLayoutWidget; +}; + +export const EmailWidget = ({ widget: _widget }: EmailWidgetProps) => { + const { isInRightDrawer } = useLayoutRenderingContext(); + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidget.tsx index 75d1cb74dc..3a0c0d1df2 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidget.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidget.tsx @@ -8,6 +8,7 @@ import { type PageLayoutWidget } from '~/generated/graphql'; const StyledContainer = styled.div` display: flex; flex-direction: column; + width: 100%; `; type FieldsWidgetProps = { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/files/components/FileWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/files/components/FileWidget.tsx new file mode 100644 index 0000000000..0940fca7e7 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/files/components/FileWidget.tsx @@ -0,0 +1,27 @@ +import { FilesCard } from '@/activities/files/components/FilesCard'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; +import styled from '@emotion/styled'; +import { type PageLayoutWidget } from '~/generated/graphql'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +type FileWidgetProps = { + widget: PageLayoutWidget; +}; + +export const FileWidget = ({ widget: _widget }: FileWidgetProps) => { + const { isInRightDrawer } = useLayoutRenderingContext(); + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/notes/components/NoteWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/notes/components/NoteWidget.tsx new file mode 100644 index 0000000000..8f821f1154 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/notes/components/NoteWidget.tsx @@ -0,0 +1,27 @@ +import { NotesCard } from '@/activities/notes/components/NotesCard'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; +import styled from '@emotion/styled'; +import { type PageLayoutWidget } from '~/generated/graphql'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +type NoteWidgetProps = { + widget: PageLayoutWidget; +}; + +export const NoteWidget = ({ widget: _widget }: NoteWidgetProps) => { + const { isInRightDrawer } = useLayoutRenderingContext(); + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/tasks/components/TaskWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/tasks/components/TaskWidget.tsx new file mode 100644 index 0000000000..8bc00a81ad --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/tasks/components/TaskWidget.tsx @@ -0,0 +1,27 @@ +import { TasksCard } from '@/activities/tasks/components/TasksCard'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; +import styled from '@emotion/styled'; +import { type PageLayoutWidget } from '~/generated/graphql'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +type TaskWidgetProps = { + widget: PageLayoutWidget; +}; + +export const TaskWidget = ({ widget: _widget }: TaskWidgetProps) => { + const { isInRightDrawer } = useLayoutRenderingContext(); + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/timeline/components/TimelineWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/timeline/components/TimelineWidget.tsx new file mode 100644 index 0000000000..353c0fe757 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/timeline/components/TimelineWidget.tsx @@ -0,0 +1,27 @@ +import { TimelineCard } from '@/activities/timeline-activities/components/TimelineCard'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; +import styled from '@emotion/styled'; +import { type PageLayoutWidget } from '~/generated/graphql'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +type TimelineWidgetProps = { + widget: PageLayoutWidget; +}; + +export const TimelineWidget = ({ widget: _widget }: TimelineWidgetProps) => { + const { isInRightDrawer } = useLayoutRenderingContext(); + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/ShowPageContainer.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/ShowPageContainer.tsx index 9f6bd71b64..32421b521b 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/ShowPageContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/ShowPageContainer.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled'; -import { type ReactElement } from 'react'; +import { type ReactNode } from 'react'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; @@ -24,7 +24,7 @@ const StyledScrollWrapper = styled(ScrollWrapper)` `; export type ShowPageContainerProps = { - children: ReactElement[] | ReactElement; + children: ReactNode; }; export const ShowPageContainer = ({ children }: ShowPageContainerProps) => { diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1760628085765-addNewWidgetTypes.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1760628085765-addNewWidgetTypes.ts new file mode 100644 index 0000000000..63b458fa6f --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1760628085765-addNewWidgetTypes.ts @@ -0,0 +1,45 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class AddNewWidgetTypes1760628085765 implements MigrationInterface { + name = 'AddNewWidgetTypes1760628085765'; + + 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')`, + ); + 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('FIELDS', 'GRAPH', 'IFRAME', 'VIEW')`, + ); + 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/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 95fc301213..4460192661 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -1132,7 +1132,18 @@ export const computeMetadataSchemaComponents = ( title: { type: 'string' }, type: { type: 'string', - enum: ['VIEW', 'IFRAME', 'FIELDS', 'GRAPH'], + enum: [ + 'VIEW', + 'IFRAME', + 'FIELDS', + 'GRAPH', + 'TIMELINE', + 'TASKS', + 'NOTES', + 'FILES', + 'EMAILS', + 'CALENDAR', + ], default: 'VIEW', }, objectMetadataId: { type: 'string', format: 'uuid' }, 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 20493a53ac..311476a945 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 @@ -3,4 +3,10 @@ export enum WidgetType { IFRAME = 'IFRAME', FIELDS = 'FIELDS', GRAPH = 'GRAPH', + TIMELINE = 'TIMELINE', + TASKS = 'TASKS', + NOTES = 'NOTES', + FILES = 'FILES', + EMAILS = 'EMAILS', + CALENDAR = 'CALENDAR', } diff --git a/packages/twenty-server/test/integration/constants/widget-configuration-test-data.constants.ts b/packages/twenty-server/test/integration/constants/widget-configuration-test-data.constants.ts index 3d01c60c4f..78062659c2 100644 --- a/packages/twenty-server/test/integration/constants/widget-configuration-test-data.constants.ts +++ b/packages/twenty-server/test/integration/constants/widget-configuration-test-data.constants.ts @@ -268,6 +268,12 @@ export function getValidConfigForWidgetType(widgetType: string): any { } case 'VIEW': case 'FIELDS': + case 'TIMELINE': + case 'TASKS': + case 'NOTES': + case 'FILES': + case 'EMAILS': + case 'CALENDAR': return null; default: return null;