From e80c552ae751c282e010653862b74b9b8b19a135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:26:26 +0200 Subject: [PATCH] Create the Dashboard record show page (#14423) Closes https://github.com/twentyhq/core-team-issues/issues/1438 - Reorganized PageLayout module - Created `DashboardRenderer` and `PageLayoutRenderer` - Created stories for the `PageLayoutRenderer` - Refactored the Widget components https://github.com/user-attachments/assets/27e9ac8f-b237-4c21-8494-3fab6d65af3a --- .../src/generated-metadata/graphql.ts | 1 + .../twenty-front/src/generated/graphql.ts | 1 + .../CommandMenuPageLayoutGraphTypeSelect.tsx | 7 +- .../CommandMenuPageLayoutIframeConfig.tsx | 8 +- .../CommandMenuPageLayoutWidgetTypeSelect.tsx | 4 +- .../components/DashboardRenderer.tsx | 36 ++ .../dashboards/components/types/Dashboard.ts | 6 + .../graphql/fragments/pageLayoutFragment.ts | 15 + .../fragments/pageLayoutTabFragment.ts | 14 + .../fragments/pageLayoutWidgetFragment.ts | 17 + .../graphql/queries/findOnePageLayout.ts | 11 + .../record-show/components/CardComponents.tsx | 5 + .../hooks/useRecordShowContainerTabs.ts | 34 +- .../record-show/types/CardType.ts | 1 + .../record-show/types/RecordLayout.ts | 1 + .../PageLayoutInitializationEffect.tsx | 18 +- .../components/PageLayoutRenderer.tsx | 156 +++++++ .../PageLayoutRenderer.stories.tsx | 224 ++++++++++ .../page-layout/constants/EmptyLayout.ts | 0 .../page-layout/constants/GridBufferRows.ts | 0 .../page-layout/constants/GridMinRows.ts | 0 .../constants/PageLayoutBreakpoints.ts | 0 .../SettingsPageLayoutTabsInstanceId.ts | 0 .../useChangePageLayoutDragSelection.test.ts | 0 .../__tests__/useCreatePageLayoutTab.test.ts | 4 +- .../useCreatePageLayoutWidget.test.ts | 16 +- .../useDeletePageLayoutWidget.test.ts | 0 .../useEndPageLayoutDragSelection.test.ts | 0 .../__tests__/usePageLayoutDraftState.test.ts | 7 +- .../usePageLayoutHandleLayoutChange.test.ts | 0 .../useStartPageLayoutDragSelection.test.ts | 0 .../hooks/useChangePageLayoutDragSelection.ts | 0 .../hooks/useCreatePageLayoutIframeWidget.ts | 23 +- .../hooks/useCreatePageLayoutTab.ts | 11 +- .../hooks/useCreatePageLayoutWidget.ts | 35 +- .../hooks/useDeletePageLayoutWidget.ts | 0 .../hooks/useEndPageLayoutDragSelection.ts | 0 .../hooks/usePageLayoutDraftState.ts | 0 .../hooks/usePageLayoutHandleLayoutChange.ts | 4 +- .../hooks/usePageLayoutSaveHandler.ts | 14 +- .../hooks/useStartPageLayoutDragSelection.ts | 0 .../hooks/useUpdatePageLayoutWidget.ts | 6 +- .../page-layout/mocks/mockWidgets.ts | 4 +- .../pageLayoutCurrentBreakpointState.ts | 0 .../states/pageLayoutCurrentLayoutsState.ts | 0 .../states/pageLayoutDraftState.ts | 5 +- .../states/pageLayoutDraggedAreaState.ts | 0 .../states/pageLayoutEditingWidgetIdState.ts | 0 .../states/pageLayoutPersistedState.ts | 4 +- .../states/pageLayoutSelectedCellsState.ts | 0 .../states/savedPageLayoutsState.ts | 7 + .../page-layout/types/pageLayoutTypes.ts | 18 + .../utils/__tests__/addWidgetToTab.test.ts | 12 +- ...lculateGridBoundsFromSelectedCells.test.ts | 0 .../__tests__/calculateTotalGridRows.test.ts | 0 .../__tests__/convertLayoutsToWidgets.test.ts | 4 +- .../__tests__/createEmptyTabLayout.test.ts | 0 .../__tests__/createUpdatedTabLayouts.test.ts | 0 .../utils/__tests__/generateCellId.test.ts | 0 .../getDefaultWidgetPosition.test.ts | 0 .../parseCellIdToCoordinates.test.ts | 0 .../__tests__/removeWidgetFromTab.test.ts | 4 +- .../removeWidgetLayoutFromTab.test.ts | 0 .../page-layout/utils/addWidgetToTab.ts | 12 +- .../calculateGridBoundsFromSelectedCells.ts | 0 .../utils/calculateTotalGridRows.ts | 0 .../utils/convertLayoutsToWidgets.ts | 6 +- .../page-layout/utils/createEmptyTabLayout.ts | 0 .../utils/createUpdatedTabLayouts.ts | 0 .../page-layout/utils/generateCellId.ts | 0 .../page-layout/utils/getDefaultWidgetData.ts | 7 + .../utils/getDefaultWidgetPosition.ts | 0 .../utils/normalizePageLayoutData.ts | 17 + .../utils/parseCellIdToCoordinates.ts | 0 .../page-layout/utils/removeWidgetFromTab.ts | 6 +- .../utils/removeWidgetLayoutFromTab.ts | 0 .../widgets/components/WidgetContainer.tsx | 34 ++ .../components/WidgetContentRenderer.tsx | 27 ++ .../widgets/components/WidgetHeader.tsx | 75 ++++ .../widgets/components/WidgetPlaceholder.tsx | 36 ++ .../widgets/components/WidgetRenderer.tsx | 41 ++ .../__stories__/WidgetPlaceholder.stories.tsx | 41 ++ .../__stories__/WidgetRenderer.stories.tsx | 397 ++++++++++++++++++ .../graph/components/GraphWidgetBarChart.tsx | 0 .../components/GraphWidgetGaugeChart.tsx | 0 .../graph/components/GraphWidgetLegend.tsx | 0 .../graph/components/GraphWidgetLineChart.tsx | 0 .../components/GraphWidgetNumberChart.tsx | 0 .../graph/components/GraphWidgetPieChart.tsx | 10 +- .../graph/components/GraphWidgetRenderer.tsx | 101 +++++ .../graph/components/GraphWidgetTooltip.tsx | 0 .../GraphWidgetBarChart.stories.tsx | 2 +- .../GraphWidgetGaugeChart.stories.tsx | 2 +- .../__stories__/GraphWidgetLegend.stories.tsx | 2 +- .../GraphWidgetLineChart.stories.tsx | 4 +- .../GraphWidgetNumberChart.stories.tsx | 2 +- .../GraphWidgetPieChart.stories.tsx | 2 +- .../GraphWidgetTooltip.stories.tsx | 2 +- .../widgets/graph/types/GraphColor.ts | 0 .../widgets/graph/types/GraphColorRegistry.ts | 0 .../widgets/graph/types/GraphColorScheme.ts | 0 .../widgets/graph/types/GraphWidget.ts | 10 + .../graph/utils/calculateAngularGradient.ts | 0 .../widgets/graph/utils/createGradientDef.ts | 0 .../graph/utils/createGraphColorRegistry.ts | 0 .../widgets/graph/utils/getColorScheme.ts | 0 .../graph/utils/getColorSchemeByIndex.ts | 0 .../graph/utils/getColorSchemeByName.ts | 0 .../widgets/graph/utils/graphFormatters.ts | 0 .../iframe/components/IframeWidget.tsx | 0 .../widgets/iframe/types/IframeWidget.ts | 9 + .../page-layout/widgets/types/Widget.ts | 4 + .../components/GraphWidgetRenderer.tsx | 98 ----- .../PageLayoutWidgetPlaceholder.tsx | 141 ------- .../page-layout/components/WidgetRenderer.tsx | 32 -- .../PageLayoutWidgetPlaceholder.stories.tsx | 313 -------------- .../states/savedPageLayoutsState.ts | 56 --- .../page-layout/SettingsPageLayoutEdit.tsx | 84 ++-- .../page-layout/SettingsPageLayouts.tsx | 2 +- .../page-layout/dtos/page-layout-tab.dto.ts | 4 + .../services/page-layout.service.ts | 6 +- .../tests/page-layout.service.spec.ts | 12 +- .../display/icon/components/TablerIcons.ts | 1 + packages/twenty-ui/src/display/index.ts | 1 + 124 files changed, 1528 insertions(+), 808 deletions(-) create mode 100644 packages/twenty-front/src/modules/dashboards/components/DashboardRenderer.tsx create mode 100644 packages/twenty-front/src/modules/dashboards/components/types/Dashboard.ts create mode 100644 packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutFragment.ts create mode 100644 packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutTabFragment.ts create mode 100644 packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutWidgetFragment.ts create mode 100644 packages/twenty-front/src/modules/dashboards/graphql/queries/findOnePageLayout.ts rename packages/twenty-front/src/modules/{settings => }/page-layout/components/PageLayoutInitializationEffect.tsx (88%) create mode 100644 packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx rename packages/twenty-front/src/modules/{settings => }/page-layout/constants/EmptyLayout.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/constants/GridBufferRows.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/constants/GridMinRows.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/constants/PageLayoutBreakpoints.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/constants/SettingsPageLayoutTabsInstanceId.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/__tests__/useChangePageLayoutDragSelection.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts (95%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts (90%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/__tests__/useDeletePageLayoutWidget.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/__tests__/useEndPageLayoutDragSelection.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts (94%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/__tests__/useStartPageLayoutDragSelection.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/useChangePageLayoutDragSelection.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/useCreatePageLayoutIframeWidget.ts (72%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/useCreatePageLayoutTab.ts (77%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/useCreatePageLayoutWidget.ts (71%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/useDeletePageLayoutWidget.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/useEndPageLayoutDragSelection.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/usePageLayoutDraftState.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/usePageLayoutHandleLayoutChange.ts (94%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/usePageLayoutSaveHandler.ts (87%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/useStartPageLayoutDragSelection.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/hooks/useUpdatePageLayoutWidget.ts (68%) rename packages/twenty-front/src/modules/{settings => }/page-layout/mocks/mockWidgets.ts (96%) rename packages/twenty-front/src/modules/{settings => }/page-layout/states/pageLayoutCurrentBreakpointState.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/states/pageLayoutCurrentLayoutsState.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/states/pageLayoutDraftState.ts (71%) rename packages/twenty-front/src/modules/{settings => }/page-layout/states/pageLayoutDraggedAreaState.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/states/pageLayoutEditingWidgetIdState.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/states/pageLayoutPersistedState.ts (63%) rename packages/twenty-front/src/modules/{settings => }/page-layout/states/pageLayoutSelectedCellsState.ts (100%) create mode 100644 packages/twenty-front/src/modules/page-layout/states/savedPageLayoutsState.ts create mode 100644 packages/twenty-front/src/modules/page-layout/types/pageLayoutTypes.ts rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/addWidgetToTab.test.ts (90%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/calculateGridBoundsFromSelectedCells.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/calculateTotalGridRows.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts (94%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/createEmptyTabLayout.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/createUpdatedTabLayouts.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/generateCellId.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/parseCellIdToCoordinates.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/removeWidgetFromTab.test.ts (96%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/__tests__/removeWidgetLayoutFromTab.test.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/addWidgetToTab.ts (54%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/calculateGridBoundsFromSelectedCells.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/calculateTotalGridRows.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/convertLayoutsToWidgets.ts (75%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/createEmptyTabLayout.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/createUpdatedTabLayouts.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/generateCellId.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/getDefaultWidgetData.ts (93%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/getDefaultWidgetPosition.ts (100%) create mode 100644 packages/twenty-front/src/modules/page-layout/utils/normalizePageLayoutData.ts rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/parseCellIdToCoordinates.ts (100%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/removeWidgetFromTab.ts (66%) rename packages/twenty-front/src/modules/{settings => }/page-layout/utils/removeWidgetLayoutFromTab.ts (100%) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContainer.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/WidgetHeader.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetPlaceholder.stories.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/GraphWidgetBarChart.tsx (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/GraphWidgetGaugeChart.tsx (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/GraphWidgetLegend.tsx (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/GraphWidgetLineChart.tsx (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/GraphWidgetNumberChart.tsx (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/GraphWidgetPieChart.tsx (95%) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetRenderer.tsx rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/GraphWidgetTooltip.tsx (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx (99%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx (99%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/__stories__/GraphWidgetLegend.stories.tsx (95%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/__stories__/GraphWidgetLineChart.stories.tsx (99%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/__stories__/GraphWidgetNumberChart.stories.tsx (95%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx (99%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/components/__stories__/GraphWidgetTooltip.stories.tsx (95%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/types/GraphColor.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/types/GraphColorRegistry.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/types/GraphColorScheme.ts (100%) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphWidget.ts rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/utils/calculateAngularGradient.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/utils/createGradientDef.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/utils/createGraphColorRegistry.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/utils/getColorScheme.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/utils/getColorSchemeByIndex.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/utils/getColorSchemeByName.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/graph/utils/graphFormatters.ts (100%) rename packages/twenty-front/src/modules/{dashboards => page-layout}/widgets/iframe/components/IframeWidget.tsx (100%) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/iframe/types/IframeWidget.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/types/Widget.ts delete mode 100644 packages/twenty-front/src/modules/settings/page-layout/components/GraphWidgetRenderer.tsx delete mode 100644 packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutWidgetPlaceholder.tsx delete mode 100644 packages/twenty-front/src/modules/settings/page-layout/components/WidgetRenderer.tsx delete mode 100644 packages/twenty-front/src/modules/settings/page-layout/components/__stories__/PageLayoutWidgetPlaceholder.stories.tsx delete mode 100644 packages/twenty-front/src/modules/settings/page-layout/states/savedPageLayoutsState.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 740f6e54df..c54bc9b292 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -2547,6 +2547,7 @@ export type PageLayoutTab = { position: Scalars['Float']; title: Scalars['String']; updatedAt: Scalars['DateTime']; + widgets?: Maybe>; }; export enum PageLayoutType { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 6d383e11f9..1255bf309b 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -2458,6 +2458,7 @@ export type PageLayoutTab = { position: Scalars['Float']; title: Scalars['String']; updatedAt: Scalars['DateTime']; + widgets?: Maybe>; }; export enum PageLayoutType { diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect.tsx index 1dd4a3042d..2c5d8a17fd 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect.tsx @@ -1,9 +1,6 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; -import { useCreatePageLayoutWidget } from '@/settings/page-layout/hooks/useCreatePageLayoutWidget'; -import { - GraphType, - WidgetType, -} from '@/settings/page-layout/mocks/mockWidgets'; +import { useCreatePageLayoutWidget } from '@/page-layout/hooks/useCreatePageLayoutWidget'; +import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets'; import styled from '@emotion/styled'; import { diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx index cbe7263d64..4160e23511 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx @@ -1,9 +1,9 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; -import { useCreatePageLayoutIframeWidget } from '@/settings/page-layout/hooks/useCreatePageLayoutIframeWidget'; -import { useUpdatePageLayoutWidget } from '@/settings/page-layout/hooks/useUpdatePageLayoutWidget'; -import { pageLayoutDraftState } from '@/settings/page-layout/states/pageLayoutDraftState'; -import { pageLayoutEditingWidgetIdState } from '@/settings/page-layout/states/pageLayoutEditingWidgetIdState'; +import { useCreatePageLayoutIframeWidget } from '@/page-layout/hooks/useCreatePageLayoutIframeWidget'; +import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget'; +import { pageLayoutDraftState } from '@/page-layout/states/pageLayoutDraftState'; +import { pageLayoutEditingWidgetIdState } from '@/page-layout/states/pageLayoutEditingWidgetIdState'; import styled from '@emotion/styled'; import { isString } from '@sniptt/guards'; import { useState } from 'react'; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect.tsx index af8dcb7d8e..53666089a3 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect.tsx @@ -1,7 +1,7 @@ import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; -import { WidgetType } from '@/settings/page-layout/mocks/mockWidgets'; -import { pageLayoutDraggedAreaState } from '@/settings/page-layout/states/pageLayoutDraggedAreaState'; +import { WidgetType } from '@/page-layout/mocks/mockWidgets'; +import { pageLayoutDraggedAreaState } from '@/page-layout/states/pageLayoutDraggedAreaState'; import styled from '@emotion/styled'; import { useSetRecoilState } from 'recoil'; import { IconChartPie, IconFrame, IconList } from 'twenty-ui/display'; diff --git a/packages/twenty-front/src/modules/dashboards/components/DashboardRenderer.tsx b/packages/twenty-front/src/modules/dashboards/components/DashboardRenderer.tsx new file mode 100644 index 0000000000..82372332fd --- /dev/null +++ b/packages/twenty-front/src/modules/dashboards/components/DashboardRenderer.tsx @@ -0,0 +1,36 @@ +import { type Dashboard } from '@/dashboards/components/types/Dashboard'; +import { FIND_ONE_PAGE_LAYOUT } from '@/dashboards/graphql/queries/findOnePageLayout'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer'; +import { normalizePageLayoutData } from '@/page-layout/utils/normalizePageLayoutData'; +import { useQuery } from '@apollo/client'; + +type DashboardRendererProps = { + recordId: string; +}; + +export const DashboardRenderer = ({ recordId }: DashboardRendererProps) => { + const { record: dashboard, loading: dashboardLoading } = + useFindOneRecord({ + objectNameSingular: CoreObjectNameSingular.Dashboard, + objectRecordId: recordId, + }); + + const { data } = useQuery(FIND_ONE_PAGE_LAYOUT, { + variables: { + id: dashboard?.pageLayoutId, + }, + skip: dashboardLoading || !dashboard?.pageLayoutId, + }); + + const pageLayout = data?.getPageLayout; + + if (!pageLayout) { + return null; + } + + const normalizedPageLayout = normalizePageLayoutData(pageLayout); + + return ; +}; diff --git a/packages/twenty-front/src/modules/dashboards/components/types/Dashboard.ts b/packages/twenty-front/src/modules/dashboards/components/types/Dashboard.ts new file mode 100644 index 0000000000..7c7f1bcf1c --- /dev/null +++ b/packages/twenty-front/src/modules/dashboards/components/types/Dashboard.ts @@ -0,0 +1,6 @@ +export type Dashboard = { + __typename: 'Dashboard'; + id: string; + title: string; + pageLayoutId: string; +}; diff --git a/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutFragment.ts b/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutFragment.ts new file mode 100644 index 0000000000..c7864b1072 --- /dev/null +++ b/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutFragment.ts @@ -0,0 +1,15 @@ +import { PAGE_LAYOUT_TAB_FRAGMENT } from '@/dashboards/graphql/fragments/pageLayoutTabFragment'; +import { gql } from '@apollo/client'; + +export const PAGE_LAYOUT_FRAGMENT = gql` + ${PAGE_LAYOUT_TAB_FRAGMENT} + fragment PageLayoutFragment on PageLayout { + id + name + objectMetadataId + type + tabs { + ...PageLayoutTabFragment + } + } +`; diff --git a/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutTabFragment.ts b/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutTabFragment.ts new file mode 100644 index 0000000000..1e5fa09aef --- /dev/null +++ b/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutTabFragment.ts @@ -0,0 +1,14 @@ +import { PAGE_LAYOUT_WIDGET_FRAGMENT } from '@/dashboards/graphql/fragments/pageLayoutWidgetFragment'; +import { gql } from '@apollo/client'; + +export const PAGE_LAYOUT_TAB_FRAGMENT = gql` + ${PAGE_LAYOUT_WIDGET_FRAGMENT} + fragment PageLayoutTabFragment on PageLayoutTab { + id + title + position + widgets { + ...PageLayoutWidgetFragment + } + } +`; diff --git a/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutWidgetFragment.ts b/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutWidgetFragment.ts new file mode 100644 index 0000000000..ea60ebf84f --- /dev/null +++ b/packages/twenty-front/src/modules/dashboards/graphql/fragments/pageLayoutWidgetFragment.ts @@ -0,0 +1,17 @@ +import { gql } from '@apollo/client'; + +export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql` + fragment PageLayoutWidgetFragment on PageLayoutWidget { + id + title + type + objectMetadataId + gridPosition { + column + columnSpan + row + rowSpan + } + configuration + } +`; diff --git a/packages/twenty-front/src/modules/dashboards/graphql/queries/findOnePageLayout.ts b/packages/twenty-front/src/modules/dashboards/graphql/queries/findOnePageLayout.ts new file mode 100644 index 0000000000..2f64a7be51 --- /dev/null +++ b/packages/twenty-front/src/modules/dashboards/graphql/queries/findOnePageLayout.ts @@ -0,0 +1,11 @@ +import { PAGE_LAYOUT_FRAGMENT } from '@/dashboards/graphql/fragments/pageLayoutFragment'; +import { gql } from '@apollo/client'; + +export const FIND_ONE_PAGE_LAYOUT = gql` + ${PAGE_LAYOUT_FRAGMENT} + query FindOnePageLayout($id: String!) { + getPageLayout(id: $id) { + ...PageLayoutFragment + } + } +`; diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx index 956f658a07..bdcac6d7b4 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx @@ -6,6 +6,7 @@ import { Notes } from '@/activities/notes/components/Notes'; import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks'; import { TimelineActivities } from '@/activities/timeline-activities/components/TimelineActivities'; import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; +import { DashboardRenderer } from '@/dashboards/components/DashboardRenderer'; import { FieldsCard } from '@/object-record/record-show/components/FieldsCard'; import { CardType } from '@/object-record/record-show/types/CardType'; import { ListenRecordUpdatesEffect } from '@/subscription/components/ListenRecordUpdatesEffect'; @@ -212,4 +213,8 @@ export const CardComponents: Record = { ); }, + + [CardType.DashboardCard]: ({ targetableObject }) => { + return ; + }, }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts index d5fbbdb555..c610a5920e 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts @@ -16,6 +16,7 @@ import { isDefined } from 'twenty-shared/utils'; import { IconCalendarEvent, IconHome, + IconLayoutDashboard, IconMail, IconNotes, IconSettings, @@ -243,6 +244,30 @@ export const useRecordShowContainerTabs = ( files: null, }, }, + [CoreObjectNameSingular.Dashboard]: { + hideSummaryAndFields: true, + hideFieldsInSidePanel: true, + tabs: { + dashboard: { + title: 'Dashboard', + position: 101, + Icon: IconLayoutDashboard, + cards: [{ type: CardType.DashboardCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, + }, + timeline: null, + tasks: null, + notes: null, + files: null, + }, + }, }), [], ); @@ -277,7 +302,9 @@ export const useRecordShowContainerTabs = ( title, Icon, cards, - hide: !(isMobile || isInRightDrawer), + hide: + !(isMobile || isInRightDrawer) || + recordLayout.hideFieldsInSidePanel, }; } @@ -349,7 +376,10 @@ export const useRecordShowContainerTabs = ( id: 'home', title: 'Home', Icon: IconHome, - cards: [...tab.cards, ...array[1].cards], + cards: [ + ...(tab.hide ? [] : tab.cards), + ...(array[1].hide ? [] : array[1].cards), + ], hide: false, }, ]; diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts b/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts index 2045ba3d65..eba0ffb2b3 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts @@ -10,4 +10,5 @@ export enum CardType { WorkflowVersionCard = 'WorkflowVersionCard', WorkflowRunCard = 'WorkflowRunCard', RichTextCard = 'RichTextCard', + DashboardCard = 'DashboardCard', } diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayout.ts b/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayout.ts index 78c7700bd9..d7333374cd 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayout.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayout.ts @@ -2,5 +2,6 @@ import { type RecordLayoutTab } from '@/ui/layout/tab-list/types/RecordLayoutTab export type RecordLayout = { hideSummaryAndFields?: boolean; + hideFieldsInSidePanel?: boolean; tabs: Record; }; diff --git a/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutInitializationEffect.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationEffect.tsx similarity index 88% rename from packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutInitializationEffect.tsx rename to packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationEffect.tsx index e334a2ab08..b99c5e0609 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutInitializationEffect.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationEffect.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; import { useRecoilCallback, useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; +import { PageLayoutType } from '~/generated/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { pageLayoutCurrentLayoutsState, @@ -10,27 +11,26 @@ import { } from '../states/pageLayoutCurrentLayoutsState'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; import { pageLayoutPersistedState } from '../states/pageLayoutPersistedState'; -import { - PageLayoutType, - savedPageLayoutsState, - type SavedPageLayout, -} from '../states/savedPageLayoutsState'; +import { savedPageLayoutsState } from '../states/savedPageLayoutsState'; +import { type PageLayoutWithData } from '../types/pageLayoutTypes'; type PageLayoutInitializationEffectProps = { layoutId: string | undefined; isEditMode: boolean; + pageLayout?: PageLayoutWithData; }; export const PageLayoutInitializationEffect = ({ layoutId, isEditMode, + pageLayout, }: PageLayoutInitializationEffectProps) => { const [isInitialized, setIsInitialized] = useState(false); const savedPageLayouts = useRecoilValue(savedPageLayoutsState); const initializePageLayout = useRecoilCallback( ({ set, snapshot }) => - (layout: SavedPageLayout | undefined) => { + (layout: PageLayoutWithData | undefined) => { const currentPersisted = getSnapshotValue( snapshot, pageLayoutPersistedState, @@ -98,7 +98,10 @@ export const PageLayoutInitializationEffect = ({ const existingLayout = isEditMode ? savedPageLayouts.find((l) => l.id === layoutId) : undefined; - initializePageLayout(existingLayout); + + const layoutToInitialize = existingLayout || pageLayout; + + initializePageLayout(layoutToInitialize); setIsInitialized(true); } }, [ @@ -107,6 +110,7 @@ export const PageLayoutInitializationEffect = ({ initializePageLayout, isInitialized, isEditMode, + pageLayout, ]); return null; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx new file mode 100644 index 0000000000..ca5dd9f449 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx @@ -0,0 +1,156 @@ +import { PageLayoutInitializationEffect } from '@/page-layout/components/PageLayoutInitializationEffect'; +import { EMPTY_LAYOUT } from '@/page-layout/constants/EmptyLayout'; +import { + PAGE_LAYOUT_CONFIG, + type PageLayoutBreakpoint, +} from '@/page-layout/constants/PageLayoutBreakpoints'; +import { pageLayoutCurrentBreakpointState } from '@/page-layout/states/pageLayoutCurrentBreakpointState'; +import { pageLayoutCurrentLayoutsState } from '@/page-layout/states/pageLayoutCurrentLayoutsState'; +import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; +import { type Widget } from '@/page-layout/widgets/types/Widget'; +import { TabList } from '@/ui/layout/tab-list/components/TabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; +import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import styled from '@emotion/styled'; +import { useMemo } from 'react'; +import { + Responsive, + WidthProvider, + type ResponsiveProps, +} from 'react-grid-layout'; +import 'react-grid-layout/css/styles.css'; +import 'react-resizable/css/styles.css'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { type PageLayoutWithData } from '../types/pageLayoutTypes'; + +const StyledGridContainer = styled.div` + background: ${({ theme }) => theme.background.secondary}; + box-sizing: border-box; + flex: 1; + min-height: 100%; + overflow-y: auto; + overflow-x: hidden; + position: relative; + padding: ${({ theme }) => theme.spacing(2)}; + width: 100%; + user-select: none; + + .react-grid-placeholder { + background: ${({ theme }) => theme.adaptiveColors.blue3} !important; + + border-radius: ${({ theme }) => theme.border.radius.sm}; + } + + .react-grid-item:not(.react-draggable-dragging) { + user-select: auto; + } +`; + +const StyledTabList = styled(TabList)` + padding-left: ${({ theme }) => theme.spacing(2)}; +`; + +type ExtendedResponsiveProps = ResponsiveProps & { + maxCols?: number; + preventCollision?: boolean; +}; + +const ResponsiveGridLayout = WidthProvider( + Responsive, +) as React.ComponentType; + +type PageLayoutRendererProps = { + pageLayout: PageLayoutWithData; +}; + +type PageLayoutRendererContentProps = { + pageLayout: PageLayoutWithData; +}; + +const PageLayoutRendererContent = ({ + pageLayout, +}: PageLayoutRendererContentProps) => { + const [, setPageLayoutCurrentBreakpoint] = useRecoilState( + pageLayoutCurrentBreakpointState, + ); + + const pageLayoutCurrentLayouts = useRecoilValue( + pageLayoutCurrentLayoutsState, + ); + + const activeTabId = useRecoilComponentValue(activeTabIdComponentState); + + const activeTabWidgets = pageLayout.tabs.find( + (tab) => tab.id === activeTabId, + )?.widgets; + + const tabListTabs: SingleTabProps[] = useMemo(() => { + return [...pageLayout.tabs] + .sort((a, b) => a.position - b.position) + .map((tab) => ({ + id: tab.id, + title: tab.title, + })); + }, [pageLayout.tabs]); + + return ( + <> + + {pageLayout.tabs.length > 0 && ( + + )} + + + setPageLayoutCurrentBreakpoint( + newBreakpoint as PageLayoutBreakpoint, + ) + } + > + {activeTabWidgets?.map((widget) => ( +
+ +
+ ))} +
+
+ + ); +}; + +export const PageLayoutRenderer = ({ pageLayout }: PageLayoutRendererProps) => { + return ( + + + + ); +}; 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 new file mode 100644 index 0000000000..4a657a7275 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx @@ -0,0 +1,224 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { expect, within } from '@storybook/test'; +import { MemoryRouter } from 'react-router-dom'; + +import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer'; +import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets'; +import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; +import { RecoilRoot } from 'recoil'; +import { PageLayoutType } from '~/generated/graphql'; +import { type PageLayoutWidgetWithData } from '../../types/pageLayoutTypes'; + +const validatePageLayoutContent = async (canvasElement: HTMLElement) => { + const canvas = within(canvasElement); + + const revenueElements = await canvas.findAllByText('Revenue'); + await expect(revenueElements).toHaveLength(2); + const goalProgressElements = await canvas.findAllByText('Goal Progress'); + await expect(goalProgressElements).toHaveLength(2); + await expect(await canvas.findByText('Revenue Sources')).toBeVisible(); + await expect(await canvas.findByText('Quarterly Comparison')).toBeVisible(); + + await expect(await canvas.findByText('$125,000')).toBeVisible(); + + await expect(await canvas.findByText('Product Sales')).toBeVisible(); + await expect(await canvas.findByText('Services')).toBeVisible(); + await expect(await canvas.findByText('Support')).toBeVisible(); +}; + +const mixedGraphsPageLayout = { + id: 'mixed-graphs-layout', + name: 'Mixed Graph Dashboard', + type: PageLayoutType.DASHBOARD, + objectMetadataId: null, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + deletedAt: null, + tabs: [ + { + id: 'mixed-tab', + title: 'Mixed Graphs', + position: 0, + pageLayoutId: 'mixed-graphs-layout', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + deletedAt: null, + widgets: [ + { + id: 'number-widget', + pageLayoutTabId: 'mixed-tab', + type: WidgetType.GRAPH, + title: 'Revenue', + objectMetadataId: null, + gridPosition: { + row: 0, + column: 0, + rowSpan: 2, + columnSpan: 3, + }, + configuration: { + graphType: GraphType.NUMBER, + }, + data: { + value: '$125,000', + trendPercentage: 8.3, + }, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + deletedAt: null, + } as PageLayoutWidgetWithData, + { + id: 'gauge-widget', + pageLayoutTabId: 'mixed-tab', + type: WidgetType.GRAPH, + title: 'Goal Progress', + objectMetadataId: null, + gridPosition: { + row: 0, + column: 3, + rowSpan: 4, + columnSpan: 3, + }, + configuration: { + graphType: GraphType.GAUGE, + }, + data: { + value: 0.75, + min: 0, + max: 1, + label: 'Goal Progress', + }, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + deletedAt: null, + } as PageLayoutWidgetWithData, + { + id: 'pie-widget', + pageLayoutTabId: 'mixed-tab', + type: WidgetType.GRAPH, + title: 'Revenue Sources', + objectMetadataId: null, + gridPosition: { + row: 0, + column: 6, + rowSpan: 4, + columnSpan: 3, + }, + configuration: { + graphType: GraphType.PIE, + }, + data: { + items: [ + { id: 'product', value: 60, label: 'Product Sales' }, + { id: 'services', value: 30, label: 'Services' }, + { id: 'support', value: 10, label: 'Support' }, + ], + }, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + deletedAt: null, + } as PageLayoutWidgetWithData, + { + id: 'bar-widget', + pageLayoutTabId: 'mixed-tab', + type: WidgetType.GRAPH, + title: 'Quarterly Comparison', + objectMetadataId: null, + gridPosition: { + row: 2, + column: 0, + rowSpan: 4, + columnSpan: 6, + }, + configuration: { + graphType: GraphType.BAR, + }, + data: { + items: [ + { quarter: 'Q1', revenue: 100000, expenses: 80000 }, + { quarter: 'Q2', revenue: 125000, expenses: 90000 }, + { quarter: 'Q3', revenue: 150000, expenses: 95000 }, + { quarter: 'Q4', revenue: 180000, expenses: 100000 }, + ], + indexBy: 'quarter', + keys: ['revenue', 'expenses'], + layout: 'vertical', + seriesLabels: { + revenue: 'Revenue', + expenses: 'Expenses', + }, + }, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + deletedAt: null, + } as PageLayoutWidgetWithData, + ], + }, + ], +}; + +const meta: Meta = { + title: 'Modules/PageLayout/PageLayoutRenderer', + component: PageLayoutRenderer, + decorators: [ + (Story, { args }: { args: any }) => ( + + { + set( + activeTabIdComponentState.atomFamily({ + instanceId: 'page-layout-stories', + }), + args.activeTabId, + ); + }} + > + + + + + + ), + ], + parameters: { + layout: 'fullscreen', + }, + args: { + pageLayout: mixedGraphsPageLayout, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const DesktopView: Story = { + args: { + activeTabId: 'mixed-tab', + }, + parameters: { + viewport: { + defaultViewport: 'desktop1', + }, + }, + play: async ({ canvasElement }) => { + await validatePageLayoutContent(canvasElement); + }, +}; + +export const MobileView: Story = { + args: { + activeTabId: 'mixed-tab', + }, + parameters: { + viewport: { + defaultViewport: 'mobile1', + }, + }, + play: async ({ canvasElement }) => { + await validatePageLayoutContent(canvasElement); + }, +}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/constants/EmptyLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/EmptyLayout.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/constants/EmptyLayout.ts rename to packages/twenty-front/src/modules/page-layout/constants/EmptyLayout.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/constants/GridBufferRows.ts b/packages/twenty-front/src/modules/page-layout/constants/GridBufferRows.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/constants/GridBufferRows.ts rename to packages/twenty-front/src/modules/page-layout/constants/GridBufferRows.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/constants/GridMinRows.ts b/packages/twenty-front/src/modules/page-layout/constants/GridMinRows.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/constants/GridMinRows.ts rename to packages/twenty-front/src/modules/page-layout/constants/GridMinRows.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/constants/PageLayoutBreakpoints.ts b/packages/twenty-front/src/modules/page-layout/constants/PageLayoutBreakpoints.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/constants/PageLayoutBreakpoints.ts rename to packages/twenty-front/src/modules/page-layout/constants/PageLayoutBreakpoints.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId.ts b/packages/twenty-front/src/modules/page-layout/constants/SettingsPageLayoutTabsInstanceId.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId.ts rename to packages/twenty-front/src/modules/page-layout/constants/SettingsPageLayoutTabsInstanceId.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useChangePageLayoutDragSelection.test.ts b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useChangePageLayoutDragSelection.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useChangePageLayoutDragSelection.test.ts rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/useChangePageLayoutDragSelection.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts similarity index 95% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts index 7c938be7cc..39b567b74c 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts @@ -1,5 +1,5 @@ -import { pageLayoutCurrentLayoutsState } from '@/settings/page-layout/states/pageLayoutCurrentLayoutsState'; -import { pageLayoutDraftState } from '@/settings/page-layout/states/pageLayoutDraftState'; +import { pageLayoutCurrentLayoutsState } from '@/page-layout/states/pageLayoutCurrentLayoutsState'; +import { pageLayoutDraftState } from '@/page-layout/states/pageLayoutDraftState'; import { act, renderHook } from '@testing-library/react'; import { RecoilRoot, useRecoilValue } from 'recoil'; import { useCreatePageLayoutTab } from '../useCreatePageLayoutTab'; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts similarity index 90% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts index c109851893..d8f289a575 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts @@ -1,14 +1,11 @@ -import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '@/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId'; -import { - GraphType, - WidgetType, -} from '@/settings/page-layout/mocks/mockWidgets'; -import { pageLayoutCurrentLayoutsState } from '@/settings/page-layout/states/pageLayoutCurrentLayoutsState'; -import { pageLayoutDraftState } from '@/settings/page-layout/states/pageLayoutDraftState'; -import { PageLayoutType } from '@/settings/page-layout/states/savedPageLayoutsState'; +import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '@/page-layout/constants/SettingsPageLayoutTabsInstanceId'; +import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets'; +import { pageLayoutCurrentLayoutsState } from '@/page-layout/states/pageLayoutCurrentLayoutsState'; +import { pageLayoutDraftState } from '@/page-layout/states/pageLayoutDraftState'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { act, renderHook } from '@testing-library/react'; import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil'; +import { PageLayoutType } from '~/generated/graphql'; import { useCreatePageLayoutWidget } from '../useCreatePageLayoutWidget'; jest.mock('uuid', () => ({ @@ -158,9 +155,8 @@ describe('useCreatePageLayoutWidget', () => { const widget = result.current.allWidgets[index]; expect(widget.type).toBe(WidgetType.GRAPH); expect(widget.pageLayoutTabId).toBe('tab-1'); - expect(widget.configuration?.graphType).toBe(graphType); + expect(widget.configuration.graphType).toBe(graphType); expect(widget.id).toBe('widget-mock-uuid'); - expect(widget.data).toBeDefined(); }); expect(result.current.pageLayoutCurrentLayouts['tab-1']).toBeDefined(); diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useDeletePageLayoutWidget.test.ts b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useDeletePageLayoutWidget.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useDeletePageLayoutWidget.test.ts rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/useDeletePageLayoutWidget.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useEndPageLayoutDragSelection.test.ts b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useEndPageLayoutDragSelection.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useEndPageLayoutDragSelection.test.ts rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/useEndPageLayoutDragSelection.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts similarity index 94% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts index 3e9326c82a..d5cfd74804 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts @@ -1,10 +1,7 @@ -import { - GraphType, - WidgetType, -} from '@/settings/page-layout/mocks/mockWidgets'; -import { PageLayoutType } from '@/settings/page-layout/states/savedPageLayoutsState'; +import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets'; import { act, renderHook } from '@testing-library/react'; import { RecoilRoot } from 'recoil'; +import { PageLayoutType } from '~/generated/graphql'; import { usePageLayoutDraftState } from '../usePageLayoutDraftState'; describe('usePageLayoutDraftState', () => { diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.ts b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.ts rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutHandleLayoutChange.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useStartPageLayoutDragSelection.test.ts b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useStartPageLayoutDragSelection.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useStartPageLayoutDragSelection.test.ts rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/useStartPageLayoutDragSelection.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useChangePageLayoutDragSelection.ts b/packages/twenty-front/src/modules/page-layout/hooks/useChangePageLayoutDragSelection.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useChangePageLayoutDragSelection.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useChangePageLayoutDragSelection.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutIframeWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts similarity index 72% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutIframeWidget.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts index 3ff4adccda..97ca591902 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutIframeWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts @@ -1,16 +1,16 @@ -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '@/page-layout/constants/SettingsPageLayoutTabsInstanceId'; +import { pageLayoutCurrentLayoutsState } from '@/page-layout/states/pageLayoutCurrentLayoutsState'; +import { pageLayoutDraftState } from '@/page-layout/states/pageLayoutDraftState'; +import { pageLayoutDraggedAreaState } from '@/page-layout/states/pageLayoutDraggedAreaState'; +import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; +import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab'; +import { createUpdatedTabLayouts } from '@/page-layout/utils/createUpdatedTabLayouts'; +import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useRecoilCallback } from 'recoil'; import { v4 as uuidv4 } from 'uuid'; -import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '../constants/SettingsPageLayoutTabsInstanceId'; -import { WidgetType } from '../mocks/mockWidgets'; -import { pageLayoutCurrentLayoutsState } from '../states/pageLayoutCurrentLayoutsState'; -import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; -import { pageLayoutDraggedAreaState } from '../states/pageLayoutDraggedAreaState'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; -import { addWidgetToTab } from '../utils/addWidgetToTab'; -import { createUpdatedTabLayouts } from '../utils/createUpdatedTabLayouts'; -import { getDefaultWidgetPosition } from '../utils/getDefaultWidgetPosition'; +import { WidgetType } from '~/generated/graphql'; export const useCreatePageLayoutIframeWidget = () => { const activeTabId = useRecoilComponentValue( @@ -39,7 +39,7 @@ export const useCreatePageLayoutIframeWidget = () => { defaultSize, ); - const newWidget: PageLayoutWidget = { + const newWidget: PageLayoutWidgetWithData = { id: widgetId, pageLayoutTabId: activeTabId, title, @@ -53,6 +53,7 @@ export const useCreatePageLayoutIframeWidget = () => { configuration: { url, }, + data: {}, objectMetadataId: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts similarity index 77% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutTab.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts index e930b42f7b..1293eebfc7 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutTab.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts @@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil'; import { v4 as uuidv4 } from 'uuid'; import { pageLayoutCurrentLayoutsState } from '../states/pageLayoutCurrentLayoutsState'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; -import { type PageLayoutTab } from '../states/savedPageLayoutsState'; +import { type PageLayoutTabWithData } from '../types/pageLayoutTypes'; import { createEmptyTabLayout } from '../utils/createEmptyTabLayout'; export const useCreatePageLayoutTab = () => { @@ -14,10 +14,11 @@ export const useCreatePageLayoutTab = () => { .getValue(); const newTabId = `tab-${uuidv4()}`; - const newTab: PageLayoutTab = { + const tabsLength = pageLayoutDraft.tabs.length; + const newTab: PageLayoutTabWithData = { id: newTabId, - title: title || `Tab ${pageLayoutDraft.tabs.length + 1}`, - position: pageLayoutDraft.tabs.length, + title: title || `Tab ${tabsLength + 1}`, + position: tabsLength, pageLayoutId: '', widgets: [], createdAt: new Date().toISOString(), @@ -25,7 +26,7 @@ export const useCreatePageLayoutTab = () => { deletedAt: null, }; - const updatedTabs = [...pageLayoutDraft.tabs, newTab]; + const updatedTabs = [...(pageLayoutDraft.tabs || []), newTab]; set(pageLayoutDraftState, (prev) => ({ ...prev, diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutWidget.ts similarity index 71% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutWidget.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutWidget.ts index 7bf4d18853..f3e9429e25 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutWidget.ts @@ -1,21 +1,22 @@ -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilCallback } from 'recoil'; -import { v4 as uuidv4 } from 'uuid'; -import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '../constants/SettingsPageLayoutTabsInstanceId'; -import { type GraphType, type WidgetType } from '../mocks/mockWidgets'; -import { pageLayoutCurrentLayoutsState } from '../states/pageLayoutCurrentLayoutsState'; -import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; -import { pageLayoutDraggedAreaState } from '../states/pageLayoutDraggedAreaState'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; -import { addWidgetToTab } from '../utils/addWidgetToTab'; -import { createUpdatedTabLayouts } from '../utils/createUpdatedTabLayouts'; +import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '@/page-layout/constants/SettingsPageLayoutTabsInstanceId'; +import { type GraphType } from '@/page-layout/mocks/mockWidgets'; +import { pageLayoutCurrentLayoutsState } from '@/page-layout/states/pageLayoutCurrentLayoutsState'; +import { pageLayoutDraftState } from '@/page-layout/states/pageLayoutDraftState'; +import { pageLayoutDraggedAreaState } from '@/page-layout/states/pageLayoutDraggedAreaState'; +import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; +import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab'; +import { createUpdatedTabLayouts } from '@/page-layout/utils/createUpdatedTabLayouts'; import { getDefaultWidgetData, getWidgetSize, getWidgetTitle, -} from '../utils/getDefaultWidgetData'; -import { getDefaultWidgetPosition } from '../utils/getDefaultWidgetPosition'; +} from '@/page-layout/utils/getDefaultWidgetData'; +import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition'; +import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRecoilCallback } from 'recoil'; +import { v4 as uuidv4 } from 'uuid'; +import { type WidgetType } from '~/generated/graphql'; export const useCreatePageLayoutWidget = () => { const activeTabId = useRecoilComponentValue( @@ -45,7 +46,7 @@ export const useCreatePageLayoutWidget = () => { const allWidgets = pageLayoutDraft.tabs.flatMap((tab) => tab.widgets); const existingWidgetCount = allWidgets.filter( (w) => - w.type === widgetType && w.configuration?.graphType === graphType, + w.type === widgetType && w.configuration.graphType === graphType, ).length; const title = getWidgetTitle(graphType, existingWidgetCount); const widgetId = `widget-${uuidv4()}`; @@ -56,7 +57,7 @@ export const useCreatePageLayoutWidget = () => { defaultSize, ); - const newWidget: PageLayoutWidget = { + const newWidget: PageLayoutWidgetWithData = { id: widgetId, pageLayoutTabId: activeTabId, title, @@ -70,7 +71,7 @@ export const useCreatePageLayoutWidget = () => { configuration: { graphType, }, - data: widgetData, + data: widgetData as Record, objectMetadataId: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useDeletePageLayoutWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutWidget.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useDeletePageLayoutWidget.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutWidget.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useEndPageLayoutDragSelection.ts b/packages/twenty-front/src/modules/page-layout/hooks/useEndPageLayoutDragSelection.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useEndPageLayoutDragSelection.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useEndPageLayoutDragSelection.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDraftState.ts b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutDraftState.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDraftState.ts rename to packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutDraftState.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutHandleLayoutChange.ts b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts similarity index 94% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutHandleLayoutChange.ts rename to packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts index b7cb7882d2..6cbb965603 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutHandleLayoutChange.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts @@ -3,7 +3,7 @@ import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { pageLayoutCurrentLayoutsState } from '../states/pageLayoutCurrentLayoutsState'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; +import { type PageLayoutWidgetWithData } from '../types/pageLayoutTypes'; import { convertLayoutsToWidgets } from '../utils/convertLayoutsToWidgets'; export const usePageLayoutHandleLayoutChange = (activeTabId: string | null) => { @@ -38,7 +38,7 @@ export const usePageLayoutHandleLayoutChange = (activeTabId: string | null) => { ...prev, tabs: prev.tabs.map((tab) => { if (tab.id === activeTabId) { - const tabWidgets: PageLayoutWidget[] = updatedWidgets + const tabWidgets: PageLayoutWidgetWithData[] = updatedWidgets .filter((w) => w.pageLayoutTabId === activeTabId) .map((widget) => ({ id: widget.id, diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutSaveHandler.ts b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutSaveHandler.ts similarity index 87% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutSaveHandler.ts rename to packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutSaveHandler.ts index 03f1a87acb..b0fd37dead 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutSaveHandler.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutSaveHandler.ts @@ -1,3 +1,8 @@ +import { savedPageLayoutsState } from '@/page-layout/states/savedPageLayoutsState'; +import { + type PageLayoutWidgetWithData, + type PageLayoutWithData, +} from '@/page-layout/types/pageLayoutTypes'; import { useParams } from 'react-router-dom'; import { useRecoilCallback } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; @@ -6,11 +11,6 @@ import { v4 as uuidv4 } from 'uuid'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; import { pageLayoutPersistedState } from '../states/pageLayoutPersistedState'; -import { - savedPageLayoutsState, - type PageLayoutWidget, - type SavedPageLayout, -} from '../states/savedPageLayoutsState'; export const usePageLayoutSaveHandler = () => { const navigateSettings = useNavigateSettings(); @@ -19,7 +19,7 @@ export const usePageLayoutSaveHandler = () => { const savePageLayout = useRecoilCallback( ({ snapshot, set }) => - async (widgetsWithPositions?: PageLayoutWidget[]) => { + async (widgetsWithPositions?: PageLayoutWidgetWithData[]) => { const pageLayoutDraft = snapshot .getLoadable(pageLayoutDraftState) .getValue(); @@ -40,7 +40,7 @@ export const usePageLayoutSaveHandler = () => { })) : pageLayoutDraft.tabs; - const layoutToSave: SavedPageLayout = { + const layoutToSave: PageLayoutWithData = { id: isEditMode ? id : uuidv4(), name: pageLayoutDraft.name, type: pageLayoutDraft.type, diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useStartPageLayoutDragSelection.ts b/packages/twenty-front/src/modules/page-layout/hooks/useStartPageLayoutDragSelection.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useStartPageLayoutDragSelection.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useStartPageLayoutDragSelection.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useUpdatePageLayoutWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useUpdatePageLayoutWidget.ts similarity index 68% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useUpdatePageLayoutWidget.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useUpdatePageLayoutWidget.ts index 772428991a..9e027c8ed0 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useUpdatePageLayoutWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useUpdatePageLayoutWidget.ts @@ -1,11 +1,11 @@ +import { pageLayoutDraftState } from '@/page-layout/states/pageLayoutDraftState'; +import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; import { useRecoilCallback } from 'recoil'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; -import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; export const useUpdatePageLayoutWidget = () => { const updatePageLayoutWidget = useRecoilCallback( ({ set }) => - (widgetId: string, updates: Partial) => { + (widgetId: string, updates: Partial) => { set(pageLayoutDraftState, (prev) => ({ ...prev, tabs: prev.tabs.map((tab) => ({ diff --git a/packages/twenty-front/src/modules/settings/page-layout/mocks/mockWidgets.ts b/packages/twenty-front/src/modules/page-layout/mocks/mockWidgets.ts similarity index 96% rename from packages/twenty-front/src/modules/settings/page-layout/mocks/mockWidgets.ts rename to packages/twenty-front/src/modules/page-layout/mocks/mockWidgets.ts index f1369e661b..402445f737 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/mocks/mockWidgets.ts +++ b/packages/twenty-front/src/modules/page-layout/mocks/mockWidgets.ts @@ -1,5 +1,5 @@ import { type Layouts } from 'react-grid-layout'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; +import { type PageLayoutWidgetWithData } from '../types/pageLayoutTypes'; export enum WidgetType { VIEW = 'VIEW', @@ -16,7 +16,7 @@ export enum GraphType { LINE = 'LINE', } -export const mockPageLayoutWidgets: PageLayoutWidget[] = [ +export const mockPageLayoutWidgets: PageLayoutWidgetWithData[] = [ { id: 'widget-1', pageLayoutTabId: 'tab-overview', diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutCurrentBreakpointState.ts b/packages/twenty-front/src/modules/page-layout/states/pageLayoutCurrentBreakpointState.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutCurrentBreakpointState.ts rename to packages/twenty-front/src/modules/page-layout/states/pageLayoutCurrentBreakpointState.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutCurrentLayoutsState.ts b/packages/twenty-front/src/modules/page-layout/states/pageLayoutCurrentLayoutsState.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutCurrentLayoutsState.ts rename to packages/twenty-front/src/modules/page-layout/states/pageLayoutCurrentLayoutsState.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraftState.ts b/packages/twenty-front/src/modules/page-layout/states/pageLayoutDraftState.ts similarity index 71% rename from packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraftState.ts rename to packages/twenty-front/src/modules/page-layout/states/pageLayoutDraftState.ts index e6c21e9a26..e268f4d6d1 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraftState.ts +++ b/packages/twenty-front/src/modules/page-layout/states/pageLayoutDraftState.ts @@ -1,8 +1,9 @@ import { createState } from 'twenty-ui/utilities'; -import { PageLayoutType, type SavedPageLayout } from './savedPageLayoutsState'; +import { PageLayoutType } from '~/generated/graphql'; +import { type PageLayoutWithData } from '../types/pageLayoutTypes'; export type DraftPageLayout = Omit< - SavedPageLayout, + PageLayoutWithData, 'id' | 'createdAt' | 'updatedAt' | 'deletedAt' >; diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraggedAreaState.ts b/packages/twenty-front/src/modules/page-layout/states/pageLayoutDraggedAreaState.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraggedAreaState.ts rename to packages/twenty-front/src/modules/page-layout/states/pageLayoutDraggedAreaState.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutEditingWidgetIdState.ts b/packages/twenty-front/src/modules/page-layout/states/pageLayoutEditingWidgetIdState.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutEditingWidgetIdState.ts rename to packages/twenty-front/src/modules/page-layout/states/pageLayoutEditingWidgetIdState.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutPersistedState.ts b/packages/twenty-front/src/modules/page-layout/states/pageLayoutPersistedState.ts similarity index 63% rename from packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutPersistedState.ts rename to packages/twenty-front/src/modules/page-layout/states/pageLayoutPersistedState.ts index aa1dc4a6fc..def8b9b951 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutPersistedState.ts +++ b/packages/twenty-front/src/modules/page-layout/states/pageLayoutPersistedState.ts @@ -1,8 +1,8 @@ import { createState } from 'twenty-ui/utilities'; -import { type SavedPageLayout } from './savedPageLayoutsState'; +import { type PageLayoutWithData } from '../types/pageLayoutTypes'; export const pageLayoutPersistedState = createState< - SavedPageLayout | undefined + PageLayoutWithData | undefined >({ key: 'pageLayoutPersistedState', defaultValue: undefined, diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutSelectedCellsState.ts b/packages/twenty-front/src/modules/page-layout/states/pageLayoutSelectedCellsState.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutSelectedCellsState.ts rename to packages/twenty-front/src/modules/page-layout/states/pageLayoutSelectedCellsState.ts diff --git a/packages/twenty-front/src/modules/page-layout/states/savedPageLayoutsState.ts b/packages/twenty-front/src/modules/page-layout/states/savedPageLayoutsState.ts new file mode 100644 index 0000000000..fb82c445ae --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/states/savedPageLayoutsState.ts @@ -0,0 +1,7 @@ +import { createState } from 'twenty-ui/utilities'; +import { type PageLayoutWithData } from '../types/pageLayoutTypes'; + +export const savedPageLayoutsState = createState({ + key: 'savedPageLayoutsState', + defaultValue: [], +}); diff --git a/packages/twenty-front/src/modules/page-layout/types/pageLayoutTypes.ts b/packages/twenty-front/src/modules/page-layout/types/pageLayoutTypes.ts new file mode 100644 index 0000000000..55ce02298c --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/types/pageLayoutTypes.ts @@ -0,0 +1,18 @@ +import { + type PageLayout, + type PageLayoutTab, + type PageLayoutWidget, +} from '~/generated/graphql'; + +// TODO: Remove this once we query the data from the database +export type PageLayoutWidgetWithData = PageLayoutWidget & { + data?: Record; +}; + +export type PageLayoutTabWithData = Omit & { + widgets: PageLayoutWidgetWithData[]; +}; + +export type PageLayoutWithData = Omit & { + tabs: PageLayoutTabWithData[]; +}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/addWidgetToTab.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/addWidgetToTab.test.ts similarity index 90% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/addWidgetToTab.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/addWidgetToTab.test.ts index 94041629ab..d1396f0456 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/addWidgetToTab.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/addWidgetToTab.test.ts @@ -1,12 +1,12 @@ import { WidgetType } from '../../mocks/mockWidgets'; import { - type PageLayoutTab, - type PageLayoutWidget, -} from '../../states/savedPageLayoutsState'; + type PageLayoutTabWithData, + type PageLayoutWidgetWithData, +} from '../../types/pageLayoutTypes'; import { addWidgetToTab } from '../addWidgetToTab'; describe('addWidgetToTab', () => { - const mockWidget: PageLayoutWidget = { + const mockWidget: PageLayoutWidgetWithData = { id: 'widget-1', pageLayoutTabId: 'tab-1', title: 'Test Widget', @@ -18,7 +18,7 @@ describe('addWidgetToTab', () => { deletedAt: null, }; - const mockTabs: PageLayoutTab[] = [ + const mockTabs: PageLayoutTabWithData[] = [ { id: 'tab-1', title: 'Tab 1', @@ -65,7 +65,7 @@ describe('addWidgetToTab', () => { }); it('should add multiple widgets to the same tab', () => { - const secondWidget: PageLayoutWidget = { + const secondWidget: PageLayoutWidgetWithData = { ...mockWidget, id: 'widget-2', title: 'Second Widget', diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/calculateGridBoundsFromSelectedCells.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/calculateGridBoundsFromSelectedCells.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/calculateGridBoundsFromSelectedCells.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/calculateGridBoundsFromSelectedCells.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/calculateTotalGridRows.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/calculateTotalGridRows.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/calculateTotalGridRows.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/calculateTotalGridRows.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts similarity index 94% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts index fc917b473a..5ddbe34737 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts @@ -1,9 +1,9 @@ import { GraphType, WidgetType } from '../../mocks/mockWidgets'; -import { type PageLayoutWidget } from '../../states/savedPageLayoutsState'; +import { type PageLayoutWidgetWithData } from '../../types/pageLayoutTypes'; import { convertLayoutsToWidgets } from '../convertLayoutsToWidgets'; describe('convertLayoutsToWidgets', () => { - const mockWidgets: PageLayoutWidget[] = [ + const mockWidgets: PageLayoutWidgetWithData[] = [ { id: 'widget-1', pageLayoutTabId: 'tab-1', diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/createEmptyTabLayout.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/createEmptyTabLayout.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/createEmptyTabLayout.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/createEmptyTabLayout.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/createUpdatedTabLayouts.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/createUpdatedTabLayouts.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/createUpdatedTabLayouts.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/createUpdatedTabLayouts.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/generateCellId.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/generateCellId.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/generateCellId.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/generateCellId.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/getDefaultWidgetPosition.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/parseCellIdToCoordinates.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/parseCellIdToCoordinates.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/parseCellIdToCoordinates.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/parseCellIdToCoordinates.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/removeWidgetFromTab.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/removeWidgetFromTab.test.ts similarity index 96% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/removeWidgetFromTab.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/removeWidgetFromTab.test.ts index 7890f86258..13d03f6345 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/removeWidgetFromTab.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/removeWidgetFromTab.test.ts @@ -1,9 +1,9 @@ import { WidgetType } from '../../mocks/mockWidgets'; -import { type PageLayoutTab } from '../../states/savedPageLayoutsState'; +import { type PageLayoutTabWithData } from '../../types/pageLayoutTypes'; import { removeWidgetFromTab } from '../removeWidgetFromTab'; describe('removeWidgetFromTab', () => { - const mockTabs: PageLayoutTab[] = [ + const mockTabs: PageLayoutTabWithData[] = [ { id: 'tab-1', title: 'Tab 1', diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/removeWidgetLayoutFromTab.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/removeWidgetLayoutFromTab.test.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/removeWidgetLayoutFromTab.test.ts rename to packages/twenty-front/src/modules/page-layout/utils/__tests__/removeWidgetLayoutFromTab.test.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/addWidgetToTab.ts b/packages/twenty-front/src/modules/page-layout/utils/addWidgetToTab.ts similarity index 54% rename from packages/twenty-front/src/modules/settings/page-layout/utils/addWidgetToTab.ts rename to packages/twenty-front/src/modules/page-layout/utils/addWidgetToTab.ts index 50a2f3b94b..97664ef841 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/addWidgetToTab.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/addWidgetToTab.ts @@ -1,13 +1,13 @@ import { - type PageLayoutTab, - type PageLayoutWidget, -} from '../states/savedPageLayoutsState'; + type PageLayoutTabWithData, + type PageLayoutWidgetWithData, +} from '../types/pageLayoutTypes'; export const addWidgetToTab = ( - tabs: PageLayoutTab[], + tabs: PageLayoutTabWithData[], activeTabId: string, - newWidget: PageLayoutWidget, -): PageLayoutTab[] => { + newWidget: PageLayoutWidgetWithData, +): PageLayoutTabWithData[] => { return tabs.map((tab) => { if (tab.id === activeTabId) { return { diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/calculateGridBoundsFromSelectedCells.ts b/packages/twenty-front/src/modules/page-layout/utils/calculateGridBoundsFromSelectedCells.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/calculateGridBoundsFromSelectedCells.ts rename to packages/twenty-front/src/modules/page-layout/utils/calculateGridBoundsFromSelectedCells.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/calculateTotalGridRows.ts b/packages/twenty-front/src/modules/page-layout/utils/calculateTotalGridRows.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/calculateTotalGridRows.ts rename to packages/twenty-front/src/modules/page-layout/utils/calculateTotalGridRows.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/convertLayoutsToWidgets.ts b/packages/twenty-front/src/modules/page-layout/utils/convertLayoutsToWidgets.ts similarity index 75% rename from packages/twenty-front/src/modules/settings/page-layout/utils/convertLayoutsToWidgets.ts rename to packages/twenty-front/src/modules/page-layout/utils/convertLayoutsToWidgets.ts index 1f42c4ef44..5943be2cdd 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/convertLayoutsToWidgets.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/convertLayoutsToWidgets.ts @@ -1,10 +1,10 @@ +import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; import { type Layouts } from 'react-grid-layout'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; export const convertLayoutsToWidgets = ( - widgets: PageLayoutWidget[], + widgets: PageLayoutWidgetWithData[], layouts: Layouts, -): PageLayoutWidget[] => { +): PageLayoutWidgetWithData[] => { const activeLayouts = layouts.desktop || layouts.mobile || []; return widgets.map((widget) => { diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/createEmptyTabLayout.ts b/packages/twenty-front/src/modules/page-layout/utils/createEmptyTabLayout.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/createEmptyTabLayout.ts rename to packages/twenty-front/src/modules/page-layout/utils/createEmptyTabLayout.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/createUpdatedTabLayouts.ts b/packages/twenty-front/src/modules/page-layout/utils/createUpdatedTabLayouts.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/createUpdatedTabLayouts.ts rename to packages/twenty-front/src/modules/page-layout/utils/createUpdatedTabLayouts.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/generateCellId.ts b/packages/twenty-front/src/modules/page-layout/utils/generateCellId.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/generateCellId.ts rename to packages/twenty-front/src/modules/page-layout/utils/generateCellId.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetData.ts b/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetData.ts similarity index 93% rename from packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetData.ts rename to packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetData.ts index 1bc8c78454..95c795c45b 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetData.ts @@ -97,9 +97,16 @@ export const getDefaultWidgetData = (graphType: GraphType) => { showLegend: true, showGrid: true, enablePoints: false, + xAxisLabel: 'Time', + yAxisLabel: 'Value', curve: 'monotoneX', displayType: 'shortNumber', prefix: '$', + suffix: '', + xScale: { type: 'linear' }, + yScale: { type: 'linear', min: 0, max: 'auto' }, + stackedArea: false, + enableSlices: false, }; default: diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetPosition.ts b/packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetPosition.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetPosition.ts rename to packages/twenty-front/src/modules/page-layout/utils/getDefaultWidgetPosition.ts diff --git a/packages/twenty-front/src/modules/page-layout/utils/normalizePageLayoutData.ts b/packages/twenty-front/src/modules/page-layout/utils/normalizePageLayoutData.ts new file mode 100644 index 0000000000..c7e0b1ce80 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/utils/normalizePageLayoutData.ts @@ -0,0 +1,17 @@ +import { type PageLayout } from '~/generated/graphql'; +import { type PageLayoutWithData } from '../types/pageLayoutTypes'; + +export const normalizePageLayoutData = ( + pageLayout: PageLayout, +): PageLayoutWithData => { + return { + ...pageLayout, + tabs: (pageLayout.tabs || []).map((tab) => ({ + ...tab, + widgets: (tab.widgets || []).map((widget) => ({ + ...widget, + data: undefined, + })), + })), + }; +}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/parseCellIdToCoordinates.ts b/packages/twenty-front/src/modules/page-layout/utils/parseCellIdToCoordinates.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/parseCellIdToCoordinates.ts rename to packages/twenty-front/src/modules/page-layout/utils/parseCellIdToCoordinates.ts diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/removeWidgetFromTab.ts b/packages/twenty-front/src/modules/page-layout/utils/removeWidgetFromTab.ts similarity index 66% rename from packages/twenty-front/src/modules/settings/page-layout/utils/removeWidgetFromTab.ts rename to packages/twenty-front/src/modules/page-layout/utils/removeWidgetFromTab.ts index abb6fe42c7..8546946e16 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/removeWidgetFromTab.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/removeWidgetFromTab.ts @@ -1,10 +1,10 @@ -import { type PageLayoutTab } from '../states/savedPageLayoutsState'; +import { type PageLayoutTabWithData } from '../types/pageLayoutTypes'; export const removeWidgetFromTab = ( - tabs: PageLayoutTab[], + tabs: PageLayoutTabWithData[], tabId: string, widgetId: string, -): PageLayoutTab[] => { +): PageLayoutTabWithData[] => { return tabs.map((tab) => { if (tab.id === tabId) { return { diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/removeWidgetLayoutFromTab.ts b/packages/twenty-front/src/modules/page-layout/utils/removeWidgetLayoutFromTab.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/page-layout/utils/removeWidgetLayoutFromTab.ts rename to packages/twenty-front/src/modules/page-layout/utils/removeWidgetLayoutFromTab.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContainer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContainer.tsx new file mode 100644 index 0000000000..546b8703d3 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContainer.tsx @@ -0,0 +1,34 @@ +import styled from '@emotion/styled'; +import { type ReactNode } from 'react'; +import { isDefined } from 'twenty-shared/utils'; + +const StyledContainer = styled.div<{ onClick?: () => void }>` + background: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.md}; + box-sizing: border-box; + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + position: relative; + padding: ${({ theme }) => theme.spacing(4)}; + + &:hover { + cursor: ${({ onClick }) => (isDefined(onClick) ? 'pointer' : 'default')}; + border: 1px solid ${({ theme }) => theme.color.blue}; + background: ${({ theme }) => theme.background.primary}; + } +`; + +type WidgetContainerProps = { + children?: ReactNode; + onClick?: () => void; +}; + +export const WidgetContainer = ({ + children, + onClick, +}: WidgetContainerProps) => { + return {children}; +}; 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 new file mode 100644 index 0000000000..4e48fe5304 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx @@ -0,0 +1,27 @@ +import { GraphWidgetRenderer } from '@/page-layout/widgets/graph/components/GraphWidgetRenderer'; +import { IframeWidget } from '@/page-layout/widgets/iframe/components/IframeWidget'; +import { type Widget } from '@/page-layout/widgets/types/Widget'; +import { isString } from '@sniptt/guards'; +import { WidgetType } from '~/generated/graphql'; + +type WidgetContentRendererProps = { + widget: Widget; +}; + +export const WidgetContentRenderer = ({ + widget, +}: WidgetContentRendererProps) => { + switch (widget.type) { + case WidgetType.GRAPH: + return ; + + case WidgetType.IFRAME: { + const url = widget.configuration?.url; + return ( + + ); + } + default: + return null; + } +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetHeader.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetHeader.tsx new file mode 100644 index 0000000000..f2d056193e --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetHeader.tsx @@ -0,0 +1,75 @@ +import styled from '@emotion/styled'; +import { IconGripVertical, IconPencil, IconX } from 'twenty-ui/display'; +import { IconButton } from 'twenty-ui/input'; + +const StyledHeader = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledDragHandleButton = styled(IconButton)` + cursor: grab; + display: flex; + align-items: center; + user-select: none; + + &:active { + cursor: grabbing; + } +`; + +const StyledTitle = styled.span` + color: ${({ theme }) => theme.font.color.secondary}; + flex: 1; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + user-select: none; +`; + +type WidgetHeaderProps = { + displayDragHandle: boolean; + isEmpty?: boolean; + title: string; + onRemove?: () => void; + onEdit?: () => void; +}; + +export const WidgetHeader = ({ + displayDragHandle = false, + isEmpty = false, + title, + onEdit, + onRemove, +}: WidgetHeaderProps) => { + return ( + + {displayDragHandle && ( + + )} + {isEmpty ? 'Add Widget' : title} + {!isEmpty && onEdit && ( + + )} + {!isEmpty && onRemove && ( + + )} + + ); +}; 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 new file mode 100644 index 0000000000..92215c0a01 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx @@ -0,0 +1,36 @@ +import { WidgetContainer } from '@/page-layout/widgets/components/WidgetContainer'; +import { WidgetHeader } from '@/page-layout/widgets/components/WidgetHeader'; +import { + AnimatedPlaceholder, + AnimatedPlaceholderEmptyContainer, + AnimatedPlaceholderEmptySubTitle, + AnimatedPlaceholderEmptyTextContainer, + AnimatedPlaceholderEmptyTitle, + EMPTY_PLACEHOLDER_TRANSITION_PROPS, +} from 'twenty-ui/layout'; + +type WidgetPlaceholderProps = { + onClick: () => void; +}; + +export const WidgetPlaceholder = ({ onClick }: WidgetPlaceholderProps) => { + return ( + + + + + + + No widgets yet + + + Click to add your first widget + + + + + ); +}; 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 new file mode 100644 index 0000000000..ee867ab024 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx @@ -0,0 +1,41 @@ +import { WidgetContainer } from '@/page-layout/widgets/components/WidgetContainer'; +import { WidgetContentRenderer } from '@/page-layout/widgets/components/WidgetContentRenderer'; +import { WidgetHeader } from '@/page-layout/widgets/components/WidgetHeader'; +import { type Widget as WidgetType } from '@/page-layout/widgets/types/Widget'; +import styled from '@emotion/styled'; + +type WidgetRendererProps = { + widget: WidgetType; + displayDragHandle?: boolean; + onEdit?: () => void; + onRemove?: () => void; +}; + +const StyledContent = styled.div` + align-items: center; + display: flex; + height: 100%; + width: 100%; + justify-content: center; +`; + +export const WidgetRenderer = ({ + widget, + displayDragHandle = false, + onEdit, + onRemove, +}: WidgetRendererProps) => { + return ( + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetPlaceholder.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetPlaceholder.stories.tsx new file mode 100644 index 0000000000..afb863b4c6 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetPlaceholder.stories.tsx @@ -0,0 +1,41 @@ +import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder'; +import { type Meta, type StoryObj } from '@storybook/react'; +import { ComponentDecorator } from 'twenty-ui/testing'; + +const meta: Meta = { + title: 'Modules/PageLayout/Widgets/WidgetPlaceholder', + component: WidgetPlaceholder, + decorators: [ComponentDecorator], + parameters: { + layout: 'centered', + docs: { + description: { + component: + 'A placeholder widget that appears when no widgets are present. Shows an empty state with a call-to-action to add the first widget.', + }, + }, + }, + argTypes: { + onClick: { + action: 'onClick', + description: + 'Callback function triggered when the placeholder is clicked', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + onClick: () => {}, + }, + parameters: { + docs: { + description: { + story: 'Default widget placeholder state.', + }, + }, + }, +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx new file mode 100644 index 0000000000..a1d79b4e5c --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx @@ -0,0 +1,397 @@ +import { GraphType } from '@/page-layout/mocks/mockWidgets'; +import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; +import { type Meta, type StoryObj } from '@storybook/react'; +import { ComponentDecorator } from 'twenty-ui/testing'; +import { WidgetType } from '~/generated/graphql'; + +const meta: Meta = { + title: 'Modules/PageLayout/Widgets/WidgetRenderer', + component: WidgetRenderer, + decorators: [ComponentDecorator], + parameters: { + layout: 'centered', + }, + argTypes: { + widget: { + control: 'object', + description: 'Widget', + }, + displayDragHandle: { + control: 'boolean', + description: 'Display drag handle', + }, + onRemove: { + action: 'onRemove', + }, + onEdit: { + action: 'onEdit', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const WithNumberChart: Story = { + args: { + widget: { + title: 'Sales Pipeline', + type: WidgetType.GRAPH, + configuration: { + graphType: GraphType.NUMBER, + }, + gridPosition: { + row: 0, + column: 0, + rowSpan: 2, + columnSpan: 3, + }, + id: 'widget-1', + pageLayoutTabId: 'tab-overview', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + data: { + value: '1,234', + trendPercentage: 12.5, + }, + }, + displayDragHandle: true, + }, + render: (args) => ( +
+ +
+ ), +}; + +export const WithGaugeChart: Story = { + args: { + widget: { + title: 'Conversion Rate', + type: WidgetType.GRAPH, + configuration: { + graphType: GraphType.GAUGE, + }, + data: { + value: 0.5, + min: 0, + max: 1, + label: 'Conversion rate', + }, + createdAt: '2024-01-01T00:00:00Z', + gridPosition: { + row: 0, + column: 0, + rowSpan: 5, + columnSpan: 3, + }, + id: 'widget-1', + pageLayoutTabId: 'tab-overview', + updatedAt: '2024-01-01T00:00:00Z', + }, + }, + render: (args) => ( +
+ +
+ ), +}; + +export const WithPieChart: Story = { + args: { + widget: { + title: 'Lead Distribution', + type: WidgetType.GRAPH, + configuration: { + graphType: GraphType.PIE, + }, + data: { + items: [ + { + id: 'qualified', + value: 35, + label: 'Qualified', + to: '/leads/qualified', + }, + { + id: 'contacted', + value: 25, + label: 'Contacted', + to: '/leads/contacted', + }, + { + id: 'unqualified', + value: 20, + label: 'Unqualified', + to: '/leads/unqualified', + }, + { + id: 'proposal', + value: 15, + label: 'Proposal', + to: '/leads/proposal', + }, + { + id: 'negotiation', + value: 5, + label: 'Negotiation', + to: '/leads/negotiation', + }, + ], + }, + createdAt: '2024-01-01T00:00:00Z', + gridPosition: { + row: 0, + column: 0, + rowSpan: 5, + columnSpan: 3, + }, + id: 'widget-1', + pageLayoutTabId: 'tab-overview', + updatedAt: '2024-01-01T00:00:00Z', + }, + }, + render: (args) => ( +
+ +
+ ), +}; + +export const SmallWidget: Story = { + args: { + widget: { + title: 'Small Widget (2x2 grid)', + type: WidgetType.GRAPH, + configuration: { + graphType: GraphType.NUMBER, + }, + data: { + value: '42', + trendPercentage: 5, + }, + createdAt: '2024-01-01T00:00:00Z', + gridPosition: { + row: 0, + column: 0, + rowSpan: 2, + columnSpan: 2, + }, + id: 'widget-1', + pageLayoutTabId: 'tab-overview', + updatedAt: '2024-01-01T00:00:00Z', + }, + }, + parameters: { + docs: { + description: { + story: 'Simulates a 2x2 grid cell widget', + }, + }, + }, + render: (args) => ( +
+ +
+ ), +}; + +export const MediumWidget: Story = { + args: { + widget: { + title: 'Medium Widget (4x3 grid)', + type: WidgetType.GRAPH, + configuration: { + graphType: GraphType.GAUGE, + }, + data: { + value: 0.75, + min: 0, + max: 1, + label: 'Progress', + }, + createdAt: '2024-01-01T00:00:00Z', + gridPosition: { + row: 0, + column: 0, + rowSpan: 3, + columnSpan: 4, + }, + id: 'widget-1', + pageLayoutTabId: 'tab-overview', + updatedAt: '2024-01-01T00:00:00Z', + }, + }, + parameters: { + docs: { + description: { + story: 'Simulates a 4x3 grid cell widget', + }, + }, + }, + render: (args) => ( +
+ +
+ ), +}; + +export const LargeWidget: Story = { + args: { + widget: { + title: 'Large Widget (6x4 grid)', + type: WidgetType.GRAPH, + configuration: { + graphType: GraphType.PIE, + }, + data: { + items: [ + { id: 'a', value: 40, label: 'Category A', to: '/a' }, + { id: 'b', value: 30, label: 'Category B', to: '/b' }, + { id: 'c', value: 20, label: 'Category C', to: '/c' }, + { id: 'd', value: 10, label: 'Category D', to: '/d' }, + ], + }, + createdAt: '2024-01-01T00:00:00Z', + gridPosition: { + row: 0, + column: 0, + rowSpan: 4, + columnSpan: 6, + }, + id: 'widget-1', + pageLayoutTabId: 'tab-overview', + updatedAt: '2024-01-01T00:00:00Z', + }, + }, + parameters: { + docs: { + description: { + story: 'Simulates a 6x4 grid cell widget', + }, + }, + }, + render: (args) => ( +
+ +
+ ), +}; + +export const WideWidget: Story = { + args: { + widget: { + title: 'Wide Widget (8x2 grid)', + type: WidgetType.GRAPH, + configuration: { + graphType: GraphType.NUMBER, + }, + data: { + value: '1,234,567', + trendPercentage: 23.4, + }, + createdAt: '2024-01-01T00:00:00Z', + gridPosition: { + row: 0, + column: 0, + rowSpan: 2, + columnSpan: 8, + }, + id: 'widget-1', + pageLayoutTabId: 'tab-overview', + updatedAt: '2024-01-01T00:00:00Z', + }, + }, + parameters: { + docs: { + description: { + story: 'Simulates a wide 8x2 grid cell widget', + }, + }, + }, + render: (args) => ( +
+ +
+ ), +}; + +export const TallWidget: Story = { + args: { + widget: { + title: 'Tall Widget (3x6 grid)', + type: WidgetType.GRAPH, + configuration: { + graphType: GraphType.GAUGE, + }, + data: { + value: 0.33, + min: 0, + max: 1, + label: 'Utilization', + }, + createdAt: '2024-01-01T00:00:00Z', + gridPosition: { + row: 0, + column: 0, + rowSpan: 6, + columnSpan: 3, + }, + id: 'widget-1', + pageLayoutTabId: 'tab-overview', + updatedAt: '2024-01-01T00:00:00Z', + }, + }, + parameters: { + docs: { + description: { + story: 'Simulates a tall 3x6 grid cell widget', + }, + }, + }, + render: (args) => ( +
+ +
+ ), +}; diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetBarChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetBarChart.tsx similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetBarChart.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetBarChart.tsx diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetGaugeChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetGaugeChart.tsx similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetGaugeChart.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetGaugeChart.tsx diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetLegend.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetLegend.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetLineChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLineChart.tsx similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetLineChart.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLineChart.tsx diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetNumberChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetNumberChart.tsx similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetNumberChart.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetNumberChart.tsx diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetPieChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetPieChart.tsx similarity index 95% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetPieChart.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetPieChart.tsx index f364bb80f0..ba50dae262 100644 --- a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetPieChart.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetPieChart.tsx @@ -131,7 +131,10 @@ export const GraphWidgetPieChart = ({ if (!item) return null; const dataItem = data.find((d) => d.id === datum.id); - const formattedValue = formatGraphValue(item.value, formatOptions); + const formattedValue = formatGraphValue( + displayType === 'percentage' ? item.percentage / 100 : item.value, + formatOptions, + ); const formattedWithPercentage = `${formattedValue} (${item.percentage.toFixed(1)}%)`; return ( @@ -217,7 +220,10 @@ export const GraphWidgetPieChart = ({ items={enrichedData.map((item) => ({ id: item.id, label: item.label || item.id, - formattedValue: formatGraphValue(item.value, formatOptions), + formattedValue: formatGraphValue( + displayType === 'percentage' ? item.percentage / 100 : item.value, + formatOptions, + ), color: item.colorScheme.solid, }))} /> diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetRenderer.tsx new file mode 100644 index 0000000000..d5c0352ed9 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetRenderer.tsx @@ -0,0 +1,101 @@ +import { GraphType } from '@/page-layout/mocks/mockWidgets'; +import { getDefaultWidgetData } from '@/page-layout/utils/getDefaultWidgetData'; +import { GraphWidgetBarChart } from '@/page-layout/widgets/graph/components/GraphWidgetBarChart'; +import { GraphWidgetGaugeChart } from '@/page-layout/widgets/graph/components/GraphWidgetGaugeChart'; +import { GraphWidgetLineChart } from '@/page-layout/widgets/graph/components/GraphWidgetLineChart'; +import { GraphWidgetNumberChart } from '@/page-layout/widgets/graph/components/GraphWidgetNumberChart'; +import { GraphWidgetPieChart } from '@/page-layout/widgets/graph/components/GraphWidgetPieChart'; +import { type GraphWidget } from '@/page-layout/widgets/graph/types/GraphWidget'; + +type GraphWidgetRendererProps = { + widget: GraphWidget; +}; + +export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => { + const graphType = widget.configuration?.graphType; + + if (!Object.values(GraphType).includes(graphType)) { + return null; + } + + const data = widget.data ?? getDefaultWidgetData(graphType); + + if (!data) { + return null; + } + + switch (graphType as GraphType) { + case GraphType.NUMBER: + return ( + + ); + + case GraphType.GAUGE: + return ( + + ); + + case GraphType.PIE: + return ( + + ); + + case GraphType.BAR: + return ( + + ); + + case GraphType.LINE: + return ( + + ); + + default: + return null; + } +}; diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetTooltip.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetTooltip.tsx similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/GraphWidgetTooltip.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetTooltip.tsx diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx similarity index 99% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx index e9a5dc8960..8b6fa42c90 100644 --- a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx @@ -4,7 +4,7 @@ import { CatalogDecorator, ComponentDecorator } from 'twenty-ui/testing'; import { GraphWidgetBarChart } from '../GraphWidgetBarChart'; const meta: Meta = { - title: 'Modules/Dashboards/Graphs/GraphWidgetBarChart', + title: 'Modules/PageLayout/Widgets/GraphWidgetBarChart', component: GraphWidgetBarChart, decorators: [ComponentDecorator], parameters: { diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx similarity index 99% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx index d5df861278..7234459b4e 100644 --- a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx @@ -4,7 +4,7 @@ import { CatalogDecorator, ComponentDecorator } from 'twenty-ui/testing'; import { GraphWidgetGaugeChart } from '../GraphWidgetGaugeChart'; const meta: Meta = { - title: 'Modules/Dashboards/Graphs/GraphWidgetGaugeChart', + title: 'Modules/PageLayout/Widgets/GraphWidgetGaugeChart', component: GraphWidgetGaugeChart, decorators: [ComponentDecorator], parameters: { diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetLegend.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetLegend.stories.tsx similarity index 95% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetLegend.stories.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetLegend.stories.tsx index 15e64aa2d8..15718130cf 100644 --- a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetLegend.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetLegend.stories.tsx @@ -4,7 +4,7 @@ import { ComponentDecorator } from 'twenty-ui/testing'; import { GraphWidgetLegend } from '../GraphWidgetLegend'; const meta: Meta = { - title: 'Modules/Dashboards/Graphs/GraphWidgetLegend', + title: 'Modules/PageLayout/Widgets/GraphWidgetLegend', component: GraphWidgetLegend, decorators: [ComponentDecorator], parameters: { diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetLineChart.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetLineChart.stories.tsx similarity index 99% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetLineChart.stories.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetLineChart.stories.tsx index b6da10d375..c80523d041 100644 --- a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetLineChart.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetLineChart.stories.tsx @@ -1,11 +1,11 @@ import { type Meta, type StoryObj } from '@storybook/react'; +import { type ComponentProps } from 'react'; import { CatalogDecorator, ComponentDecorator } from 'twenty-ui/testing'; import { GraphWidgetLineChart } from '../GraphWidgetLineChart'; -import { type ComponentProps } from 'react'; const meta: Meta = { - title: 'Modules/Dashboards/Graphs/GraphWidgetLineChart', + title: 'Modules/PageLayout/Widgets/GraphWidgetLineChart', component: GraphWidgetLineChart, decorators: [ComponentDecorator], parameters: { diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetNumberChart.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetNumberChart.stories.tsx similarity index 95% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetNumberChart.stories.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetNumberChart.stories.tsx index 3629efd356..c18694dce9 100644 --- a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetNumberChart.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetNumberChart.stories.tsx @@ -4,7 +4,7 @@ import { ComponentDecorator } from 'twenty-ui/testing'; import { GraphWidgetNumberChart } from '../GraphWidgetNumberChart'; const meta: Meta = { - title: 'Modules/Dashboards/Graphs/GraphWidgetNumberChart', + title: 'Modules/PageLayout/Widgets/GraphWidgetNumberChart', component: GraphWidgetNumberChart, decorators: [ComponentDecorator], }; diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx similarity index 99% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx index 264b3bb470..531cc1a169 100644 --- a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx @@ -4,7 +4,7 @@ import { CatalogDecorator, ComponentDecorator } from 'twenty-ui/testing'; import { GraphWidgetPieChart } from '../GraphWidgetPieChart'; const meta: Meta = { - title: 'Modules/Dashboards/Graphs/GraphWidgetPieChart', + title: 'Modules/PageLayout/Widgets/GraphWidgetPieChart', component: GraphWidgetPieChart, decorators: [ComponentDecorator], parameters: { diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetTooltip.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetTooltip.stories.tsx similarity index 95% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetTooltip.stories.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetTooltip.stories.tsx index 1162255621..202d447067 100644 --- a/packages/twenty-front/src/modules/dashboards/widgets/graph/components/__stories__/GraphWidgetTooltip.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetTooltip.stories.tsx @@ -4,7 +4,7 @@ import { ComponentDecorator } from 'twenty-ui/testing'; import { GraphWidgetTooltip } from '../GraphWidgetTooltip'; const meta: Meta = { - title: 'Modules/Dashboards/Graphs/GraphWidgetTooltip', + title: 'Modules/PageLayout/Widgets/GraphWidgetTooltip', component: GraphWidgetTooltip, decorators: [ComponentDecorator], parameters: { diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/types/GraphColor.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphColor.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/types/GraphColor.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphColor.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/types/GraphColorRegistry.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphColorRegistry.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/types/GraphColorRegistry.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphColorRegistry.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/types/GraphColorScheme.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphColorScheme.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/types/GraphColorScheme.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphColorScheme.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphWidget.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphWidget.ts new file mode 100644 index 0000000000..a80e13cffb --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GraphWidget.ts @@ -0,0 +1,10 @@ +import { type GraphType } from '@/page-layout/mocks/mockWidgets'; +import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; +import { type WidgetType } from '~/generated/graphql'; + +export type GraphWidget = PageLayoutWidgetWithData & { + type: WidgetType.GRAPH; + configuration: { + graphType: GraphType; + }; +}; diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/utils/calculateAngularGradient.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/calculateAngularGradient.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/utils/calculateAngularGradient.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/calculateAngularGradient.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/utils/createGradientDef.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/createGradientDef.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/utils/createGradientDef.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/createGradientDef.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/utils/createGraphColorRegistry.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/createGraphColorRegistry.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/utils/createGraphColorRegistry.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/createGraphColorRegistry.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/utils/getColorScheme.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getColorScheme.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/utils/getColorScheme.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getColorScheme.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/utils/getColorSchemeByIndex.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getColorSchemeByIndex.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/utils/getColorSchemeByIndex.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getColorSchemeByIndex.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/utils/getColorSchemeByName.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getColorSchemeByName.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/utils/getColorSchemeByName.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getColorSchemeByName.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/graph/utils/graphFormatters.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/graphFormatters.ts similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/graph/utils/graphFormatters.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/graphFormatters.ts diff --git a/packages/twenty-front/src/modules/dashboards/widgets/iframe/components/IframeWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/iframe/components/IframeWidget.tsx similarity index 100% rename from packages/twenty-front/src/modules/dashboards/widgets/iframe/components/IframeWidget.tsx rename to packages/twenty-front/src/modules/page-layout/widgets/iframe/components/IframeWidget.tsx diff --git a/packages/twenty-front/src/modules/page-layout/widgets/iframe/types/IframeWidget.ts b/packages/twenty-front/src/modules/page-layout/widgets/iframe/types/IframeWidget.ts new file mode 100644 index 0000000000..1dc9248357 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/iframe/types/IframeWidget.ts @@ -0,0 +1,9 @@ +import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; +import { type WidgetType } from '~/generated/graphql'; + +export type IframeWidget = PageLayoutWidgetWithData & { + type: WidgetType.IFRAME; + configuration: { + url: string; + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/types/Widget.ts b/packages/twenty-front/src/modules/page-layout/widgets/types/Widget.ts new file mode 100644 index 0000000000..87af872dcb --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/types/Widget.ts @@ -0,0 +1,4 @@ +import { type GraphWidget } from '@/page-layout/widgets/graph/types/GraphWidget'; +import { type IframeWidget } from '@/page-layout/widgets/iframe/types/IframeWidget'; + +export type Widget = GraphWidget | IframeWidget; diff --git a/packages/twenty-front/src/modules/settings/page-layout/components/GraphWidgetRenderer.tsx b/packages/twenty-front/src/modules/settings/page-layout/components/GraphWidgetRenderer.tsx deleted file mode 100644 index c01effca5c..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/components/GraphWidgetRenderer.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { GraphWidgetBarChart } from '@/dashboards/widgets/graph/components/GraphWidgetBarChart'; -import { GraphWidgetGaugeChart } from '@/dashboards/widgets/graph/components/GraphWidgetGaugeChart'; -import { GraphWidgetLineChart } from '@/dashboards/widgets/graph/components/GraphWidgetLineChart'; -import { GraphWidgetNumberChart } from '@/dashboards/widgets/graph/components/GraphWidgetNumberChart'; -import { GraphWidgetPieChart } from '@/dashboards/widgets/graph/components/GraphWidgetPieChart'; -import { GraphType } from '../mocks/mockWidgets'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; - -type GraphWidgetRendererProps = { - widget: PageLayoutWidget; -}; - -export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => { - const graphType = widget.configuration?.graphType; - - if (!graphType || typeof graphType !== 'string') { - return null; - } - - if (!Object.values(GraphType).includes(graphType as GraphType)) { - return null; - } - - switch (graphType as GraphType) { - case GraphType.NUMBER: - return ( - - ); - - case GraphType.GAUGE: - return ( - - ); - - case GraphType.PIE: - return ( - - ); - - case GraphType.BAR: - return ( - - ); - - case GraphType.LINE: - return ( - - ); - - default: - return null; - } -}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutWidgetPlaceholder.tsx b/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutWidgetPlaceholder.tsx deleted file mode 100644 index ed03791534..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutWidgetPlaceholder.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import styled from '@emotion/styled'; -import { type ReactNode } from 'react'; -import { IconGripVertical, IconPencil, IconX } from 'twenty-ui/display'; -import { IconButton } from 'twenty-ui/input'; -import { - AnimatedPlaceholder, - AnimatedPlaceholderEmptyContainer, - AnimatedPlaceholderEmptySubTitle, - AnimatedPlaceholderEmptyTextContainer, - AnimatedPlaceholderEmptyTitle, - EMPTY_PLACEHOLDER_TRANSITION_PROPS, -} from 'twenty-ui/layout'; - -const StyledPlaceholderContainer = styled.div<{ isEmpty?: boolean }>` - background: ${({ theme }) => theme.background.secondary}; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.md}; - box-sizing: border-box; - display: flex; - flex-direction: column; - height: 100%; - position: relative; - width: 100%; - padding: ${({ theme }) => theme.spacing(4)}; - - &:hover { - cursor: ${({ isEmpty }) => (isEmpty ? 'pointer' : 'default')}; - border: 1px solid ${({ theme }) => theme.color.blue}; - background: ${({ theme }) => theme.background.primary}; - } -`; - -const StyledHeader = styled.div` - align-items: center; - display: flex; - gap: ${({ theme }) => theme.spacing(2)}; -`; - -const StyledDragHandleButton = styled(IconButton)` - cursor: grab; - display: flex; - align-items: center; - user-select: none; - - &:active { - cursor: grabbing; - } -`; - -const StyledTitle = styled.span` - color: ${({ theme }) => theme.font.color.secondary}; - flex: 1; - font-size: ${({ theme }) => theme.font.size.sm}; - font-weight: ${({ theme }) => theme.font.weight.medium}; - user-select: none; -`; - -const StyledContent = styled.div` - align-items: center; - display: flex; - height: 100%; - width: 100%; - justify-content: center; -`; - -type PageLayoutWidgetPlaceholderProps = { - title?: string; - onRemove?: () => void; - onEdit?: () => void; - children?: ReactNode; - isEmpty?: boolean; -}; - -export const PageLayoutWidgetPlaceholder = ({ - title = 'Graph Title', - onRemove, - onEdit, - children, - isEmpty = false, -}: PageLayoutWidgetPlaceholderProps) => { - if (isEmpty) { - return ( - - - - Add Widget - - - - - - No widgets yet - - - Click to add your first widget - - - - - ); - } - - return ( - - - - {title} - {onEdit && ( - - )} - {onRemove && ( - - )} - - {children} - - ); -}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/components/WidgetRenderer.tsx b/packages/twenty-front/src/modules/settings/page-layout/components/WidgetRenderer.tsx deleted file mode 100644 index 1eb0fedaa0..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/components/WidgetRenderer.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { IframeWidget } from '@/dashboards/widgets/iframe/components/IframeWidget'; -import { isString } from '@sniptt/guards'; -import { WidgetType } from '../mocks/mockWidgets'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; -import { GraphWidgetRenderer } from './GraphWidgetRenderer'; - -type WidgetRendererProps = { - widget: PageLayoutWidget; -}; - -export const WidgetRenderer = ({ widget }: WidgetRendererProps) => { - switch (widget.type) { - case WidgetType.GRAPH: - return ; - - case WidgetType.IFRAME: { - const url = widget.configuration?.url; - return ( - - ); - } - - case WidgetType.VIEW: - return null; - - case WidgetType.FIELDS: - return null; - - default: - return null; - } -}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/components/__stories__/PageLayoutWidgetPlaceholder.stories.tsx b/packages/twenty-front/src/modules/settings/page-layout/components/__stories__/PageLayoutWidgetPlaceholder.stories.tsx deleted file mode 100644 index 4edfc8224c..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/components/__stories__/PageLayoutWidgetPlaceholder.stories.tsx +++ /dev/null @@ -1,313 +0,0 @@ -import { GraphWidgetGaugeChart } from '@/dashboards/widgets/graph/components/GraphWidgetGaugeChart'; -import { GraphWidgetNumberChart } from '@/dashboards/widgets/graph/components/GraphWidgetNumberChart'; -import { GraphWidgetPieChart } from '@/dashboards/widgets/graph/components/GraphWidgetPieChart'; -import { type Meta, type StoryObj } from '@storybook/react'; -import { ComponentDecorator } from 'twenty-ui/testing'; -import { PageLayoutWidgetPlaceholder } from '../PageLayoutWidgetPlaceholder'; - -const meta: Meta = { - title: 'Modules/Settings/PageLayout/PageLayoutWidgetPlaceholder', - component: PageLayoutWidgetPlaceholder, - decorators: [ComponentDecorator], - parameters: { - layout: 'centered', - }, - argTypes: { - title: { - control: 'text', - description: 'Widget title', - }, - isEmpty: { - control: 'boolean', - description: 'Show empty state', - }, - onRemove: { - action: 'onRemove', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const WithNumberChart: Story = { - args: { - title: 'Sales Pipeline', - children: , - }, - render: (args) => ( -
- -
- ), -}; - -export const WithGaugeChart: Story = { - args: { - title: 'Conversion Rate', - children: ( - - ), - }, - render: (args) => ( -
- -
- ), -}; - -export const EmptyState: Story = { - args: { - isEmpty: true, - }, - parameters: { - docs: { - description: { - story: 'Empty widget placeholder state with dashed border', - }, - }, - }, - render: (args) => ( -
- -
- ), -}; - -export const WithPieChart: Story = { - args: { - title: 'Lead Distribution', - children: ( - - ), - }, - render: (args) => ( -
- -
- ), -}; - -export const SmallWidget: Story = { - args: { - title: 'Small Widget (2x2 grid)', - children: , - }, - parameters: { - docs: { - description: { - story: 'Simulates a 2x2 grid cell widget', - }, - }, - }, - render: (args) => ( -
- -
- ), -}; - -export const MediumWidget: Story = { - args: { - title: 'Medium Widget (4x3 grid)', - children: ( - - ), - }, - parameters: { - docs: { - description: { - story: 'Simulates a 4x3 grid cell widget', - }, - }, - }, - render: (args) => ( -
- -
- ), -}; - -export const LargeWidget: Story = { - args: { - title: 'Large Widget (6x4 grid)', - children: ( - - ), - }, - parameters: { - docs: { - description: { - story: 'Simulates a 6x4 grid cell widget', - }, - }, - }, - render: (args) => ( -
- -
- ), -}; - -export const WideWidget: Story = { - args: { - title: 'Wide Widget (8x2 grid)', - children: ( - - ), - }, - parameters: { - docs: { - description: { - story: 'Simulates a wide 8x2 grid cell widget', - }, - }, - }, - render: (args) => ( -
- -
- ), -}; - -export const TallWidget: Story = { - args: { - title: 'Tall Widget (3x6 grid)', - children: ( - - ), - }, - parameters: { - docs: { - description: { - story: 'Simulates a tall 3x6 grid cell widget', - }, - }, - }, - render: (args) => ( -
- -
- ), -}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/savedPageLayoutsState.ts b/packages/twenty-front/src/modules/settings/page-layout/states/savedPageLayoutsState.ts deleted file mode 100644 index c73da73da8..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/states/savedPageLayoutsState.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { type WidgetType } from '@/settings/page-layout/mocks/mockWidgets'; -import { createState } from 'twenty-ui/utilities'; - -export enum PageLayoutType { - DASHBOARD = 'DASHBOARD', - RECORD_INDEX = 'RECORD_INDEX', - RECORD_PAGE = 'RECORD_PAGE', -} - -export type GridPosition = { - row: number; - column: number; - rowSpan: number; - columnSpan: number; -}; - -export type PageLayoutWidget = { - id: string; - pageLayoutTabId: string; - title: string; - type: WidgetType; - objectMetadataId?: string | null; - gridPosition: GridPosition; - configuration?: Record | null; - data?: any; - createdAt: string; - updatedAt: string; - deletedAt?: string | null; -}; - -export type PageLayoutTab = { - id: string; - title: string; - position: number; - pageLayoutId: string; - widgets: PageLayoutWidget[]; - createdAt: string; - updatedAt: string; - deletedAt?: string | null; -}; - -export type SavedPageLayout = { - id: string; - name: string; - type: PageLayoutType; - objectMetadataId?: string | null; - tabs: PageLayoutTab[]; - createdAt: string; - updatedAt: string; - deletedAt?: string | null; -}; - -export const savedPageLayoutsState = createState({ - key: 'savedPageLayoutsState', - defaultValue: [], -}); diff --git a/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayoutEdit.tsx b/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayoutEdit.tsx index 41e9372830..ce79be86e1 100644 --- a/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayoutEdit.tsx +++ b/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayoutEdit.tsx @@ -1,32 +1,33 @@ import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; -import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; -import { SettingsPageFullWidthContainer } from '@/settings/components/SettingsPageFullWidthContainer'; -import { PageLayoutInitializationEffect } from '@/settings/page-layout/components/PageLayoutInitializationEffect'; -import { PageLayoutWidgetPlaceholder } from '@/settings/page-layout/components/PageLayoutWidgetPlaceholder'; -import { WidgetRenderer } from '@/settings/page-layout/components/WidgetRenderer'; -import { EMPTY_LAYOUT } from '@/settings/page-layout/constants/EmptyLayout'; +import { PageLayoutInitializationEffect } from '@/page-layout/components/PageLayoutInitializationEffect'; +import { EMPTY_LAYOUT } from '@/page-layout/constants/EmptyLayout'; import { PAGE_LAYOUT_CONFIG, type PageLayoutBreakpoint, -} from '@/settings/page-layout/constants/PageLayoutBreakpoints'; -import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '@/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId'; -import { useChangePageLayoutDragSelection } from '@/settings/page-layout/hooks/useChangePageLayoutDragSelection'; -import { useCreatePageLayoutTab } from '@/settings/page-layout/hooks/useCreatePageLayoutTab'; -import { useDeletePageLayoutWidget } from '@/settings/page-layout/hooks/useDeletePageLayoutWidget'; -import { useEndPageLayoutDragSelection } from '@/settings/page-layout/hooks/useEndPageLayoutDragSelection'; -import { usePageLayoutDraftState } from '@/settings/page-layout/hooks/usePageLayoutDraftState'; -import { usePageLayoutHandleLayoutChange } from '@/settings/page-layout/hooks/usePageLayoutHandleLayoutChange'; -import { usePageLayoutSaveHandler } from '@/settings/page-layout/hooks/usePageLayoutSaveHandler'; -import { useStartPageLayoutDragSelection } from '@/settings/page-layout/hooks/useStartPageLayoutDragSelection'; -import { WidgetType } from '@/settings/page-layout/mocks/mockWidgets'; -import { pageLayoutCurrentBreakpointState } from '@/settings/page-layout/states/pageLayoutCurrentBreakpointState'; -import { pageLayoutCurrentLayoutsState } from '@/settings/page-layout/states/pageLayoutCurrentLayoutsState'; -import { pageLayoutEditingWidgetIdState } from '@/settings/page-layout/states/pageLayoutEditingWidgetIdState'; -import { pageLayoutSelectedCellsState } from '@/settings/page-layout/states/pageLayoutSelectedCellsState'; -import { type PageLayoutWidget } from '@/settings/page-layout/states/savedPageLayoutsState'; -import { calculateTotalGridRows } from '@/settings/page-layout/utils/calculateTotalGridRows'; -import { generateCellId } from '@/settings/page-layout/utils/generateCellId'; +} from '@/page-layout/constants/PageLayoutBreakpoints'; +import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '@/page-layout/constants/SettingsPageLayoutTabsInstanceId'; +import { useChangePageLayoutDragSelection } from '@/page-layout/hooks/useChangePageLayoutDragSelection'; +import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab'; +import { useDeletePageLayoutWidget } from '@/page-layout/hooks/useDeletePageLayoutWidget'; +import { useEndPageLayoutDragSelection } from '@/page-layout/hooks/useEndPageLayoutDragSelection'; +import { usePageLayoutDraftState } from '@/page-layout/hooks/usePageLayoutDraftState'; +import { usePageLayoutHandleLayoutChange } from '@/page-layout/hooks/usePageLayoutHandleLayoutChange'; +import { usePageLayoutSaveHandler } from '@/page-layout/hooks/usePageLayoutSaveHandler'; +import { useStartPageLayoutDragSelection } from '@/page-layout/hooks/useStartPageLayoutDragSelection'; +import { WidgetType } from '@/page-layout/mocks/mockWidgets'; +import { pageLayoutCurrentBreakpointState } from '@/page-layout/states/pageLayoutCurrentBreakpointState'; +import { pageLayoutCurrentLayoutsState } from '@/page-layout/states/pageLayoutCurrentLayoutsState'; +import { pageLayoutEditingWidgetIdState } from '@/page-layout/states/pageLayoutEditingWidgetIdState'; +import { pageLayoutSelectedCellsState } from '@/page-layout/states/pageLayoutSelectedCellsState'; +import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; +import { calculateTotalGridRows } from '@/page-layout/utils/calculateTotalGridRows'; +import { generateCellId } from '@/page-layout/utils/generateCellId'; +import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder'; +import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; +import { type Widget } from '@/page-layout/widgets/types/Widget'; +import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; +import { SettingsPageFullWidthContainer } from '@/settings/components/SettingsPageFullWidthContainer'; import { TitleInput } from '@/ui/input/components/TitleInput'; import { TabList } from '@/ui/layout/tab-list/components/TabList'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; @@ -46,7 +47,7 @@ import 'react-resizable/css/styles.css'; import { useParams } from 'react-router-dom'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { IconAppWindow, IconPlus } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; @@ -168,11 +169,11 @@ export const SettingsPageLayoutEdit = () => { const activeTab = pageLayoutDraft.tabs.find( (tab) => tab.id === activeTabId, ); - return activeTab?.widgets || []; + return activeTab?.widgets ?? []; }, [pageLayoutDraft.tabs, activeTabId]); const allWidgets = useMemo( - () => pageLayoutDraft.tabs.flatMap((tab) => tab.widgets), + () => pageLayoutDraft.tabs.flatMap((tab) => tab.widgets).filter(isDefined), [pageLayoutDraft.tabs], ); @@ -232,7 +233,7 @@ export const SettingsPageLayoutEdit = () => { }, [createPageLayoutTab, setActiveTabId]); const tabListTabs: SingleTabProps[] = useMemo(() => { - return [...pageLayoutDraft.tabs] + return [...(pageLayoutDraft.tabs ?? [])] .sort((a, b) => a.position - b.position) .map((tab) => ({ id: tab.id, @@ -243,17 +244,17 @@ export const SettingsPageLayoutEdit = () => { const handleSaveClick = async () => { setIsSaving(true); try { - const allWidgets: PageLayoutWidget[] = pageLayoutDraft.tabs.flatMap( - (tab) => + const allWidgets: PageLayoutWidgetWithData[] = + pageLayoutDraft.tabs.flatMap((tab) => tab.widgets.map((widget) => ({ ...widget, - pageLayoutTabId: widget.pageLayoutTabId || tab.id, + pageLayoutTabId: widget.pageLayoutTabId ?? tab.id, objectMetadataId: widget.objectMetadataId || null, - createdAt: widget.createdAt || new Date().toISOString(), + createdAt: widget.createdAt ?? new Date().toISOString(), updatedAt: new Date().toISOString(), - deletedAt: widget.deletedAt || null, + deletedAt: widget.deletedAt ?? null, })), - ); + ); setPageLayoutDraft((prev) => ({ ...prev, @@ -352,7 +353,7 @@ export const SettingsPageLayoutEdit = () => { layouts={ isEmptyState || !activeTabId ? EMPTY_LAYOUT - : pageLayoutCurrentLayouts[activeTabId] || EMPTY_LAYOUT + : (pageLayoutCurrentLayouts[activeTabId] ?? EMPTY_LAYOUT) } breakpoints={PAGE_LAYOUT_CONFIG.breakpoints} cols={PAGE_LAYOUT_CONFIG.columns} @@ -373,19 +374,18 @@ export const SettingsPageLayoutEdit = () => { } > {isEmptyState ? ( -
- +
+
) : ( activeTabWidgets.map((widget) => (
- deletePageLayoutWidget(widget.id)} onEdit={() => handleEditWidget(widget.id)} - > - - + />
)) )} diff --git a/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayouts.tsx b/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayouts.tsx index 2b4e69f7c3..c57fe97561 100644 --- a/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayouts.tsx +++ b/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayouts.tsx @@ -1,5 +1,5 @@ +import { savedPageLayoutsState } from '@/page-layout/states/savedPageLayoutsState'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; -import { savedPageLayoutsState } from '@/settings/page-layout/states/savedPageLayoutsState'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { Table } from '@/ui/layout/table/components/Table'; import { TableCell } from '@/ui/layout/table/components/TableCell'; diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/page-layout-tab.dto.ts b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/page-layout-tab.dto.ts index f346e11689..65ee938717 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/page-layout-tab.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/page-layout-tab.dto.ts @@ -3,6 +3,7 @@ import { Field, Float, ObjectType } from '@nestjs/graphql'; import { IDField } from '@ptc-org/nestjs-query-graphql'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { PageLayoutWidgetDTO } from 'src/engine/core-modules/page-layout/dtos/page-layout-widget.dto'; @ObjectType('PageLayoutTab') export class PageLayoutTabDTO { @@ -18,6 +19,9 @@ export class PageLayoutTabDTO { @Field(() => UUIDScalarType, { nullable: false }) pageLayoutId: string; + @Field(() => [PageLayoutWidgetDTO], { nullable: true }) + widgets?: PageLayoutWidgetDTO[] | null; + @Field() createdAt: Date; diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout.service.ts b/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout.service.ts index cca013d15b..838f14d195 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout.service.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout.service.ts @@ -45,7 +45,7 @@ export class PageLayoutService { workspaceId, deletedAt: IsNull(), }, - relations: ['tabs'], + relations: ['tabs', 'tabs.widgets'], }); } @@ -62,7 +62,7 @@ export class PageLayoutService { objectMetadataId, deletedAt: IsNull(), }, - relations: ['tabs'], + relations: ['tabs', 'tabs.widgets'], }); } @@ -79,7 +79,7 @@ export class PageLayoutService { workspaceId, deletedAt: IsNull(), }, - relations: ['tabs'], + relations: ['tabs', 'tabs.widgets'], }); if (!isDefined(pageLayout)) { diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/services/tests/page-layout.service.spec.ts b/packages/twenty-server/src/engine/core-modules/page-layout/services/tests/page-layout.service.spec.ts index d2154283ec..de38d17c30 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/services/tests/page-layout.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/services/tests/page-layout.service.spec.ts @@ -86,7 +86,7 @@ describe('PageLayoutService', () => { workspaceId, deletedAt: IsNull(), }, - relations: ['tabs'], + relations: ['tabs', 'tabs.widgets'], }); expect(result).toEqual(expectedPageLayouts); }); @@ -113,7 +113,7 @@ describe('PageLayoutService', () => { objectMetadataId, deletedAt: IsNull(), }, - relations: ['tabs'], + relations: ['tabs', 'tabs.widgets'], }); expect(result).toEqual(expectedPageLayouts); }); @@ -130,6 +130,14 @@ describe('PageLayoutService', () => { const result = await pageLayoutService.findByIdOrThrow(id, workspaceId); + expect(pageLayoutRepository.findOne).toHaveBeenCalledWith({ + where: { + id, + workspaceId, + deletedAt: IsNull(), + }, + relations: ['tabs', 'tabs.widgets'], + }); expect(result).toEqual(mockPageLayout); }); diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 82cb9a7435..f944c70aea 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -191,6 +191,7 @@ export { IconLanguage, IconLayersLinked, IconLayout, + IconLayoutDashboard, IconLayoutKanban, IconLayoutList, IconLayoutNavbar, diff --git a/packages/twenty-ui/src/display/index.ts b/packages/twenty-ui/src/display/index.ts index 68ab6f7c31..63a5e984cf 100644 --- a/packages/twenty-ui/src/display/index.ts +++ b/packages/twenty-ui/src/display/index.ts @@ -253,6 +253,7 @@ export { IconLanguage, IconLayersLinked, IconLayout, + IconLayoutDashboard, IconLayoutKanban, IconLayoutList, IconLayoutNavbar,