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
@@ -11,13 +11,14 @@ import {
import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition';
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
import { type GraphType } from '~/generated-metadata/graphql';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
import { type WidgetType } from '~/generated/graphql';
import { type GraphType } from '~/generated-metadata/graphql';
import { WidgetType, type PageLayoutWidget } from '~/generated/graphql';
export const useCreatePageLayoutGraphWidget = (
pageLayoutIdFromProps?: string,
@@ -49,13 +50,15 @@ export const useCreatePageLayoutGraphWidget = (
pageLayoutId,
);
const createPageLayoutWidget = useRecoilCallback(
const createPageLayoutGraphWidget = useRecoilCallback(
({ snapshot, set }) =>
(widgetType: WidgetType, graphType: GraphType) => {
(graphType: GraphType): PageLayoutWidget => {
const activeTabId = snapshot.getLoadable(activeTabIdState).getValue();
if (!activeTabId) {
return;
if (!isDefined(activeTabId)) {
throw new Error(
'A tab must be selected to create a new graph widget',
);
}
const pageLayoutDraft = snapshot
@@ -73,7 +76,7 @@ export const useCreatePageLayoutGraphWidget = (
const allWidgets = pageLayoutDraft.tabs.flatMap((tab) => tab.widgets);
const existingWidgetCount = allWidgets.filter(
(w) =>
w.type === widgetType &&
w.type === WidgetType.GRAPH &&
w.configuration &&
'graphType' in w.configuration &&
w.configuration.graphType === graphType,
@@ -87,18 +90,18 @@ export const useCreatePageLayoutGraphWidget = (
defaultSize,
);
const newWidget = createDefaultGraphWidget(
widgetId,
activeTabId,
const newWidget = createDefaultGraphWidget({
id: widgetId,
pageLayoutTabId: activeTabId,
title,
graphType,
{
gridPosition: {
row: position.y,
column: position.x,
rowSpan: position.h,
columnSpan: position.w,
},
);
});
const newLayout = {
i: widgetId,
@@ -121,6 +124,8 @@ export const useCreatePageLayoutGraphWidget = (
}));
set(pageLayoutDraggedAreaState, null);
return newWidget;
},
[
activeTabIdState,
@@ -130,5 +135,5 @@ export const useCreatePageLayoutGraphWidget = (
],
);
return { createPageLayoutWidget };
return { createPageLayoutGraphWidget };
};