Create fields widget (#16403)
https://github.com/user-attachments/assets/b150815a-5d54-4d4a-b984-20dcf7b29aa7
This commit is contained in:
committed by
GitHub
parent
7b98804ec1
commit
fd54a2af5c
+1
-1
@@ -2,6 +2,7 @@ import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constan
|
||||
import { isChartWidget } from '@/command-menu/pages/page-layout/utils/isChartWidget';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
IconPlus,
|
||||
type IconComponent,
|
||||
} from 'twenty-ui/display';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
type PageLayoutHeaderInfo = {
|
||||
headerIcon: IconComponent | undefined;
|
||||
|
||||
+5
-6
@@ -10,7 +10,6 @@ import { useChartSettingsValues } from '@/command-menu/pages/page-layout/hooks/u
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
|
||||
import { useGetConfigToUpdateAfterGraphTypeChange } from '@/command-menu/pages/page-layout/hooks/useUpdateGraphTypeConfig';
|
||||
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { shouldHideChartSetting } from '@/command-menu/pages/page-layout/utils/shouldHideChartSetting';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
@@ -20,7 +19,9 @@ import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
|
||||
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { assertChartWidgetOrThrow } from '@/command-menu/pages/page-layout/utils/assertChartWidgetOrThrow';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { GraphType } from '~/generated/graphql';
|
||||
|
||||
const StyledCommandMenuContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -36,11 +37,9 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
if (widget.configuration?.__typename === 'IframeConfiguration') {
|
||||
throw new Error(t`IframeConfiguration is not supported`);
|
||||
}
|
||||
assertChartWidgetOrThrow(widget);
|
||||
|
||||
const configuration = widget.configuration as ChartConfiguration;
|
||||
const configuration = widget.configuration;
|
||||
const currentGraphType = configuration?.graphType;
|
||||
|
||||
const { getChartSettingsValues } = useChartSettingsValues({
|
||||
|
||||
+15
-10
@@ -26,9 +26,11 @@ export const CommandMenuPageLayoutIframeSettings = () => {
|
||||
throw new Error('Widget ID must be present while editing the widget');
|
||||
}
|
||||
|
||||
const widgetConfiguration = widgetInEditMode.configuration;
|
||||
|
||||
const configUrl =
|
||||
widgetInEditMode.configuration && 'url' in widgetInEditMode.configuration
|
||||
? widgetInEditMode.configuration.url
|
||||
widgetConfiguration && 'url' in widgetConfiguration
|
||||
? widgetConfiguration.url
|
||||
: null;
|
||||
|
||||
const [url, setUrl] = useState<string | null>(
|
||||
@@ -56,15 +58,18 @@ export const CommandMenuPageLayoutIframeSettings = () => {
|
||||
const handleUrlChange = (value: string) => {
|
||||
setUrl(value);
|
||||
|
||||
if (validateUrl(value)) {
|
||||
const trimmedValue = value.trim();
|
||||
updatePageLayoutWidget(widgetInEditMode.id, {
|
||||
configuration: {
|
||||
...widgetInEditMode.configuration,
|
||||
url: trimmedValue || null,
|
||||
},
|
||||
});
|
||||
if (!validateUrl(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedValue = value.trim();
|
||||
|
||||
updatePageLayoutWidget(widgetInEditMode.id, {
|
||||
configuration: {
|
||||
__typename: 'IframeConfiguration',
|
||||
url: isNonEmptyString(trimmedValue) ? trimmedValue : null,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import type { PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import type { PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
export const useUpdateCurrentWidgetConfig = (pageLayoutIdFromProps: string) => {
|
||||
const pageLayoutDraftCallbackState = useRecoilComponentCallbackState(
|
||||
|
||||
+2
-1
@@ -7,13 +7,14 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
|
||||
import { updateWidgetMinimumSizeForGraphType } from '@/page-layout/utils/updateWidgetMinimumSizeForGraphType';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { GraphType } from '~/generated/graphql';
|
||||
|
||||
export const useGetConfigToUpdateAfterGraphTypeChange = ({
|
||||
pageLayoutId,
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
import { type ChartFilters } from '@/command-menu/pages/page-layout/types/ChartFilters';
|
||||
import { type ChartWidgetConfiguration } from '@/command-menu/pages/page-layout/types/ChartWidgetConfiguration';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { type ModifiedProperties } from 'twenty-shared/types';
|
||||
import { type PageLayoutWidget, type WidgetType } from '~/generated/graphql';
|
||||
import { type WidgetType } from '~/generated/graphql';
|
||||
|
||||
export type ChartWidget = ModifiedProperties<
|
||||
PageLayoutWidget,
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
|
||||
type AssertChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => asserts widget is PageLayoutWidget & {
|
||||
objectMetadataId: string;
|
||||
configuration: ChartConfiguration;
|
||||
};
|
||||
|
||||
const VALID_CHART_TYPES: ReadonlyArray<ChartConfiguration['__typename']> = [
|
||||
'BarChartConfiguration',
|
||||
'LineChartConfiguration',
|
||||
'PieChartConfiguration',
|
||||
'AggregateChartConfiguration',
|
||||
'GaugeChartConfiguration',
|
||||
] as const;
|
||||
|
||||
export const assertChartWidgetOrThrow: AssertChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => {
|
||||
assertIsDefinedOrThrow(
|
||||
widget.objectMetadataId,
|
||||
new Error('Widget objectMetadataId is required'),
|
||||
);
|
||||
|
||||
assertIsDefinedOrThrow(
|
||||
widget.configuration,
|
||||
new Error('Widget configuration is required'),
|
||||
);
|
||||
|
||||
if (
|
||||
!isNonEmptyString(widget.configuration.__typename) ||
|
||||
!VALID_CHART_TYPES.includes(
|
||||
widget.configuration.__typename as ChartConfiguration['__typename'],
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`Expected chart configuration but got ${widget.configuration.__typename}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledCanvasContainer = styled.div`
|
||||
display: grid;
|
||||
|
||||
+1
-1
@@ -1,4 +1,5 @@
|
||||
import { pageLayoutDraggingWidgetIdComponentState } from '@/page-layout/states/pageLayoutDraggingWidgetIdComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
type DropResult,
|
||||
} from '@hello-pangea/dnd';
|
||||
import { useId } from 'react';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledVerticalListContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledVerticalListContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
@@ -38,6 +38,9 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
|
||||
rowSpan: 12,
|
||||
columnSpan: 12,
|
||||
},
|
||||
// Note: Configuration is null by default. For testing purposes,
|
||||
// use useTempNoteFieldsConfiguration() hook at runtime to get
|
||||
// a configuration with actual field metadata IDs from the backend.
|
||||
configuration: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pag
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { type GraphWidgetFieldSelection } from '@/page-layout/types/GraphWidgetFieldSelection';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
|
||||
import { createDefaultGraphWidget } from '@/page-layout/utils/createDefaultGraphWidget';
|
||||
import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition';
|
||||
@@ -18,7 +19,7 @@ import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { type GraphType } from '~/generated-metadata/graphql';
|
||||
import { WidgetType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { WidgetType } from '~/generated/graphql';
|
||||
|
||||
export const useCreatePageLayoutGraphWidget = (
|
||||
pageLayoutIdFromProps?: string,
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ 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 PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
|
||||
import { createDefaultIframeWidget } from '@/page-layout/utils/createDefaultIframeWidget';
|
||||
import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition';
|
||||
@@ -15,7 +16,7 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { type PageLayoutWidget, WidgetType } from '~/generated/graphql';
|
||||
import { WidgetType } from '~/generated/graphql';
|
||||
|
||||
export const useCreatePageLayoutIframeWidget = (
|
||||
pageLayoutIdFromProps?: string,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/context
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
|
||||
import { getScrollWrapperInstanceIdFromPageLayoutId } from '@/page-layout/utils/getScrollWrapperInstanceIdFromPageLayoutId';
|
||||
import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
|
||||
@@ -12,7 +13,6 @@ import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { generateDuplicatedTimestamps } from '../utils/generateDuplicatedTimestamps';
|
||||
import { getDuplicatedTitle } from '../utils/getDuplicatedTitle';
|
||||
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useTemporaryFieldsConfiguration = (
|
||||
objectNameSingular: string,
|
||||
): FieldsConfiguration | null => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const configuration = useMemo<FieldsConfiguration | null>(() => {
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fieldsToDisplay = objectMetadataItem.fields.filter(
|
||||
(field) =>
|
||||
field.type !== FieldMetadataType.RELATION &&
|
||||
field.type !== FieldMetadataType.MORPH_RELATION &&
|
||||
field.type !== FieldMetadataType.RICH_TEXT_V2,
|
||||
);
|
||||
|
||||
const fields = fieldsToDisplay.map((field, index) => ({
|
||||
fieldMetadataId: field.id,
|
||||
position: index,
|
||||
}));
|
||||
|
||||
if (fields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
__typename: 'FieldsConfiguration',
|
||||
sections: [
|
||||
{
|
||||
id: `${objectNameSingular}-section-general`,
|
||||
title: 'General',
|
||||
position: 0,
|
||||
fields,
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [objectMetadataItem, objectNameSingular]);
|
||||
|
||||
return configuration;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { type RulesLogic } from 'json-logic-js';
|
||||
|
||||
export type FieldsConfigurationFieldItem = {
|
||||
fieldMetadataId: string;
|
||||
position: number;
|
||||
conditionalDisplay?: RulesLogic;
|
||||
};
|
||||
|
||||
export type FieldsConfigurationSection = {
|
||||
id: string;
|
||||
title: string;
|
||||
position: number;
|
||||
fields: FieldsConfigurationFieldItem[];
|
||||
};
|
||||
|
||||
export type FieldsConfiguration = {
|
||||
__typename: 'FieldsConfiguration';
|
||||
sections: FieldsConfigurationSection[];
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
|
||||
export type GridLayoutWidgetItem = {
|
||||
id: string;
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode';
|
||||
import { type RulesLogic } from 'json-logic-js';
|
||||
import { type ModifiedProperties, type Nullable } from 'twenty-shared/types';
|
||||
import {
|
||||
type PageLayoutTab as PageLayoutTabGenerated,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated/graphql';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { type PageLayoutTab as PageLayoutTabGenerated } from '~/generated/graphql';
|
||||
|
||||
export type PageLayoutTab = Omit<PageLayoutTabGenerated, 'widgets'> & {
|
||||
widgets: ModifiedProperties<
|
||||
PageLayoutWidget,
|
||||
{
|
||||
objectMetadataId?: Nullable<string>;
|
||||
conditionalDisplay?: RulesLogic;
|
||||
}
|
||||
>[];
|
||||
widgets: PageLayoutWidget[];
|
||||
/**
|
||||
* Only available behind IS_RECORD_PAGE_LAYOUT_ENABLED for now.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
|
||||
import { type RulesLogic } from 'json-logic-js';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
import {
|
||||
type PageLayoutWidget as PageLayoutWidgetGenerated,
|
||||
type WidgetConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export type PageLayoutWidget = Omit<
|
||||
PageLayoutWidgetGenerated,
|
||||
'objectMetadataId' | 'configuration'
|
||||
> & {
|
||||
objectMetadataId?: Nullable<string>;
|
||||
conditionalDisplay?: RulesLogic;
|
||||
configuration?: WidgetConfiguration | FieldsConfiguration | null;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
|
||||
export const addWidgetToTab = (
|
||||
tabs: PageLayoutTab[],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { type Layouts } from 'react-grid-layout';
|
||||
|
||||
export const convertLayoutsToWidgets = (
|
||||
|
||||
+2
-4
@@ -1,8 +1,6 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type PageLayoutWidget,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { WidgetType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const extractFieldMetadataIdsFromWidget = (
|
||||
widget: PageLayoutWidget,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { PENDING_WIDGET_PLACEHOLDER_LAYOUT_KEY } from '@/page-layout/constants/PendingWidgetPlaceholderLayoutKey';
|
||||
import { type GridLayoutItem } from '@/page-layout/types/GridLayoutItem';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
export const prepareGridLayoutItemsWithPlaceholders = (
|
||||
widgets: PageLayoutWidget[] | undefined,
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { CalendarEventsCard } from '@/activities/calendar/components/CalendarEventsCard';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+2
-4
@@ -1,3 +1,4 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { CalendarWidget } from '@/page-layout/widgets/calendar/components/CalendarWidget';
|
||||
import { EmailWidget } from '@/page-layout/widgets/emails/components/EmailWidget';
|
||||
import { FieldRichTextWidget } from '@/page-layout/widgets/field-rich-text/components/FieldRichTextWidget';
|
||||
@@ -11,10 +12,7 @@ import { TimelineWidget } from '@/page-layout/widgets/timeline/components/Timeli
|
||||
import { WorkflowRunWidget } from '@/page-layout/widgets/workflow/components/WorkflowRunWidget';
|
||||
import { WorkflowVersionWidget } from '@/page-layout/widgets/workflow/components/WorkflowVersionWidget';
|
||||
import { WorkflowWidget } from '@/page-layout/widgets/workflow/components/WorkflowWidget';
|
||||
import {
|
||||
type PageLayoutWidget,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { WidgetType } from '~/generated-metadata/graphql';
|
||||
|
||||
type WidgetContentRendererProps = {
|
||||
widget: PageLayoutWidget;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPag
|
||||
import { pageLayoutDraggingWidgetIdComponentState } from '@/page-layout/states/pageLayoutDraggingWidgetIdComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { pageLayoutResizingWidgetIdComponentState } from '@/page-layout/states/pageLayoutResizingWidgetIdComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { PageLayoutWidgetForbiddenDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetForbiddenDisplay';
|
||||
import { WidgetContentRenderer } from '@/page-layout/widgets/components/WidgetContentRenderer';
|
||||
import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab';
|
||||
@@ -20,7 +21,6 @@ import { useSetRecoilComponentFamilyState } from '@/ui/utilities/state/component
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { type MouseEvent } from 'react';
|
||||
import { IconLock } from 'twenty-ui/display';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
type WidgetRendererProps = {
|
||||
widget: PageLayoutWidget;
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { EmailsCard } from '@/activities/emails/components/EmailsCard';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { FieldRichTextCard } from '@/ui/layout/show-page/components/FieldRichTextCard';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+172
-12
@@ -1,9 +1,38 @@
|
||||
import { RecordFieldList } from '@/object-record/record-field-list/components/RecordFieldList';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
|
||||
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
|
||||
import { RecordDetailSectionContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailSectionContainer';
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
|
||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { FieldsWidgetCellEditModePortal } from '@/page-layout/widgets/fields/components/FieldsWidgetCellEditModePortal';
|
||||
import { FieldsWidgetCellHoveredPortal } from '@/page-layout/widgets/fields/components/FieldsWidgetCellHoveredPortal';
|
||||
import { useFieldsWidgetSectionsWithIndices } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetSectionsWithIndices';
|
||||
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
AnimatedPlaceholderEmptySubTitle,
|
||||
AnimatedPlaceholderEmptyTextContainer,
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -19,17 +48,148 @@ export const FieldsWidget = ({ widget: _widget }: FieldsWidgetProps) => {
|
||||
const targetRecord = useTargetRecord();
|
||||
const { isInRightDrawer } = useLayoutRenderingContext();
|
||||
|
||||
const instanceId = `fields-widget-${targetRecord.id}-${isInRightDrawer ? 'right-drawer' : ''}`;
|
||||
|
||||
const { recordLoading, isPrefetchLoading } = useRecordShowContainerData({
|
||||
objectRecordId: targetRecord.id,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetRecord.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||
objectNameSingular: targetRecord.targetObjectNameSingular,
|
||||
objectRecordId: targetRecord.id,
|
||||
});
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: targetRecord.id,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
const setRecordFieldListHoverPosition = useSetRecoilComponentState(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const { sectionsWithFieldIndices } = useFieldsWidgetSectionsWithIndices(
|
||||
targetRecord.targetObjectNameSingular,
|
||||
);
|
||||
|
||||
if (sectionsWithFieldIndices.length === 0) {
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<StyledContainer>
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noRecord" />
|
||||
<AnimatedPlaceholderEmptyTextContainer>
|
||||
<AnimatedPlaceholderEmptyTitle>
|
||||
{t`No fields to display`}
|
||||
</AnimatedPlaceholderEmptyTitle>
|
||||
<AnimatedPlaceholderEmptySubTitle>
|
||||
{t`Configure this widget to display fields`}
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
</StyledContainer>
|
||||
</RightDrawerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<StyledContainer>
|
||||
<RecordFieldList
|
||||
instanceId={`fields-widget-${targetRecord.id}-${isInRightDrawer ? 'right-drawer' : ''}`}
|
||||
objectNameSingular={targetRecord.targetObjectNameSingular}
|
||||
objectRecordId={targetRecord.id}
|
||||
// TODO: pick the value from the widget configuration
|
||||
showDuplicatesSection={true}
|
||||
<StyledContainer>
|
||||
<RecordFieldListComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId,
|
||||
}}
|
||||
>
|
||||
{sectionsWithFieldIndices.map((section) => (
|
||||
<RecordDetailSectionContainer key={section.id} title={section.title}>
|
||||
<PropertyBox>
|
||||
{isPrefetchLoading ? (
|
||||
<PropertyBoxSkeletonLoader />
|
||||
) : (
|
||||
<>
|
||||
{section.fields.map(
|
||||
({ field: fieldMetadataItem, globalIndex }) => {
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
key={targetRecord.id + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId: targetRecord.id,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition:
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: globalIndex,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
objectPermissions:
|
||||
getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
}),
|
||||
fieldMetadataItem: {
|
||||
id: fieldMetadataItem.id,
|
||||
isUIReadOnly:
|
||||
fieldMetadataItem.isUIReadOnly ?? false,
|
||||
},
|
||||
}),
|
||||
onMouseEnter: () =>
|
||||
setRecordFieldListHoverPosition(globalIndex),
|
||||
anchorId: `${getRecordFieldInputInstanceId({
|
||||
recordId: targetRecord.id,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: instanceId,
|
||||
})}`,
|
||||
}}
|
||||
>
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getRecordFieldInputInstanceId({
|
||||
recordId: targetRecord.id,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: instanceId,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell
|
||||
loading={recordLoading}
|
||||
instanceIdPrefix={instanceId}
|
||||
/>
|
||||
</RecordFieldComponentInstanceContext.Provider>
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PropertyBox>
|
||||
</RecordDetailSectionContainer>
|
||||
))}
|
||||
|
||||
<FieldsWidgetCellHoveredPortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={targetRecord.id}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</RightDrawerProvider>
|
||||
<FieldsWidgetCellEditModePortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={targetRecord.id}
|
||||
/>
|
||||
</RecordFieldListComponentInstanceContext.Provider>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RecordFieldListInputContextProvider } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider';
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState';
|
||||
import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput';
|
||||
import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal';
|
||||
import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode';
|
||||
import { useFieldsWidgetFlattenedFields } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetFlattenedFields';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type FieldsWidgetCellEditModePortalProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
recordId: string;
|
||||
};
|
||||
|
||||
export const FieldsWidgetCellEditModePortal = ({
|
||||
objectMetadataItem,
|
||||
recordId,
|
||||
}: FieldsWidgetCellEditModePortalProps) => {
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordFieldListComponentInstanceContext,
|
||||
);
|
||||
|
||||
const editModePosition = useRecoilComponentValue(
|
||||
recordFieldListCellEditModePositionComponentState,
|
||||
);
|
||||
|
||||
const { flattenedFieldMetadataItems } = useFieldsWidgetFlattenedFields(
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
|
||||
const editedFieldMetadataItem = isDefined(editModePosition)
|
||||
? flattenedFieldMetadataItems.at(editModePosition)
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(editModePosition) || !isDefined(editedFieldMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordInlineCellAnchoredPortal
|
||||
fieldMetadataItem={editedFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordInlineCellEditMode>
|
||||
<FieldInput />
|
||||
</RecordInlineCellEditMode>
|
||||
</RecordFieldListInputContextProvider>
|
||||
</RecordInlineCellAnchoredPortal>
|
||||
);
|
||||
};
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RecordFieldListCellHoveredPortalContent } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortalContent';
|
||||
import { RecordFieldListInputContextProvider } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider';
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal';
|
||||
import { useFieldsWidgetFlattenedFields } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetFlattenedFields';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type FieldsWidgetCellHoveredPortalProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
recordId: string;
|
||||
};
|
||||
|
||||
export const FieldsWidgetCellHoveredPortal = ({
|
||||
objectMetadataItem,
|
||||
recordId,
|
||||
}: FieldsWidgetCellHoveredPortalProps) => {
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordFieldListComponentInstanceContext,
|
||||
);
|
||||
|
||||
const hoverPosition = useRecoilComponentValue(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
);
|
||||
|
||||
const { flattenedFieldMetadataItems } = useFieldsWidgetFlattenedFields(
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
|
||||
const hoveredFieldMetadataItem = isDefined(hoverPosition)
|
||||
? flattenedFieldMetadataItems.at(hoverPosition)
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(hoverPosition) || !isDefined(hoveredFieldMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordInlineCellAnchoredPortal
|
||||
fieldMetadataItem={hoveredFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListCellHoveredPortalContent />
|
||||
</RecordFieldListInputContextProvider>
|
||||
</RecordInlineCellAnchoredPortal>
|
||||
);
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
type UseFieldsWidgetFieldMetadataItemsProps = {
|
||||
objectNameSingular: string;
|
||||
};
|
||||
|
||||
export const useFieldsWidgetFieldMetadataItems = ({
|
||||
objectNameSingular,
|
||||
}: UseFieldsWidgetFieldMetadataItemsProps): FieldMetadataItem[] => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const fieldMetadataItems = objectMetadataItem.readableFields.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.type !== FieldMetadataType.RELATION &&
|
||||
fieldMetadataItem.type !== FieldMetadataType.MORPH_RELATION &&
|
||||
fieldMetadataItem.type !== FieldMetadataType.RICH_TEXT_V2,
|
||||
);
|
||||
|
||||
return fieldMetadataItems;
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { useFieldsWidgetSectionsWithFields } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetSectionsWithFields';
|
||||
|
||||
export const useFieldsWidgetFlattenedFields = (objectNameSingular: string) => {
|
||||
const { sectionsWithFields } =
|
||||
useFieldsWidgetSectionsWithFields(objectNameSingular);
|
||||
|
||||
const flattenedFieldMetadataItems = sectionsWithFields.flatMap(
|
||||
(section) => section.fields,
|
||||
);
|
||||
|
||||
return { flattenedFieldMetadataItems };
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { useTemporaryFieldsConfiguration } from '@/page-layout/hooks/useTemporaryFieldsConfiguration';
|
||||
import { buildWidgetVisibilityContext } from '@/page-layout/utils/buildWidgetVisibilityContext';
|
||||
import { useFieldsWidgetFieldMetadataItems } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetFieldMetadataItems';
|
||||
import { filterAndOrderFieldsFromConfiguration } from '@/page-layout/widgets/fields/utils/filterAndOrderFieldsFromConfiguration';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
export const useFieldsWidgetSectionsWithFields = (
|
||||
objectNameSingular: string,
|
||||
) => {
|
||||
const isMobile = useIsMobile();
|
||||
const { isInRightDrawer } = useLayoutRenderingContext();
|
||||
const fieldMetadataItems = useFieldsWidgetFieldMetadataItems({
|
||||
objectNameSingular,
|
||||
});
|
||||
const temporaryConfiguration =
|
||||
useTemporaryFieldsConfiguration(objectNameSingular);
|
||||
|
||||
const context = buildWidgetVisibilityContext({ isMobile, isInRightDrawer });
|
||||
|
||||
const sectionsWithFields = filterAndOrderFieldsFromConfiguration({
|
||||
configuration: temporaryConfiguration ?? {
|
||||
__typename: 'FieldsConfiguration',
|
||||
sections: [],
|
||||
},
|
||||
availableFieldMetadataItems: fieldMetadataItems,
|
||||
context,
|
||||
});
|
||||
|
||||
return { sectionsWithFields };
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { useFieldsWidgetSectionsWithFields } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetSectionsWithFields';
|
||||
|
||||
export const useFieldsWidgetSectionsWithIndices = (
|
||||
objectNameSingular: string,
|
||||
) => {
|
||||
const { sectionsWithFields } =
|
||||
useFieldsWidgetSectionsWithFields(objectNameSingular);
|
||||
|
||||
const sectionsWithFieldIndices = sectionsWithFields.map(
|
||||
(section, sectionIndex) => {
|
||||
const startIndex = sectionsWithFields
|
||||
.slice(0, sectionIndex)
|
||||
.reduce((sum, s) => sum + s.fields.length, 0);
|
||||
|
||||
return {
|
||||
...section,
|
||||
fields: section.fields.map((field, fieldIndex) => ({
|
||||
field,
|
||||
globalIndex: startIndex + fieldIndex,
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return { sectionsWithFieldIndices };
|
||||
};
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
|
||||
import { type WidgetVisibilityContext } from '@/page-layout/types/WidgetVisibilityContext';
|
||||
import { evaluateWidgetVisibility } from '@/page-layout/utils/evaluateWidgetVisibility';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export type FieldsConfigurationSectionWithFields = {
|
||||
id: string;
|
||||
title: string;
|
||||
position: number;
|
||||
fields: FieldMetadataItem[];
|
||||
};
|
||||
|
||||
type FilterAndOrderFieldsFromConfigurationParams = {
|
||||
configuration: FieldsConfiguration;
|
||||
availableFieldMetadataItems: FieldMetadataItem[];
|
||||
context: WidgetVisibilityContext;
|
||||
};
|
||||
|
||||
export const filterAndOrderFieldsFromConfiguration = ({
|
||||
configuration,
|
||||
availableFieldMetadataItems,
|
||||
context,
|
||||
}: FilterAndOrderFieldsFromConfigurationParams): FieldsConfigurationSectionWithFields[] => {
|
||||
const fieldMetadataItemsMap = new Map(
|
||||
availableFieldMetadataItems.map((field) => [field.id, field]),
|
||||
);
|
||||
|
||||
const sortedSections = [...configuration.sections].sort(
|
||||
(a, b) => a.position - b.position,
|
||||
);
|
||||
|
||||
const sectionsWithFields = sortedSections
|
||||
.map((section) => {
|
||||
const sortedFields = [...section.fields].sort(
|
||||
(a, b) => a.position - b.position,
|
||||
);
|
||||
|
||||
const visibleFields = sortedFields
|
||||
.map((fieldConfig) => {
|
||||
const fieldMetadataItem = fieldMetadataItemsMap.get(
|
||||
fieldConfig.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isVisible = evaluateWidgetVisibility({
|
||||
conditionalDisplay: fieldConfig.conditionalDisplay,
|
||||
context,
|
||||
});
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fieldMetadataItem;
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
if (visibleFields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: section.id,
|
||||
title: section.title,
|
||||
position: section.position,
|
||||
fields: visibleFields,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
return sectionsWithFields;
|
||||
};
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { FilesCard } from '@/activities/files/components/FilesCard';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { getDefaultWidgetData } from '@/page-layout/utils/getDefaultWidgetData';
|
||||
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
@@ -8,7 +9,7 @@ import { GraphWidgetLineChartRenderer } from '@/page-layout/widgets/graph/graphW
|
||||
import { GraphWidgetPieChartRenderer } from '@/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer';
|
||||
import { areChartConfigurationFieldsValidForQuery } from '@/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { GraphType } from '~/generated/graphql';
|
||||
|
||||
const GraphWidgetGaugeChart = lazy(() =>
|
||||
import(
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
|
||||
import { GraphWidget } from '@/page-layout/widgets/graph/components/GraphWidget';
|
||||
import { GraphWidgetComponentInstanceContext } from '@/page-layout/widgets/graph/states/contexts/GraphWidgetComponentInstanceContext';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import { GraphType } from '~/generated/graphql';
|
||||
|
||||
type GraphWidgetRendererProps = {
|
||||
widget: PageLayoutWidget;
|
||||
|
||||
+6
-8
@@ -1,10 +1,8 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { useGraphWidgetAggregateQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery';
|
||||
import { assertAggregateChartWidgetOrThrow } from '@/page-layout/widgets/graph/utils/assertAggregateChartWidget';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import {
|
||||
type AggregateChartConfiguration,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const GraphWidgetAggregateChart = lazy(() =>
|
||||
import(
|
||||
@@ -19,11 +17,11 @@ export const GraphWidgetAggregateChartRenderer = ({
|
||||
}: {
|
||||
widget: PageLayoutWidget;
|
||||
}) => {
|
||||
const configuration = widget.configuration as AggregateChartConfiguration;
|
||||
assertAggregateChartWidgetOrThrow(widget);
|
||||
|
||||
const { value, loading } = useGraphWidgetAggregateQuery({
|
||||
objectMetadataItemId: widget.objectMetadataId,
|
||||
configuration,
|
||||
configuration: widget.configuration,
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -34,8 +32,8 @@ export const GraphWidgetAggregateChartRenderer = ({
|
||||
<Suspense fallback={<ChartSkeletonLoader />}>
|
||||
<GraphWidgetAggregateChart
|
||||
value={value ?? '-'}
|
||||
prefix={configuration.prefix ?? undefined}
|
||||
suffix={configuration.suffix ?? undefined}
|
||||
prefix={widget.configuration.prefix ?? undefined}
|
||||
suffix={widget.configuration.suffix ?? undefined}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
+6
-6
@@ -1,8 +1,10 @@
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { GraphWidgetChartHasTooManyGroupsEffect } from '@/page-layout/widgets/graph/components/GraphWidgetChartHasTooManyGroupsEffect';
|
||||
import { useGraphBarChartWidgetData } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData';
|
||||
import { getEffectiveGroupMode } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getEffectiveGroupMode';
|
||||
import { assertBarChartWidgetOrThrow } from '@/page-layout/widgets/graph/utils/assertBarChartWidget';
|
||||
import { buildChartDrilldownQueryParams } from '@/page-layout/widgets/graph/utils/buildChartDrilldownQueryParams';
|
||||
import { generateChartAggregateFilterKey } from '@/page-layout/widgets/graph/utils/generateChartAggregateFilterKey';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
@@ -13,10 +15,6 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { getAppPath, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type BarChartConfiguration,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const GraphWidgetBarChart = lazy(() =>
|
||||
import(
|
||||
@@ -31,6 +29,8 @@ export const GraphWidgetBarChartRenderer = ({
|
||||
}: {
|
||||
widget: PageLayoutWidget;
|
||||
}) => {
|
||||
assertBarChartWidgetOrThrow(widget);
|
||||
|
||||
const {
|
||||
data,
|
||||
indexBy,
|
||||
@@ -47,11 +47,11 @@ export const GraphWidgetBarChartRenderer = ({
|
||||
objectMetadataItem,
|
||||
} = useGraphBarChartWidgetData({
|
||||
objectMetadataItemId: widget.objectMetadataId,
|
||||
configuration: widget.configuration as BarChartConfiguration,
|
||||
configuration: widget.configuration,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
const configuration = widget.configuration as BarChartConfiguration;
|
||||
const configuration = widget.configuration;
|
||||
const isPageLayoutInEditMode = useRecoilComponentValue(
|
||||
isPageLayoutInEditModeComponentState,
|
||||
);
|
||||
|
||||
+6
-6
@@ -1,9 +1,11 @@
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { GraphWidgetChartHasTooManyGroupsEffect } from '@/page-layout/widgets/graph/components/GraphWidgetChartHasTooManyGroupsEffect';
|
||||
import { LINE_CHART_IS_STACKED_DEFAULT } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartIsStackedDefault';
|
||||
import { useGraphLineChartWidgetData } from '@/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData';
|
||||
import { type LineChartDataPoint } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint';
|
||||
import { assertLineChartWidgetOrThrow } from '@/page-layout/widgets/graph/utils/assertLineChartWidget';
|
||||
import { buildChartDrilldownQueryParams } from '@/page-layout/widgets/graph/utils/buildChartDrilldownQueryParams';
|
||||
import { generateChartAggregateFilterKey } from '@/page-layout/widgets/graph/utils/generateChartAggregateFilterKey';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
@@ -14,10 +16,6 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { getAppPath, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type LineChartConfiguration,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const GraphWidgetLineChart = lazy(() =>
|
||||
import(
|
||||
@@ -32,6 +30,8 @@ export const GraphWidgetLineChartRenderer = ({
|
||||
}: {
|
||||
widget: PageLayoutWidget;
|
||||
}) => {
|
||||
assertLineChartWidgetOrThrow(widget);
|
||||
|
||||
const {
|
||||
series,
|
||||
xAxisLabel,
|
||||
@@ -44,11 +44,11 @@ export const GraphWidgetLineChartRenderer = ({
|
||||
objectMetadataItem,
|
||||
} = useGraphLineChartWidgetData({
|
||||
objectMetadataItemId: widget.objectMetadataId,
|
||||
configuration: widget.configuration as LineChartConfiguration,
|
||||
configuration: widget.configuration,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
const configuration = widget.configuration as LineChartConfiguration;
|
||||
const configuration = widget.configuration;
|
||||
const isPageLayoutInEditMode = useRecoilComponentValue(
|
||||
isPageLayoutInEditModeComponentState,
|
||||
);
|
||||
|
||||
+8
-9
@@ -1,8 +1,10 @@
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { GraphWidgetChartHasTooManyGroupsEffect } from '@/page-layout/widgets/graph/components/GraphWidgetChartHasTooManyGroupsEffect';
|
||||
import { useGraphPieChartWidgetData } from '@/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData';
|
||||
import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem';
|
||||
import { assertPieChartWidgetOrThrow } from '@/page-layout/widgets/graph/utils/assertPieChartWidget';
|
||||
import { buildChartDrilldownQueryParams } from '@/page-layout/widgets/graph/utils/buildChartDrilldownQueryParams';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector';
|
||||
@@ -11,10 +13,6 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { getAppPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
type PageLayoutWidget,
|
||||
type PieChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const GraphWidgetPieChart = lazy(() =>
|
||||
import(
|
||||
@@ -29,6 +27,8 @@ export const GraphWidgetPieChartRenderer = ({
|
||||
}: {
|
||||
widget: PageLayoutWidget;
|
||||
}) => {
|
||||
assertPieChartWidgetOrThrow(widget);
|
||||
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
@@ -40,11 +40,10 @@ export const GraphWidgetPieChartRenderer = ({
|
||||
formattedToRawLookup,
|
||||
} = useGraphPieChartWidgetData({
|
||||
objectMetadataItemId: widget.objectMetadataId,
|
||||
configuration: widget.configuration as PieChartConfiguration,
|
||||
configuration: widget.configuration,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
const configuration = widget.configuration as PieChartConfiguration;
|
||||
|
||||
const isPageLayoutInEditMode = useRecoilComponentValue(
|
||||
isPageLayoutInEditModeComponentState,
|
||||
@@ -60,12 +59,12 @@ export const GraphWidgetPieChartRenderer = ({
|
||||
|
||||
const drilldownQueryParams = buildChartDrilldownQueryParams({
|
||||
objectMetadataItem,
|
||||
configuration,
|
||||
configuration: widget.configuration,
|
||||
clickedData: {
|
||||
primaryBucketRawValue: rawValue,
|
||||
},
|
||||
viewId: indexViewId,
|
||||
timezone: configuration.timezone ?? undefined,
|
||||
timezone: widget.configuration.timezone ?? undefined,
|
||||
});
|
||||
|
||||
const url = getAppPath(
|
||||
@@ -92,7 +91,7 @@ export const GraphWidgetPieChartRenderer = ({
|
||||
data={data}
|
||||
id={widget.id}
|
||||
objectMetadataItemId={widget.objectMetadataId}
|
||||
configuration={configuration}
|
||||
configuration={widget.configuration}
|
||||
showLegend={showLegend}
|
||||
displayType="shortNumber"
|
||||
onSliceClick={isPageLayoutInEditMode ? undefined : handleSliceClick}
|
||||
|
||||
+2
-4
@@ -1,10 +1,8 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
type PageLayoutWidget,
|
||||
type RatioAggregateConfig,
|
||||
} from '~/generated/graphql';
|
||||
import { type RatioAggregateConfig } from '~/generated/graphql';
|
||||
|
||||
const fieldExists = (
|
||||
fieldId: string | undefined | null,
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { type AggregateChartConfiguration } from '~/generated/graphql';
|
||||
|
||||
type AssertAggregateChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => asserts widget is PageLayoutWidget & {
|
||||
objectMetadataId: string;
|
||||
configuration: AggregateChartConfiguration;
|
||||
};
|
||||
|
||||
export const assertAggregateChartWidgetOrThrow: AssertAggregateChartWidgetOrThrow =
|
||||
(widget: PageLayoutWidget) => {
|
||||
assertIsDefinedOrThrow(
|
||||
widget.objectMetadataId,
|
||||
new Error('Widget objectMetadataId is required'),
|
||||
);
|
||||
|
||||
if (widget.configuration?.__typename !== 'AggregateChartConfiguration') {
|
||||
throw new Error(
|
||||
`Expected AggregateChartConfiguration but got ${widget.configuration?.__typename}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { type BarChartConfiguration } from '~/generated/graphql';
|
||||
|
||||
type AssertBarChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => asserts widget is PageLayoutWidget & {
|
||||
objectMetadataId: string;
|
||||
configuration: BarChartConfiguration;
|
||||
};
|
||||
|
||||
export const assertBarChartWidgetOrThrow: AssertBarChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => {
|
||||
assertIsDefinedOrThrow(
|
||||
widget.objectMetadataId,
|
||||
new Error('Widget objectMetadataId is required'),
|
||||
);
|
||||
|
||||
if (widget.configuration?.__typename !== 'BarChartConfiguration') {
|
||||
throw new Error(
|
||||
`Expected BarChartConfiguration but got ${widget.configuration?.__typename}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { type LineChartConfiguration } from '~/generated/graphql';
|
||||
|
||||
type AssertLineChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => asserts widget is PageLayoutWidget & {
|
||||
objectMetadataId: string;
|
||||
configuration: LineChartConfiguration;
|
||||
};
|
||||
|
||||
export const assertLineChartWidgetOrThrow: AssertLineChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => {
|
||||
assertIsDefinedOrThrow(
|
||||
widget.objectMetadataId,
|
||||
new Error('Widget objectMetadataId is required'),
|
||||
);
|
||||
|
||||
if (widget.configuration?.__typename !== 'LineChartConfiguration') {
|
||||
throw new Error(
|
||||
`Expected LineChartConfiguration but got ${widget.configuration?.__typename}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { type PieChartConfiguration } from '~/generated/graphql';
|
||||
|
||||
type AssertPieChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => asserts widget is PageLayoutWidget & {
|
||||
objectMetadataId: string;
|
||||
configuration: PieChartConfiguration;
|
||||
};
|
||||
|
||||
export const assertPieChartWidgetOrThrow: AssertPieChartWidgetOrThrow = (
|
||||
widget: PageLayoutWidget,
|
||||
) => {
|
||||
assertIsDefinedOrThrow(
|
||||
widget.objectMetadataId,
|
||||
new Error('Widget objectMetadataId is required'),
|
||||
);
|
||||
|
||||
if (widget.configuration?.__typename !== 'PieChartConfiguration') {
|
||||
throw new Error(
|
||||
`Expected PieChartConfiguration but got ${widget.configuration?.__typename}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { checkFieldPermissions } from '@/page-layout/utils/checkFieldPermissions';
|
||||
import { extractFieldMetadataIdsFromWidget } from '@/page-layout/utils/extractFieldMetadataIdsFromWidget';
|
||||
import { type WidgetAccessDenialInfo } from '@/page-layout/widgets/types/WidgetAccessDenialInfo';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type PageLayoutWidget } from '~/generated-metadata/graphql';
|
||||
|
||||
export type UseWidgetPermissionsReturn = {
|
||||
hasAccess: boolean;
|
||||
|
||||
+1
-1
@@ -1,11 +1,11 @@
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type PageLayoutWidget } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div<{ $isEditMode: boolean }>`
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { NotesCard } from '@/activities/notes/components/NotesCard';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { TasksCard } from '@/activities/tasks/components/TasksCard';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { TimelineCard } from '@/activities/timeline-activities/components/TimelineCard';
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ const StyledWidgetCardHeader = styled.div`
|
||||
const StyledTitleContainer = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
flex: 1;
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
padding-inline: ${({ theme }) => theme.spacing(1)};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
user-select: none;
|
||||
|
||||
Reference in New Issue
Block a user