Add configuration types for widgets (#14717)
closes https://github.com/twentyhq/core-team-issues/issues/1532
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget';
|
||||
import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets';
|
||||
import { GraphType, WidgetType } from '~/generated-metadata/graphql';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import {
|
||||
|
||||
+4
-1
@@ -62,7 +62,10 @@ export const CommandMenuPageLayoutIframeConfig = () => {
|
||||
|
||||
const [title, setTitle] = useState(editingWidget?.title || '');
|
||||
|
||||
const configUrl = editingWidget?.configuration?.url;
|
||||
const configUrl =
|
||||
editingWidget?.configuration && 'url' in editingWidget.configuration
|
||||
? editingWidget.configuration.url
|
||||
: undefined;
|
||||
|
||||
const [url, setUrl] = useState(isString(configUrl) ? configUrl : '');
|
||||
|
||||
|
||||
+1
-1
@@ -1,12 +1,12 @@
|
||||
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { WidgetType } from '@/page-layout/mocks/mockWidgets';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconChartPie, IconFrame, IconList } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { WidgetType } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+1
-1
@@ -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 PageLayout } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { isPageLayoutEmpty } from '@/page-layout/utils/isPageLayoutEmpty';
|
||||
|
||||
type DashboardContentRendererProps = {
|
||||
|
||||
+1
-18
@@ -1,18 +1 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
fragment PageLayoutWidgetFragment on PageLayoutWidget {
|
||||
id
|
||||
title
|
||||
type
|
||||
objectMetadataId
|
||||
gridPosition {
|
||||
column
|
||||
columnSpan
|
||||
row
|
||||
rowSpan
|
||||
}
|
||||
configuration
|
||||
pageLayoutTabId
|
||||
}
|
||||
`;
|
||||
export { PAGE_LAYOUT_WIDGET_FRAGMENT } from '@/page-layout/graphql/fragments/pageLayoutWidgetFragment';
|
||||
|
||||
@@ -14,7 +14,6 @@ import { pageLayoutCurrentBreakpointComponentState } from '@/page-layout/states/
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder';
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
import { type Widget } from '@/page-layout/widgets/types/Widget';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
@@ -84,7 +83,7 @@ export const PageLayoutGridLayout = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeTabWidgets = currentPageLayout?.tabs.find(
|
||||
const activeTabWidgets = currentPageLayout.tabs.find(
|
||||
(tab) => tab.id === activeTabId,
|
||||
)?.widgets;
|
||||
|
||||
@@ -141,7 +140,7 @@ export const PageLayoutGridLayout = () => {
|
||||
) : (
|
||||
activeTabWidgets?.map((widget) => (
|
||||
<div key={widget.id} data-select-disable="true">
|
||||
<WidgetRenderer widget={widget as Widget} />
|
||||
<WidgetRenderer widget={widget} />
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
+5
-2
@@ -2,8 +2,9 @@ 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 PageLayout } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
|
||||
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useQuery } from '@apollo/client';
|
||||
@@ -29,7 +30,9 @@ export const PageLayoutInitializationQueryEffect = ({
|
||||
},
|
||||
});
|
||||
|
||||
const pageLayout: PageLayout | undefined = data?.getPageLayout;
|
||||
const pageLayout: PageLayout | undefined = data?.getPageLayout
|
||||
? transformPageLayout(data.getPageLayout)
|
||||
: undefined;
|
||||
|
||||
const pageLayoutPersistedComponentCallbackState =
|
||||
useRecoilComponentCallbackState(pageLayoutPersistedComponentState);
|
||||
|
||||
@@ -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 PageLayout } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
|
||||
import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext';
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
|
||||
+24
-1
@@ -4,8 +4,13 @@ import { expect, within } from '@storybook/test';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { FIND_ONE_PAGE_LAYOUT } from '@/dashboards/graphql/queries/findOnePageLayout';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer';
|
||||
import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets';
|
||||
import {
|
||||
GraphOrderBy,
|
||||
GraphType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
@@ -53,7 +58,10 @@ const mixedGraphsPageLayoutMocks = {
|
||||
columnSpan: 3,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'NumberChartConfiguration',
|
||||
graphType: GraphType.NUMBER,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
@@ -74,7 +82,12 @@ const mixedGraphsPageLayoutMocks = {
|
||||
columnSpan: 3,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'GaugeChartConfiguration',
|
||||
graphType: GraphType.GAUGE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
aggregateOperationTotal: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataIdTotal: 'id',
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
@@ -95,7 +108,12 @@ const mixedGraphsPageLayoutMocks = {
|
||||
columnSpan: 3,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'PieChartConfiguration',
|
||||
graphType: GraphType.PIE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
groupByFieldMetadataId: 'createdAt',
|
||||
orderBy: GraphOrderBy.VALUE_DESC,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
@@ -116,7 +134,12 @@ const mixedGraphsPageLayoutMocks = {
|
||||
columnSpan: 6,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration',
|
||||
graphType: GraphType.BAR,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
groupByFieldMetadataIdX: 'createdAt',
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
fragment PageLayoutWidgetFragment on PageLayoutWidget {
|
||||
id
|
||||
title
|
||||
type
|
||||
objectMetadataId
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
gridPosition {
|
||||
column
|
||||
columnSpan
|
||||
row
|
||||
rowSpan
|
||||
}
|
||||
configuration {
|
||||
... on BarChartConfiguration {
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
groupByFieldMetadataIdX
|
||||
orderByX
|
||||
groupByFieldMetadataIdY
|
||||
orderByY
|
||||
omitNullValues
|
||||
xAxisName
|
||||
yAxisName
|
||||
rangeMin
|
||||
rangeMax
|
||||
filter
|
||||
}
|
||||
... on LineChartConfiguration {
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
groupByFieldMetadataIdX
|
||||
orderByX
|
||||
groupByFieldMetadataIdY
|
||||
orderByY
|
||||
filter
|
||||
}
|
||||
... on PieChartConfiguration {
|
||||
graphType
|
||||
groupByFieldMetadataId
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
orderBy
|
||||
filter
|
||||
}
|
||||
... on NumberChartConfiguration {
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
description
|
||||
filter
|
||||
}
|
||||
... on GaugeChartConfiguration {
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
aggregateOperationTotal
|
||||
aggregateFieldMetadataIdTotal
|
||||
description
|
||||
filter
|
||||
}
|
||||
... on IframeConfiguration {
|
||||
url
|
||||
}
|
||||
}
|
||||
pageLayoutTabId
|
||||
}
|
||||
`;
|
||||
+4
-14
@@ -1,6 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { PAGE_LAYOUT_WIDGET_FRAGMENT } from '../fragments/pageLayoutWidgetFragment';
|
||||
|
||||
export const UPDATE_PAGE_LAYOUT_WITH_TABS_AND_WIDGETS = gql`
|
||||
${PAGE_LAYOUT_WIDGET_FRAGMENT}
|
||||
mutation UpdatePageLayoutWithTabsAndWidgets(
|
||||
$id: String!
|
||||
$input: UpdatePageLayoutWithTabsInput!
|
||||
@@ -19,20 +22,7 @@ export const UPDATE_PAGE_LAYOUT_WITH_TABS_AND_WIDGETS = gql`
|
||||
position
|
||||
pageLayoutId
|
||||
widgets {
|
||||
id
|
||||
title
|
||||
type
|
||||
pageLayoutTabId
|
||||
objectMetadataId
|
||||
gridPosition {
|
||||
row
|
||||
column
|
||||
rowSpan
|
||||
columnSpan
|
||||
}
|
||||
configuration
|
||||
createdAt
|
||||
updatedAt
|
||||
...PageLayoutWidgetFragment
|
||||
}
|
||||
createdAt
|
||||
updatedAt
|
||||
|
||||
+6
-2
@@ -1,7 +1,7 @@
|
||||
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';
|
||||
import { GraphType, WidgetType } from '~/generated-metadata/graphql';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
@@ -180,7 +180,11 @@ describe('useCreatePageLayoutGraphWidget', () => {
|
||||
const widget = result.current.allWidgets[index];
|
||||
expect(widget.type).toBe(WidgetType.GRAPH);
|
||||
expect(widget.pageLayoutTabId).toBe('tab-1');
|
||||
expect(widget.configuration.graphType).toBe(graphType);
|
||||
expect(
|
||||
widget.configuration && 'graphType' in widget.configuration
|
||||
? widget.configuration.graphType
|
||||
: null,
|
||||
).toBe(graphType);
|
||||
expect(widget.id).toBe('mock-uuid');
|
||||
});
|
||||
|
||||
|
||||
+13
-2
@@ -1,4 +1,9 @@
|
||||
import { GraphType, WidgetType } from '@/page-layout/mocks/mockWidgets';
|
||||
import {
|
||||
GraphOrderBy,
|
||||
GraphType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { PageLayoutType } from '~/generated/graphql';
|
||||
import { usePageLayoutDraftState } from '../usePageLayoutDraftState';
|
||||
@@ -95,7 +100,13 @@ describe('usePageLayoutDraftState', () => {
|
||||
title: 'New Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
gridPosition: { row: 2, column: 2, rowSpan: 2, columnSpan: 2 },
|
||||
configuration: { graphType: GraphType.BAR },
|
||||
configuration: {
|
||||
graphType: GraphType.BAR,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
groupByFieldMetadataIdX: 'createdAt',
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
},
|
||||
objectMetadataId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
|
||||
+13
-16
@@ -1,9 +1,9 @@
|
||||
import { type GraphType } from '@/page-layout/mocks/mockWidgets';
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
|
||||
import { createDefaultGraphWidget } from '@/page-layout/utils/createDefaultGraphWidget';
|
||||
import {
|
||||
getWidgetSize,
|
||||
getWidgetTitle,
|
||||
@@ -11,12 +11,13 @@ import {
|
||||
import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition';
|
||||
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
|
||||
import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
|
||||
import { type GraphType } from '~/generated-metadata/graphql';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
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 { v4 as uuidv4 } from 'uuid';
|
||||
import { type PageLayoutWidget, type WidgetType } from '~/generated/graphql';
|
||||
import { type WidgetType } from '~/generated/graphql';
|
||||
|
||||
export const useCreatePageLayoutGraphWidget = (
|
||||
pageLayoutIdFromProps?: string,
|
||||
@@ -72,7 +73,10 @@ export const useCreatePageLayoutGraphWidget = (
|
||||
const allWidgets = pageLayoutDraft.tabs.flatMap((tab) => tab.widgets);
|
||||
const existingWidgetCount = allWidgets.filter(
|
||||
(w) =>
|
||||
w.type === widgetType && w.configuration.graphType === graphType,
|
||||
w.type === widgetType &&
|
||||
w.configuration &&
|
||||
'graphType' in w.configuration &&
|
||||
w.configuration.graphType === graphType,
|
||||
).length;
|
||||
const title = getWidgetTitle(graphType, existingWidgetCount);
|
||||
const widgetId = uuidv4();
|
||||
@@ -83,25 +87,18 @@ export const useCreatePageLayoutGraphWidget = (
|
||||
defaultSize,
|
||||
);
|
||||
|
||||
const newWidget: PageLayoutWidget = {
|
||||
id: widgetId,
|
||||
pageLayoutTabId: activeTabId,
|
||||
const newWidget = createDefaultGraphWidget(
|
||||
widgetId,
|
||||
activeTabId,
|
||||
title,
|
||||
type: widgetType,
|
||||
gridPosition: {
|
||||
graphType,
|
||||
{
|
||||
row: position.y,
|
||||
column: position.x,
|
||||
rowSpan: position.h,
|
||||
columnSpan: position.w,
|
||||
},
|
||||
configuration: {
|
||||
graphType,
|
||||
},
|
||||
objectMetadataId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
};
|
||||
);
|
||||
|
||||
const newLayout = {
|
||||
i: widgetId,
|
||||
|
||||
+7
-14
@@ -3,6 +3,7 @@ import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pag
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
|
||||
import { createDefaultIframeWidget } from '@/page-layout/utils/createDefaultIframeWidget';
|
||||
import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition';
|
||||
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
|
||||
import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
|
||||
@@ -12,7 +13,6 @@ 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 { type PageLayoutWidget, WidgetType } from '~/generated/graphql';
|
||||
|
||||
export const useCreatePageLayoutIframeWidget = (
|
||||
pageLayoutIdFromProps?: string,
|
||||
@@ -64,25 +64,18 @@ export const useCreatePageLayoutIframeWidget = (
|
||||
defaultSize,
|
||||
);
|
||||
|
||||
const newWidget: PageLayoutWidget = {
|
||||
id: widgetId,
|
||||
pageLayoutTabId: activeTabId,
|
||||
const newWidget = createDefaultIframeWidget(
|
||||
widgetId,
|
||||
activeTabId,
|
||||
title,
|
||||
type: WidgetType.IFRAME,
|
||||
gridPosition: {
|
||||
url,
|
||||
{
|
||||
row: position.y,
|
||||
column: position.x,
|
||||
rowSpan: position.h,
|
||||
columnSpan: position.w,
|
||||
},
|
||||
configuration: {
|
||||
url,
|
||||
},
|
||||
objectMetadataId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
};
|
||||
);
|
||||
|
||||
const newLayout = {
|
||||
i: widgetId,
|
||||
|
||||
@@ -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 PageLayoutTab } from '../types/pageLayoutTypes';
|
||||
import { type PageLayoutTab } from '../types/PageLayoutTab';
|
||||
import { getEmptyTabLayout } from '../utils/getEmptyTabLayout';
|
||||
|
||||
export const useCreatePageLayoutTab = (pageLayoutIdFromProps?: string) => {
|
||||
|
||||
+3
-16
@@ -71,22 +71,9 @@ export const usePageLayoutHandleLayoutChange = (
|
||||
...prev,
|
||||
tabs: prev.tabs.map((tab) => {
|
||||
if (tab.id === activeTabId) {
|
||||
const tabWidgets = updatedWidgets
|
||||
.filter((w) => w.pageLayoutTabId === activeTabId)
|
||||
.map((widget) => ({
|
||||
id: widget.id,
|
||||
pageLayoutTabId: widget.pageLayoutTabId || activeTabId,
|
||||
title: widget.title,
|
||||
type: widget.type,
|
||||
objectMetadataId: null,
|
||||
gridPosition: widget.gridPosition,
|
||||
configuration: widget.configuration || undefined,
|
||||
createdAt:
|
||||
tab.widgets.find((w) => w.id === widget.id)?.createdAt ||
|
||||
new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
}));
|
||||
const tabWidgets = updatedWidgets.filter(
|
||||
(w) => w.pageLayoutTabId === activeTabId,
|
||||
);
|
||||
return {
|
||||
...tab,
|
||||
widgets: tabWidgets,
|
||||
|
||||
@@ -2,9 +2,10 @@ import { usePageLayoutDraftState } from '@/page-layout/hooks/usePageLayoutDraftS
|
||||
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 { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { convertPageLayoutDraftToUpdateInput } from '@/page-layout/utils/convertPageLayoutDraftToUpdateInput';
|
||||
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
|
||||
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
@@ -48,35 +49,8 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
|
||||
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,
|
||||
};
|
||||
const pageLayoutToPersist: PageLayout =
|
||||
transformPageLayout(updatedPageLayout);
|
||||
|
||||
set(pageLayoutPersistedCallbackState, pageLayoutToPersist);
|
||||
set(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
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(
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
import { type Layouts } from 'react-grid-layout';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
export enum WidgetType {
|
||||
VIEW = 'VIEW',
|
||||
IFRAME = 'IFRAME',
|
||||
FIELDS = 'FIELDS',
|
||||
GRAPH = 'GRAPH',
|
||||
}
|
||||
|
||||
export enum GraphType {
|
||||
NUMBER = 'NUMBER',
|
||||
GAUGE = 'GAUGE',
|
||||
PIE = 'PIE',
|
||||
BAR = 'BAR',
|
||||
LINE = 'LINE',
|
||||
}
|
||||
|
||||
export const mockPageLayoutWidgets: PageLayoutWidget[] = [
|
||||
{
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
type: WidgetType.GRAPH,
|
||||
title: 'Sales Pipeline',
|
||||
objectMetadataId: null,
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 2,
|
||||
columnSpan: 3,
|
||||
},
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: 'widget-2',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
type: WidgetType.GRAPH,
|
||||
title: 'Conversion Rate',
|
||||
objectMetadataId: null,
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 6,
|
||||
rowSpan: 5,
|
||||
columnSpan: 3,
|
||||
},
|
||||
configuration: {
|
||||
graphType: GraphType.GAUGE,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: 'widget-3',
|
||||
pageLayoutTabId: 'tab-analytics',
|
||||
type: WidgetType.GRAPH,
|
||||
title: 'Lead Distribution',
|
||||
objectMetadataId: null,
|
||||
gridPosition: {
|
||||
row: 2,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 6,
|
||||
},
|
||||
configuration: {
|
||||
graphType: GraphType.PIE,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: 'widget-4',
|
||||
pageLayoutTabId: 'tab-reports',
|
||||
type: WidgetType.GRAPH,
|
||||
title: 'Monthly Performance',
|
||||
objectMetadataId: null,
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 9,
|
||||
rowSpan: 8,
|
||||
columnSpan: 4,
|
||||
},
|
||||
configuration: {
|
||||
graphType: GraphType.BAR,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
deletedAt: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockLayouts: Layouts = {
|
||||
desktop: [
|
||||
{
|
||||
i: 'widget-1',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 3,
|
||||
h: 2,
|
||||
},
|
||||
{
|
||||
i: 'widget-2',
|
||||
x: 6,
|
||||
y: 0,
|
||||
w: 3,
|
||||
h: 5,
|
||||
},
|
||||
{
|
||||
i: 'widget-3',
|
||||
x: 0,
|
||||
y: 2,
|
||||
w: 6,
|
||||
h: 5,
|
||||
},
|
||||
{
|
||||
i: 'widget-4',
|
||||
x: 9,
|
||||
y: 0,
|
||||
w: 4,
|
||||
h: 8,
|
||||
},
|
||||
],
|
||||
mobile: [
|
||||
{
|
||||
i: 'widget-1',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 1,
|
||||
h: 2,
|
||||
},
|
||||
{
|
||||
i: 'widget-2',
|
||||
x: 0,
|
||||
y: 2,
|
||||
w: 1,
|
||||
h: 5,
|
||||
},
|
||||
{
|
||||
i: 'widget-3',
|
||||
x: 0,
|
||||
y: 7,
|
||||
w: 1,
|
||||
h: 5,
|
||||
},
|
||||
{
|
||||
i: 'widget-4',
|
||||
x: 0,
|
||||
y: 12,
|
||||
w: 1,
|
||||
h: 5,
|
||||
},
|
||||
],
|
||||
};
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
import { type PageLayout } from '../types/pageLayoutTypes';
|
||||
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { PageLayoutComponentInstanceContext } from './contexts/PageLayoutComponentInstanceContext';
|
||||
|
||||
export const pageLayoutPersistedComponentState = createComponentState<
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
import { type PageLayout } from '../types/pageLayoutTypes';
|
||||
import { type PageLayout } from '../types/PageLayout';
|
||||
|
||||
import { PageLayoutComponentInstanceContext } from './contexts/PageLayoutComponentInstanceContext';
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { type PageLayout as PageLayoutGenerated } from '~/generated/graphql';
|
||||
|
||||
export type PageLayout = Omit<PageLayoutGenerated, 'tabs'> & {
|
||||
tabs: PageLayoutTab[];
|
||||
};
|
||||
-5
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
type PageLayout as PageLayoutGenerated,
|
||||
type PageLayoutTab as PageLayoutTabGenerated,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated/graphql';
|
||||
@@ -7,7 +6,3 @@ import {
|
||||
export type PageLayoutTab = Omit<PageLayoutTabGenerated, 'widgets'> & {
|
||||
widgets: PageLayoutWidget[];
|
||||
};
|
||||
|
||||
export type PageLayout = Omit<PageLayoutGenerated, 'tabs'> & {
|
||||
tabs: PageLayoutTab[];
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type PageLayout } from './pageLayoutTypes';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
|
||||
export type DraftPageLayout = Omit<
|
||||
PageLayout,
|
||||
|
||||
+9
-2
@@ -1,14 +1,21 @@
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { GraphType, WidgetType } from '~/generated-metadata/graphql';
|
||||
import { type PageLayoutTab } from '../../types/PageLayoutTab';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { WidgetType } from '../../mocks/mockWidgets';
|
||||
import { type PageLayoutTab } from '../../types/pageLayoutTypes';
|
||||
import { addWidgetToTab } from '../addWidgetToTab';
|
||||
|
||||
describe('addWidgetToTab', () => {
|
||||
const mockWidget: PageLayoutWidget = {
|
||||
__typename: 'PageLayoutWidget',
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Test Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
},
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 2 },
|
||||
objectMetadataId: null,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
|
||||
+13
-2
@@ -1,5 +1,10 @@
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { GraphType, WidgetType } from '../../mocks/mockWidgets';
|
||||
import {
|
||||
GraphOrderBy,
|
||||
GraphType,
|
||||
WidgetType,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { convertLayoutsToWidgets } from '../convertLayoutsToWidgets';
|
||||
|
||||
describe('convertLayoutsToWidgets', () => {
|
||||
@@ -18,6 +23,8 @@ describe('convertLayoutsToWidgets', () => {
|
||||
},
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
@@ -37,6 +44,10 @@ describe('convertLayoutsToWidgets', () => {
|
||||
},
|
||||
configuration: {
|
||||
graphType: GraphType.PIE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
groupByFieldMetadataId: 'status',
|
||||
orderBy: GraphOrderBy.VALUE_DESC,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
|
||||
+22
-2
@@ -1,6 +1,12 @@
|
||||
import { type PageLayout } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
|
||||
import { PageLayoutType, WidgetType } from '~/generated/graphql';
|
||||
import {
|
||||
GraphOrderBy,
|
||||
GraphType,
|
||||
PageLayoutType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
describe('convertPageLayoutToTabLayouts', () => {
|
||||
it('should convert page layout to tab layouts', () => {
|
||||
@@ -17,10 +23,16 @@ describe('convertPageLayoutToTabLayouts', () => {
|
||||
pageLayoutId: 'page-layout-1',
|
||||
widgets: [
|
||||
{
|
||||
__typename: 'PageLayoutWidget',
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Widget 1',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
},
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 2 },
|
||||
objectMetadataId: 'object-metadata-1',
|
||||
createdAt: '2025-01-01T00:00:00.000Z',
|
||||
@@ -28,10 +40,18 @@ describe('convertPageLayoutToTabLayouts', () => {
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
__typename: 'PageLayoutWidget',
|
||||
id: 'widget-2',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Widget 2',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.PIE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
groupByFieldMetadataId: 'status',
|
||||
orderBy: GraphOrderBy.VALUE_DESC,
|
||||
},
|
||||
gridPosition: { row: 2, column: 0, rowSpan: 2, columnSpan: 2 },
|
||||
objectMetadataId: 'object-metadata-1',
|
||||
createdAt: '2025-01-01T00:00:00.000Z',
|
||||
|
||||
+25
-2
@@ -1,5 +1,10 @@
|
||||
import { WidgetType } from '../../mocks/mockWidgets';
|
||||
import { type PageLayoutTab } from '../../types/pageLayoutTypes';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import {
|
||||
GraphOrderBy,
|
||||
GraphType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { type PageLayoutTab } from '../../types/PageLayoutTab';
|
||||
import { removeWidgetFromTab } from '../removeWidgetFromTab';
|
||||
|
||||
describe('removeWidgetFromTab', () => {
|
||||
@@ -11,10 +16,16 @@ describe('removeWidgetFromTab', () => {
|
||||
pageLayoutId: 'layout-1',
|
||||
widgets: [
|
||||
{
|
||||
__typename: 'PageLayoutWidget' as const,
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Widget 1',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
},
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 2 },
|
||||
objectMetadataId: null,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -22,10 +33,18 @@ describe('removeWidgetFromTab', () => {
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
__typename: 'PageLayoutWidget' as const,
|
||||
id: 'widget-2',
|
||||
pageLayoutTabId: 'tab-1',
|
||||
title: 'Widget 2',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.PIE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
groupByFieldMetadataId: 'status',
|
||||
orderBy: GraphOrderBy.VALUE_DESC,
|
||||
},
|
||||
gridPosition: { row: 2, column: 0, rowSpan: 2, columnSpan: 2 },
|
||||
objectMetadataId: null,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -44,10 +63,14 @@ describe('removeWidgetFromTab', () => {
|
||||
pageLayoutId: 'layout-1',
|
||||
widgets: [
|
||||
{
|
||||
__typename: 'PageLayoutWidget' as const,
|
||||
id: 'widget-3',
|
||||
pageLayoutTabId: 'tab-2',
|
||||
title: 'Widget 3',
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: {
|
||||
url: 'https://example.com',
|
||||
},
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 2 },
|
||||
objectMetadataId: null,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
export const addWidgetToTab = (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type Layouts } from 'react-grid-layout';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { type Layouts } from 'react-grid-layout';
|
||||
|
||||
export const convertLayoutsToWidgets = (
|
||||
widgets: PageLayoutWidget[],
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type PageLayout } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { type TabLayouts } from '@/page-layout/types/tab-layouts';
|
||||
|
||||
export const convertPageLayoutToTabLayouts = (
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { GraphOrderBy, GraphType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
type GridPosition,
|
||||
type PageLayoutWidget,
|
||||
type WidgetConfiguration,
|
||||
WidgetType,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const createDefaultGraphConfiguration = (
|
||||
graphType: GraphType,
|
||||
): WidgetConfiguration => {
|
||||
const placeholderFieldId1 = uuidv4();
|
||||
const placeholderFieldId2 = uuidv4();
|
||||
|
||||
switch (graphType) {
|
||||
case GraphType.NUMBER:
|
||||
return {
|
||||
graphType: GraphType.NUMBER,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: placeholderFieldId1,
|
||||
};
|
||||
|
||||
case GraphType.PIE:
|
||||
return {
|
||||
graphType: GraphType.PIE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: placeholderFieldId1,
|
||||
groupByFieldMetadataId: placeholderFieldId2,
|
||||
orderBy: GraphOrderBy.VALUE_DESC,
|
||||
};
|
||||
|
||||
case GraphType.BAR:
|
||||
return {
|
||||
graphType: GraphType.BAR,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: placeholderFieldId1,
|
||||
groupByFieldMetadataIdX: placeholderFieldId2,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
};
|
||||
|
||||
case GraphType.LINE:
|
||||
return {
|
||||
graphType: GraphType.LINE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: placeholderFieldId1,
|
||||
groupByFieldMetadataIdX: placeholderFieldId2,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
};
|
||||
|
||||
case GraphType.GAUGE:
|
||||
return {
|
||||
graphType: GraphType.GAUGE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: placeholderFieldId1,
|
||||
aggregateOperationTotal: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataIdTotal: placeholderFieldId2,
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
graphType: GraphType.NUMBER,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: placeholderFieldId1,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const createDefaultGraphWidget = (
|
||||
id: string,
|
||||
pageLayoutTabId: string,
|
||||
title: string,
|
||||
graphType: GraphType,
|
||||
gridPosition: GridPosition,
|
||||
objectMetadataId?: string | null,
|
||||
): PageLayoutWidget => {
|
||||
return {
|
||||
__typename: 'PageLayoutWidget',
|
||||
id,
|
||||
pageLayoutTabId,
|
||||
title,
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: createDefaultGraphConfiguration(graphType),
|
||||
gridPosition,
|
||||
objectMetadataId: objectMetadataId ?? null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
type GridPosition,
|
||||
type PageLayoutWidget,
|
||||
WidgetType,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const createDefaultIframeWidget = (
|
||||
id: string,
|
||||
pageLayoutTabId: string,
|
||||
title: string,
|
||||
url: string,
|
||||
gridPosition: GridPosition,
|
||||
objectMetadataId?: string | null,
|
||||
): PageLayoutWidget => {
|
||||
return {
|
||||
__typename: 'PageLayoutWidget',
|
||||
id,
|
||||
pageLayoutTabId,
|
||||
title,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: {
|
||||
url,
|
||||
},
|
||||
gridPosition,
|
||||
objectMetadataId: objectMetadataId ?? null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GraphType } from '../mocks/mockWidgets';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getDefaultWidgetData = (graphType: GraphType) => {
|
||||
switch (graphType) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type PageLayout } from '~/modules/page-layout/types/pageLayoutTypes';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
|
||||
export const isPageLayoutEmpty = (pageLayout: PageLayout): boolean => {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type PageLayoutTab } from '../types/pageLayoutTypes';
|
||||
import { type PageLayoutTab } from '../types/PageLayoutTab';
|
||||
|
||||
export const removeWidgetFromTab = (
|
||||
tabs: PageLayoutTab[],
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { type PageLayout as PageLayoutGenerated } from '~/generated/graphql';
|
||||
|
||||
export const transformPageLayout = (
|
||||
pageLayout: PageLayoutGenerated,
|
||||
): PageLayout => {
|
||||
return {
|
||||
...pageLayout,
|
||||
tabs: (pageLayout.tabs ?? []).map(
|
||||
(tab): PageLayoutTab => ({
|
||||
...tab,
|
||||
widgets: tab.widgets ?? [],
|
||||
}),
|
||||
),
|
||||
};
|
||||
};
|
||||
+27
-7
@@ -1,8 +1,13 @@
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
import { type ReactNode } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledContainer = styled.div<{ onClick?: () => void }>`
|
||||
const StyledContainer = styled.div<{
|
||||
onClick?: () => void;
|
||||
showHover?: boolean;
|
||||
}>`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
@@ -14,21 +19,36 @@ const StyledContainer = styled.div<{ onClick?: () => void }>`
|
||||
position: relative;
|
||||
padding: ${({ theme }) => theme.spacing(4)};
|
||||
|
||||
&:hover {
|
||||
cursor: ${({ onClick }) => (isDefined(onClick) ? 'pointer' : 'default')};
|
||||
border: 1px solid ${({ theme }) => theme.color.blue};
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
}
|
||||
${({ showHover, onClick, theme }) =>
|
||||
showHover &&
|
||||
`
|
||||
&:hover {
|
||||
cursor: ${isDefined(onClick) ? 'pointer' : 'default'};
|
||||
border: 1px solid ${theme.color.blue};
|
||||
background: ${theme.background.primary};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
type WidgetContainerProps = {
|
||||
children?: ReactNode;
|
||||
onClick?: () => void;
|
||||
isRestricted?: boolean;
|
||||
};
|
||||
|
||||
export const WidgetContainer = ({
|
||||
children,
|
||||
onClick,
|
||||
isRestricted = false,
|
||||
}: WidgetContainerProps) => {
|
||||
return <StyledContainer onClick={onClick}>{children}</StyledContainer>;
|
||||
const isPageLayoutInEditModeComponent = useRecoilComponentValue(
|
||||
isPageLayoutInEditModeComponentState,
|
||||
);
|
||||
const showHover = !isRestricted || isPageLayoutInEditModeComponent;
|
||||
|
||||
return (
|
||||
<StyledContainer onClick={onClick} showHover={showHover}>
|
||||
{children}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+5
-10
@@ -1,11 +1,9 @@
|
||||
import { GraphWidgetRenderer } from '@/page-layout/widgets/graph/components/GraphWidgetRenderer';
|
||||
import { IframeWidget } from '@/page-layout/widgets/iframe/components/IframeWidget';
|
||||
import { type Widget } from '@/page-layout/widgets/types/Widget';
|
||||
import { isString } from '@sniptt/guards';
|
||||
import { WidgetType } from '~/generated/graphql';
|
||||
import { type PageLayoutWidget, WidgetType } from '~/generated/graphql';
|
||||
|
||||
type WidgetContentRendererProps = {
|
||||
widget: Widget;
|
||||
widget: PageLayoutWidget;
|
||||
};
|
||||
|
||||
export const WidgetContentRenderer = ({
|
||||
@@ -15,12 +13,9 @@ export const WidgetContentRenderer = ({
|
||||
case WidgetType.GRAPH:
|
||||
return <GraphWidgetRenderer widget={widget} />;
|
||||
|
||||
case WidgetType.IFRAME: {
|
||||
const url = widget.configuration?.url;
|
||||
return (
|
||||
<IframeWidget url={isString(url) ? url : ''} title={widget.title} />
|
||||
);
|
||||
}
|
||||
case WidgetType.IFRAME:
|
||||
return <IframeWidget widget={widget} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
+11
-4
@@ -1,15 +1,17 @@
|
||||
import { ForbiddenFieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/ForbiddenFieldDisplay';
|
||||
import { useDeletePageLayoutWidget } from '@/page-layout/hooks/useDeletePageLayoutWidget';
|
||||
import { useEditPageLayoutWidget } from '@/page-layout/hooks/useEditPageLayoutWidget';
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { WidgetContainer } from '@/page-layout/widgets/components/WidgetContainer';
|
||||
import { WidgetContentRenderer } from '@/page-layout/widgets/components/WidgetContentRenderer';
|
||||
import { WidgetHeader } from '@/page-layout/widgets/components/WidgetHeader';
|
||||
import { type Widget as WidgetType } from '@/page-layout/widgets/types/Widget';
|
||||
import { useWidgetObjectPermissions } from '@/page-layout/widgets/hooks/useWidgetObjectPermissions';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type WidgetRendererProps = {
|
||||
widget: WidgetType;
|
||||
widget: PageLayoutWidget;
|
||||
};
|
||||
|
||||
const StyledContent = styled.div`
|
||||
@@ -23,13 +25,14 @@ const StyledContent = styled.div`
|
||||
export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
|
||||
const { deletePageLayoutWidget } = useDeletePageLayoutWidget();
|
||||
const { handleEditWidget } = useEditPageLayoutWidget();
|
||||
const { haveAccessToWidgetsObject } = useWidgetObjectPermissions(widget);
|
||||
|
||||
const isPageLayoutInEditMode = useRecoilComponentValue(
|
||||
isPageLayoutInEditModeComponentState,
|
||||
);
|
||||
|
||||
return (
|
||||
<WidgetContainer>
|
||||
<WidgetContainer isRestricted={!haveAccessToWidgetsObject}>
|
||||
<WidgetHeader
|
||||
isInEditMode={isPageLayoutInEditMode}
|
||||
title={widget.title}
|
||||
@@ -39,7 +42,11 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
|
||||
onRemove={() => deletePageLayoutWidget(widget.id)}
|
||||
/>
|
||||
<StyledContent>
|
||||
<WidgetContentRenderer widget={widget} />
|
||||
{!haveAccessToWidgetsObject ? (
|
||||
<ForbiddenFieldDisplay />
|
||||
) : (
|
||||
<WidgetContentRenderer widget={widget} />
|
||||
)}
|
||||
</StyledContent>
|
||||
</WidgetContainer>
|
||||
);
|
||||
|
||||
+9
-1
@@ -1,3 +1,4 @@
|
||||
import { PageLayoutTestWrapper } from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
|
||||
import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder';
|
||||
import { type Meta, type StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
@@ -5,7 +6,14 @@ import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
const meta: Meta<typeof WidgetPlaceholder> = {
|
||||
title: 'Modules/PageLayout/Widgets/WidgetPlaceholder',
|
||||
component: WidgetPlaceholder,
|
||||
decorators: [ComponentDecorator],
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<PageLayoutTestWrapper>
|
||||
<Story />
|
||||
</PageLayoutTestWrapper>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
|
||||
+58
-98
@@ -1,9 +1,9 @@
|
||||
import { PageLayoutTestWrapper } from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
|
||||
import { GraphType } from '@/page-layout/mocks/mockWidgets';
|
||||
import { createDefaultGraphWidget } from '@/page-layout/utils/createDefaultGraphWidget';
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
import { type Meta, type StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { WidgetType } from '~/generated/graphql';
|
||||
|
||||
const meta: Meta<typeof WidgetRenderer> = {
|
||||
title: 'Modules/PageLayout/Widgets/WidgetRenderer',
|
||||
@@ -30,23 +30,18 @@ type Story = StoryObj<typeof WidgetRenderer>;
|
||||
|
||||
export const WithNumberChart: Story = {
|
||||
args: {
|
||||
widget: {
|
||||
title: 'Sales Pipeline',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
},
|
||||
gridPosition: {
|
||||
widget: createDefaultGraphWidget(
|
||||
'widget-1',
|
||||
'tab-overview',
|
||||
'Sales Pipeline',
|
||||
GraphType.NUMBER,
|
||||
{
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 2,
|
||||
columnSpan: 3,
|
||||
},
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
),
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '300px', height: '100px' }}>
|
||||
@@ -57,23 +52,18 @@ export const WithNumberChart: Story = {
|
||||
|
||||
export const WithGaugeChart: Story = {
|
||||
args: {
|
||||
widget: {
|
||||
title: 'Conversion Rate',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.GAUGE,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
gridPosition: {
|
||||
widget: createDefaultGraphWidget(
|
||||
'widget-1',
|
||||
'tab-overview',
|
||||
'Conversion Rate',
|
||||
GraphType.GAUGE,
|
||||
{
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 3,
|
||||
},
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
),
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '300px', height: '400px' }}>
|
||||
@@ -84,23 +74,18 @@ export const WithGaugeChart: Story = {
|
||||
|
||||
export const WithPieChart: Story = {
|
||||
args: {
|
||||
widget: {
|
||||
title: 'Lead Distribution',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.PIE,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
gridPosition: {
|
||||
widget: createDefaultGraphWidget(
|
||||
'widget-1',
|
||||
'tab-overview',
|
||||
'Lead Distribution',
|
||||
GraphType.PIE,
|
||||
{
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 3,
|
||||
},
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
),
|
||||
},
|
||||
render: (args) => (
|
||||
<div style={{ width: '300px', height: '500px' }}>
|
||||
@@ -111,23 +96,18 @@ export const WithPieChart: Story = {
|
||||
|
||||
export const SmallWidget: Story = {
|
||||
args: {
|
||||
widget: {
|
||||
title: 'Small Widget (2x2 grid)',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
gridPosition: {
|
||||
widget: createDefaultGraphWidget(
|
||||
'widget-1',
|
||||
'tab-overview',
|
||||
'Small Widget (2x2 grid)',
|
||||
GraphType.NUMBER,
|
||||
{
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 2,
|
||||
columnSpan: 2,
|
||||
},
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
),
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
@@ -145,23 +125,18 @@ export const SmallWidget: Story = {
|
||||
|
||||
export const MediumWidget: Story = {
|
||||
args: {
|
||||
widget: {
|
||||
title: 'Medium Widget (4x3 grid)',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.GAUGE,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
gridPosition: {
|
||||
widget: createDefaultGraphWidget(
|
||||
'widget-1',
|
||||
'tab-overview',
|
||||
'Medium Widget (4x3 grid)',
|
||||
GraphType.GAUGE,
|
||||
{
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 3,
|
||||
columnSpan: 4,
|
||||
},
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
),
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
@@ -179,23 +154,18 @@ export const MediumWidget: Story = {
|
||||
|
||||
export const LargeWidget: Story = {
|
||||
args: {
|
||||
widget: {
|
||||
title: 'Large Widget (6x4 grid)',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.PIE,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
gridPosition: {
|
||||
widget: createDefaultGraphWidget(
|
||||
'widget-1',
|
||||
'tab-overview',
|
||||
'Large Widget (6x4 grid)',
|
||||
GraphType.PIE,
|
||||
{
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 4,
|
||||
columnSpan: 6,
|
||||
},
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
),
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
@@ -213,23 +183,18 @@ export const LargeWidget: Story = {
|
||||
|
||||
export const WideWidget: Story = {
|
||||
args: {
|
||||
widget: {
|
||||
title: 'Wide Widget (8x2 grid)',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
gridPosition: {
|
||||
widget: createDefaultGraphWidget(
|
||||
'widget-1',
|
||||
'tab-overview',
|
||||
'Wide Widget (8x2 grid)',
|
||||
GraphType.NUMBER,
|
||||
{
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 2,
|
||||
columnSpan: 8,
|
||||
},
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
),
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
@@ -247,23 +212,18 @@ export const WideWidget: Story = {
|
||||
|
||||
export const TallWidget: Story = {
|
||||
args: {
|
||||
widget: {
|
||||
title: 'Tall Widget (3x6 grid)',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.GAUGE,
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
gridPosition: {
|
||||
widget: createDefaultGraphWidget(
|
||||
'widget-1',
|
||||
'tab-overview',
|
||||
'Tall Widget (3x6 grid)',
|
||||
GraphType.GAUGE,
|
||||
{
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 6,
|
||||
columnSpan: 3,
|
||||
},
|
||||
id: 'widget-1',
|
||||
pageLayoutTabId: 'tab-overview',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
),
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
|
||||
+13
-6
@@ -1,9 +1,8 @@
|
||||
import { GraphType } from '@/page-layout/mocks/mockWidgets';
|
||||
import { getDefaultWidgetData } from '@/page-layout/utils/getDefaultWidgetData';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { GraphWidgetNumberChart } from '@/page-layout/widgets/graph/graphWidgetNumberChart/components/GraphWidgetNumberChart';
|
||||
import { type GraphWidget } from '@/page-layout/widgets/graph/types/GraphWidget';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { GraphType, type PageLayoutWidget } from '~/generated-metadata/graphql';
|
||||
|
||||
const GraphWidgetBarChart = lazy(() =>
|
||||
import(
|
||||
@@ -38,14 +37,22 @@ const GraphWidgetGaugeChart = lazy(() =>
|
||||
);
|
||||
|
||||
type GraphWidgetRendererProps = {
|
||||
widget: GraphWidget;
|
||||
widget: PageLayoutWidget;
|
||||
};
|
||||
|
||||
export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => {
|
||||
const graphType = widget.configuration?.graphType;
|
||||
if (!widget.configuration || !('graphType' in widget.configuration)) {
|
||||
throw new Error(
|
||||
`Invalid configuration for widget ${widget.id}: missing graphType`,
|
||||
);
|
||||
}
|
||||
|
||||
const graphType = widget.configuration.graphType;
|
||||
|
||||
if (!Object.values(GraphType).includes(graphType)) {
|
||||
return null;
|
||||
throw new Error(
|
||||
`Unsupported graph type ${graphType} for widget ${widget.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data: any = getDefaultWidgetData(graphType);
|
||||
@@ -54,7 +61,7 @@ export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (graphType as GraphType) {
|
||||
switch (graphType) {
|
||||
case GraphType.NUMBER:
|
||||
return (
|
||||
<GraphWidgetNumberChart
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { type GraphType } from '@/page-layout/mocks/mockWidgets';
|
||||
import { type PageLayoutWidget, type WidgetType } from '~/generated/graphql';
|
||||
|
||||
export type GraphWidget = PageLayoutWidget & {
|
||||
type: WidgetType.GRAPH;
|
||||
configuration: {
|
||||
graphType: GraphType;
|
||||
};
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type PageLayoutWidget } from '~/generated-metadata/graphql';
|
||||
|
||||
type UseWidgetObjectPermissionsReturn = {
|
||||
haveAccessToWidgetsObject: boolean;
|
||||
};
|
||||
|
||||
export const useWidgetObjectPermissions = (
|
||||
widget: PageLayoutWidget,
|
||||
): UseWidgetObjectPermissionsReturn => {
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
if (!isDefined(widget.objectMetadataId)) {
|
||||
return {
|
||||
haveAccessToWidgetsObject: true,
|
||||
};
|
||||
}
|
||||
|
||||
const objectPermissions = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
widget.objectMetadataId,
|
||||
);
|
||||
|
||||
return {
|
||||
haveAccessToWidgetsObject: objectPermissions.canReadObjectRecords,
|
||||
};
|
||||
};
|
||||
+14
-6
@@ -1,4 +1,5 @@
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { type PageLayoutWidget } from '~/generated-metadata/graphql';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -57,14 +58,21 @@ const StyledErrorUrl = styled.div`
|
||||
`;
|
||||
|
||||
export type IframeWidgetProps = {
|
||||
url: string;
|
||||
title?: string;
|
||||
widget: PageLayoutWidget;
|
||||
};
|
||||
|
||||
export const IframeWidget = ({
|
||||
url,
|
||||
title = 'Embedded Content',
|
||||
}: IframeWidgetProps) => {
|
||||
export const IframeWidget = ({ widget }: IframeWidgetProps) => {
|
||||
const configuration = widget.configuration;
|
||||
|
||||
if (!configuration || !('url' in configuration)) {
|
||||
throw new Error(
|
||||
`Invalid configuration for widget ${widget.id}: missing url`,
|
||||
);
|
||||
}
|
||||
|
||||
const url = configuration.url;
|
||||
const title = widget.title;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { type PageLayoutWidget, type WidgetType } from '~/generated/graphql';
|
||||
|
||||
export type IframeWidget = PageLayoutWidget & {
|
||||
type: WidgetType.IFRAME;
|
||||
configuration: {
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
import { type GraphWidget } from '@/page-layout/widgets/graph/types/GraphWidget';
|
||||
import { type IframeWidget } from '@/page-layout/widgets/iframe/types/IframeWidget';
|
||||
|
||||
export type Widget = GraphWidget | IframeWidget;
|
||||
Reference in New Issue
Block a user