[Dashboards] add ratio aggregate on aggregate chart (#16413)

closes https://github.com/twentyhq/core-team-issues/issues/1960
video QA


https://github.com/user-attachments/assets/e90cee44-5edc-430d-b003-03ab4ea921f7
This commit is contained in:
nitin
2025-12-09 19:53:21 +05:30
committed by GitHub
parent bd49fd8d5f
commit 7af3da9393
25 changed files with 1304 additions and 84 deletions
@@ -0,0 +1,91 @@
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation';
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant';
import { type AggregateChartOperation } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isDefined } from 'twenty-shared/utils';
import { MenuItemSelect } from 'twenty-ui/navigation';
const isExtendedAggregateOperation = (
operation: AggregateChartOperation,
): operation is ExtendedAggregateOperations => {
return operation !== DASHBOARD_AGGREGATE_OPERATION_RATIO;
};
export const ChartAggregateOperationSelectableListItem = ({
operation,
label,
currentFieldMetadataId,
}: {
operation: AggregateChartOperation;
label: string;
currentFieldMetadataId: string;
}) => {
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
const { updateCurrentWidgetConfig } =
useUpdateCurrentWidgetConfig(pageLayoutId);
const { closeDropdown } = useCloseDropdown();
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
);
const selectedItemId = useRecoilComponentValue(
selectedItemIdComponentState,
dropdownId,
);
const configuration = widgetInEditMode?.configuration;
const currentAggregateOperation =
configuration &&
'aggregateOperation' in configuration &&
configuration.aggregateOperation;
const isCurrentlyRatio =
configuration?.__typename === 'AggregateChartConfiguration' &&
isDefined(configuration.ratioAggregateConfig);
if (!isExtendedAggregateOperation(operation)) {
return null;
}
const aggregateOperation =
convertExtendedAggregateOperationToAggregateOperation(operation);
const isSelected =
currentAggregateOperation === aggregateOperation && !isCurrentlyRatio;
const isFocused = selectedItemId === operation;
const handleClick = () => {
updateCurrentWidgetConfig({
configToUpdate: {
aggregateFieldMetadataId: currentFieldMetadataId,
aggregateOperation,
ratioAggregateConfig: null,
},
});
closeDropdown();
};
return (
<SelectableListItem itemId={operation} onEnter={handleClick}>
<MenuItemSelect
text={label}
selected={isSelected}
focused={isFocused}
onClick={handleClick}
/>
</SelectableListItem>
);
};
@@ -1,29 +1,28 @@
import { ChartAggregateOperationSelectableListItem } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem';
import { ChartRatioAggregateOperationSelectableListItem } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem';
import { ChartRatioOptionValueSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation';
import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant';
import { type AggregateChartOperation } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation';
import { getAggregateChartOperationLabel } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel';
import { getAvailableAggregateOperationsForAggregateChart } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAvailableAggregateOperationsForAggregateChart';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronLeft } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { GraphType, type AggregateOperations } from '~/generated/graphql';
import { GraphType } from '~/generated/graphql';
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
export const ChartAggregateOperationSelectionDropdownContent = ({
@@ -34,6 +33,7 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
setIsSubMenuOpen: (isSubMenuOpen: boolean) => void;
}) => {
const [searchQuery, setSearchQuery] = useState('');
const [isOptionValueMenuOpen, setIsOptionValueMenuOpen] = useState(false);
const { objectMetadataItems } = useObjectMetadataItems();
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
@@ -48,9 +48,6 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
throw new Error('Invalid configuration type');
}
const currentAggregateOperation =
widgetInEditMode.configuration.aggregateOperation;
const sourceObjectMetadataItem = objectMetadataItems.find(
(item) => item.id === widgetInEditMode.objectMetadataId,
);
@@ -63,21 +60,23 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
DropdownComponentInstanceContext,
);
const selectedItemId = useRecoilComponentValue(
selectedItemIdComponentState,
dropdownId,
);
const availableAggregateOperations = selectedField
? getAvailableAggregateOperationsForFieldMetadataType({
fieldMetadataType: selectedField.type,
})
: [];
const isAggregateChart =
widgetInEditMode.configuration.graphType === GraphType.AGGREGATE;
const isAggregateOrGaugeChart =
widgetInEditMode.configuration.graphType === GraphType.AGGREGATE ||
isAggregateChart ||
widgetInEditMode.configuration.graphType === GraphType.GAUGE;
const availableAggregateOperations: AggregateChartOperation[] = selectedField
? isAggregateChart
? getAvailableAggregateOperationsForAggregateChart({
fieldMetadataType: selectedField.type,
})
: getAvailableAggregateOperationsForFieldMetadataType({
fieldMetadataType: selectedField.type,
})
: [];
const filteredAggregateOperations = availableAggregateOperations.filter(
(operation) => {
return (
@@ -91,7 +90,7 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
const aggregateOperationsWithLabels = filteredAggregateOperations.map(
(operation) => ({
operation,
label: getAggregateOperationLabel(operation),
label: getAggregateChartOperationLabel(operation),
}),
);
@@ -101,27 +100,23 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
getSearchableValues: (item) => [item.label],
});
const { updateCurrentWidgetConfig } =
useUpdateCurrentWidgetConfig(pageLayoutId);
const { closeDropdown } = useCloseDropdown();
if (!isDefined(sourceObjectMetadataItem) || !isDefined(selectedField)) {
return null;
}
const handleSelectAggregateOperation = (
aggregateOperation: AggregateOperations,
) => {
updateCurrentWidgetConfig({
configToUpdate: {
aggregateFieldMetadataId: currentFieldMetadataId,
aggregateOperation,
},
});
closeDropdown();
const handleSelectRatio = () => {
setIsOptionValueMenuOpen(true);
};
if (isOptionValueMenuOpen) {
return (
<ChartRatioOptionValueSelectionDropdownContent
currentFieldMetadataId={currentFieldMetadataId}
setIsOptionValueMenuOpen={setIsOptionValueMenuOpen}
/>
);
}
return (
<>
<DropdownMenuHeader
@@ -150,37 +145,22 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
(item) => item.operation,
)}
>
{filteredAggregateOperationsWithLabels.map((item) => (
<SelectableListItem
key={item.operation}
itemId={item.operation}
onEnter={() => {
handleSelectAggregateOperation(
convertExtendedAggregateOperationToAggregateOperation(
item.operation,
),
);
}}
>
<MenuItemSelect
text={item.label}
selected={
currentAggregateOperation ===
convertExtendedAggregateOperationToAggregateOperation(
item.operation,
)
}
focused={selectedItemId === item.operation}
onClick={() => {
handleSelectAggregateOperation(
convertExtendedAggregateOperationToAggregateOperation(
item.operation,
),
);
}}
{filteredAggregateOperationsWithLabels.map((item) =>
item.operation === DASHBOARD_AGGREGATE_OPERATION_RATIO ? (
<ChartRatioAggregateOperationSelectableListItem
key={item.operation}
label={item.label}
onSelect={handleSelectRatio}
/>
</SelectableListItem>
))}
) : (
<ChartAggregateOperationSelectableListItem
key={item.operation}
operation={item.operation}
label={item.label}
currentFieldMetadataId={currentFieldMetadataId}
/>
),
)}
</SelectableList>
</DropdownMenuItemsContainer>
</>
@@ -101,6 +101,7 @@ export const ChartDataSourceDropdownContent = () => {
groupByFieldMetadataId: null,
groupBySubFieldName: null,
filter: {},
ratioAggregateConfig: null,
},
});
@@ -0,0 +1,52 @@
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isDefined } from 'twenty-shared/utils';
import { MenuItemSelect } from 'twenty-ui/navigation';
export const ChartRatioAggregateOperationSelectableListItem = ({
label,
onSelect,
}: {
label: string;
onSelect: () => void;
}) => {
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
);
const selectedItemId = useRecoilComponentValue(
selectedItemIdComponentState,
dropdownId,
);
const isCurrentlyRatio =
widgetInEditMode?.configuration?.__typename ===
'AggregateChartConfiguration' &&
isDefined(widgetInEditMode.configuration.ratioAggregateConfig);
const isFocused = selectedItemId === DASHBOARD_AGGREGATE_OPERATION_RATIO;
return (
<SelectableListItem
itemId={DASHBOARD_AGGREGATE_OPERATION_RATIO}
onEnter={onSelect}
>
<MenuItemSelect
text={label}
selected={isCurrentlyRatio}
focused={isFocused}
hasSubMenu={true}
onClick={onSelect}
/>
</SelectableListItem>
);
};
@@ -0,0 +1,72 @@
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { IconCheck, IconX } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { AggregateOperations } from '~/generated/graphql';
export const ChartRatioOptionBooleanSelectableListItem = ({
optionValue,
label,
currentFieldMetadataId,
}: {
optionValue: string;
label: string;
currentFieldMetadataId: string;
}) => {
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
const { updateCurrentWidgetConfig } =
useUpdateCurrentWidgetConfig(pageLayoutId);
const { closeDropdown } = useCloseDropdown();
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
);
const selectedItemId = useRecoilComponentValue(
selectedItemIdComponentState,
dropdownId,
);
const currentRatioConfig =
widgetInEditMode?.configuration?.__typename ===
'AggregateChartConfiguration'
? widgetInEditMode.configuration.ratioAggregateConfig
: undefined;
const isSelected = currentRatioConfig?.optionValue === optionValue;
const isFocused = selectedItemId === optionValue;
const handleClick = () => {
updateCurrentWidgetConfig({
configToUpdate: {
aggregateFieldMetadataId: currentFieldMetadataId,
aggregateOperation: AggregateOperations.COUNT,
ratioAggregateConfig: {
fieldMetadataId: currentFieldMetadataId,
optionValue,
},
},
});
closeDropdown();
};
return (
<SelectableListItem itemId={optionValue} onEnter={handleClick}>
<MenuItemSelect
text={label}
LeftIcon={optionValue === 'true' ? IconCheck : IconX}
selected={isSelected}
focused={isFocused}
onClick={handleClick}
/>
</SelectableListItem>
);
};
@@ -0,0 +1,74 @@
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { MenuItemSelectTag } from 'twenty-ui/navigation';
import { type ThemeColor } from 'twenty-ui/theme';
import { AggregateOperations } from '~/generated/graphql';
export const ChartRatioOptionSelectSelectableListItem = ({
optionValue,
label,
color,
currentFieldMetadataId,
}: {
optionValue: string;
label: string;
color: ThemeColor | undefined;
currentFieldMetadataId: string;
}) => {
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
const { updateCurrentWidgetConfig } =
useUpdateCurrentWidgetConfig(pageLayoutId);
const { closeDropdown } = useCloseDropdown();
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
);
const selectedItemId = useRecoilComponentValue(
selectedItemIdComponentState,
dropdownId,
);
const currentRatioConfig =
widgetInEditMode?.configuration?.__typename ===
'AggregateChartConfiguration'
? widgetInEditMode.configuration.ratioAggregateConfig
: undefined;
const isSelected = currentRatioConfig?.optionValue === optionValue;
const isFocused = selectedItemId === optionValue;
const handleClick = () => {
updateCurrentWidgetConfig({
configToUpdate: {
aggregateFieldMetadataId: currentFieldMetadataId,
aggregateOperation: AggregateOperations.COUNT,
ratioAggregateConfig: {
fieldMetadataId: currentFieldMetadataId,
optionValue,
},
},
});
closeDropdown();
};
return (
<SelectableListItem itemId={optionValue} onEnter={handleClick}>
<MenuItemSelectTag
text={label}
color={color ?? 'transparent'}
selected={isSelected}
focused={isFocused}
onClick={handleClick}
/>
</SelectableListItem>
);
};
@@ -0,0 +1,143 @@
import { ChartRatioOptionBooleanSelectableListItem } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionBooleanSelectableListItem';
import { ChartRatioOptionSelectSelectableListItem } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionSelectSelectableListItem';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronLeft } from 'twenty-ui/display';
import { type ThemeColor } from 'twenty-ui/theme';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
type RatioOption = {
value: string;
label: string;
color?: ThemeColor;
};
export const ChartRatioOptionValueSelectionDropdownContent = ({
currentFieldMetadataId,
setIsOptionValueMenuOpen,
}: {
currentFieldMetadataId: string;
setIsOptionValueMenuOpen: (isOpen: boolean) => void;
}) => {
const [searchQuery, setSearchQuery] = useState('');
const { objectMetadataItems } = useObjectMetadataItems();
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
);
if (
widgetInEditMode?.configuration?.__typename !==
'AggregateChartConfiguration'
) {
return null;
}
const sourceObjectMetadataItem = objectMetadataItems.find(
(item) => item.id === widgetInEditMode.objectMetadataId,
);
const selectedField = sourceObjectMetadataItem?.fields.find(
(field) => field.id === currentFieldMetadataId,
);
if (!isDefined(sourceObjectMetadataItem) || !isDefined(selectedField)) {
return null;
}
const getOptionsForField = (): RatioOption[] => {
if (selectedField.type === FieldMetadataType.BOOLEAN) {
return [
{ value: 'true', label: t`True` },
{ value: 'false', label: t`False` },
];
}
if (
selectedField.type === FieldMetadataType.SELECT ||
selectedField.type === FieldMetadataType.MULTI_SELECT
) {
const fieldOptions = selectedField.options ?? [];
return fieldOptions.map((option) => ({
value: option.value,
label: option.label,
color: option.color as ThemeColor,
}));
}
return [];
};
const isBoolean = selectedField.type === FieldMetadataType.BOOLEAN;
const options = getOptionsForField();
const filteredOptions = filterBySearchQuery({
items: options,
searchQuery,
getSearchableValues: (item) => [item.label],
});
return (
<>
<DropdownMenuHeader
StartComponent={
<DropdownMenuHeaderLeftComponent
onClick={() => setIsOptionValueMenuOpen(false)}
Icon={IconChevronLeft}
/>
}
>
{t`Select value`}
</DropdownMenuHeader>
<DropdownMenuSearchInput
autoFocus
type="text"
placeholder={t`Search options`}
onChange={(event) => setSearchQuery(event.target.value)}
value={searchQuery}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={filteredOptions.map((item) => item.value)}
>
{filteredOptions.map((option) =>
isBoolean ? (
<ChartRatioOptionBooleanSelectableListItem
key={option.value}
optionValue={option.value}
label={option.label}
currentFieldMetadataId={currentFieldMetadataId}
/>
) : (
<ChartRatioOptionSelectSelectableListItem
key={option.value}
optionValue={option.value}
label={option.label}
color={option.color}
currentFieldMetadataId={currentFieldMetadataId}
/>
),
)}
</SelectableList>
</DropdownMenuItemsContainer>
</>
);
};
@@ -9,7 +9,7 @@ import { getFieldLabelWithSubField } from '@/command-menu/pages/page-layout/util
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
import { plural } from '@lingui/core/macro';
import { plural, t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
@@ -163,14 +163,39 @@ export const useChartSettingsValues = ({
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y:
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_AGGREGATE:
case CHART_CONFIGURATION_SETTING_IDS.EACH_SLICE_REPRESENTS: {
const hasAggregateLabel = isDefined(aggregateField?.label);
const hasAggregateOperation = isDefined(aggregateOperation);
if (
configuration.__typename === 'AggregateChartConfiguration' &&
isDefined(configuration.ratioAggregateConfig)
) {
const ratioField = objectMetadataItem?.fields.find(
(field) =>
field.id === configuration.ratioAggregateConfig?.fieldMetadataId,
);
return `${aggregateField?.label ?? ''}${
hasAggregateLabel && hasAggregateOperation
? ` (${getAggregateOperationLabel(aggregateOperation)})`
: ''
}`;
const optionValue = configuration.ratioAggregateConfig.optionValue;
const getBooleanLabel = (value: string) =>
value === 'true' ? t`True` : t`False`;
const ratioOptionLabel =
ratioField?.options?.find((option) => option.value === optionValue)
?.label ??
(ratioField?.type === 'BOOLEAN'
? getBooleanLabel(optionValue)
: capitalize(optionValue));
return [aggregateField?.label, `(${t`Ratio`}: ${ratioOptionLabel})`]
.filter(isDefined)
.join(' ');
}
const operationLabel =
isDefined(aggregateField?.label) && isDefined(aggregateOperation)
? `(${getAggregateOperationLabel(aggregateOperation)})`
: undefined;
return [aggregateField?.label, operationLabel]
.filter(isDefined)
.join(' ');
}
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_PIE_CHART: {
const pieChartGroupByField = isDefined(finalGroupByFieldYId)