Add a banner in the side panel to alert if the bar chart has too many bars (#15267)

Closes https://github.com/twentyhq/core-team-issues/issues/1714

- Created `SidePanelInformationBanner` component
- Created a component state `hasWidgetTooManyGroupsComponentState`
- Displayed the banner in the side panel if the state is true



https://github.com/user-attachments/assets/343a4053-f0d5-4e9b-935d-ead191d70eb2
This commit is contained in:
Raphaël Bosi
2025-10-22 18:03:00 +02:00
committed by GitHub
parent 63a75dd182
commit 95b405edac
14 changed files with 193 additions and 25 deletions
@@ -16,11 +16,20 @@ import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
import { isChartSettingDisabled } from '@/command-menu/pages/page-layout/utils/isChartSettingDisabled';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { GRAPH_MAXIMUM_NUMBER_OF_GROUPS } from '@/page-layout/widgets/graph/constants/GraphMaximumNumberOfGroups.constant';
import { hasWidgetTooManyGroupsComponentState } from '@/page-layout/widgets/graph/states/hasWidgetTooManyGroupsComponentState';
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { SidePanelInformationBanner } from 'twenty-ui/display';
import { type GraphType, type PageLayoutWidget } from '~/generated/graphql';
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
const StyledSidePanelInformationBanner = styled(SidePanelInformationBanner)`
margin-top: ${({ theme }) => theme.spacing(2)};
`;
export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo();
@@ -56,6 +65,8 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
const isGroupByEnabled = getChartSettingsValues(
CHART_CONFIGURATION_SETTING_IDS.GROUP_BY,
);
const [hasWidgetTooManyGroups, setHasWidgetTooManyGroups] =
useRecoilComponentState(hasWidgetTooManyGroupsComponentState);
const handleGraphTypeChange = (graphType: GraphType) => {
updateCurrentWidgetConfig({
@@ -68,6 +79,13 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
updateCommandMenuPageInfo({
pageIcon: GRAPH_TYPE_INFORMATION[graphType].icon,
});
if (
graphType !== GraphType.VERTICAL_BAR &&
graphType !== GraphType.HORIZONTAL_BAR
) {
setHasWidgetTooManyGroups(false);
}
};
const chartSettings = GRAPH_TYPE_INFORMATION[currentGraphType].settings;
@@ -83,6 +101,11 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
currentGraphType={currentGraphType}
setCurrentGraphType={handleGraphTypeChange}
/>
{hasWidgetTooManyGroups && (
<StyledSidePanelInformationBanner
message={t`Max ${GRAPH_MAXIMUM_NUMBER_OF_GROUPS} bars per chart. Consider adding a filter`}
/>
)}
{chartSettings.map((group) => (
<CommandGroup key={group.heading} heading={group.heading}>
{group.items.map((item) => {
@@ -5,6 +5,7 @@ import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pa
import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
import { GraphWidgetComponentInstanceContext } from '@/page-layout/widgets/graph/states/contexts/GraphWidgetComponentInstanceContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
@@ -53,7 +54,9 @@ export const CommandMenuPageLayoutGraphTypeSelect = () => {
const graphTypeLabel = t(GRAPH_TYPE_INFORMATION[currentGraphType].label);
return (
<>
<GraphWidgetComponentInstanceContext.Provider
value={{ instanceId: widgetInEditMode.id }}
>
<SidePanelHeader
Icon={GRAPH_TYPE_INFORMATION[currentGraphType].icon}
iconColor={theme.font.color.tertiary}
@@ -68,6 +71,6 @@ export const CommandMenuPageLayoutGraphTypeSelect = () => {
}}
/>
<ChartSettings widget={widgetInEditMode} />
</>
</GraphWidgetComponentInstanceContext.Provider>
);
};
@@ -1,5 +1,6 @@
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
import { GraphWidget } from '@/page-layout/widgets/graph/components/GraphWidget';
import { GraphWidgetComponentInstanceContext } from '@/page-layout/widgets/graph/states/contexts/GraphWidgetComponentInstanceContext';
import { isDefined } from 'twenty-shared/utils';
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
@@ -22,10 +23,14 @@ export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => {
}
return (
<GraphWidget
widget={widget}
objectMetadataId={widget.objectMetadataId}
graphType={graphType}
/>
<GraphWidgetComponentInstanceContext.Provider
value={{ instanceId: widget.id }}
>
<GraphWidget
widget={widget}
objectMetadataId={widget.objectMetadataId}
graphType={graphType}
/>
</GraphWidgetComponentInstanceContext.Provider>
);
};
@@ -0,0 +1,21 @@
import { hasWidgetTooManyGroupsComponentState } from '@/page-layout/widgets/graph/states/hasWidgetTooManyGroupsComponentState';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useEffect } from 'react';
type GraphWidgetBarChartHasTooManyGroupsEffectProps = {
hasTooManyGroups: boolean;
};
export const GraphWidgetBarChartHasTooManyGroupsEffect = ({
hasTooManyGroups,
}: GraphWidgetBarChartHasTooManyGroupsEffectProps) => {
const setHasWidgetTooManyGroups = useSetRecoilComponentState(
hasWidgetTooManyGroupsComponentState,
);
useEffect(() => {
setHasWidgetTooManyGroups(hasTooManyGroups);
}, [hasTooManyGroups, setHasWidgetTooManyGroups]);
return null;
};
@@ -1,4 +1,5 @@
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
import { GraphWidgetBarChartHasTooManyGroupsEffect } from '@/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChartHasTooManyGroupsEffect';
import { useGraphBarChartWidgetData } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData';
import { lazy, Suspense, useMemo } from 'react';
import {
@@ -29,6 +30,7 @@ export const GraphWidgetBarChartRenderer = ({
showDataLabels,
layout,
loading,
hasTooManyGroups,
} = useGraphBarChartWidgetData({
objectMetadataItemId: widget.objectMetadataId,
configuration: widget.configuration as BarChartConfiguration,
@@ -53,23 +55,28 @@ export const GraphWidgetBarChartRenderer = ({
}
return (
<Suspense fallback={<ChartSkeletonLoader />}>
<GraphWidgetBarChart
key={filterStateKey}
data={data}
series={series}
indexBy={indexBy}
keys={keys}
xAxisLabel={xAxisLabel}
yAxisLabel={yAxisLabel}
showValues={showDataLabels}
layout={layout}
groupMode={groupMode}
id={widget.id}
displayType="shortNumber"
rangeMin={configuration.rangeMin ?? undefined}
rangeMax={configuration.rangeMax ?? undefined}
<>
<GraphWidgetBarChartHasTooManyGroupsEffect
hasTooManyGroups={hasTooManyGroups}
/>
</Suspense>
<Suspense fallback={<ChartSkeletonLoader />}>
<GraphWidgetBarChart
key={filterStateKey}
data={data}
series={series}
indexBy={indexBy}
keys={keys}
xAxisLabel={xAxisLabel}
yAxisLabel={yAxisLabel}
showValues={showDataLabels}
layout={layout}
groupMode={groupMode}
id={widget.id}
displayType="shortNumber"
rangeMin={configuration.rangeMin ?? undefined}
rangeMax={configuration.rangeMax ?? undefined}
/>
</Suspense>
</>
);
};
@@ -22,6 +22,7 @@ type UseGraphBarChartWidgetDataResult = {
layout?: 'vertical' | 'horizontal';
loading: boolean;
error?: Error;
hasTooManyGroups: boolean;
};
export const useGraphBarChartWidgetData = ({
@@ -0,0 +1,4 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const GraphWidgetComponentInstanceContext =
createComponentInstanceContext();
@@ -0,0 +1,8 @@
import { GraphWidgetComponentInstanceContext } from '@/page-layout/widgets/graph/states/contexts/GraphWidgetComponentInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const hasWidgetTooManyGroupsComponentState = createComponentState({
key: 'hasWidgetTooManyGroupsComponentState',
defaultValue: false,
componentInstanceContext: GraphWidgetComponentInstanceContext,
});
@@ -33,6 +33,7 @@ type TransformGroupByDataToBarChartDataResult = {
yAxisLabel?: string;
showDataLabels: boolean;
layout?: 'vertical' | 'horizontal';
hasTooManyGroups: boolean;
};
const EMPTY_BAR_CHART_RESULT: TransformGroupByDataToBarChartDataResult = {
@@ -44,6 +45,7 @@ const EMPTY_BAR_CHART_RESULT: TransformGroupByDataToBarChartDataResult = {
yAxisLabel: undefined,
showDataLabels: false,
layout: 'vertical',
hasTooManyGroups: false,
};
export const transformGroupByDataToBarChartData = ({
@@ -28,6 +28,7 @@ type TransformOneDimensionalGroupByToBarChartDataResult = {
indexBy: string;
keys: string[];
series: BarChartSeries[];
hasTooManyGroups: boolean;
};
export const transformOneDimensionalGroupByToBarChartData = ({
@@ -87,5 +88,6 @@ export const transformOneDimensionalGroupByToBarChartData = ({
indexBy: indexByKey,
keys: [aggregateField.name],
series,
hasTooManyGroups: rawResults.length > GRAPH_MAXIMUM_NUMBER_OF_GROUPS,
};
};
@@ -28,6 +28,7 @@ type TransformTwoDimensionalGroupByToBarChartDataResult = {
indexBy: string;
keys: string[];
series: BarChartSeries[];
hasTooManyGroups: boolean;
};
export const transformTwoDimensionalGroupByToBarChartData = ({
@@ -49,6 +50,8 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
const xValues = new Set<string>();
const yValues = new Set<string>();
let hasTooManyGroups = false;
rawResults.forEach((result) => {
const dimensionValues = result.groupByDimensionValues;
if (!isDefined(dimensionValues) || dimensionValues.length < 2) return;
@@ -75,6 +78,7 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
totalUniqueDimensions + additionalDimensions >
GRAPH_MAXIMUM_NUMBER_OF_GROUPS
) {
hasTooManyGroups = true;
return;
}
@@ -118,5 +122,6 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
indexBy: indexByKey,
keys,
series,
hasTooManyGroups,
};
};