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
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -3684,7 +3684,7 @@ export type UpdatePageLayoutWidgetWithIdInput = {
|
||||
|
||||
export type UpdatePageLayoutWithTabsInput = {
|
||||
name: Scalars['String'];
|
||||
objectMetadataId: Scalars['UUID'];
|
||||
objectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
tabs: Array<UpdatePageLayoutTabWithWidgetsInput>;
|
||||
type: PageLayoutType;
|
||||
};
|
||||
|
||||
@@ -3530,7 +3530,7 @@ export type UpdatePageLayoutWidgetWithIdInput = {
|
||||
|
||||
export type UpdatePageLayoutWithTabsInput = {
|
||||
name: Scalars['String'];
|
||||
objectMetadataId: Scalars['UUID'];
|
||||
objectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
tabs: Array<UpdatePageLayoutTabWithWidgetsInput>;
|
||||
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<Sea
|
||||
export type SearchQueryHookResult = ReturnType<typeof useSearchQuery>;
|
||||
export type SearchLazyQueryHookResult = ReturnType<typeof useSearchLazyQuery>;
|
||||
export type SearchQueryResult = Apollo.QueryResult<SearchQuery, SearchQueryVariables>;
|
||||
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<UpdatePageLayoutWithTabsAndWidgetsMutation, UpdatePageLayoutWithTabsAndWidgetsMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<UpdatePageLayoutWithTabsAndWidgetsMutation, UpdatePageLayoutWithTabsAndWidgetsMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdatePageLayoutWithTabsAndWidgetsMutation, UpdatePageLayoutWithTabsAndWidgetsMutationVariables>(UpdatePageLayoutWithTabsAndWidgetsDocument, options);
|
||||
}
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutationHookResult = ReturnType<typeof useUpdatePageLayoutWithTabsAndWidgetsMutation>;
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutationResult = Apollo.MutationResult<UpdatePageLayoutWithTabsAndWidgetsMutation>;
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutationOptions = Apollo.BaseMutationOptions<UpdatePageLayoutWithTabsAndWidgetsMutation, UpdatePageLayoutWithTabsAndWidgetsMutationVariables>;
|
||||
export const OnDbEventDocument = gql`
|
||||
subscription OnDbEvent($input: OnDbEventInput!) {
|
||||
onDbEvent(input: $input) {
|
||||
|
||||
+20
-2
@@ -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: <EditDashboardSingleRecordAction />,
|
||||
},
|
||||
[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: <SaveDashboardSingleRecordAction />,
|
||||
},
|
||||
[DashboardSingleRecordActionKeys.CANCEL_DASHBOARD_EDITION]: {
|
||||
key: DashboardSingleRecordActionKeys.CANCEL_DASHBOARD_EDITION,
|
||||
label: msg`Cancel Edition`,
|
||||
|
||||
+26
@@ -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 <Action onClick={handleClick} />;
|
||||
};
|
||||
+1
@@ -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',
|
||||
}
|
||||
|
||||
+2
-4
@@ -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<string, boolean | undefined>
|
||||
>({
|
||||
key: 'forceRegisteredActionsByKeyComponentState',
|
||||
defaultValue: {},
|
||||
componentInstanceContext: ActionMenuComponentInstanceContext,
|
||||
});
|
||||
|
||||
+3
-3
@@ -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 {
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
+4
-4
@@ -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,
|
||||
|
||||
@@ -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 = ({
|
||||
|
||||
+5
-38
@@ -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,
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
+42
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
+6
-6
@@ -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 };
|
||||
-1
@@ -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(),
|
||||
|
||||
+6
-9
@@ -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<string, unknown>,
|
||||
objectMetadataId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
+3
-5
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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<PageLayoutWidgetWithData>) => {
|
||||
(widgetId: string, updates: Partial<PageLayoutWidget>) => {
|
||||
set(pageLayoutDraftState, (prev) => ({
|
||||
...prev,
|
||||
tabs: prev.tabs.map((tab) => ({
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+2
-2
@@ -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: [],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type PageLayoutWithData } from './pageLayoutTypes';
|
||||
import { type PageLayout } from './pageLayoutTypes';
|
||||
|
||||
export type DraftPageLayout = Omit<
|
||||
PageLayoutWithData,
|
||||
PageLayout,
|
||||
'createdAt' | 'updatedAt' | 'deletedAt'
|
||||
>;
|
||||
|
||||
@@ -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<string, any>;
|
||||
export type PageLayoutTab = Omit<PageLayoutTabGenerated, 'widgets'> & {
|
||||
widgets: PageLayoutWidget[];
|
||||
};
|
||||
|
||||
export type PageLayoutTabWithData = Omit<PageLayoutTab, 'widgets'> & {
|
||||
widgets: PageLayoutWidgetWithData[];
|
||||
};
|
||||
|
||||
export type PageLayoutWithData = Omit<PageLayout, 'tabs'> & {
|
||||
tabs: PageLayoutTabWithData[];
|
||||
export type PageLayout = Omit<PageLayoutGenerated, 'tabs'> & {
|
||||
tabs: PageLayoutTab[];
|
||||
};
|
||||
|
||||
+8
-10
@@ -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', () => {
|
||||
|
||||
+2
-4
@@ -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,
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+2
-2
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
+31
@@ -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,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
};
|
||||
+2
-2
@@ -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 {};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
-72
@@ -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,
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-1
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user