[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:
+91
@@ -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>
|
||||
);
|
||||
};
|
||||
+49
-69
@@ -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>
|
||||
</>
|
||||
|
||||
+1
@@ -101,6 +101,7 @@ export const ChartDataSourceDropdownContent = () => {
|
||||
groupByFieldMetadataId: null,
|
||||
groupBySubFieldName: null,
|
||||
filter: {},
|
||||
ratioAggregateConfig: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+52
@@ -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>
|
||||
);
|
||||
};
|
||||
+72
@@ -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>
|
||||
);
|
||||
};
|
||||
+74
@@ -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>
|
||||
);
|
||||
};
|
||||
+143
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+33
-8
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user