Dashboards followups 2 (#15107)

adding min height and widths on each chart

this Pr address this followups - 
-
https://github.com/twentyhq/core-team-issues/issues/1710#issuecomment-3405312108
- <img width="843" height="38" alt="Screenshot 2025-10-15 at 15 19 27"
src="https://github.com/user-attachments/assets/f33da21f-a605-40f9-b7fa-02be9dff5908"
/>
- <img width="830" height="42" alt="Screenshot 2025-10-15 at 15 19 40"
src="https://github.com/user-attachments/assets/bf6f3783-b5cc-44c0-94a2-029a489c852e"
/>

video QA
before:


https://github.com/user-attachments/assets/28262a7b-531f-4dcb-9047-c587fe3d8af6


after: 


https://github.com/user-attachments/assets/b0fdbc18-3a7e-4c85-98b6-5799d7207bb4
This commit is contained in:
nitin
2025-10-15 19:35:56 +05:30
committed by GitHub
parent 9aa44ef1e8
commit bd16c77fed
12 changed files with 165 additions and 53 deletions
@@ -0,0 +1,4 @@
export const DEFAULT_WIDGET_SIZE = {
default: { w: 4, h: 4 },
minimum: { w: 2, h: 2 },
};
@@ -0,0 +1,33 @@
import { GraphType } from '~/generated-metadata/graphql';
type WidgetSizeConfig = {
default: { w: number; h: number };
minimum: { w: number; h: number };
};
export const WIDGET_SIZES: Record<GraphType, WidgetSizeConfig> = {
[GraphType.NUMBER]: {
default: { w: 3, h: 2 },
minimum: { w: 2, h: 2 },
},
[GraphType.GAUGE]: {
default: { w: 3, h: 4 },
minimum: { w: 3, h: 4 },
},
[GraphType.PIE]: {
default: { w: 4, h: 4 },
minimum: { w: 3, h: 4 },
},
[GraphType.VERTICAL_BAR]: {
default: { w: 6, h: 6 },
minimum: { w: 5, h: 5 },
},
[GraphType.HORIZONTAL_BAR]: {
default: { w: 6, h: 6 },
minimum: { w: 5, h: 5 },
},
[GraphType.LINE]: {
default: { w: 6, h: 10 },
minimum: { w: 5, h: 5 },
},
};
@@ -5,13 +5,11 @@ import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLa
import { type GraphWidgetFieldSelection } from '@/page-layout/types/GraphWidgetFieldSelection';
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
import { createDefaultGraphWidget } from '@/page-layout/utils/createDefaultGraphWidget';
import {
getWidgetSize,
getWidgetTitle,
} from '@/page-layout/utils/getDefaultWidgetData';
import { getDefaultWidgetPosition } from '@/page-layout/utils/getDefaultWidgetPosition';
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
import { getUpdatedTabLayouts } from '@/page-layout/utils/getUpdatedTabLayouts';
import { getWidgetSize } from '@/page-layout/utils/getWidgetSize';
import { getWidgetTitle } from '@/page-layout/utils/getWidgetTitle';
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';
@@ -91,10 +89,12 @@ export const useCreatePageLayoutGraphWidget = (
const title = getWidgetTitle(graphType, existingWidgetCount);
const widgetId = uuidv4();
const defaultSize = getWidgetSize(graphType);
const defaultSize = getWidgetSize(graphType, 'default');
const minimumSize = getWidgetSize(graphType, 'minimum');
const position = getDefaultWidgetPosition(
pageLayoutDraggedArea,
defaultSize,
minimumSize,
);
const newWidget = createDefaultGraphWidget({
@@ -1,3 +1,4 @@
import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
@@ -59,9 +60,11 @@ export const useCreatePageLayoutIframeWidget = (
const widgetId = uuidv4();
const defaultSize = { w: 6, h: 6 };
const minimumSize = DEFAULT_WIDGET_SIZE.minimum;
const position = getDefaultWidgetPosition(
pageLayoutDraggedArea,
defaultSize,
minimumSize,
);
const newWidget = createDefaultIframeWidget(
@@ -76,12 +76,12 @@ describe('convertPageLayoutToTabLayouts', () => {
expect(result).toEqual({
'tab-1': {
desktop: [
{ i: 'widget-1', x: 0, y: 0, w: 2, h: 2 },
{ i: 'widget-2', x: 0, y: 2, w: 2, h: 2 },
{ i: 'widget-1', x: 0, y: 0, w: 2, h: 2, minW: 2, minH: 2 },
{ i: 'widget-2', x: 0, y: 2, w: 2, h: 2, minW: 3, minH: 4 },
],
mobile: [
{ i: 'widget-1', x: 0, y: 0, w: 1, h: 2 },
{ i: 'widget-2', x: 0, y: 2, w: 1, h: 2 },
{ i: 'widget-1', x: 0, y: 0, w: 1, h: 2, minW: 2, minH: 2 },
{ i: 'widget-2', x: 0, y: 2, w: 1, h: 2, minW: 3, minH: 4 },
],
},
});
@@ -1,19 +1,66 @@
import { getDefaultWidgetPosition } from '../getDefaultWidgetPosition';
describe('getDefaultWidgetPosition', () => {
it('should return dragged area when provided', () => {
it('should return dragged area when it meets minimum size', () => {
const draggedArea = { x: 2, y: 3, w: 4, h: 5 };
const defaultSize = { w: 2, h: 2 };
const minimumSize = { w: 3, h: 3 };
expect(getDefaultWidgetPosition(draggedArea, defaultSize)).toEqual(
draggedArea,
);
expect(
getDefaultWidgetPosition(draggedArea, defaultSize, minimumSize),
).toEqual(draggedArea);
});
it('should expand dragged area to meet minimum width', () => {
const draggedArea = { x: 2, y: 3, w: 2, h: 5 };
const defaultSize = { w: 4, h: 4 };
const minimumSize = { w: 5, h: 3 };
expect(
getDefaultWidgetPosition(draggedArea, defaultSize, minimumSize),
).toEqual({
x: 2,
y: 3,
w: 5, // expanded from 2 to meet minimum
h: 5,
});
});
it('should expand dragged area to meet minimum height', () => {
const draggedArea = { x: 1, y: 2, w: 6, h: 2 };
const defaultSize = { w: 4, h: 4 };
const minimumSize = { w: 3, h: 5 };
expect(
getDefaultWidgetPosition(draggedArea, defaultSize, minimumSize),
).toEqual({
x: 1,
y: 2,
w: 6,
h: 5, // expanded from 2 to meet minimum
});
});
it('should expand dragged area to meet both minimum dimensions', () => {
const draggedArea = { x: 0, y: 0, w: 2, h: 2 };
const defaultSize = { w: 6, h: 6 };
const minimumSize = { w: 5, h: 5 };
expect(
getDefaultWidgetPosition(draggedArea, defaultSize, minimumSize),
).toEqual({
x: 0,
y: 0,
w: 5, // expanded from 2 to meet minimum
h: 5, // expanded from 2 to meet minimum
});
});
it('should return default position with size when no dragged area', () => {
const defaultSize = { w: 3, h: 4 };
const minimumSize = { w: 2, h: 2 };
expect(getDefaultWidgetPosition(null, defaultSize)).toEqual({
expect(getDefaultWidgetPosition(null, defaultSize, minimumSize)).toEqual({
x: 0,
y: 0,
w: 3,
@@ -1,5 +1,9 @@
import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
import { type PageLayout } from '@/page-layout/types/PageLayout';
import { type TabLayouts } from '@/page-layout/types/tab-layouts';
import { getWidgetSize } from '@/page-layout/utils/getWidgetSize';
import { isDefined } from 'twenty-shared/utils';
import { WidgetType } from '~/generated/graphql';
export const convertPageLayoutToTabLayouts = (
pageLayout: PageLayout,
@@ -11,13 +15,33 @@ export const convertPageLayoutToTabLayouts = (
const tabLayouts: TabLayouts = {};
pageLayout.tabs.forEach((tab) => {
const layouts = tab.widgets.map((widget) => ({
i: widget.id,
x: widget.gridPosition.column,
y: widget.gridPosition.row,
w: widget.gridPosition.columnSpan,
h: widget.gridPosition.rowSpan,
}));
const layouts = tab.widgets.map((widget) => {
let minW = DEFAULT_WIDGET_SIZE.minimum.w;
let minH = DEFAULT_WIDGET_SIZE.minimum.h;
if (widget.type === WidgetType.GRAPH && isDefined(widget.configuration)) {
const graphType =
'graphType' in widget.configuration
? widget.configuration.graphType
: undefined;
if (isDefined(graphType)) {
const minimumSize = getWidgetSize(graphType, 'minimum');
minW = minimumSize.w;
minH = minimumSize.h;
}
}
return {
i: widget.id,
x: widget.gridPosition.column,
y: widget.gridPosition.row,
w: widget.gridPosition.columnSpan,
h: widget.gridPosition.rowSpan,
minW,
minH,
};
});
tabLayouts[tab.id] = {
desktop: layouts,
@@ -128,34 +128,3 @@ export const getDefaultWidgetData = (graphType: GraphType) => {
return {};
}
};
export const getWidgetTitle = (graphType: GraphType, index: number): string => {
const baseNames: Record<GraphType, string> = {
[GraphType.NUMBER]: 'Number',
[GraphType.GAUGE]: 'Gauge',
[GraphType.PIE]: 'Pie Chart',
[GraphType.VERTICAL_BAR]: 'Vertical Bar Chart',
[GraphType.HORIZONTAL_BAR]: 'Horizontal Bar Chart',
[GraphType.LINE]: 'Line Chart',
};
return `${baseNames[graphType] || 'Widget'} ${index + 1}`;
};
export const getWidgetSize = (graphType: GraphType) => {
switch (graphType) {
case GraphType.NUMBER:
return { w: 3, h: 2 };
case GraphType.GAUGE:
return { w: 3, h: 3 };
case GraphType.PIE:
return { w: 4, h: 4 };
case GraphType.VERTICAL_BAR:
case GraphType.HORIZONTAL_BAR:
return { w: 6, h: 6 };
case GraphType.LINE:
return { w: 6, h: 10 };
default:
return { w: 4, h: 4 };
}
};
@@ -3,9 +3,15 @@ import { type GridBounds } from './calculateGridBoundsFromSelectedCells';
export const getDefaultWidgetPosition = (
draggedArea: GridBounds | null,
defaultSize: { w: number; h: number },
minimumSize: { w: number; h: number },
): GridBounds => {
if (draggedArea !== null) {
return draggedArea;
return {
x: draggedArea.x,
y: draggedArea.y,
w: Math.max(draggedArea.w, minimumSize.w),
h: Math.max(draggedArea.h, minimumSize.h),
};
}
return {
@@ -0,0 +1,11 @@
import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
import { WIDGET_SIZES } from '@/page-layout/constants/WidgetSizes';
import { type GraphType } from '~/generated/graphql';
export const getWidgetSize = (
graphType: GraphType,
type: 'default' | 'minimum',
): { w: number; h: number } => {
const sizeConfig = WIDGET_SIZES[graphType] ?? DEFAULT_WIDGET_SIZE;
return sizeConfig[type];
};
@@ -0,0 +1,14 @@
import { GraphType } from '~/generated/graphql';
export const getWidgetTitle = (graphType: GraphType, index: number): string => {
const baseNames: Record<GraphType, string> = {
[GraphType.NUMBER]: 'Number',
[GraphType.GAUGE]: 'Gauge',
[GraphType.PIE]: 'Pie Chart',
[GraphType.VERTICAL_BAR]: 'Vertical Bar Chart',
[GraphType.HORIZONTAL_BAR]: 'Horizontal Bar Chart',
[GraphType.LINE]: 'Line Chart',
};
return `${baseNames[graphType] || 'Widget'} ${index + 1}`;
};
@@ -51,6 +51,7 @@ export const WidgetHeader = ({
variant="tertiary"
size="small"
disabled={isEmpty}
onClick={(e) => e.stopPropagation()}
/>
)}
<StyledTitle>{isEmpty ? 'Add Widget' : title}</StyledTitle>