Chart editor - Part 1 (#14820)

This PR is the first part of the creation of the Chart editor.


https://github.com/user-attachments/assets/8b0af8ea-be41-4506-84cb-e40f5521cbf0

Done:
- Bar chart settings (except filters)
- Line chart settings (except filters)

In progress:
- Pie chart settings
- Number chart settings
- Gauge chart settings

Left to do:
- Loosen the backend validation to allow the user to save a partial
configuration, validate the graph configuration in the frontend and
display a error friendly message in the graph if the config is not
completed yet
- Implement the filter edition
- Finish the other graph types settings
This commit is contained in:
Raphaël Bosi
2025-10-03 10:25:45 +02:00
committed by GitHub
parent fc8b4f813c
commit d10ab5f67c
112 changed files with 3046 additions and 503 deletions
@@ -1,7 +1,10 @@
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { assertUnreachable } from 'twenty-shared/utils';
import { type ThemeColor } from 'twenty-ui/theme';
import { v4 as uuidv4 } from 'uuid';
import { GraphOrderBy, GraphType } from '~/generated-metadata/graphql';
import {
AxisNameDisplay,
ExtendedAggregateOperations,
type GridPosition,
type PageLayoutWidget,
type WidgetConfiguration,
@@ -17,64 +20,81 @@ const createDefaultGraphConfiguration = (
switch (graphType) {
case GraphType.NUMBER:
return {
__typename: 'NumberChartConfiguration',
graphType: GraphType.NUMBER,
aggregateOperation: AggregateOperations.COUNT,
aggregateOperation: ExtendedAggregateOperations.COUNT,
aggregateFieldMetadataId: placeholderFieldId1,
displayDataLabel: false,
};
case GraphType.PIE:
return {
__typename: 'PieChartConfiguration',
graphType: GraphType.PIE,
aggregateOperation: AggregateOperations.COUNT,
aggregateOperation: ExtendedAggregateOperations.COUNT,
aggregateFieldMetadataId: placeholderFieldId1,
groupByFieldMetadataId: placeholderFieldId2,
orderBy: GraphOrderBy.VALUE_DESC,
displayDataLabel: false,
color: 'blue' satisfies ThemeColor,
};
case GraphType.BAR:
return {
__typename: 'BarChartConfiguration',
graphType: GraphType.BAR,
aggregateOperation: AggregateOperations.COUNT,
aggregateOperation: ExtendedAggregateOperations.COUNT,
aggregateFieldMetadataId: placeholderFieldId1,
groupByFieldMetadataIdX: placeholderFieldId2,
orderByX: GraphOrderBy.FIELD_ASC,
displayDataLabel: false,
axisNameDisplay: AxisNameDisplay.BOTH,
color: 'blue' satisfies ThemeColor,
};
case GraphType.LINE:
return {
__typename: 'LineChartConfiguration',
graphType: GraphType.LINE,
aggregateOperation: AggregateOperations.COUNT,
aggregateOperation: ExtendedAggregateOperations.COUNT,
aggregateFieldMetadataId: placeholderFieldId1,
groupByFieldMetadataIdX: placeholderFieldId2,
orderByX: GraphOrderBy.FIELD_ASC,
displayDataLabel: false,
axisNameDisplay: AxisNameDisplay.BOTH,
color: 'blue' satisfies ThemeColor,
};
case GraphType.GAUGE:
return {
__typename: 'GaugeChartConfiguration',
graphType: GraphType.GAUGE,
aggregateOperation: AggregateOperations.COUNT,
aggregateOperation: ExtendedAggregateOperations.COUNT,
aggregateFieldMetadataId: placeholderFieldId1,
aggregateOperationTotal: AggregateOperations.COUNT,
aggregateFieldMetadataIdTotal: placeholderFieldId2,
displayDataLabel: false,
color: 'blue' satisfies ThemeColor,
};
default:
return {
graphType: GraphType.NUMBER,
aggregateOperation: AggregateOperations.COUNT,
aggregateFieldMetadataId: placeholderFieldId1,
};
assertUnreachable(graphType);
}
};
export const createDefaultGraphWidget = (
id: string,
pageLayoutTabId: string,
title: string,
graphType: GraphType,
gridPosition: GridPosition,
objectMetadataId?: string | null,
): PageLayoutWidget => {
export const createDefaultGraphWidget = ({
id,
pageLayoutTabId,
title,
graphType,
gridPosition,
objectMetadataId,
}: {
id: string;
pageLayoutTabId: string;
title: string;
graphType: GraphType;
gridPosition: GridPosition;
objectMetadataId?: string | null;
}): PageLayoutWidget => {
return {
__typename: 'PageLayoutWidget',
id,