From 66a6190e3c948408404710dcceb8e89316944c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:03:27 +0200 Subject: [PATCH] Create save dashboard record action (#14665) Closes https://github.com/twentyhq/core-team-issues/issues/1502 - Created the save action - Removed the temporary data attribute from the page layout widget since we will connect widgets to real data soon https://github.com/user-attachments/assets/2b46b511-f2c2-4e11-873c-a76fd31704e5 --- packages/twenty-front/codegen.cjs | 2 + .../src/generated-metadata/graphql.ts | 2 +- .../twenty-front/src/generated/graphql.ts | 74 +++++++++++++- .../constants/DashboardActionsConfig.tsx | 22 ++++- .../SaveDashboardSingleRecordAction.tsx | 26 +++++ .../types/DashboardSingleRecordActionKeys.ts | 1 + ...forceRegisteredActionsMapComponentState.ts | 6 +- .../useShouldActionBeRegisteredParams.ts | 6 +- .../CommandMenuPageLayoutGraphTypeSelect.tsx | 5 +- .../components/DashboardContentRenderer.tsx | 4 +- .../hooks/useSetDashboardInEditMode.ts | 9 +- .../PageLayoutInitializationQueryEffect.tsx | 8 +- .../components/PageLayoutRenderer.tsx | 4 +- .../PageLayoutRenderer.stories.tsx | 43 +------- .../updatePageLayoutWithTabsAndWidgets.ts | 42 ++++++++ ...> useCreatePageLayoutGraphWidget.test.tsx} | 12 +-- .../usePageLayoutDraftState.test.tsx | 1 - ...t.ts => useCreatePageLayoutGraphWidget.ts} | 15 ++- .../hooks/useCreatePageLayoutIframeWidget.ts | 8 +- .../hooks/useCreatePageLayoutTab.ts | 4 +- .../hooks/usePageLayoutHandleLayoutChange.ts | 1 - .../page-layout/hooks/useSavePageLayout.ts | 98 +++++++++++++++++++ .../hooks/useUpdatePageLayoutWidget.ts | 4 +- .../modules/page-layout/mocks/mockWidgets.ts | 94 +----------------- .../pageLayoutPersistedComponentState.ts | 4 +- .../states/savedPageLayoutsComponentState.ts | 4 +- .../page-layout/types/draft-page-layout.ts | 4 +- .../page-layout/types/pageLayoutTypes.ts | 17 ++-- .../utils/__tests__/addWidgetToTab.test.ts | 18 ++-- .../__tests__/convertLayoutsToWidgets.test.ts | 6 +- .../convertPageLayoutToTabLayouts.test.ts | 4 +- .../__tests__/removeWidgetFromTab.test.ts | 4 +- .../page-layout/utils/addWidgetToTab.ts | 14 ++- .../utils/convertLayoutsToWidgets.ts | 6 +- .../convertPageLayoutDraftToUpdateInput.ts | 31 ++++++ .../utils/convertPageLayoutToTabLayouts.ts | 4 +- .../page-layout/utils/isPageLayoutEmpty.ts | 4 +- .../page-layout/utils/removeWidgetFromTab.ts | 6 +- .../__stories__/WidgetRenderer.stories.tsx | 72 -------------- .../graph/components/GraphWidgetRenderer.tsx | 2 +- .../widgets/graph/types/GraphWidget.ts | 5 +- .../widgets/iframe/types/IframeWidget.ts | 5 +- .../update-page-layout-with-tabs.input.ts | 4 +- 43 files changed, 389 insertions(+), 316 deletions(-) create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/dashboard-actions/components/SaveDashboardSingleRecordAction.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/graphql/mutations/updatePageLayoutWithTabsAndWidgets.ts rename packages/twenty-front/src/modules/page-layout/hooks/__tests__/{useCreatePageLayoutWidget.test.tsx => useCreatePageLayoutGraphWidget.test.tsx} (94%) rename packages/twenty-front/src/modules/page-layout/hooks/{useCreatePageLayoutWidget.ts => useCreatePageLayoutGraphWidget.ts} (90%) create mode 100644 packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts create mode 100644 packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts diff --git a/packages/twenty-front/codegen.cjs b/packages/twenty-front/codegen.cjs index 4330a4616d..262769bd9c 100644 --- a/packages/twenty-front/codegen.cjs +++ b/packages/twenty-front/codegen.cjs @@ -19,6 +19,8 @@ module.exports = { './src/modules/prefetch/graphql/**/*.{ts,tsx}', './src/modules/subscription/graphql/**/*.{ts,tsx}', + './src/modules/page-layout/graphql/**/*.{ts,tsx}', + '!./src/**/*.test.{ts,tsx}', '!./src/**/*.stories.{ts,tsx}', '!./src/**/__mocks__/*.ts', diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 9dd4823233..0b9f952b80 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -3684,7 +3684,7 @@ export type UpdatePageLayoutWidgetWithIdInput = { export type UpdatePageLayoutWithTabsInput = { name: Scalars['String']; - objectMetadataId: Scalars['UUID']; + objectMetadataId?: InputMaybe; tabs: Array; type: PageLayoutType; }; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 6ea8044763..8d1c0a7515 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -3530,7 +3530,7 @@ export type UpdatePageLayoutWidgetWithIdInput = { export type UpdatePageLayoutWithTabsInput = { name: Scalars['String']; - objectMetadataId: Scalars['UUID']; + objectMetadataId?: InputMaybe; tabs: Array; type: PageLayoutType; }; @@ -4069,6 +4069,14 @@ export type SearchQueryVariables = Exact<{ export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } }; +export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{ + id: Scalars['String']; + input: UpdatePageLayoutWithTabsInput; +}>; + + +export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, pageLayoutTabId: any, objectMetadataId?: any | null, configuration?: any | null, createdAt: string, updatedAt: string, gridPosition: { __typename?: 'GridPosition', row: number, column: number, rowSpan: number, columnSpan: number } }> | null }> | null } }; + export type OnDbEventSubscriptionVariables = Exact<{ input: OnDbEventInput; }>; @@ -4496,6 +4504,70 @@ export function useSearchLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions; export type SearchLazyQueryHookResult = ReturnType; export type SearchQueryResult = Apollo.QueryResult; +export const UpdatePageLayoutWithTabsAndWidgetsDocument = gql` + mutation UpdatePageLayoutWithTabsAndWidgets($id: String!, $input: UpdatePageLayoutWithTabsInput!) { + updatePageLayoutWithTabsAndWidgets(id: $id, input: $input) { + id + name + type + objectMetadataId + createdAt + updatedAt + deletedAt + tabs { + id + title + position + pageLayoutId + widgets { + id + title + type + pageLayoutTabId + objectMetadataId + gridPosition { + row + column + rowSpan + columnSpan + } + configuration + createdAt + updatedAt + } + createdAt + updatedAt + } + } +} + `; +export type UpdatePageLayoutWithTabsAndWidgetsMutationFn = Apollo.MutationFunction; + +/** + * __useUpdatePageLayoutWithTabsAndWidgetsMutation__ + * + * To run a mutation, you first call `useUpdatePageLayoutWithTabsAndWidgetsMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdatePageLayoutWithTabsAndWidgetsMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updatePageLayoutWithTabsAndWidgetsMutation, { data, loading, error }] = useUpdatePageLayoutWithTabsAndWidgetsMutation({ + * variables: { + * id: // value for 'id' + * input: // value for 'input' + * }, + * }); + */ +export function useUpdatePageLayoutWithTabsAndWidgetsMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdatePageLayoutWithTabsAndWidgetsDocument, options); + } +export type UpdatePageLayoutWithTabsAndWidgetsMutationHookResult = ReturnType; +export type UpdatePageLayoutWithTabsAndWidgetsMutationResult = Apollo.MutationResult; +export type UpdatePageLayoutWithTabsAndWidgetsMutationOptions = Apollo.BaseMutationOptions; export const OnDbEventDocument = gql` subscription OnDbEvent($input: OnDbEventInput!) { onDbEvent(input: $input) { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DashboardActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DashboardActionsConfig.tsx index 9ddb3548b2..ead41a6287 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DashboardActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DashboardActionsConfig.tsx @@ -1,5 +1,6 @@ import { CancelDashboardSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/components/CancelDashboardSingleRecordAction'; import { EditDashboardSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/components/EditDashboardSingleRecordAction'; +import { SaveDashboardSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/components/SaveDashboardSingleRecordAction'; import { DashboardSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys'; import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; import { inheritActionsFromDefaultConfig } from '@/action-menu/actions/record-actions/utils/inheritActionsFromDefaultConfig'; @@ -8,7 +9,7 @@ import { ActionType } from '@/action-menu/actions/types/ActionType'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; import { msg } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; -import { IconPencil, IconX } from 'twenty-ui/display'; +import { IconDeviceFloppy, IconPencil, IconX } from 'twenty-ui/display'; export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ config: { @@ -17,7 +18,7 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ label: msg`Edit Dashboard`, shortLabel: msg`Edit`, isPinned: true, - position: 1, + position: 0, Icon: IconPencil, type: ActionType.Standard, scope: ActionScope.RecordSelection, @@ -29,6 +30,23 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ availableOn: [ActionViewType.SHOW_PAGE], component: , }, + [DashboardSingleRecordActionKeys.SAVE_DASHBOARD]: { + key: DashboardSingleRecordActionKeys.SAVE_DASHBOARD, + label: msg`Save Dashboard`, + shortLabel: msg`Save`, + isPinned: true, + position: 1, + Icon: IconDeviceFloppy, + type: ActionType.Standard, + scope: ActionScope.RecordSelection, + shouldBeRegistered: ({ selectedRecord }) => + isDefined(selectedRecord) && + !selectedRecord?.isRemote && + !isDefined(selectedRecord?.deletedAt) && + isDefined(selectedRecord?.pageLayoutId), + availableOn: [ActionViewType.SHOW_PAGE], + component: , + }, [DashboardSingleRecordActionKeys.CANCEL_DASHBOARD_EDITION]: { key: DashboardSingleRecordActionKeys.CANCEL_DASHBOARD_EDITION, label: msg`Cancel Edition`, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/dashboard-actions/components/SaveDashboardSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/dashboard-actions/components/SaveDashboardSingleRecordAction.tsx new file mode 100644 index 0000000000..bedaf71759 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/dashboard-actions/components/SaveDashboardSingleRecordAction.tsx @@ -0,0 +1,26 @@ +import { Action } from '@/action-menu/actions/components/Action'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { useSetIsDashboardInEditMode } from '@/dashboards/hooks/useSetDashboardInEditMode'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useSavePageLayout } from '@/page-layout/hooks/useSavePageLayout'; +import { useRecoilValue } from 'recoil'; + +export const SaveDashboardSingleRecordAction = () => { + const recordId = useSelectedRecordIdOrThrow(); + + const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); + + const pageLayoutId = selectedRecord?.pageLayoutId; + + const { savePageLayout } = useSavePageLayout(pageLayoutId); + + const { setIsDashboardInEditMode } = + useSetIsDashboardInEditMode(pageLayoutId); + + const handleClick = () => { + savePageLayout(); + setIsDashboardInEditMode(false); + }; + + return ; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys.ts index 0fb6b7db0d..e3858ae994 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys.ts @@ -1,4 +1,5 @@ export enum DashboardSingleRecordActionKeys { EDIT_DASHBOARD = 'edit-dashboard-single-record', + SAVE_DASHBOARD = 'save-dashboard-single-record', CANCEL_DASHBOARD_EDITION = 'cancel-dashboard-edition-single-record', } diff --git a/packages/twenty-front/src/modules/action-menu/actions/states/forceRegisteredActionsMapComponentState.ts b/packages/twenty-front/src/modules/action-menu/actions/states/forceRegisteredActionsMapComponentState.ts index 0eb30cdaec..46ebfedc07 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/states/forceRegisteredActionsMapComponentState.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/states/forceRegisteredActionsMapComponentState.ts @@ -1,10 +1,8 @@ -import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; +import { createState } from 'twenty-ui/utilities'; -export const forceRegisteredActionsByKeyComponentState = createComponentState< +export const forceRegisteredActionsByKeyState = createState< Record >({ key: 'forceRegisteredActionsByKeyComponentState', defaultValue: {}, - componentInstanceContext: ActionMenuComponentInstanceContext, }); diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts b/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts index b407cbc0ff..62d3b1a9ad 100644 --- a/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts +++ b/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts @@ -1,4 +1,4 @@ -import { forceRegisteredActionsByKeyComponentState } from '@/action-menu/actions/states/forceRegisteredActionsMapComponentState'; +import { forceRegisteredActionsByKeyState } from '@/action-menu/actions/states/forceRegisteredActionsMapComponentState'; import { type ShouldBeRegisteredFunctionParams } from '@/action-menu/actions/types/ShouldBeRegisteredFunctionParams'; import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; @@ -101,8 +101,8 @@ export const useShouldActionBeRegisteredParams = ({ [], ); - const forceRegisteredActionsByKey = useRecoilComponentValue( - forceRegisteredActionsByKeyComponentState, + const forceRegisteredActionsByKey = useRecoilValue( + forceRegisteredActionsByKeyState, ); return { 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 487a376b37..127f3ecdef 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,6 +1,6 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; -import { useCreatePageLayoutWidget } from '@/page-layout/hooks/useCreatePageLayoutWidget'; +import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget'; import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets'; import styled from '@emotion/styled'; @@ -61,7 +61,8 @@ export const CommandMenuPageLayoutGraphTypeSelect = () => { const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); - const { createPageLayoutWidget } = useCreatePageLayoutWidget(pageLayoutId); + const { createPageLayoutWidget } = + useCreatePageLayoutGraphWidget(pageLayoutId); const handleSelectGraphType = (graphType: GraphType) => { createPageLayoutWidget(WidgetType.GRAPH, graphType); diff --git a/packages/twenty-front/src/modules/dashboards/components/DashboardContentRenderer.tsx b/packages/twenty-front/src/modules/dashboards/components/DashboardContentRenderer.tsx index 9f137856e3..ba70264b1a 100644 --- a/packages/twenty-front/src/modules/dashboards/components/DashboardContentRenderer.tsx +++ b/packages/twenty-front/src/modules/dashboards/components/DashboardContentRenderer.tsx @@ -1,7 +1,7 @@ import { type Dashboard } from '@/dashboards/components/types/Dashboard'; import { useSetIsDashboardInEditMode } from '@/dashboards/hooks/useSetDashboardInEditMode'; import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer'; -import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes'; +import { type PageLayout } from '@/page-layout/types/pageLayoutTypes'; import { isPageLayoutEmpty } from '@/page-layout/utils/isPageLayoutEmpty'; type DashboardContentRendererProps = { @@ -16,7 +16,7 @@ export const DashboardContentRenderer = ({ const { setIsDashboardInEditMode } = useSetIsDashboardInEditMode(pageLayoutId); - const onInitialized = (pageLayout: PageLayoutWithData) => { + const onInitialized = (pageLayout: PageLayout) => { if (isPageLayoutEmpty(pageLayout)) { setIsDashboardInEditMode(true); } else { diff --git a/packages/twenty-front/src/modules/dashboards/hooks/useSetDashboardInEditMode.ts b/packages/twenty-front/src/modules/dashboards/hooks/useSetDashboardInEditMode.ts index ca20d44e04..e82010f679 100644 --- a/packages/twenty-front/src/modules/dashboards/hooks/useSetDashboardInEditMode.ts +++ b/packages/twenty-front/src/modules/dashboards/hooks/useSetDashboardInEditMode.ts @@ -1,5 +1,5 @@ import { DashboardSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys'; -import { forceRegisteredActionsByKeyComponentState } from '@/action-menu/actions/states/forceRegisteredActionsMapComponentState'; +import { forceRegisteredActionsByKeyState } from '@/action-menu/actions/states/forceRegisteredActionsMapComponentState'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -17,10 +17,6 @@ export const useSetIsDashboardInEditMode = (pageLayoutIdFromProps: string) => { pageLayoutId, ); - const forceRegisteredActionsByKeyState = useRecoilComponentCallbackState( - forceRegisteredActionsByKeyComponentState, - ); - const setIsDashboardInEditMode = useRecoilCallback( ({ set }) => (value: boolean) => { @@ -28,10 +24,11 @@ export const useSetIsDashboardInEditMode = (pageLayoutIdFromProps: string) => { set(forceRegisteredActionsByKeyState, (prev) => ({ ...prev, [DashboardSingleRecordActionKeys.EDIT_DASHBOARD]: !value, + [DashboardSingleRecordActionKeys.SAVE_DASHBOARD]: value, [DashboardSingleRecordActionKeys.CANCEL_DASHBOARD_EDITION]: value, })); }, - [forceRegisteredActionsByKeyState, isPageLayoutInEditModeState], + [isPageLayoutInEditModeState], ); return { setIsDashboardInEditMode }; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx index 625f015bca..20fa70141e 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx @@ -2,7 +2,7 @@ import { FIND_ONE_PAGE_LAYOUT } from '@/dashboards/graphql/queries/findOnePageLa import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState'; -import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes'; +import { type PageLayout } from '@/page-layout/types/pageLayoutTypes'; import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; @@ -14,7 +14,7 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; type PageLayoutInitializationQueryEffectProps = { pageLayoutId: string; - onInitialized: (pageLayout: PageLayoutWithData) => void; + onInitialized: (pageLayout: PageLayout) => void; }; export const PageLayoutInitializationQueryEffect = ({ @@ -29,7 +29,7 @@ export const PageLayoutInitializationQueryEffect = ({ }, }); - const pageLayout: PageLayoutWithData | undefined = data?.getPageLayout; + const pageLayout: PageLayout | undefined = data?.getPageLayout; const pageLayoutPersistedComponentCallbackState = useRecoilComponentCallbackState(pageLayoutPersistedComponentState); @@ -43,7 +43,7 @@ export const PageLayoutInitializationQueryEffect = ({ const initializePageLayout = useRecoilCallback( ({ set, snapshot }) => - (layout: PageLayoutWithData) => { + (layout: PageLayout) => { const currentPersisted = getSnapshotValue( snapshot, pageLayoutPersistedComponentCallbackState, diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx index 323e16d64c..55b5753c2e 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx @@ -2,7 +2,7 @@ import { PageLayoutInitializationQueryEffect } from '@/page-layout/components/Pa import { PageLayoutRendererContent } from '@/page-layout/components/PageLayoutRendererContent'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; -import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes'; +import { type PageLayout } from '@/page-layout/types/pageLayoutTypes'; import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; import 'react-grid-layout/css/styles.css'; @@ -10,7 +10,7 @@ import 'react-resizable/css/styles.css'; type PageLayoutRendererProps = { pageLayoutId: string; - onInitialized: (pageLayout: PageLayoutWithData) => void; + onInitialized: (pageLayout: PageLayout) => void; }; export const PageLayoutRenderer = ({ diff --git a/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx b/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx index 97e4ce8747..9aaae30b99 100644 --- a/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx @@ -7,8 +7,7 @@ import { FIND_ONE_PAGE_LAYOUT } from '@/dashboards/graphql/queries/findOnePageLa import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer'; import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets'; import { RecoilRoot } from 'recoil'; -import { PageLayoutType } from '~/generated/graphql'; -import { type PageLayoutWidgetWithData } from '../../types/pageLayoutTypes'; +import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql'; const validatePageLayoutContent = async (canvasElement: HTMLElement) => { const canvas = within(canvasElement); @@ -56,14 +55,10 @@ const mixedGraphsPageLayoutMocks = { 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, + } as PageLayoutWidget, { __typename: 'PageLayoutWidget', id: 'gauge-widget', @@ -81,16 +76,10 @@ const mixedGraphsPageLayoutMocks = { 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, + } as PageLayoutWidget, { __typename: 'PageLayoutWidget', id: 'pie-widget', @@ -108,17 +97,10 @@ const mixedGraphsPageLayoutMocks = { 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, + } as PageLayoutWidget, { __typename: 'PageLayoutWidget', id: 'bar-widget', @@ -136,25 +118,10 @@ const mixedGraphsPageLayoutMocks = { 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, + } as PageLayoutWidget, ], }, ], diff --git a/packages/twenty-front/src/modules/page-layout/graphql/mutations/updatePageLayoutWithTabsAndWidgets.ts b/packages/twenty-front/src/modules/page-layout/graphql/mutations/updatePageLayoutWithTabsAndWidgets.ts new file mode 100644 index 0000000000..d9203b9410 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/graphql/mutations/updatePageLayoutWithTabsAndWidgets.ts @@ -0,0 +1,42 @@ +import { gql } from '@apollo/client'; + +export const UPDATE_PAGE_LAYOUT_WITH_TABS_AND_WIDGETS = gql` + mutation UpdatePageLayoutWithTabsAndWidgets( + $id: String! + $input: UpdatePageLayoutWithTabsInput! + ) { + updatePageLayoutWithTabsAndWidgets(id: $id, input: $input) { + id + name + type + objectMetadataId + createdAt + updatedAt + deletedAt + tabs { + id + title + position + pageLayoutId + widgets { + id + title + type + pageLayoutTabId + objectMetadataId + gridPosition { + row + column + rowSpan + columnSpan + } + configuration + createdAt + updatedAt + } + createdAt + updatedAt + } + } + } +`; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.tsx b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutGraphWidget.test.tsx similarity index 94% rename from packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.tsx rename to packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutGraphWidget.test.tsx index 5d74243aae..14a65baea4 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.tsx +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/useCreatePageLayoutGraphWidget.test.tsx @@ -1,3 +1,4 @@ +import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget'; import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; @@ -7,7 +8,6 @@ import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state import { act, renderHook } from '@testing-library/react'; import { useSetRecoilState } from 'recoil'; import { PageLayoutType } from '~/generated/graphql'; -import { useCreatePageLayoutWidget } from '../useCreatePageLayoutWidget'; import { PAGE_LAYOUT_TEST_INSTANCE_ID, PageLayoutTestWrapper, @@ -17,7 +17,7 @@ jest.mock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid'), })); -describe('useCreatePageLayoutWidget', () => { +describe('useCreatePageLayoutGraphWidget', () => { beforeEach(() => { jest.clearAllMocks(); }); @@ -43,7 +43,7 @@ describe('useCreatePageLayoutWidget', () => { pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, ); - const createWidget = useCreatePageLayoutWidget( + const createWidget = useCreatePageLayoutGraphWidget( PAGE_LAYOUT_TEST_INSTANCE_ID, ); return { @@ -119,7 +119,7 @@ describe('useCreatePageLayoutWidget', () => { pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, ); - const createWidget = useCreatePageLayoutWidget( + const createWidget = useCreatePageLayoutGraphWidget( PAGE_LAYOUT_TEST_INSTANCE_ID, ); return { @@ -181,7 +181,7 @@ describe('useCreatePageLayoutWidget', () => { expect(widget.type).toBe(WidgetType.GRAPH); expect(widget.pageLayoutTabId).toBe('tab-1'); expect(widget.configuration.graphType).toBe(graphType); - expect(widget.id).toBe('widget-mock-uuid'); + expect(widget.id).toBe('mock-uuid'); }); expect(result.current.pageLayoutCurrentLayouts['tab-1']).toBeDefined(); @@ -207,7 +207,7 @@ describe('useCreatePageLayoutWidget', () => { pageLayoutCurrentLayoutsComponentState, PAGE_LAYOUT_TEST_INSTANCE_ID, ); - const createWidget = useCreatePageLayoutWidget( + const createWidget = useCreatePageLayoutGraphWidget( PAGE_LAYOUT_TEST_INSTANCE_ID, ); return { allWidgets, pageLayoutCurrentLayouts, createWidget }; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutDraftState.test.tsx b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutDraftState.test.tsx index 1fcf3c0744..47ab4094f1 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutDraftState.test.tsx +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutDraftState.test.tsx @@ -96,7 +96,6 @@ describe('usePageLayoutDraftState', () => { type: WidgetType.GRAPH, gridPosition: { row: 2, column: 2, rowSpan: 2, columnSpan: 2 }, configuration: { graphType: GraphType.BAR }, - data: {}, objectMetadataId: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts similarity index 90% rename from packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutWidget.ts rename to packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts index c7b1940449..f4a345febb 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutGraphWidget.ts @@ -3,10 +3,8 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/context import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState'; -import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab'; import { - getDefaultWidgetData, getWidgetSize, getWidgetTitle, } from '@/page-layout/utils/getDefaultWidgetData'; @@ -18,9 +16,11 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useRecoilCallback } from 'recoil'; import { v4 as uuidv4 } from 'uuid'; -import { type WidgetType } from '~/generated/graphql'; +import { type PageLayoutWidget, type WidgetType } from '~/generated/graphql'; -export const useCreatePageLayoutWidget = (pageLayoutIdFromProps?: string) => { +export const useCreatePageLayoutGraphWidget = ( + pageLayoutIdFromProps?: string, +) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( PageLayoutComponentInstanceContext, pageLayoutIdFromProps, @@ -57,8 +57,6 @@ export const useCreatePageLayoutWidget = (pageLayoutIdFromProps?: string) => { return; } - const widgetData = getDefaultWidgetData(graphType); - const pageLayoutDraft = snapshot .getLoadable(pageLayoutDraftState) .getValue(); @@ -77,7 +75,7 @@ export const useCreatePageLayoutWidget = (pageLayoutIdFromProps?: string) => { w.type === widgetType && w.configuration.graphType === graphType, ).length; const title = getWidgetTitle(graphType, existingWidgetCount); - const widgetId = `widget-${uuidv4()}`; + const widgetId = uuidv4(); const defaultSize = getWidgetSize(graphType); const position = getDefaultWidgetPosition( @@ -85,7 +83,7 @@ export const useCreatePageLayoutWidget = (pageLayoutIdFromProps?: string) => { defaultSize, ); - const newWidget: PageLayoutWidgetWithData = { + const newWidget: PageLayoutWidget = { id: widgetId, pageLayoutTabId: activeTabId, title, @@ -99,7 +97,6 @@ export const useCreatePageLayoutWidget = (pageLayoutIdFromProps?: string) => { configuration: { graphType, }, - data: widgetData as Record, objectMetadataId: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts index e8d99deacb..3b1e1f6c3a 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutIframeWidget.ts @@ -2,7 +2,6 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/context import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState'; -import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab'; import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition'; import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; @@ -13,7 +12,7 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component- import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useRecoilCallback } from 'recoil'; import { v4 as uuidv4 } from 'uuid'; -import { WidgetType } from '~/generated/graphql'; +import { type PageLayoutWidget, WidgetType } from '~/generated/graphql'; export const useCreatePageLayoutIframeWidget = ( pageLayoutIdFromProps?: string, @@ -58,14 +57,14 @@ export const useCreatePageLayoutIframeWidget = ( return; } - const widgetId = `widget-${uuidv4()}`; + const widgetId = uuidv4(); const defaultSize = { w: 6, h: 6 }; const position = getDefaultWidgetPosition( pageLayoutDraggedArea, defaultSize, ); - const newWidget: PageLayoutWidgetWithData = { + const newWidget: PageLayoutWidget = { id: widgetId, pageLayoutTabId: activeTabId, title, @@ -79,7 +78,6 @@ export const useCreatePageLayoutIframeWidget = ( configuration: { url, }, - data: {}, objectMetadataId: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts index fa625226ff..6b9784ebb5 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutTab.ts @@ -5,7 +5,7 @@ import { useRecoilCallback } from 'recoil'; import { v4 as uuidv4 } from 'uuid'; import { pageLayoutCurrentLayoutsComponentState } from '../states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '../states/pageLayoutDraftComponentState'; -import { type PageLayoutTabWithData } from '../types/pageLayoutTypes'; +import { type PageLayoutTab } from '../types/pageLayoutTypes'; import { getEmptyTabLayout } from '../utils/getEmptyTabLayout'; export const useCreatePageLayoutTab = (pageLayoutIdFromProps?: string) => { @@ -33,7 +33,7 @@ export const useCreatePageLayoutTab = (pageLayoutIdFromProps?: string) => { const newTabId = `tab-${uuidv4()}`; const tabsLength = pageLayoutDraft.tabs.length; - const newTab: PageLayoutTabWithData = { + const newTab: PageLayoutTab = { id: newTabId, title: title || `Tab ${tabsLength + 1}`, position: tabsLength, diff --git a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts index f137225e49..15655a8ce1 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutHandleLayoutChange.ts @@ -81,7 +81,6 @@ export const usePageLayoutHandleLayoutChange = ( objectMetadataId: null, gridPosition: widget.gridPosition, configuration: widget.configuration || undefined, - data: widget.data, createdAt: tab.widgets.find((w) => w.id === widget.id)?.createdAt || new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts b/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts new file mode 100644 index 0000000000..79a1a1afb1 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts @@ -0,0 +1,98 @@ +import { usePageLayoutDraftState } from '@/page-layout/hooks/usePageLayoutDraftState'; +import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; +import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; +import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState'; +import { type PageLayout } from '@/page-layout/types/pageLayoutTypes'; +import { convertPageLayoutDraftToUpdateInput } from '@/page-layout/utils/convertPageLayoutDraftToUpdateInput'; +import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; +import { useRecoilCallback } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { useUpdatePageLayoutWithTabsAndWidgetsMutation } from '~/generated/graphql'; + +export const useSavePageLayout = (pageLayoutIdFromProps: string) => { + const pageLayoutId = useAvailableComponentInstanceIdOrThrow( + PageLayoutComponentInstanceContext, + pageLayoutIdFromProps, + ); + + const pageLayoutPersistedCallbackState = useRecoilComponentCallbackState( + pageLayoutPersistedComponentState, + pageLayoutId, + ); + + const pageLayoutCurrentLayoutsCallbackState = useRecoilComponentCallbackState( + pageLayoutCurrentLayoutsComponentState, + pageLayoutId, + ); + + const { pageLayoutDraft } = usePageLayoutDraftState(pageLayoutId); + + const [updatePageLayoutWithTabsAndWidgets] = + useUpdatePageLayoutWithTabsAndWidgetsMutation(); + + const savePageLayout = useRecoilCallback( + ({ set }) => + async () => { + const updateInput = + convertPageLayoutDraftToUpdateInput(pageLayoutDraft); + + const { data } = await updatePageLayoutWithTabsAndWidgets({ + variables: { + id: pageLayoutId, + input: updateInput, + }, + }); + + const updatedPageLayout = data?.updatePageLayoutWithTabsAndWidgets; + + if (isDefined(updatedPageLayout)) { + const pageLayoutToPersist: PageLayout = { + id: updatedPageLayout.id, + name: updatedPageLayout.name, + type: updatedPageLayout.type, + objectMetadataId: updatedPageLayout.objectMetadataId, + tabs: + updatedPageLayout.tabs?.map((tab) => ({ + id: tab.id, + title: tab.title, + position: tab.position, + pageLayoutId: tab.pageLayoutId, + createdAt: tab.createdAt, + updatedAt: tab.updatedAt, + widgets: + tab.widgets?.map((widget) => ({ + id: widget.id, + title: widget.title, + type: widget.type, + pageLayoutTabId: widget.pageLayoutTabId, + objectMetadataId: widget.objectMetadataId, + configuration: widget.configuration, + gridPosition: widget.gridPosition, + createdAt: widget.createdAt, + updatedAt: widget.updatedAt, + })) ?? [], + })) ?? [], + createdAt: updatedPageLayout.createdAt, + updatedAt: updatedPageLayout.updatedAt, + }; + + set(pageLayoutPersistedCallbackState, pageLayoutToPersist); + set( + pageLayoutCurrentLayoutsCallbackState, + convertPageLayoutToTabLayouts(pageLayoutToPersist), + ); + } + }, + [ + pageLayoutCurrentLayoutsCallbackState, + pageLayoutDraft, + pageLayoutId, + pageLayoutPersistedCallbackState, + updatePageLayoutWithTabsAndWidgets, + ], + ); + + return { savePageLayout }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useUpdatePageLayoutWidget.ts b/packages/twenty-front/src/modules/page-layout/hooks/useUpdatePageLayoutWidget.ts index 7d20578b04..c863350c67 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useUpdatePageLayoutWidget.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useUpdatePageLayoutWidget.ts @@ -1,9 +1,9 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; -import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useRecoilCallback } from 'recoil'; +import { type PageLayoutWidget } from '~/generated/graphql'; export const useUpdatePageLayoutWidget = (pageLayoutIdFromProps?: string) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( @@ -18,7 +18,7 @@ export const useUpdatePageLayoutWidget = (pageLayoutIdFromProps?: string) => { 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/page-layout/mocks/mockWidgets.ts b/packages/twenty-front/src/modules/page-layout/mocks/mockWidgets.ts index 402445f737..05d0cd3af3 100644 --- a/packages/twenty-front/src/modules/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 PageLayoutWidgetWithData } from '../types/pageLayoutTypes'; +import { type PageLayoutWidget } from '~/generated/graphql'; export enum WidgetType { VIEW = 'VIEW', @@ -16,7 +16,7 @@ export enum GraphType { LINE = 'LINE', } -export const mockPageLayoutWidgets: PageLayoutWidgetWithData[] = [ +export const mockPageLayoutWidgets: PageLayoutWidget[] = [ { id: 'widget-1', pageLayoutTabId: 'tab-overview', @@ -32,10 +32,6 @@ export const mockPageLayoutWidgets: PageLayoutWidgetWithData[] = [ configuration: { graphType: GraphType.NUMBER, }, - data: { - value: '1,234', - trendPercentage: 12.5, - }, createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', deletedAt: null, @@ -55,12 +51,6 @@ export const mockPageLayoutWidgets: PageLayoutWidgetWithData[] = [ configuration: { graphType: GraphType.GAUGE, }, - data: { - value: 0.5, - min: 0, - max: 1, - label: 'Conversion rate', - }, createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', deletedAt: null, @@ -80,40 +70,6 @@ export const mockPageLayoutWidgets: PageLayoutWidgetWithData[] = [ 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', updatedAt: '2024-01-01T00:00:00Z', deletedAt: null, @@ -133,52 +89,6 @@ export const mockPageLayoutWidgets: PageLayoutWidgetWithData[] = [ configuration: { graphType: GraphType.BAR, }, - data: { - items: [ - { - month: 'Jan', - sales: 120, - leads: 45, - conversions: 12, - to: '/metrics/january', - }, - { - month: 'Feb', - sales: 150, - leads: 52, - conversions: 15, - to: '/metrics/february', - }, - { - month: 'Mar', - sales: 180, - leads: 48, - conversions: 18, - to: '/metrics/march', - }, - { - month: 'Apr', - sales: 140, - leads: 60, - conversions: 14, - to: '/metrics/april', - }, - { - month: 'May', - sales: 200, - leads: 55, - conversions: 20, - to: '/metrics/may', - }, - ], - indexBy: 'month', - keys: ['sales', 'leads', 'conversions'], - showLegend: true, - showGrid: true, - displayType: 'number', - xAxisLabel: 'Month', - yAxisLabel: 'Count', - }, createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', deletedAt: null, diff --git a/packages/twenty-front/src/modules/page-layout/states/pageLayoutPersistedComponentState.ts b/packages/twenty-front/src/modules/page-layout/states/pageLayoutPersistedComponentState.ts index 0f4ba738af..9ade31fcef 100644 --- a/packages/twenty-front/src/modules/page-layout/states/pageLayoutPersistedComponentState.ts +++ b/packages/twenty-front/src/modules/page-layout/states/pageLayoutPersistedComponentState.ts @@ -1,10 +1,10 @@ import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; -import { type PageLayoutWithData } from '../types/pageLayoutTypes'; +import { type PageLayout } from '../types/pageLayoutTypes'; import { PageLayoutComponentInstanceContext } from './contexts/PageLayoutComponentInstanceContext'; export const pageLayoutPersistedComponentState = createComponentState< - PageLayoutWithData | undefined + PageLayout | undefined >({ key: 'pageLayoutPersistedComponentState', defaultValue: undefined, diff --git a/packages/twenty-front/src/modules/page-layout/states/savedPageLayoutsComponentState.ts b/packages/twenty-front/src/modules/page-layout/states/savedPageLayoutsComponentState.ts index 5269b53175..6215bda47f 100644 --- a/packages/twenty-front/src/modules/page-layout/states/savedPageLayoutsComponentState.ts +++ b/packages/twenty-front/src/modules/page-layout/states/savedPageLayoutsComponentState.ts @@ -1,10 +1,10 @@ import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; -import { type PageLayoutWithData } from '../types/pageLayoutTypes'; +import { type PageLayout } from '../types/pageLayoutTypes'; import { PageLayoutComponentInstanceContext } from './contexts/PageLayoutComponentInstanceContext'; export const savedPageLayoutsComponentState = createComponentState< - PageLayoutWithData[] + PageLayout[] >({ key: 'savedPageLayoutsComponentState', defaultValue: [], diff --git a/packages/twenty-front/src/modules/page-layout/types/draft-page-layout.ts b/packages/twenty-front/src/modules/page-layout/types/draft-page-layout.ts index e2bef1cd62..6750fa48d8 100644 --- a/packages/twenty-front/src/modules/page-layout/types/draft-page-layout.ts +++ b/packages/twenty-front/src/modules/page-layout/types/draft-page-layout.ts @@ -1,6 +1,6 @@ -import { type PageLayoutWithData } from './pageLayoutTypes'; +import { type PageLayout } from './pageLayoutTypes'; export type DraftPageLayout = Omit< - PageLayoutWithData, + PageLayout, 'createdAt' | 'updatedAt' | 'deletedAt' >; diff --git a/packages/twenty-front/src/modules/page-layout/types/pageLayoutTypes.ts b/packages/twenty-front/src/modules/page-layout/types/pageLayoutTypes.ts index 55ce02298c..1c0466e07c 100644 --- a/packages/twenty-front/src/modules/page-layout/types/pageLayoutTypes.ts +++ b/packages/twenty-front/src/modules/page-layout/types/pageLayoutTypes.ts @@ -1,18 +1,13 @@ import { - type PageLayout, - type PageLayoutTab, + type PageLayout as PageLayoutGenerated, + type PageLayoutTab as PageLayoutTabGenerated, type PageLayoutWidget, } from '~/generated/graphql'; -// TODO: Remove this once we query the data from the database -export type PageLayoutWidgetWithData = PageLayoutWidget & { - data?: Record; +export type PageLayoutTab = Omit & { + widgets: PageLayoutWidget[]; }; -export type PageLayoutTabWithData = Omit & { - widgets: PageLayoutWidgetWithData[]; -}; - -export type PageLayoutWithData = Omit & { - tabs: PageLayoutTabWithData[]; +export type PageLayout = Omit & { + tabs: PageLayoutTab[]; }; diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/addWidgetToTab.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/addWidgetToTab.test.ts index d1396f0456..c59c3f9f06 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/addWidgetToTab.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/addWidgetToTab.test.ts @@ -1,12 +1,10 @@ +import { type PageLayoutWidget } from '~/generated/graphql'; import { WidgetType } from '../../mocks/mockWidgets'; -import { - type PageLayoutTabWithData, - type PageLayoutWidgetWithData, -} from '../../types/pageLayoutTypes'; +import { type PageLayoutTab } from '../../types/pageLayoutTypes'; import { addWidgetToTab } from '../addWidgetToTab'; describe('addWidgetToTab', () => { - const mockWidget: PageLayoutWidgetWithData = { + const mockWidget: PageLayoutWidget = { id: 'widget-1', pageLayoutTabId: 'tab-1', title: 'Test Widget', @@ -18,7 +16,7 @@ describe('addWidgetToTab', () => { deletedAt: null, }; - const mockTabs: PageLayoutTabWithData[] = [ + const mockTabs: PageLayoutTab[] = [ { id: 'tab-1', title: 'Tab 1', @@ -45,7 +43,7 @@ describe('addWidgetToTab', () => { const result = addWidgetToTab(mockTabs, 'tab-1', mockWidget); expect(result[0].widgets).toHaveLength(1); - expect(result[0].widgets[0]).toEqual(mockWidget); + expect(result[0].widgets?.[0]).toEqual(mockWidget); expect(result[1].widgets).toHaveLength(0); }); @@ -65,7 +63,7 @@ describe('addWidgetToTab', () => { }); it('should add multiple widgets to the same tab', () => { - const secondWidget: PageLayoutWidgetWithData = { + const secondWidget: PageLayoutWidget = { ...mockWidget, id: 'widget-2', title: 'Second Widget', @@ -75,8 +73,8 @@ describe('addWidgetToTab', () => { result = addWidgetToTab(result, 'tab-1', secondWidget); expect(result[0].widgets).toHaveLength(2); - expect(result[0].widgets[0]).toEqual(mockWidget); - expect(result[0].widgets[1]).toEqual(secondWidget); + expect(result[0].widgets?.[0]).toEqual(mockWidget); + expect(result[0].widgets?.[1]).toEqual(secondWidget); }); it('should return a new array without mutating the original', () => { diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts index 5ddbe34737..6cdcc23d68 100644 --- a/packages/twenty-front/src/modules/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 { type PageLayoutWidget } from '~/generated/graphql'; import { GraphType, WidgetType } from '../../mocks/mockWidgets'; -import { type PageLayoutWidgetWithData } from '../../types/pageLayoutTypes'; import { convertLayoutsToWidgets } from '../convertLayoutsToWidgets'; describe('convertLayoutsToWidgets', () => { - const mockWidgets: PageLayoutWidgetWithData[] = [ + const mockWidgets: PageLayoutWidget[] = [ { id: 'widget-1', pageLayoutTabId: 'tab-1', @@ -19,7 +19,6 @@ describe('convertLayoutsToWidgets', () => { configuration: { graphType: GraphType.NUMBER, }, - data: { value: 100 }, createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', deletedAt: null, @@ -39,7 +38,6 @@ describe('convertLayoutsToWidgets', () => { configuration: { graphType: GraphType.PIE, }, - data: { items: [] }, createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', deletedAt: null, diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutToTabLayouts.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutToTabLayouts.test.ts index 3818592412..beb22ff48f 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutToTabLayouts.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutToTabLayouts.test.ts @@ -1,10 +1,10 @@ -import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes'; +import { type PageLayout } from '@/page-layout/types/pageLayoutTypes'; import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; import { PageLayoutType, WidgetType } from '~/generated/graphql'; describe('convertPageLayoutToTabLayouts', () => { it('should convert page layout to tab layouts', () => { - const pageLayout: PageLayoutWithData = { + const pageLayout: PageLayout = { id: 'page-layout-1', name: 'Page Layout 1', type: PageLayoutType.RECORD_PAGE, diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/removeWidgetFromTab.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/removeWidgetFromTab.test.ts index 13d03f6345..5e3a9f6462 100644 --- a/packages/twenty-front/src/modules/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 PageLayoutTabWithData } from '../../types/pageLayoutTypes'; +import { type PageLayoutTab } from '../../types/pageLayoutTypes'; import { removeWidgetFromTab } from '../removeWidgetFromTab'; describe('removeWidgetFromTab', () => { - const mockTabs: PageLayoutTabWithData[] = [ + const mockTabs: PageLayoutTab[] = [ { id: 'tab-1', title: 'Tab 1', diff --git a/packages/twenty-front/src/modules/page-layout/utils/addWidgetToTab.ts b/packages/twenty-front/src/modules/page-layout/utils/addWidgetToTab.ts index 97664ef841..415319751f 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/addWidgetToTab.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/addWidgetToTab.ts @@ -1,18 +1,16 @@ -import { - type PageLayoutTabWithData, - type PageLayoutWidgetWithData, -} from '../types/pageLayoutTypes'; +import { type PageLayoutTab } from '@/page-layout/types/pageLayoutTypes'; +import { type PageLayoutWidget } from '~/generated/graphql'; export const addWidgetToTab = ( - tabs: PageLayoutTabWithData[], + tabs: PageLayoutTab[], activeTabId: string, - newWidget: PageLayoutWidgetWithData, -): PageLayoutTabWithData[] => { + newWidget: PageLayoutWidget, +): PageLayoutTab[] => { return tabs.map((tab) => { if (tab.id === activeTabId) { return { ...tab, - widgets: [...tab.widgets, newWidget], + widgets: [...(tab?.widgets ?? []), newWidget], }; } return tab; diff --git a/packages/twenty-front/src/modules/page-layout/utils/convertLayoutsToWidgets.ts b/packages/twenty-front/src/modules/page-layout/utils/convertLayoutsToWidgets.ts index 650406784b..b63c727962 100644 --- a/packages/twenty-front/src/modules/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 '~/generated/graphql'; export const convertLayoutsToWidgets = ( - widgets: PageLayoutWidgetWithData[], + widgets: PageLayoutWidget[], layouts: Layouts, -): PageLayoutWidgetWithData[] => { +): PageLayoutWidget[] => { const activeLayouts = layouts.desktop || layouts.mobile || []; return widgets.map((widget) => { diff --git a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts new file mode 100644 index 0000000000..fb5811ccc2 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts @@ -0,0 +1,31 @@ +import { type DraftPageLayout } from '@/page-layout/types/draft-page-layout'; +import { type UpdatePageLayoutWithTabsInput } from '~/generated/graphql'; + +export const convertPageLayoutDraftToUpdateInput = ( + pageLayoutDraft: DraftPageLayout, +): UpdatePageLayoutWithTabsInput => { + return { + name: pageLayoutDraft.name, + type: pageLayoutDraft.type, + objectMetadataId: pageLayoutDraft.objectMetadataId ?? null, + tabs: pageLayoutDraft.tabs.map((tab) => ({ + id: tab.id, + title: tab.title, + position: tab.position, + widgets: tab.widgets.map((widget) => ({ + id: widget.id, + pageLayoutTabId: widget.pageLayoutTabId, + title: widget.title, + type: widget.type, + objectMetadataId: widget.objectMetadataId ?? null, + gridPosition: { + row: widget.gridPosition.row, + column: widget.gridPosition.column, + rowSpan: widget.gridPosition.rowSpan, + columnSpan: widget.gridPosition.columnSpan, + }, + configuration: widget.configuration ?? null, + })), + })), + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutToTabLayouts.ts b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutToTabLayouts.ts index 0788d1f235..af59d7e5fd 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutToTabLayouts.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutToTabLayouts.ts @@ -1,8 +1,8 @@ -import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes'; +import { type PageLayout } from '@/page-layout/types/pageLayoutTypes'; import { type TabLayouts } from '@/page-layout/types/tab-layouts'; export const convertPageLayoutToTabLayouts = ( - pageLayout: PageLayoutWithData, + pageLayout: PageLayout, ): TabLayouts => { if (pageLayout.tabs.length === 0) { return {}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/isPageLayoutEmpty.ts b/packages/twenty-front/src/modules/page-layout/utils/isPageLayoutEmpty.ts index 92d2cfde12..f8108532a0 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/isPageLayoutEmpty.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/isPageLayoutEmpty.ts @@ -1,6 +1,6 @@ -import { type PageLayoutWithData } from '~/modules/page-layout/types/pageLayoutTypes'; +import { type PageLayout } from '~/modules/page-layout/types/pageLayoutTypes'; -export const isPageLayoutEmpty = (pageLayout: PageLayoutWithData): boolean => { +export const isPageLayoutEmpty = (pageLayout: PageLayout): boolean => { return ( pageLayout.tabs.length === 1 && pageLayout.tabs[0].widgets.length === 0 ); diff --git a/packages/twenty-front/src/modules/page-layout/utils/removeWidgetFromTab.ts b/packages/twenty-front/src/modules/page-layout/utils/removeWidgetFromTab.ts index 8546946e16..2efad46735 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/removeWidgetFromTab.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/removeWidgetFromTab.ts @@ -1,10 +1,10 @@ -import { type PageLayoutTabWithData } from '../types/pageLayoutTypes'; +import { type PageLayoutTab } from '../types/pageLayoutTypes'; export const removeWidgetFromTab = ( - tabs: PageLayoutTabWithData[], + tabs: PageLayoutTab[], tabId: string, widgetId: string, -): PageLayoutTabWithData[] => { +): PageLayoutTab[] => { return tabs.map((tab) => { if (tab.id === tabId) { return { 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 index bbe197e275..15c9b01c88 100644 --- 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 @@ -46,10 +46,6 @@ export const WithNumberChart: Story = { pageLayoutTabId: 'tab-overview', createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', - data: { - value: '1,234', - trendPercentage: 12.5, - }, }, }, render: (args) => ( @@ -67,12 +63,6 @@ export const WithGaugeChart: Story = { configuration: { graphType: GraphType.GAUGE, }, - data: { - value: 0.5, - min: 0, - max: 1, - label: 'Conversion rate', - }, createdAt: '2024-01-01T00:00:00Z', gridPosition: { row: 0, @@ -100,40 +90,6 @@ export const WithPieChart: Story = { 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, @@ -161,10 +117,6 @@ export const SmallWidget: Story = { configuration: { graphType: GraphType.NUMBER, }, - data: { - value: '42', - trendPercentage: 5, - }, createdAt: '2024-01-01T00:00:00Z', gridPosition: { row: 0, @@ -199,12 +151,6 @@ export const MediumWidget: Story = { configuration: { graphType: GraphType.GAUGE, }, - data: { - value: 0.75, - min: 0, - max: 1, - label: 'Progress', - }, createdAt: '2024-01-01T00:00:00Z', gridPosition: { row: 0, @@ -239,14 +185,6 @@ export const LargeWidget: Story = { 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, @@ -281,10 +219,6 @@ export const WideWidget: Story = { configuration: { graphType: GraphType.NUMBER, }, - data: { - value: '1,234,567', - trendPercentage: 23.4, - }, createdAt: '2024-01-01T00:00:00Z', gridPosition: { row: 0, @@ -319,12 +253,6 @@ export const TallWidget: Story = { configuration: { graphType: GraphType.GAUGE, }, - data: { - value: 0.33, - min: 0, - max: 1, - label: 'Utilization', - }, createdAt: '2024-01-01T00:00:00Z', gridPosition: { row: 0, 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 index ce3dbdd6b5..844192ad39 100644 --- 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 @@ -48,7 +48,7 @@ export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => { return null; } - const data = widget.data ?? getDefaultWidgetData(graphType); + const data: any = getDefaultWidgetData(graphType); if (!data) { return null; 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 index a80e13cffb..982bdaf1fe 100644 --- 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 @@ -1,8 +1,7 @@ import { type GraphType } from '@/page-layout/mocks/mockWidgets'; -import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; -import { type WidgetType } from '~/generated/graphql'; +import { type PageLayoutWidget, type WidgetType } from '~/generated/graphql'; -export type GraphWidget = PageLayoutWidgetWithData & { +export type GraphWidget = PageLayoutWidget & { type: WidgetType.GRAPH; configuration: { graphType: GraphType; 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 index 1dc9248357..6e81316617 100644 --- 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 @@ -1,7 +1,6 @@ -import { type PageLayoutWidgetWithData } from '@/page-layout/types/pageLayoutTypes'; -import { type WidgetType } from '~/generated/graphql'; +import { type PageLayoutWidget, type WidgetType } from '~/generated/graphql'; -export type IframeWidget = PageLayoutWidgetWithData & { +export type IframeWidget = PageLayoutWidget & { type: WidgetType.IFRAME; configuration: { url: string; diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-with-tabs.input.ts b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-with-tabs.input.ts index b72744f054..ce8c45b735 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-with-tabs.input.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-with-tabs.input.ts @@ -6,6 +6,7 @@ import { IsArray, IsEnum, IsNotEmpty, + IsOptional, IsString, IsUUID, ValidateNested, @@ -27,8 +28,9 @@ export class UpdatePageLayoutWithTabsInput { @IsNotEmpty() type: PageLayoutType; - @Field(() => UUIDScalarType) + @Field(() => UUIDScalarType, { nullable: true }) @IsUUID() + @IsOptional() objectMetadataId: string | null; @Field(() => [UpdatePageLayoutTabWithWidgetsInput])