diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f46d1266be..dd1bec3615 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -3152,6 +3152,7 @@ export type PieChartConfiguration = { groupByFieldMetadataId: Scalars['UUID']; groupBySubFieldName?: Maybe; orderBy?: Maybe; + showCenterMetric?: Maybe; timezone?: Maybe; }; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 27f7972201..66eac9fb87 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -3073,6 +3073,7 @@ export type PieChartConfiguration = { groupByFieldMetadataId: Scalars['UUID']; groupBySubFieldName?: Maybe; orderBy?: Maybe; + showCenterMetric?: Maybe; timezone?: Maybe; }; @@ -4781,7 +4782,7 @@ export type SearchQueryVariables = Exact<{ export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } }; -export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }; +export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }; export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{ id: Scalars['String']; @@ -4789,7 +4790,7 @@ export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{ }>; -export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } }; +export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } }; export type OnDbEventSubscriptionVariables = Exact<{ input: OnDbEventInput; @@ -5159,6 +5160,7 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql` dateGranularity orderBy displayDataLabel + showCenterMetric displayLegend color description diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/PieChartSettings.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/PieChartSettings.ts index 887a562bea..c4a0f71bf8 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/PieChartSettings.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/PieChartSettings.ts @@ -1,3 +1,4 @@ +import { CENTER_METRIC_SETTING } from '@/command-menu/pages/page-layout/constants/settings/CenterMetricSetting'; import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting'; import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting'; import { DATA_DISPLAY_PIE_CHART_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayPieChartSetting'; @@ -23,6 +24,11 @@ export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [ }, { heading: 'Style', - items: [COLORS_SETTING, DATA_LABELS_SETTING, SHOW_LEGEND_SETTING], + items: [ + COLORS_SETTING, + DATA_LABELS_SETTING, + SHOW_LEGEND_SETTING, + CENTER_METRIC_SETTING, + ], }, ]; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/CenterMetricSetting.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/CenterMetricSetting.ts new file mode 100644 index 0000000000..86ebf3b587 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/CenterMetricSetting.ts @@ -0,0 +1,11 @@ +import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels'; +import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds'; +import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup'; +import { IconNumber123 } from 'twenty-ui/display'; + +export const CENTER_METRIC_SETTING: ChartSettingsItem = { + isBoolean: true, + Icon: IconNumber123, + label: CHART_CONFIGURATION_SETTING_LABELS.CENTER_METRIC, + id: CHART_CONFIGURATION_SETTING_IDS.CENTER_METRIC, +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts index c5dfefef27..130e44b846 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts @@ -3,6 +3,7 @@ import { msg } from '@lingui/core/macro'; export const CHART_CONFIGURATION_SETTING_LABELS = { SOURCE: msg`Source`, DATA_LABELS: msg`Data labels`, + CENTER_METRIC: msg`Show value in center`, COLORS: msg`Colors`, FILTER: msg`Filter`, GROUP_BY: msg`Group by`, diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts index 9ff28d8a7b..721d14cc1b 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts @@ -199,6 +199,10 @@ export const useChartSettingsValues = ({ return groupByOrderByLabel; case CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS: return configuration.displayDataLabel ?? undefined; + case CHART_CONFIGURATION_SETTING_IDS.CENTER_METRIC: + return isPieChart + ? (configuration.showCenterMetric ?? undefined) + : undefined; case CHART_CONFIGURATION_SETTING_IDS.STACKED_BARS: return configuration.__typename === 'BarChartConfiguration' ? configuration.groupMode !== 'GROUPED' diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartConfigurationSettingIds.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartConfigurationSettingIds.ts index a0c26bb9e5..55ff19a273 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartConfigurationSettingIds.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartConfigurationSettingIds.ts @@ -1,6 +1,7 @@ export enum CHART_CONFIGURATION_SETTING_IDS { SOURCE = 'SOURCE', DATA_LABELS = 'DATA_LABELS', + CENTER_METRIC = 'CENTER_METRIC', COLORS = 'COLORS', FILTER = 'FILTER', GROUP_BY = 'GROUP_BY', @@ -26,6 +27,7 @@ export enum CHART_CONFIGURATION_SETTING_IDS { export const CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP = { [CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS]: 'displayDataLabel', + [CHART_CONFIGURATION_SETTING_IDS.CENTER_METRIC]: 'showCenterMetric', [CHART_CONFIGURATION_SETTING_IDS.STACKED_BARS]: 'groupMode', [CHART_CONFIGURATION_SETTING_IDS.STACKED_LINES]: 'isStacked', [CHART_CONFIGURATION_SETTING_IDS.OMIT_NULL_VALUES]: 'omitNullValues', diff --git a/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx b/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx index b52f495ddb..97eb89731d 100644 --- a/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/__stories__/PageLayoutRenderer.stories.tsx @@ -26,6 +26,7 @@ import { PageLayoutType, type PageLayoutWidget, } from '~/generated/graphql'; +import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { getMockFieldMetadataItemOrThrow } from '~/testing/utils/getMockFieldMetadataItemOrThrow'; import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow'; @@ -268,6 +269,7 @@ const meta: Meta = { title: 'Modules/PageLayout/PageLayoutRenderer', component: PageLayoutRenderer, decorators: [ + I18nFrontDecorator, (Story) => ( diff --git a/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts b/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts index ba1eeeb424..68e5445d3f 100644 --- a/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts +++ b/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts @@ -75,6 +75,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql` dateGranularity orderBy displayDataLabel + showCenterMetric displayLegend color description diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx index 19c3329eff..cca3c5acc6 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetGaugeChart.stories.tsx @@ -53,7 +53,6 @@ export const WithCustomColors: Story = { min: 0, max: 100, color: 'purple', - to: '/metrics/progress', label: 'Progress', }, displayType: 'number', @@ -83,7 +82,6 @@ export const Default: Story = { value: 0.5, min: 0, max: 1, - to: '/metrics/conversion', label: 'Conversion rate', }, displayType: 'percentage', @@ -120,7 +118,6 @@ export const Catalog: Story = { min: 0, max: 1, label: 'Percentage', - to: '/metrics/catalog', }, displayType: 'percentage' as const, id: `gauge-chart-catalog-${value}`, @@ -157,7 +154,6 @@ export const WithoutValue: Story = { value: 65, min: 0, max: 100, - to: '/metrics/conversion-without-value', label: 'Conversion rate', }, displayType: 'percentage', @@ -186,7 +182,6 @@ export const Revenue: Story = { value: 750, min: 0, max: 1000, - to: '/financials/revenue', label: 'Revenue', }, displayType: 'shortNumber', @@ -215,7 +210,6 @@ export const Temperature: Story = { value: 22, min: -10, max: 40, - to: '/sensors/temperature', label: 'Temperature', }, suffix: '°C', @@ -244,7 +238,6 @@ export const Storage: Story = { value: 384, min: 0, max: 512, - to: '/system/storage', label: 'Storage Used', }, suffix: ' GB', @@ -273,7 +266,6 @@ export const Rating: Story = { value: 4.2, min: 0, max: 5, - to: '/reviews/rating', label: 'Average Rating', }, suffix: ' ⭐', @@ -303,7 +295,6 @@ export const WithoutLegend: Story = { value: 65, min: 0, max: 100, - to: '/metrics/conversion-no-legend', label: 'Conversion rate', }, displayType: 'percentage', diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx index 28b942b612..7e3a90ffc8 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetPieChart.stories.tsx @@ -1,12 +1,13 @@ import { type Meta, type StoryObj } from '@storybook/react'; -import { CatalogDecorator, ComponentDecorator } from 'twenty-ui/testing'; import { GraphWidgetPieChart } from '@/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart'; +import { CatalogDecorator, ComponentDecorator } from 'twenty-ui/testing'; +import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; const meta: Meta = { title: 'Modules/PageLayout/Widgets/GraphWidgetPieChart', component: GraphWidgetPieChart, - decorators: [ComponentDecorator], + decorators: [ComponentDecorator, I18nFrontDecorator], parameters: { layout: 'centered', }, @@ -33,6 +34,12 @@ const meta: Meta = { id: { control: 'text', }, + showCenterMetric: { + control: 'boolean', + }, + showDataLabels: { + control: 'boolean', + }, }, }; @@ -43,91 +50,14 @@ const Container = ({ children }: { children: React.ReactNode }) => (
{children}
); -export const WithCustomColors: Story = { - args: { - data: [ - { - id: 'segment1', - value: 30, - label: 'Segment A', - color: 'blue', - to: '/segments/a', - }, - { - id: 'segment2', - value: 25, - label: 'Segment B', - color: 'purple', - to: '/segments/b', - }, - { - id: 'segment3', - value: 20, - label: 'Segment C', - color: 'turquoise', - to: '/segments/c', - }, - { - id: 'segment4', - value: 15, - label: 'Segment D', - color: 'orange', - to: '/segments/d', - }, - { - id: 'segment5', - value: 10, - label: 'Segment E', - color: 'pink', - to: '/segments/e', - }, - ], - showLegend: true, - id: 'pie-chart-custom-colors', - }, - render: (args) => ( - - - - ), -}; - export const Default: Story = { args: { data: [ - { - id: 'qualified', - value: 35, - label: 'Qualified', - to: '/leads/qualified', - }, - { - id: 'contacted', - value: 25, - label: 'Contacted', - to: '/leads/contacted', - }, - { - id: 'unqualified', - value: 20, - label: 'Unqualified', - to: '/leads/unqualified', - }, - { id: 'proposal', value: 15, label: 'Proposal', to: '/leads/proposal' }, - { - id: 'negotiation', - value: 5, - label: 'Negotiation', - to: '/leads/negotiation', - }, + { id: 'Qualified', value: 35 }, + { id: 'Contacted', value: 25 }, + { id: 'Unqualified', value: 20 }, + { id: 'Proposal', value: 15 }, + { id: 'Negotiation', value: 5 }, ], showLegend: true, id: 'pie-chart-default', @@ -147,33 +77,54 @@ export const Default: Story = { ), }; +export const WithCenterMetric: Story = { + args: { + data: [ + { id: 'Qualified', value: 35 }, + { id: 'Contacted', value: 25 }, + { id: 'Unqualified', value: 20 }, + ], + showCenterMetric: true, + id: 'pie-chart-with-center-metric', + }, + render: (args) => ( + + + + ), +}; + +export const WithDataLabels: Story = { + args: { + data: [ + { id: 'Qualified', value: 35 }, + { id: 'Contacted', value: 25 }, + { id: 'Unqualified', value: 20 }, + ], + showDataLabels: true, + id: 'pie-chart-with-data-labels', + }, + render: (args) => ( + + + + ), +}; export const Revenue: Story = { args: { data: [ - { - id: 'product-a', - value: 420000, - label: 'Product A', - to: '/products/a/revenue', - }, - { - id: 'product-b', - value: 380000, - label: 'Product B', - to: '/products/b/revenue', - }, - { - id: 'product-c', - value: 250000, - label: 'Product C', - to: '/products/c/revenue', - }, - { - id: 'product-d', - value: 180000, - label: 'Product D', - to: '/products/d/revenue', - }, + { id: 'Product A', value: 420000 }, + { id: 'Product B', value: 380000 }, + { id: 'Product C', value: 250000 }, + { id: 'Product D', value: 180000 }, ], prefix: '$', displayType: 'shortNumber', @@ -198,19 +149,9 @@ export const Revenue: Story = { export const TaskStatus: Story = { args: { data: [ - { - id: 'completed', - value: 45, - label: 'Completed', - to: '/tasks/completed', - }, - { - id: 'in-progress', - value: 30, - label: 'In Progress', - to: '/tasks/in-progress', - }, - { id: 'todo', value: 25, label: 'To Do', to: '/tasks/todo' }, + { id: 'Completed', value: 45 }, + { id: 'In Progress', value: 30 }, + { id: 'To Do', value: 25 }, ], displayType: 'percentage', showLegend: true, @@ -234,8 +175,8 @@ export const TaskStatus: Story = { export const TwoSlices: Story = { args: { data: [ - { id: 'active', value: 75, label: 'Active', to: '/users/active' }, - { id: 'inactive', value: 25, label: 'Inactive', to: '/users/inactive' }, + { id: 'Active', value: 75 }, + { id: 'Inactive', value: 25 }, ], displayType: 'percentage', showLegend: true, @@ -259,14 +200,14 @@ export const TwoSlices: Story = { export const ManySlices: Story = { args: { data: [ - { id: 'category-1', value: 20, label: 'Category 1', to: '/categories/1' }, - { id: 'category-2', value: 18, label: 'Category 2', to: '/categories/2' }, - { id: 'category-3', value: 16, label: 'Category 3', to: '/categories/3' }, - { id: 'category-4', value: 14, label: 'Category 4', to: '/categories/4' }, - { id: 'category-5', value: 12, label: 'Category 5', to: '/categories/5' }, - { id: 'category-6', value: 10, label: 'Category 6', to: '/categories/6' }, - { id: 'category-7', value: 6, label: 'Category 7' }, - { id: 'category-8', value: 4, label: 'Category 8' }, + { id: 'Category 1', value: 20 }, + { id: 'Category 2', value: 18 }, + { id: 'Category 3', value: 16 }, + { id: 'Category 4', value: 14 }, + { id: 'Category 5', value: 12 }, + { id: 'Category 6', value: 10 }, + { id: 'Category 7', value: 6 }, + { id: 'Category 8', value: 4 }, ], showLegend: true, id: 'pie-chart-many-slices', @@ -289,9 +230,9 @@ export const ManySlices: Story = { export const WithoutLegend: Story = { args: { data: [ - { id: 'web', value: 45, label: 'Web', to: '/platforms/web' }, - { id: 'mobile', value: 35, label: 'Mobile', to: '/platforms/mobile' }, - { id: 'desktop', value: 20, label: 'Desktop', to: '/platforms/desktop' }, + { id: 'Web', value: 45 }, + { id: 'Mobile', value: 35 }, + { id: 'Desktop', value: 20 }, ], displayType: 'percentage', showLegend: false, @@ -315,10 +256,10 @@ export const WithoutLegend: Story = { export const MarketShare: Story = { args: { data: [ - { id: 'brand-a', value: 35.5, label: 'Brand A', to: '/market/brand-a' }, - { id: 'brand-b', value: 28.2, label: 'Brand B', to: '/market/brand-b' }, - { id: 'brand-c', value: 18.7, label: 'Brand C', to: '/market/brand-c' }, - { id: 'others', value: 17.6, label: 'Others', to: '/market/others' }, + { id: 'Brand A', value: 35.5 }, + { id: 'Brand B', value: 28.2 }, + { id: 'Brand C', value: 18.7 }, + { id: 'Others', value: 17.6 }, ], displayType: 'percentage', showLegend: true, @@ -342,20 +283,10 @@ export const MarketShare: Story = { export const Storage: Story = { args: { data: [ - { - id: 'documents', - value: 125, - label: 'Documents', - to: '/storage/documents', - }, - { id: 'media', value: 280, label: 'Media', to: '/storage/media' }, - { - id: 'applications', - value: 95, - label: 'Applications', - to: '/storage/applications', - }, - { id: 'system', value: 50, label: 'System', to: '/storage/system' }, + { id: 'Documents', value: 125 }, + { id: 'Media', value: 280 }, + { id: 'Applications', value: 95 }, + { id: 'System', value: 50 }, ], suffix: ' GB', showLegend: true, @@ -377,7 +308,7 @@ export const Storage: Story = { }; export const Catalog: Story = { - decorators: [CatalogDecorator], + decorators: [CatalogDecorator, I18nFrontDecorator], parameters: { catalog: { dimensions: [ @@ -387,23 +318,23 @@ export const Catalog: Story = { props: (sliceCount: number) => { const dataMap: Record< number, - Array<{ id: string; value: number; label?: string }> + Array<{ id: string; value: number }> > = { 2: [ - { id: 'yes', value: 65, label: 'Yes' }, - { id: 'no', value: 35, label: 'No' }, + { id: 'Yes', value: 65 }, + { id: 'No', value: 35 }, ], 3: [ - { id: 'gold', value: 45, label: 'Gold' }, - { id: 'silver', value: 35, label: 'Silver' }, - { id: 'bronze', value: 20, label: 'Bronze' }, + { id: 'Gold', value: 45 }, + { id: 'Silver', value: 35 }, + { id: 'Bronze', value: 20 }, ], 5: [ - { id: 'item-1', value: 30, label: 'Item 1' }, - { id: 'item-2', value: 25, label: 'Item 2' }, - { id: 'item-3', value: 20, label: 'Item 3' }, - { id: 'item-4', value: 15, label: 'Item 4' }, - { id: 'item-5', value: 10, label: 'Item 5' }, + { id: 'Item 1', value: 30 }, + { id: 'Item 2', value: 25 }, + { id: 'Item 3', value: 20 }, + { id: 'Item 4', value: 15 }, + { id: 'Item 5', value: 10 }, ], }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomTotalsLayer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomTotalsLayer.tsx index c4bdf50bf3..9aae1afeec 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomTotalsLayer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomTotalsLayer.tsx @@ -1,16 +1,12 @@ import { GraphDataLabel } from '@/page-layout/widgets/graph/components/GraphDataLabel'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartLabelData } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLabelData'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { computeBarChartGroupedLabels } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartGroupedLabels'; import { computeBarChartStackedLabels } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartStackedLabels'; import { type GraphLabelData } from '@/page-layout/widgets/graph/types/GraphLabelData'; -import { type BarCustomLayerProps } from '@nivo/bar'; +import { type BarCustomLayerProps, type BarDatum } from '@nivo/bar'; -type CustomTotalsLayerProps = Pick< - BarCustomLayerProps, - 'bars' -> & { +type CustomTotalsLayerProps = Pick, 'bars'> & { formatValue?: (value: number) => string; offset?: number; layout?: BarChartLayout; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphBarChartTooltip.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphBarChartTooltip.tsx index e1662f451e..fe88fe58ad 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphBarChartTooltip.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphBarChartTooltip.tsx @@ -1,12 +1,11 @@ import { GraphWidgetFloatingTooltip } from '@/page-layout/widgets/graph/components/GraphWidgetFloatingTooltip'; import { graphWidgetBarTooltipComponentState } from '@/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetBarTooltipComponentState'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartEnrichedKey } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartEnrichedKey'; import { getBarChartTooltipData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTooltipData'; import { getTooltipReferenceFromBarChartElementAnchor } from '@/page-layout/widgets/graph/utils/getTooltipReferenceFromBarChartElementAnchor'; import { type GraphValueFormatOptions } from '@/page-layout/widgets/graph/utils/graphFormatters'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { type ComputedDatum } from '@nivo/bar'; +import { type BarDatum, type ComputedDatum } from '@nivo/bar'; import { isDefined } from 'twenty-shared/utils'; type GraphBarChartTooltipProps = { @@ -15,7 +14,7 @@ type GraphBarChartTooltipProps = { formatOptions: GraphValueFormatOptions; enableGroupTooltip?: boolean; layout?: 'vertical' | 'horizontal'; - onBarClick?: (datum: ComputedDatum) => void; + onBarClick?: (datum: ComputedDatum) => void; onMouseEnter?: () => void; onMouseLeave?: () => void; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx index e19c2c1206..df9b96c6a0 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx @@ -6,7 +6,6 @@ import { GraphBarChartTooltip } from '@/page-layout/widgets/graph/graphWidgetBar import { BAR_CHART_MINIMUM_INNER_PADDING } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumInnerPadding'; import { useBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartData'; import { useBarChartTheme } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartTheme'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { calculateStackedBarChartValueRange } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateStackedBarChartValueRange'; @@ -25,6 +24,7 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { ResponsiveBar, + type BarDatum, type BarItemProps, type ComputedBarDatum, type ComputedDatum, @@ -37,7 +37,7 @@ import { graphWidgetBarTooltipComponentState } from '@/page-layout/widgets/graph import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; type GraphWidgetBarChartProps = { - data: BarChartDataItem[]; + data: BarDatum[]; indexBy: string; keys: string[]; series?: BarChartSeries[]; @@ -53,7 +53,7 @@ type GraphWidgetBarChartProps = { rangeMin?: number; rangeMax?: number; omitNullValues?: boolean; - onBarClick?: (datum: ComputedDatum) => void; + onBarClick?: (datum: ComputedDatum) => void; } & GraphValueFormatOptions; const StyledContainer = styled.div` @@ -131,10 +131,7 @@ export const GraphWidgetBarChart = ({ const handleTooltipMouseLeave = debouncedHideTooltip; const handleBarEnter = useCallback( - ( - datum: ComputedDatum, - event: MouseEvent, - ) => { + (datum: ComputedDatum, event: MouseEvent) => { debouncedHideTooltip.cancel(); setActiveBarTooltip({ datum, @@ -162,7 +159,7 @@ export const GraphWidgetBarChart = ({ }); const BarItemWithContext = useMemo( - () => (props: BarItemProps) => ( + () => (props: BarItemProps) => ( []; + bars: readonly ComputedBarDatum[]; }) => ( ) => { + const handleBarClick = (datum: ComputedDatum) => { const displayValue = datum.data[indexBy]; const rawValue = formattedToRawLookup.get(displayValue as string) ?? null; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/__tests__/useBarChartData.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/__tests__/useBarChartData.test.ts index 7a81fd1a00..dc39453511 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/__tests__/useBarChartData.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/__tests__/useBarChartData.test.ts @@ -1,6 +1,6 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry'; +import { type BarDatum } from '@nivo/bar'; import { renderHook } from '@testing-library/react'; import { useBarChartData } from '../useBarChartData'; @@ -56,7 +56,7 @@ describe('useBarChartData', () => { }, }; - const mockData: BarChartDataItem[] = [ + const mockData: BarDatum[] = [ { month: 'Jan', sales: 100, costs: 80 }, { month: 'Feb', sales: 120, costs: 90 }, { month: 'Mar', sales: 150, costs: 100 }, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartData.ts index 8d5255fb25..806a3a348e 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartData.ts @@ -1,13 +1,13 @@ import { type BarChartConfig } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartConfig'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartEnrichedKey } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartEnrichedKey'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry'; import { getColorScheme } from '@/page-layout/widgets/graph/utils/getColorScheme'; +import { type BarDatum } from '@nivo/bar'; import { useMemo } from 'react'; type UseBarChartDataProps = { - data: BarChartDataItem[]; + data: BarDatum[]; indexBy: string; keys: string[]; series?: BarChartSeries[]; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetBarTooltipComponentState.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetBarTooltipComponentState.ts index 48899a9241..1ed3a12ec5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetBarTooltipComponentState.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetBarTooltipComponentState.ts @@ -1,10 +1,9 @@ import { GraphWidgetComponentInstanceContext } from '@/page-layout/widgets/graph/states/contexts/GraphWidgetComponentInstanceContext'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; -import { type ComputedDatum } from '@nivo/bar'; +import { type BarDatum, type ComputedDatum } from '@nivo/bar'; export const graphWidgetBarTooltipComponentState = createComponentState<{ - datum: ComputedDatum; + datum: ComputedDatum; anchorElement: Element; } | null>({ key: 'graphWidgetBarTooltipComponentState', diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem.ts deleted file mode 100644 index a85ee24b04..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type BarDatum } from '@nivo/bar'; - -export type BarChartDataItem = BarDatum & { - to?: string; -}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartEndLineCoordinates.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartEndLineCoordinates.test.ts index 86d4bc71bb..15bb75c160 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartEndLineCoordinates.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartEndLineCoordinates.test.ts @@ -1,11 +1,10 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; -import { type ComputedBarDatum } from '@nivo/bar'; +import { type BarDatum, type ComputedBarDatum } from '@nivo/bar'; import { calculateBarChartEndLineCoordinates } from '../calculateBarChartEndLineCoordinates'; describe('calculateBarChartEndLineCoordinates', () => { const createMockBar = ( - overrides?: Partial>, - ): ComputedBarDatum => + overrides?: Partial>, + ): ComputedBarDatum => ({ x: 100, y: 50, @@ -17,13 +16,13 @@ describe('calculateBarChartEndLineCoordinates', () => { value: 100, index: 0, indexValue: 'Q1', - data: { Q1: 100 } as BarChartDataItem, + data: { Q1: 100 } as BarDatum, formattedValue: '100', hidden: false, }, label: 'Sales', ...overrides, - }) as ComputedBarDatum; + }) as ComputedBarDatum; describe('vertical layout', () => { it('should calculate horizontal line coordinates at the top of vertical bars', () => { const mockBar = createMockBar(); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartValueRange.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartValueRange.test.ts index 37b1017ea1..e11622372e 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartValueRange.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartValueRange.test.ts @@ -1,9 +1,9 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { type BarDatum } from '@nivo/bar'; import { calculateValueRangeFromBarChartKeys } from '../calculateValueRangeFromBarChartKeys'; describe('calculateValueRangeFromBarChartKeys (essential cases)', () => { it('returns minimum=0 and maximum=highest value for all positive values', () => { - const data: BarChartDataItem[] = [ + const data: BarDatum[] = [ { category: 'A', v1: 10, v2: 20 }, { category: 'B', v1: 30, v2: 15 }, { category: 'C', v1: 25, v2: 40 }, @@ -17,7 +17,7 @@ describe('calculateValueRangeFromBarChartKeys (essential cases)', () => { }); it('returns minimum=lowest and maximum=0 for all negative values', () => { - const data: BarChartDataItem[] = [ + const data: BarDatum[] = [ { category: 'A', v1: -10, v2: -20 }, { category: 'B', v1: -30, v2: -15 }, { category: 'C', v1: -25, v2: -40 }, @@ -31,7 +31,7 @@ describe('calculateValueRangeFromBarChartKeys (essential cases)', () => { }); it('includes zero and spans minimum/maximum when values cross zero', () => { - const data: BarChartDataItem[] = [ + const data: BarDatum[] = [ { category: 'A', v1: -20, v2: 30 }, { category: 'B', v1: 15, v2: -10 }, { category: 'C', v1: -5, v2: 25 }, @@ -58,7 +58,7 @@ describe('calculateValueRangeFromBarChartKeys (essential cases)', () => { }); it('ignores NaN/missing values', () => { - const data: BarChartDataItem[] = [ + const data: BarDatum[] = [ { category: 'A', v1: 10, v2: NaN }, { category: 'B', v1: 20, v2: 30 }, { category: 'C', v1: undefined as unknown as number }, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateStackedBarChartValueRange.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateStackedBarChartValueRange.test.ts index a653f4cb85..72f786fe11 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateStackedBarChartValueRange.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateStackedBarChartValueRange.test.ts @@ -1,9 +1,9 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { type BarDatum } from '@nivo/bar'; import { calculateStackedBarChartValueRange } from '../calculateStackedBarChartValueRange'; describe('calculateStackedBarChartValueRange (essential cases)', () => { it('returns minimum=0 and maximum=largest positive stack', () => { - const data: BarChartDataItem[] = [ + const data: BarDatum[] = [ { cat: 'A', v1: 100, v2: 200, v3: 50 }, { cat: 'B', v1: 150, v2: 25, v3: 75 }, { cat: 'C', v1: 300, v2: 10, v3: 0 }, @@ -17,7 +17,7 @@ describe('calculateStackedBarChartValueRange (essential cases)', () => { }); it('returns minimum=most negative stack and maximum=0 for all negative values', () => { - const data: BarChartDataItem[] = [ + const data: BarDatum[] = [ { cat: 'A', v1: -100, v2: -200, v3: 0 }, { cat: 'B', v1: -50, v2: -25, v3: -75 }, ]; @@ -30,7 +30,7 @@ describe('calculateStackedBarChartValueRange (essential cases)', () => { }); it('sums positives and negatives per index to compute range when values mix', () => { - const data: BarChartDataItem[] = [ + const data: BarDatum[] = [ { cat: 'A', v1: 100, v2: -60, v3: 20 }, { cat: 'B', v1: 50, v2: -80, v3: -30 }, { cat: 'C', v1: 10, v2: 0, v3: 0 }, @@ -54,7 +54,7 @@ describe('calculateStackedBarChartValueRange (essential cases)', () => { }); it('ignores missing keys and NaN values', () => { - const data: BarChartDataItem[] = [ + const data: BarDatum[] = [ { cat: 'A', v1: 10 }, { cat: 'B', v2: 30 }, { cat: 'C', v1: NaN as number }, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/computeBarChartStackedLabels.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/computeBarChartStackedLabels.test.ts index 5ae9eebc2b..7c2dadf67a 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/computeBarChartStackedLabels.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/computeBarChartStackedLabels.test.ts @@ -1,5 +1,4 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; -import { type ComputedBarDatum } from '@nivo/bar'; +import { type BarDatum, type ComputedBarDatum } from '@nivo/bar'; import { computeBarChartStackedLabels } from '../computeBarChartStackedLabels'; const createMockBar = ( @@ -10,7 +9,7 @@ const createMockBar = ( y: number, width: number, height: number, -): ComputedBarDatum => +): ComputedBarDatum => ({ id, key: id, @@ -25,11 +24,11 @@ const createMockBar = ( color: 'blue', label: String(indexValue), formattedValue: String(value), - }) as unknown as ComputedBarDatum; + }) as unknown as ComputedBarDatum; describe('computeBarChartStackedLabels (essential cases)', () => { it('returns total for single index with all positive values', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', 100, 50, 200, 30, 100), createMockBar('series2', 'Jan', 50, 50, 100, 30, 100), createMockBar('series3', 'Jan', 25, 50, 50, 30, 50), @@ -48,7 +47,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('returns total for single index with all negative values', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', -100, 50, 400, 30, 100), createMockBar('series2', 'Jan', -50, 50, 500, 30, 100), createMockBar('series3', 'Jan', -25, 50, 575, 30, 50), @@ -67,7 +66,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('returns total for single index with mixed positive/negative (net positive)', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', 100, 50, 200, 30, 100), createMockBar('series2', 'Jan', -30, 50, 370, 30, 70), createMockBar('series3', 'Jan', 20, 50, 250, 30, 50), @@ -86,7 +85,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('returns total for single index with mixed positive/negative (net negative)', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', -100, 50, 400, 30, 100), createMockBar('series2', 'Jan', 30, 50, 270, 30, 70), createMockBar('series3', 'Jan', 20, 50, 280, 30, 50), @@ -105,7 +104,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('returns multiple totals for multiple indices', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', 100, 50, 200, 30, 100), createMockBar('series2', 'Jan', 50, 50, 100, 30, 100), createMockBar('series1', 'Feb', 75, 150, 225, 30, 75), @@ -139,7 +138,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('handles single bar per index (no stacking)', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', 100, 50, 200, 30, 100), createMockBar('series1', 'Feb', 150, 150, 150, 30, 150), createMockBar('series1', 'Mar', -50, 250, 400, 30, 50), @@ -179,7 +178,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('handles zero values correctly', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', 100, 50, 200, 30, 100), createMockBar('series2', 'Jan', 0, 50, 300, 30, 0), createMockBar('series3', 'Jan', -100, 50, 400, 30, 100), @@ -203,7 +202,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('handles numeric index values (not just strings)', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 2024, 100, 50, 200, 30, 100), createMockBar('series2', 2024, 50, 50, 100, 30, 100), createMockBar('series1', 2025, 75, 150, 225, 30, 75), @@ -221,7 +220,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('tracks minimum Y position correctly for positive totals', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', 50, 100, 250, 30, 50), createMockBar('series2', 'Jan', 100, 100, 150, 30, 100), createMockBar('series3', 'Jan', 25, 100, 325, 30, 25), @@ -233,7 +232,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('tracks maximum bottom Y position correctly for negative totals', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', -50, 100, 350, 30, 50), createMockBar('series2', 'Jan', -100, 100, 400, 30, 100), createMockBar('series3', 'Jan', -25, 100, 325, 30, 25), @@ -245,7 +244,7 @@ describe('computeBarChartStackedLabels (essential cases)', () => { }); it('calculates center positions correctly', () => { - const bars: ComputedBarDatum[] = [ + const bars: ComputedBarDatum[] = [ createMockBar('series1', 'Jan', 100, 50, 200, 40, 100), createMockBar('series2', 'Jan', 50, 60, 150, 20, 50), ]; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartEndLineCoordinates.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartEndLineCoordinates.ts index 0ba1c3c1a4..81c5086214 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartEndLineCoordinates.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartEndLineCoordinates.ts @@ -1,9 +1,8 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; -import { type ComputedBarDatum } from '@nivo/bar'; +import { type BarDatum, type ComputedBarDatum } from '@nivo/bar'; export const calculateBarChartEndLineCoordinates = ( - bar: ComputedBarDatum, + bar: ComputedBarDatum, layout: BarChartLayout, ) => { if (layout === BarChartLayout.VERTICAL) { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateStackedBarChartValueRange.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateStackedBarChartValueRange.ts index c0469bbcf3..3338f14f68 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateStackedBarChartValueRange.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateStackedBarChartValueRange.ts @@ -1,9 +1,9 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; -import { calculateValueRangeFromValues } from '@/page-layout/widgets/graph/utils/calculateValueRangeFromValues'; import { type ChartValueRange } from '@/page-layout/widgets/graph/types/ChartValueRange'; +import { calculateValueRangeFromValues } from '@/page-layout/widgets/graph/utils/calculateValueRangeFromValues'; +import { type BarDatum } from '@nivo/bar'; export const calculateStackedBarChartValueRange = ( - data: BarChartDataItem[], + data: BarDatum[], keys: string[], ): ChartValueRange => { const stackedValues: number[] = []; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateValueRangeFromBarChartKeys.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateValueRangeFromBarChartKeys.ts index dc29862ecb..6267c3a2b1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateValueRangeFromBarChartKeys.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateValueRangeFromBarChartKeys.ts @@ -1,9 +1,9 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; -import { calculateValueRangeFromValues } from '@/page-layout/widgets/graph/utils/calculateValueRangeFromValues'; import { type ChartValueRange } from '@/page-layout/widgets/graph/types/ChartValueRange'; +import { calculateValueRangeFromValues } from '@/page-layout/widgets/graph/utils/calculateValueRangeFromValues'; +import { type BarDatum } from '@nivo/bar'; export const calculateValueRangeFromBarChartKeys = ( - data: BarChartDataItem[], + data: BarDatum[], keys: string[], ): ChartValueRange => { const values: number[] = []; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues.ts index e3c15d2220..8b5b8f2ec2 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues.ts @@ -1,9 +1,9 @@ import { BAR_CHART_MINIMUM_WIDTH_PER_TICK } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { computeMinHeightPerTick } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeMinHeightPerTick'; import { getBarChartMargins } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins'; import { computeChartCategoryTickValues } from '@/page-layout/widgets/graph/utils/computeChartCategoryTickValues'; +import { type BarDatum } from '@nivo/bar'; export const computeBarChartCategoryTickValues = ({ axisSize, @@ -16,7 +16,7 @@ export const computeBarChartCategoryTickValues = ({ }: { axisSize: number; axisFontSize: number; - data: BarChartDataItem[]; + data: BarDatum[]; indexBy: string; layout: BarChartLayout; xAxisLabel?: string; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartGroupedLabels.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartGroupedLabels.ts index da7a6e2b3a..07d4400889 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartGroupedLabels.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartGroupedLabels.ts @@ -1,9 +1,8 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartLabelData } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLabelData'; -import { type ComputedBarDatum } from '@nivo/bar'; +import { type BarDatum, type ComputedBarDatum } from '@nivo/bar'; export const computeBarChartGroupedLabels = ( - bars: readonly ComputedBarDatum[], + bars: readonly ComputedBarDatum[], ): BarChartLabelData[] => { return bars.map((bar) => { const value = Number(bar.data.value); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartStackedLabels.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartStackedLabels.ts index 57a1c47c09..dd79d426b1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartStackedLabels.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartStackedLabels.ts @@ -1,10 +1,9 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartLabelData } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLabelData'; -import { type ComputedBarDatum } from '@nivo/bar'; +import { type BarDatum, type ComputedBarDatum } from '@nivo/bar'; import { isDefined } from 'twenty-shared/utils'; export const computeBarChartStackedLabels = ( - bars: readonly ComputedBarDatum[], + bars: readonly ComputedBarDatum[], ): BarChartLabelData[] => { const stackData = new Map< string, @@ -13,7 +12,7 @@ export const computeBarChartStackedLabels = ( minimumYPosition: number; maximumBottomYPosition: number; maximumXPosition: number; - bars: ComputedBarDatum[]; + bars: ComputedBarDatum[]; } >(); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartAxisConfigs.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartAxisConfigs.ts index 412a7b41b4..6939dd80e1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartAxisConfigs.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartAxisConfigs.ts @@ -1,4 +1,3 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { getBarChartMargins } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins'; import { getBarChartTickConfig } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig'; @@ -7,6 +6,7 @@ import { formatGraphValue, type GraphValueFormatOptions, } from '@/page-layout/widgets/graph/utils/graphFormatters'; +import { type BarDatum } from '@nivo/bar'; const LEFT_AXIS_LEGEND_OFFSET_PADDING = 5; const TICK_PADDING = 5; @@ -22,7 +22,7 @@ const COMMON_AXIS_CONFIG = { type GetBarChartAxisConfigsProps = { width: number; height: number; - data: BarChartDataItem[]; + data: BarDatum[]; layout: BarChartLayout; indexBy: string; xAxisLabel?: string; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig.ts index fcc1c3dc8a..b62125c5d7 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig.ts @@ -1,10 +1,10 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { calculateMaxTickLabelLength } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateMaxTickLabelLength'; import { calculateWidthPerTick } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateWidthPerTick'; import { computeBarChartCategoryTickValues } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues'; import { computeBarChartValueTickCount } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartValueTickCount'; import { getBarChartMargins } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins'; +import { type BarDatum } from '@nivo/bar'; const MAX_LEFT_AXIS_LABEL_LENGTH = 10; @@ -27,7 +27,7 @@ export const getBarChartTickConfig = ({ }: { width: number; height: number; - data: BarChartDataItem[]; + data: BarDatum[]; indexBy: string; xAxisLabel?: string; yAxisLabel?: string; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTooltipData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTooltipData.ts index d8fb478dad..252d41c315 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTooltipData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTooltipData.ts @@ -1,15 +1,14 @@ import { type GraphWidgetTooltipItem } from '@/page-layout/widgets/graph/components/GraphWidgetTooltip'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartEnrichedKey } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartEnrichedKey'; import { formatGraphValue, type GraphValueFormatOptions, } from '@/page-layout/widgets/graph/utils/graphFormatters'; -import { type ComputedDatum } from '@nivo/bar'; +import { type BarDatum, type ComputedDatum } from '@nivo/bar'; import { isDefined } from 'twenty-shared/utils'; type GetBarChartTooltipDataParameters = { - datum: ComputedDatum; + datum: ComputedDatum; enrichedKeys: BarChartEnrichedKey[]; formatOptions: GraphValueFormatOptions; enableGroupTooltip?: boolean; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/sortBarChartDataBySecondaryDimensionSum.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/sortBarChartDataBySecondaryDimensionSum.ts index 20aa0e9bd1..979f41fb10 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/sortBarChartDataBySecondaryDimensionSum.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/sortBarChartDataBySecondaryDimensionSum.ts @@ -1,9 +1,9 @@ -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { type BarDatum } from '@nivo/bar'; import { isDefined } from 'twenty-shared/utils'; import { GraphOrderBy } from '~/generated/graphql'; type SortBarChartDataBySecondaryDimensionSumParams = { - data: BarChartDataItem[]; + data: BarDatum[]; keys: string[]; orderBy: GraphOrderBy; }; @@ -12,7 +12,7 @@ export const sortBarChartDataBySecondaryDimensionSum = ({ data, keys, orderBy, -}: SortBarChartDataBySecondaryDimensionSumParams): BarChartDataItem[] => { +}: SortBarChartDataBySecondaryDimensionSumParams): BarDatum[] => { if ( orderBy !== GraphOrderBy.VALUE_ASC && orderBy !== GraphOrderBy.VALUE_DESC @@ -20,16 +20,16 @@ export const sortBarChartDataBySecondaryDimensionSum = ({ return data; } - const dataWithSecondaryDimensionSums = data.map((barChartDataItem) => { + const dataWithSecondaryDimensionSums = data.map((datum) => { const secondaryDimensionSum = keys.reduce((sumAccumulator, segmentKey) => { - const segmentValue = barChartDataItem[segmentKey]; + const segmentValue = datum[segmentKey]; if (isDefined(segmentValue) && typeof segmentValue === 'number') { return sumAccumulator + segmentValue; } return sumAccumulator; }, 0); - return { barChartDataItem, secondaryDimensionSum }; + return { datum, secondaryDimensionSum }; }); dataWithSecondaryDimensionSums.sort((a, b) => { @@ -40,7 +40,5 @@ export const sortBarChartDataBySecondaryDimensionSum = ({ } }); - return dataWithSecondaryDimensionSums.map( - ({ barChartDataItem }) => barChartDataItem, - ); + return dataWithSecondaryDimensionSums.map(({ datum }) => datum); }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts index 89f3797a91..016dd99cfe 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts @@ -4,7 +4,6 @@ import { getAggregateOperationLabel } from '@/object-record/record-board/record- import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName'; import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { fillDateGapsInBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInBarChartData'; @@ -14,6 +13,7 @@ import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupBy import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue'; import { filterGroupByResults } from '@/page-layout/widgets/graph/utils/filterGroupByResults'; import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey'; +import { type BarDatum } from '@nivo/bar'; import { isDefined } from 'twenty-shared/utils'; import { GraphType } from '~/generated-metadata/graphql'; import { @@ -30,7 +30,7 @@ type TransformGroupByDataToBarChartDataParams = { }; type TransformGroupByDataToBarChartDataResult = { - data: BarChartDataItem[]; + data: BarDatum[]; indexBy: string; keys: string[]; series: BarChartSeries[]; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData.ts index 6ba3cbb1ca..ca84f862b1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData.ts @@ -3,16 +3,16 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { GRAPH_DEFAULT_COLOR } from '@/page-layout/widgets/graph/constants/GraphDefaultColor.constant'; import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult'; import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue'; import { buildFormattedToRawLookup } from '@/page-layout/widgets/graph/utils/buildFormattedToRawLookup'; -import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues'; import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult'; import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue'; +import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues'; import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey'; +import { type BarDatum } from '@nivo/bar'; import { isDefined } from 'twenty-shared/utils'; import { type BarChartConfiguration } from '~/generated/graphql'; @@ -27,7 +27,7 @@ type TransformOneDimensionalGroupByToBarChartDataParams = { }; type TransformOneDimensionalGroupByToBarChartDataResult = { - data: BarChartDataItem[]; + data: BarDatum[]; indexBy: string; keys: string[]; series: BarChartSeries[]; @@ -67,7 +67,7 @@ export const transformOneDimensionalGroupByToBarChartData = ({ const formattedToRawLookup = buildFormattedToRawLookup(formattedValues); - const data: BarChartDataItem[] = limitedResults.map((result) => { + const data: BarDatum[] = limitedResults.map((result) => { const dimensionValues = result.groupByDimensionValues; const xValue = isDefined(dimensionValues?.[0]) diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData.ts index b4abbdda04..29d299b7a2 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData.ts @@ -2,18 +2,18 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; -import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { sortBarChartDataBySecondaryDimensionSum } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/sortBarChartDataBySecondaryDimensionSum'; import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult'; import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue'; import { buildFormattedToRawLookup } from '@/page-layout/widgets/graph/utils/buildFormattedToRawLookup'; -import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues'; import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult'; import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue'; +import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues'; import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey'; import { getSortedKeys } from '@/page-layout/widgets/graph/utils/getSortedKeys'; +import { type BarDatum } from '@nivo/bar'; import { isDefined } from 'twenty-shared/utils'; import { BarChartGroupMode, @@ -32,7 +32,7 @@ type TransformTwoDimensionalGroupByToBarChartDataParams = { }; type TransformTwoDimensionalGroupByToBarChartDataResult = { - data: BarChartDataItem[]; + data: BarDatum[]; indexBy: string; keys: string[]; series: BarChartSeries[]; @@ -55,7 +55,7 @@ export const transformTwoDimensionalGroupByToBarChartData = ({ subFieldName: primaryAxisSubFieldName ?? undefined, }); - const dataMap = new Map(); + const dataMap = new Map(); const xValues = new Set(); const yValues = new Set(); const formattedValues = formatPrimaryDimensionValues({ diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/components/GraphWidgetGaugeChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/components/GraphWidgetGaugeChart.tsx index 5c8e97a984..d97e1c3137 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/components/GraphWidgetGaugeChart.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/components/GraphWidgetGaugeChart.tsx @@ -25,6 +25,7 @@ type GraphWidgetGaugeChartProps = { showValue?: boolean; showLegend?: boolean; id: string; + onGaugeClick?: (data: GaugeChartData) => void; } & GraphValueFormatOptions; const StyledContainer = styled.div` @@ -67,6 +68,7 @@ export const GraphWidgetGaugeChart = ({ prefix, suffix, customFormatter, + onGaugeClick, }: GraphWidgetGaugeChartProps) => { const theme = useTheme(); const instanceId = useId(); @@ -81,7 +83,7 @@ export const GraphWidgetGaugeChart = ({ }; const { isHovered, setIsHovered, handleClick, hasClickableItems } = - useGaugeChartHandlers({ data }); + useGaugeChartHandlers({ data, onGaugeClick }); const { colorScheme, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/hooks/useGaugeChartHandlers.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/hooks/useGaugeChartHandlers.ts index 002b06ff5d..3eaf9013ac 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/hooks/useGaugeChartHandlers.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/hooks/useGaugeChartHandlers.ts @@ -4,18 +4,20 @@ import { isDefined } from 'twenty-shared/utils'; type UseGaugeChartHandlersProps = { data: GaugeChartData; + onGaugeClick?: (data: GaugeChartData) => void; }; -export const useGaugeChartHandlers = ({ data }: UseGaugeChartHandlersProps) => { +export const useGaugeChartHandlers = ({ + data, + onGaugeClick, +}: UseGaugeChartHandlersProps) => { const [isHovered, setIsHovered] = useState(false); const handleClick = () => { - if (isDefined(data.to)) { - window.location.href = data.to; - } + onGaugeClick?.(data); }; - const hasClickableItems = isDefined(data.to); + const hasClickableItems = isDefined(onGaugeClick); return { isHovered, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/types/GaugeChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/types/GaugeChartData.ts index f6462f3c70..0946c58a0f 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/types/GaugeChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetGaugeChart/types/GaugeChartData.ts @@ -5,6 +5,5 @@ export type GaugeChartData = { min: number; max: number; color?: GraphColor; - to?: string; label?: string; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint.ts index 5edf158481..783746fd1c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint.ts @@ -1,5 +1,4 @@ export type LineChartDataPoint = { x: number | string | Date; y: number | null; - to?: string; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/handleLineChartPointClick.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/handleLineChartPointClick.ts deleted file mode 100644 index 4d42e8b31b..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/handleLineChartPointClick.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries'; -import { type LineSeries, type Point } from '@nivo/line'; -import { isDefined } from 'twenty-shared/utils'; - -export const handleLineChartPointClick = ( - point: Point, - dataMap: Record, -) => { - const series = dataMap[point.seriesId]; - if (isDefined(series)) { - const dataPoint = series.data[point.indexInSeries]; - if (isDefined(dataPoint?.to)) { - window.location.href = dataPoint.to; - } - } -}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart.tsx index fdd9dae0df..591906dcee 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart.tsx @@ -1,10 +1,10 @@ import { GraphWidgetChartContainer } from '@/page-layout/widgets/graph/components/GraphWidgetChartContainer'; import { GraphWidgetLegend } from '@/page-layout/widgets/graph/components/GraphWidgetLegend'; import { GraphWidgetTooltip } from '@/page-layout/widgets/graph/components/GraphWidgetTooltip'; +import { PieChartCenterMetric } from '@/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer'; import { PIE_CHART_HOVER_BRIGHTNESS } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartHoverBrightness'; import { PIE_CHART_MARGINS } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMargins'; import { usePieChartData } from '@/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData'; -import { usePieChartHandlers } from '@/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers'; import { usePieChartTooltip } from '@/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartTooltip'; import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem'; import { createGraphColorRegistry } from '@/page-layout/widgets/graph/utils/createGraphColorRegistry'; @@ -25,11 +25,10 @@ type GraphWidgetPieChartProps = { id: string; onSliceClick?: (datum: PieChartDataItem) => void; showDataLabels?: boolean; + showCenterMetric?: boolean; } & GraphValueFormatOptions; -const emptyStateData: PieChartDataItem[] = [ - { id: 'empty', label: '', value: 1 }, -]; +const emptyStateData: PieChartDataItem[] = [{ id: 'empty', value: 1 }]; const StyledContainer = styled.div` align-items: center; @@ -41,8 +40,10 @@ const StyledContainer = styled.div` `; const StyledPieChartWrapper = styled.div<{ preventHover: boolean }>` - width: 100%; + container-type: size; height: 100%; + position: relative; + width: 100%; svg g path { transition: filter 0.15s ease-in-out; @@ -65,6 +66,7 @@ export const GraphWidgetPieChart = ({ customFormatter, onSliceClick, showDataLabels = false, + showCenterMetric = false, }: GraphWidgetPieChartProps) => { const theme = useTheme(); const colorRegistry = createGraphColorRegistry(theme); @@ -77,17 +79,9 @@ export const GraphWidgetPieChart = ({ customFormatter, }; - const { - hoveredSliceId, - setHoveredSliceId, - handleSliceClick, - hasClickableItems, - } = usePieChartHandlers({ data, onSliceClick }); - const { enrichedData } = usePieChartData({ data, colorRegistry, - hoveredSliceId, }); const { createTooltipData } = usePieChartTooltip({ @@ -96,6 +90,12 @@ export const GraphWidgetPieChart = ({ displayType, }); + const handleSliceClick = (datum: ComputedDatum) => { + if (isDefined(onSliceClick)) { + onSliceClick(datum.data); + } + }; + const renderTooltip = ({ datum }: PieTooltipProps) => { const tooltipData = createTooltipData(datum); if (!isDefined(tooltipData)) return null; @@ -121,7 +121,7 @@ export const GraphWidgetPieChart = ({ return ( @@ -135,11 +135,7 @@ export const GraphWidgetPieChart = ({ enableArcLinkLabels={showDataLabels && !hasNoData} enableArcLabels={false} tooltip={hasNoData ? () => null : renderTooltip} - onClick={hasNoData ? undefined : handleSliceClick} - onMouseEnter={ - hasNoData ? undefined : (datum) => setHoveredSliceId(datum.id) - } - onMouseLeave={hasNoData ? undefined : () => setHoveredSliceId(null)} + onClick={hasNoData ? undefined : (datum) => handleSliceClick(datum)} layers={['arcs', 'arcLinkLabels']} arcLinkLabel={(datum: ComputedDatum) => { const tooltipData = createTooltipData(datum); @@ -161,13 +157,18 @@ export const GraphWidgetPieChart = ({ }, }} /> + ({ id: item.id, - label: item.label || item.id, + label: item.id, color: item.colorScheme.solid, }))} /> diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer.tsx index 816ed8b674..059e73a1a7 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer.tsx @@ -34,6 +34,7 @@ export const GraphWidgetPieChartRenderer = ({ objectMetadataItem, showLegend, showDataLabels, + showCenterMetric, formattedToRawLookup, } = useGraphPieChartWidgetData({ objectMetadataItemId: widget.objectMetadataId, @@ -89,6 +90,7 @@ export const GraphWidgetPieChartRenderer = ({ displayType="shortNumber" onSliceClick={handleSliceClick} showDataLabels={showDataLabels} + showCenterMetric={showCenterMetric} /> ); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx new file mode 100644 index 0000000000..f308e46374 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx @@ -0,0 +1,56 @@ +import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem'; +import { + formatGraphValue, + type GraphValueFormatOptions, +} from '@/page-layout/widgets/graph/utils/graphFormatters'; +import styled from '@emotion/styled'; +import { Trans } from '@lingui/react/macro'; + +type PieChartCenterMetricProps = { + data: PieChartDataItem[]; + formatOptions: GraphValueFormatOptions; + show: boolean; +}; + +const StyledCenterMetricContainer = styled.div<{ show: boolean }>` + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + left: 50%; + opacity: ${({ show }) => (show ? 1 : 0)}; + pointer-events: none; + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + transition: opacity ${({ theme }) => theme.animation.duration.fast}s + ease-in-out; +`; + +const StyledValue = styled.span` + color: ${({ theme }) => theme.font.color.primary}; + font-size: clamp(12px, 10cqmin, 48px); + font-weight: ${({ theme }) => theme.font.weight.semiBold}; +`; + +const StyledLabel = styled.span` + color: ${({ theme }) => theme.font.color.tertiary}; + font-size: clamp(10px, 5cqmin, 24px); +`; + +export const PieChartCenterMetric = ({ + data, + formatOptions, + show, +}: PieChartCenterMetricProps) => { + const total = data.reduce((sum, datum) => sum + datum.value, 0); + + return ( + + {formatGraphValue(total, formatOptions)} + + Total + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartEndLines.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartEndLines.tsx deleted file mode 100644 index 3d066924ec..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartEndLines.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { type PieChartEnrichedData } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData'; -import { calculatePieChartEndLineCoordinates } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartEndLineCoordinates'; -import { useTheme } from '@emotion/react'; -import { type ComputedDatum } from '@nivo/pie'; -import { isDefined } from 'twenty-shared/utils'; - -type PieChartEndLinesProps = { - dataWithArc: readonly ComputedDatum<{ - id: string; - value: number; - label?: string; - }>[]; - centerX: number; - centerY: number; - innerRadius: number; - radius: number; - enrichedDataMap: Map; -}; - -export const PieChartEndLines = ({ - dataWithArc, - centerX, - centerY, - innerRadius, - radius, - enrichedDataMap, -}: PieChartEndLinesProps) => { - const theme = useTheme(); - - if ( - !isDefined(dataWithArc) || - !Array.isArray(dataWithArc) || - dataWithArc.length < 2 - ) { - return null; - } - - return ( - - {dataWithArc.map((datum) => { - const enrichedItem = enrichedDataMap.get(datum.id); - const lineColor = enrichedItem - ? enrichedItem.colorScheme.solid - : theme.border.color.strong; - - const { x1, y1, x2, y2 } = calculatePieChartEndLineCoordinates( - datum.arc.endAngle, - centerX, - centerY, - innerRadius, - radius, - ); - - return ( - - ); - })} - - ); -}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartCenterMetricMinSizes.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartCenterMetricMinSizes.ts new file mode 100644 index 0000000000..41f79dc970 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartCenterMetricMinSizes.ts @@ -0,0 +1,5 @@ +export const PIE_CHART_CENTER_METRIC_MIN_SIZES = { + valueFontSize: 12, + labelFontSize: 10, + labelOffset: 12, +} as const; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartCenterMetricRatios.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartCenterMetricRatios.ts new file mode 100644 index 0000000000..85ee84dc26 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartCenterMetricRatios.ts @@ -0,0 +1,5 @@ +export const PIE_CHART_CENTER_METRIC_RATIOS = { + valueFontSize: 0.25, + labelFontSize: 0.12, + labelOffset: 0.15, +} as const; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/__tests__/usePieChartData.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/__tests__/usePieChartData.test.ts index 533d940c27..7bf68199ce 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/__tests__/usePieChartData.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/__tests__/usePieChartData.test.ts @@ -57,9 +57,9 @@ describe('usePieChartData', () => { }; const mockData: PieChartDataItem[] = [ - { id: 'item1', value: 30, label: 'Item 1' }, - { id: 'item2', value: 50, label: 'Item 2' }, - { id: 'item3', value: 20, label: 'Item 3' }, + { id: 'item1', value: 30 }, + { id: 'item2', value: 50 }, + { id: 'item3', value: 20 }, ]; it('should enrich data with color schemes and percentages', () => { @@ -67,7 +67,6 @@ describe('usePieChartData', () => { usePieChartData({ data: mockData, colorRegistry: mockColorRegistry, - hoveredSliceId: null, }), ); @@ -75,54 +74,18 @@ describe('usePieChartData', () => { expect(result.current.enrichedData[0]).toMatchObject({ id: 'item1', value: 30, - label: 'Item 1', percentage: 30, colorScheme: mockColorRegistry.red, - isHovered: false, }); expect(result.current.enrichedData[1].percentage).toBe(50); expect(result.current.enrichedData[2].percentage).toBe(20); }); - it('should calculate middle angles for each slice', () => { - const { result } = renderHook(() => - usePieChartData({ - data: mockData, - colorRegistry: mockColorRegistry, - hoveredSliceId: null, - }), - ); - - expect(result.current.enrichedData[0].middleAngle).toBe(54); - expect(result.current.enrichedData[1].middleAngle).toBe(198); - expect(result.current.enrichedData[2].middleAngle).toBe(324); - }); - - it('should handle hover state', () => { - const { result, rerender } = renderHook( - ({ hoveredSliceId }: { hoveredSliceId: DatumId | null }) => - usePieChartData({ - data: mockData, - colorRegistry: mockColorRegistry, - hoveredSliceId, - }), - { initialProps: { hoveredSliceId: null as DatumId | null } }, - ); - - expect(result.current.enrichedData[1].isHovered).toBe(false); - - rerender({ hoveredSliceId: 'item2' as DatumId }); - expect(result.current.enrichedData[1].isHovered).toBe(true); - expect(result.current.enrichedData[0].isHovered).toBe(false); - expect(result.current.enrichedData[2].isHovered).toBe(false); - }); - it('should handle empty data', () => { const { result } = renderHook(() => usePieChartData({ data: [], colorRegistry: mockColorRegistry, - hoveredSliceId: null, }), ); @@ -130,47 +93,36 @@ describe('usePieChartData', () => { }); it('should handle single data item', () => { - const singleData: PieChartDataItem[] = [ - { id: 'single', value: 100, label: 'Single Item' }, - ]; + const singleData: PieChartDataItem[] = [{ id: 'single', value: 100 }]; const { result } = renderHook(() => usePieChartData({ data: singleData, colorRegistry: mockColorRegistry, - hoveredSliceId: null, }), ); expect(result.current.enrichedData[0].percentage).toBe(100); - expect(result.current.enrichedData[0].middleAngle).toBe(180); }); - it('should handle custom colors in data items', () => { - const dataWithColors: PieChartDataItem[] = [ - { id: 'item1', value: 50, label: 'Item 1', color: 'blue' }, - { id: 'item2', value: 50, label: 'Item 2' }, - ]; - + it('should assign colors based on index', () => { const { result } = renderHook(() => usePieChartData({ - data: dataWithColors, + data: mockData, colorRegistry: mockColorRegistry, - hoveredSliceId: null, }), ); - expect(result.current.enrichedData[0].colorScheme.name).toBe('blue'); + expect(result.current.enrichedData[0].colorScheme.name).toBe('red'); expect(result.current.enrichedData[1].colorScheme.name).toBe('blue'); }); it('should memoize calculations', () => { const { result, rerender } = renderHook( - ({ hoveredSliceId }) => + () => usePieChartData({ data: mockData, colorRegistry: mockColorRegistry, - hoveredSliceId, }), { initialProps: { hoveredSliceId: null as DatumId | null } }, ); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts index 391121b8b6..cdbc279ad5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts @@ -23,6 +23,7 @@ type UseGraphPieChartWidgetDataResult = { objectMetadataItem: ObjectMetadataItem; formattedToRawLookup: Map; showDataLabels: boolean; + showCenterMetric: boolean; }; export const useGraphPieChartWidgetData = ({ @@ -60,6 +61,7 @@ export const useGraphPieChartWidgetData = ({ ...transformedData, objectMetadataItem, showDataLabels: configuration.displayDataLabel ?? false, + showCenterMetric: configuration.showCenterMetric ?? false, loading, error, }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData.ts index 18268f4ecd..b3f8f5cec1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData.ts @@ -1,50 +1,38 @@ import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem'; import { type PieChartEnrichedData } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData'; -import { calculatePieChartAngles } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartAngles'; import { calculatePieChartPercentage } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartPercentage'; import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry'; import { getColorScheme } from '@/page-layout/widgets/graph/utils/getColorScheme'; -import { type DatumId } from '@nivo/pie'; import { useMemo } from 'react'; type UsePieChartDataProps = { data: PieChartDataItem[]; colorRegistry: GraphColorRegistry; - hoveredSliceId: DatumId | null; }; export const usePieChartData = ({ data, colorRegistry, - hoveredSliceId, }: UsePieChartDataProps) => { const enrichedData = useMemo((): PieChartEnrichedData[] => { const totalValue = data.reduce((sum, item) => sum + item.value, 0); - let cumulativeAngle = 0; return data.map((item, index) => { const colorScheme = getColorScheme({ registry: colorRegistry, - colorName: item.color, fallbackIndex: index, totalGroups: data.length, }); - const isHovered = hoveredSliceId === item.id; const percentage = calculatePieChartPercentage(item.value, totalValue); - const angles = calculatePieChartAngles(percentage, cumulativeAngle); - cumulativeAngle = angles.newCumulativeAngle; - return { ...item, colorScheme, - isHovered, percentage, - middleAngle: angles.middleAngle, }; }); - }, [data, colorRegistry, hoveredSliceId]); + }, [data, colorRegistry]); const enrichedDataMap = useMemo( () => new Map(enrichedData.map((item) => [item.id, item])), diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers.ts deleted file mode 100644 index 6605add5ec..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem'; -import { type ComputedDatum, type DatumId } from '@nivo/pie'; -import { useState } from 'react'; -import { isDefined } from 'twenty-shared/utils'; - -type UsePieChartHandlersProps = { - data: PieChartDataItem[]; - onSliceClick?: (datum: PieChartDataItem) => void; -}; - -export const usePieChartHandlers = ({ - data, - onSliceClick, -}: UsePieChartHandlersProps) => { - const [hoveredSliceId, setHoveredSliceId] = useState(null); - - const handleSliceClick = ( - datum: ComputedDatum<{ id: string; value: number; label?: string }>, - ) => { - const clickedItem = data.find((d) => d.id === datum.id); - if (isDefined(clickedItem)) { - if (isDefined(onSliceClick)) { - onSliceClick(clickedItem); - } else if (isDefined(clickedItem.to)) { - window.location.href = clickedItem.to; - } - } - }; - - const hasClickableItems = - isDefined(onSliceClick) || data.some((item) => isDefined(item.to)); - - return { - hoveredSliceId, - setHoveredSliceId, - handleSliceClick, - hasClickableItems, - }; -}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartTooltip.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartTooltip.ts index 6a3b5cec29..970395f113 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartTooltip.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartTooltip.ts @@ -33,7 +33,7 @@ export const usePieChartTooltip = ({ return { tooltipItem: { key: item.id, - label: item.label || item.id, + label: item.id, formattedValue, value: item.value, dotColor: item.colorScheme.solid, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem.ts index 07aaf3f4ec..1c1a2305b1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem.ts @@ -1,9 +1,4 @@ -import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; - export type PieChartDataItem = { id: string; value: number; - label?: string; - color?: GraphColor; - to?: string; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData.ts index f7c6499670..49ac165093 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData.ts @@ -3,7 +3,5 @@ import { type GraphColorScheme } from '@/page-layout/widgets/graph/types/GraphCo export type PieChartEnrichedData = PieChartDataItem & { colorScheme: GraphColorScheme; - isHovered: boolean; percentage: number; - middleAngle: number; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartAngles.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartAngles.test.ts deleted file mode 100644 index d6996eb77b..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartAngles.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { calculatePieChartAngles } from '../calculatePieChartAngles'; -describe('calculatePieChartAngles', () => { - it('should calculate angles for a quarter slice (25%)', () => { - const result = calculatePieChartAngles(25, 0); - expect(result).toEqual({ - sliceAngle: 90, - middleAngle: 45, - newCumulativeAngle: 90, - }); - }); - it('should calculate angles for a half slice (50%)', () => { - const result = calculatePieChartAngles(50, 0); - expect(result).toEqual({ - sliceAngle: 180, - middleAngle: 90, - newCumulativeAngle: 180, - }); - }); - it('should calculate angles for a full circle (100%)', () => { - const result = calculatePieChartAngles(100, 0); - expect(result).toEqual({ - sliceAngle: 360, - middleAngle: 180, - newCumulativeAngle: 360, - }); - }); - it('should handle cumulative angles correctly', () => { - const result = calculatePieChartAngles(25, 90); - expect(result).toEqual({ - sliceAngle: 90, - middleAngle: 135, - newCumulativeAngle: 180, - }); - }); - it('should handle very small percentages', () => { - const result = calculatePieChartAngles(1, 0); - expect(result).toEqual({ - sliceAngle: 3.6, - middleAngle: 1.8, - newCumulativeAngle: 3.6, - }); - }); - it('should handle decimal percentages', () => { - const result = calculatePieChartAngles(33.33, 0); - expect(result.sliceAngle).toBeCloseTo(119.988, 2); - expect(result.middleAngle).toBeCloseTo(59.994, 2); - expect(result.newCumulativeAngle).toBeCloseTo(119.988, 2); - }); - it('should handle zero percentage', () => { - const result = calculatePieChartAngles(0, 45); - expect(result).toEqual({ - sliceAngle: 0, - middleAngle: 45, - newCumulativeAngle: 45, - }); - }); - it('should calculate sequential slices correctly', () => { - let cumulative = 0; - const slice1 = calculatePieChartAngles(30, cumulative); - expect(slice1.sliceAngle).toBe(108); - expect(slice1.middleAngle).toBe(54); - cumulative = slice1.newCumulativeAngle; - const slice2 = calculatePieChartAngles(50, cumulative); - expect(slice2.sliceAngle).toBe(180); - expect(slice2.middleAngle).toBe(198); - cumulative = slice2.newCumulativeAngle; - const slice3 = calculatePieChartAngles(20, cumulative); - expect(slice3.sliceAngle).toBe(72); - expect(slice3.middleAngle).toBe(324); - expect(slice3.newCumulativeAngle).toBe(360); - }); - it('should handle negative percentages', () => { - const result = calculatePieChartAngles(-10, 0); - expect(result).toEqual({ - sliceAngle: -36, - middleAngle: -18, - newCumulativeAngle: -36, - }); - }); - it('should handle percentages over 100', () => { - const result = calculatePieChartAngles(150, 0); - expect(result).toEqual({ - sliceAngle: 540, - middleAngle: 270, - newCumulativeAngle: 540, - }); - }); -}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartEndLineCoordinates.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartEndLineCoordinates.test.ts deleted file mode 100644 index 8bdbfe8e64..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartEndLineCoordinates.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { calculatePieChartEndLineCoordinates } from '../calculatePieChartEndLineCoordinates'; -describe('calculatePieChartEndLineCoordinates', () => { - it('should calculate coordinates for angle 0 (top)', () => { - const result = calculatePieChartEndLineCoordinates(0, 100, 100, 50, 80); - expect(result).toEqual({ - x1: 100, - y1: 50, - x2: 100, - y2: 20, - }); - }); - it('should calculate coordinates for angle π/2 (right)', () => { - const result = calculatePieChartEndLineCoordinates( - Math.PI / 2, - 100, - 100, - 50, - 80, - ); - expect(result).toEqual({ - x1: 150, - y1: 100, - x2: 180, - y2: 100, - }); - }); - it('should calculate coordinates for angle π (bottom)', () => { - const result = calculatePieChartEndLineCoordinates( - Math.PI, - 100, - 100, - 50, - 80, - ); - expect(result).toEqual({ - x1: 100, - y1: 150, - x2: 100, - y2: 180, - }); - }); - it('should calculate coordinates for angle 3π/2 (left)', () => { - const result = calculatePieChartEndLineCoordinates( - (3 * Math.PI) / 2, - 100, - 100, - 50, - 80, - ); - expect(result.x1).toBeCloseTo(50, 5); - expect(result.y1).toBeCloseTo(100, 5); - expect(result.x2).toBeCloseTo(20, 5); - expect(result.y2).toBeCloseTo(100, 5); - }); - it('should calculate coordinates for 45-degree angle', () => { - const angle = Math.PI / 4; - const result = calculatePieChartEndLineCoordinates(angle, 100, 100, 50, 80); - const expectedCos = Math.sqrt(2) / 2; - const expectedSin = -Math.sqrt(2) / 2; - expect(result.x1).toBeCloseTo(100 + expectedCos * 50, 5); - expect(result.y1).toBeCloseTo(100 + expectedSin * 50, 5); - expect(result.x2).toBeCloseTo(100 + expectedCos * 80, 5); - expect(result.y2).toBeCloseTo(100 + expectedSin * 80, 5); - }); - it('should handle different center positions', () => { - const result = calculatePieChartEndLineCoordinates( - Math.PI / 2, - 200, - 150, - 30, - 60, - ); - expect(result).toEqual({ - x1: 230, - y1: 150, - x2: 260, - y2: 150, - }); - }); - it('should handle zero radius', () => { - const result = calculatePieChartEndLineCoordinates( - Math.PI / 4, - 100, - 100, - 0, - 0, - ); - expect(result).toEqual({ - x1: 100, - y1: 100, - x2: 100, - y2: 100, - }); - }); - it('should handle negative angles', () => { - const result = calculatePieChartEndLineCoordinates( - -Math.PI / 2, - 100, - 100, - 50, - 80, - ); - expect(result.x1).toBeCloseTo(50, 5); - expect(result.y1).toBeCloseTo(100, 5); - expect(result.x2).toBeCloseTo(20, 5); - expect(result.y2).toBeCloseTo(100, 5); - }); - it('should create a line from inner to outer radius', () => { - const angle = Math.PI / 6; - const centerX = 100; - const centerY = 100; - const innerRadius = 40; - const outerRadius = 70; - const result = calculatePieChartEndLineCoordinates( - angle, - centerX, - centerY, - innerRadius, - outerRadius, - ); - const dist1 = Math.sqrt( - (result.x1 - centerX) ** 2 + (result.y1 - centerY) ** 2, - ); - const dist2 = Math.sqrt( - (result.x2 - centerX) ** 2 + (result.y2 - centerY) ** 2, - ); - expect(dist1).toBeCloseTo(innerRadius, 5); - expect(dist2).toBeCloseTo(outerRadius, 5); - const angle1 = Math.atan2(result.y1 - centerY, result.x1 - centerX); - const angle2 = Math.atan2(result.y2 - centerY, result.x2 - centerX); - expect(angle1).toBeCloseTo(angle2, 5); - }); -}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartPercentage.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartPercentage.test.ts deleted file mode 100644 index d3303d2da9..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/calculatePieChartPercentage.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { calculatePieChartPercentage } from '../calculatePieChartPercentage'; -describe('calculatePieChartPercentage', () => { - it('should calculate percentage for normal values', () => { - expect(calculatePieChartPercentage(25, 100)).toBe(25); - expect(calculatePieChartPercentage(50, 100)).toBe(50); - expect(calculatePieChartPercentage(75, 100)).toBe(75); - }); - it('should calculate percentage with decimal values', () => { - expect(calculatePieChartPercentage(33, 100)).toBe(33); - expect(calculatePieChartPercentage(1, 3)).toBeCloseTo(33.333, 2); - expect(calculatePieChartPercentage(2, 3)).toBeCloseTo(66.667, 2); - }); - it('should handle zero value', () => { - expect(calculatePieChartPercentage(0, 100)).toBe(0); - expect(calculatePieChartPercentage(0, 1)).toBe(0); - }); - it('should handle zero total (divide by zero)', () => { - expect(calculatePieChartPercentage(10, 0)).toBe(0); - expect(calculatePieChartPercentage(0, 0)).toBe(0); - expect(calculatePieChartPercentage(-5, 0)).toBe(0); - }); - it('should handle negative total', () => { - expect(calculatePieChartPercentage(10, -100)).toBe(0); - expect(calculatePieChartPercentage(-10, -100)).toBe(0); - }); - it('should handle value greater than total', () => { - expect(calculatePieChartPercentage(150, 100)).toBe(150); - expect(calculatePieChartPercentage(200, 50)).toBe(400); - }); - it('should handle very small values', () => { - expect(calculatePieChartPercentage(0.01, 100)).toBe(0.01); - expect(calculatePieChartPercentage(0.001, 1)).toBe(0.1); - }); - it('should handle very large values', () => { - expect(calculatePieChartPercentage(1000000, 10000000)).toBe(10); - expect(calculatePieChartPercentage(1e10, 1e12)).toBe(1); - }); - it('should maintain precision for financial calculations', () => { - const value1 = 33.33; - const value2 = 33.33; - const value3 = 33.34; - const total = value1 + value2 + value3; - expect(calculatePieChartPercentage(value1, total)).toBeCloseTo(33.33, 2); - expect(calculatePieChartPercentage(value2, total)).toBeCloseTo(33.33, 2); - expect(calculatePieChartPercentage(value3, total)).toBeCloseTo(33.34, 2); - }); - it('should handle edge case with Infinity', () => { - expect(calculatePieChartPercentage(Infinity, 100)).toBe(Infinity); - expect(calculatePieChartPercentage(100, Infinity)).toBe(0); - expect(calculatePieChartPercentage(Infinity, Infinity)).toBeNaN(); - }); - it('should handle NaN inputs', () => { - expect(calculatePieChartPercentage(NaN, 100)).toBeNaN(); - expect(calculatePieChartPercentage(100, NaN)).toBeNaN(); - expect(calculatePieChartPercentage(NaN, NaN)).toBeNaN(); - }); -}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartAngles.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartAngles.ts deleted file mode 100644 index 144915e9d4..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartAngles.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const calculatePieChartAngles = ( - percentage: number, - cumulativeAngle: number, -) => { - const sliceAngle = (percentage / 100) * 360; - const middleAngle = cumulativeAngle + sliceAngle / 2; - const newCumulativeAngle = cumulativeAngle + sliceAngle; - - return { - sliceAngle, - middleAngle, - newCumulativeAngle, - }; -}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartEndLineCoordinates.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartEndLineCoordinates.ts deleted file mode 100644 index 51c1f87615..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartEndLineCoordinates.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const calculatePieChartEndLineCoordinates = ( - angle: number, - centerX: number, - centerY: number, - innerRadius: number, - outerRadius: number, -) => { - const adjustedAngle = angle - Math.PI / 2; - const x1 = centerX + Math.cos(adjustedAngle) * innerRadius; - const y1 = centerY + Math.sin(adjustedAngle) * innerRadius; - const x2 = centerX + Math.cos(adjustedAngle) * outerRadius; - const y2 = centerY + Math.sin(adjustedAngle) * outerRadius; - - return { x1, y1, x2, y2 }; -}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts index 5243e5ea06..fb024589a6 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts @@ -4,7 +4,6 @@ import { type ExtendedAggregateOperations } from '@/object-record/record-table/t import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName'; import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant'; import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem'; -import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult'; import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue'; import { buildFormattedToRawLookup } from '@/page-layout/widgets/graph/utils/buildFormattedToRawLookup'; @@ -82,7 +81,7 @@ export const transformGroupByDataToPieChartData = ({ const formattedToRawLookup = buildFormattedToRawLookup(formattedValues); const data: PieChartDataItem[] = limitedResults.map((result, index) => { - const label = formattedValues[index]?.formattedPrimaryDimensionValue ?? ''; + const id = formattedValues[index]?.formattedPrimaryDimensionValue ?? ''; const value = computeAggregateValueFromGroupByResult({ rawResult: result, @@ -94,10 +93,8 @@ export const transformGroupByDataToPieChartData = ({ }); return { - id: label, - value: value, - label: label, - color: (configuration.color as GraphColor | undefined) ?? undefined, + id, + value, }; }); diff --git a/packages/twenty-front/tsconfig.json b/packages/twenty-front/tsconfig.json index 5efa7aa473..33c567774b 100644 --- a/packages/twenty-front/tsconfig.json +++ b/packages/twenty-front/tsconfig.json @@ -23,7 +23,15 @@ "paths": { "@/*": ["./src/modules/*"], "~/*": ["./src/*"] - } + }, + "plugins": [ + { + "name": "@styled/typescript-styled-plugin", + "lint": { + "validProperties": ["container-type"], + } + } + ] }, "files": [], "references": [ diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/pie-chart-configuration.dto.ts b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/pie-chart-configuration.dto.ts index 5145408eb7..13014c268d 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/dtos/pie-chart-configuration.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/dtos/pie-chart-configuration.dto.ts @@ -71,6 +71,11 @@ export class PieChartConfigurationDTO { @IsOptional() displayDataLabel?: boolean; + @Field(() => Boolean, { nullable: true, defaultValue: false }) + @IsBoolean() + @IsOptional() + showCenterMetric?: boolean; + @Field(() => Boolean, { nullable: true, defaultValue: true }) @IsBoolean() @IsOptional() diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index d3ec7d58fa..b098200d0f 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -252,6 +252,7 @@ export { IconNoteOff, IconNotes, IconNumber, + IconNumber123, IconNumber9, IconNumbers, IconPaperclip, diff --git a/packages/twenty-ui/src/display/index.ts b/packages/twenty-ui/src/display/index.ts index f047db0577..b65d92f103 100644 --- a/packages/twenty-ui/src/display/index.ts +++ b/packages/twenty-ui/src/display/index.ts @@ -317,6 +317,7 @@ export { IconNoteOff, IconNotes, IconNumber, + IconNumber123, IconNumber9, IconNumbers, IconPaperclip,