[DASHBOARDS] Manual and position-based sorting for chart widgets (#16794)
## Description SELECT fields have a defined option order that users expect to see reflected in charts. This PR allows sorting by that position and also enables custom manual ordering. ## Video QA ### Reordering on primary axis https://github.com/user-attachments/assets/994f515e-19cb-4a5e-b745-e8c77e92ae0b ### Reordering on secondary axis https://github.com/user-attachments/assets/444c16f2-1920-4dc4-8b42-312d520ab43b Note: The colors in the graph will match the colors of the select options, but this will be done in another PR <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduces new sort modes and UI for chart groupings, with full FE/BE support and updated GraphQL schema. > > - Extend `GraphOrderBy` with `FIELD_POSITION_ASC/DESC` and `MANUAL`; add corresponding fields in configs: `primaryAxisManualSortOrder`, `secondaryAxisManualSortOrder`, and `manualSortOrder` (pie) > - New UI: dropdown options filtered by field type, icons, and a draggable submenu (`ChartManualSortSubMenuContent`) to reorder select options; integrates with widget edit flow > - Sorting logic added/refactored: `sortChartData`, `sortByManualOrder`, `sortBySelectOptionPosition`, `sortLineChartSeries`, plus updates to bar/line/pie transformers to honor new modes and manual orders > - Default behaviors: select fields default to `FIELD_POSITION_ASC`; query variable builders skip `orderBy` when using manual/position sorts > - Update GraphQL generated types/fragments/queries and backend DTOs/schemas to persist new fields; add tests for sorting utilities and snapshots; add sorting icons in `twenty-ui` > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 78c9b56c0f1f2d45f7f8b270bb59ca599a005abe. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
+123
@@ -0,0 +1,123 @@
|
||||
import { type DropResult } from '@hello-pangea/dnd';
|
||||
|
||||
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 { getManualSortOrderFromConfig } from '@/command-menu/pages/page-layout/utils/getManualSortOrderFromConfig';
|
||||
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { sortOptionsForManualOrder } from '@/page-layout/widgets/graph/utils/sortOptionsForManualOrder';
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { IconChevronLeft } from 'twenty-ui/display';
|
||||
import { MenuItemDraggable } from 'twenty-ui/navigation';
|
||||
import { type WidgetConfiguration } from '~/generated/graphql';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
|
||||
type ChartManualSortSubMenuContentProps = {
|
||||
fieldMetadataItem: FieldMetadataItem;
|
||||
axis: 'primary' | 'secondary';
|
||||
onBack: () => void;
|
||||
};
|
||||
|
||||
export const ChartManualSortSubMenuContent = ({
|
||||
fieldMetadataItem,
|
||||
axis,
|
||||
onBack,
|
||||
}: ChartManualSortSubMenuContentProps) => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
// TODO: Remove this cast when FieldsConfiguration and FieldConfiguration are in the backend
|
||||
const configuration = widgetInEditMode?.configuration as WidgetConfiguration;
|
||||
const options = fieldMetadataItem.options ?? [];
|
||||
|
||||
const currentManualSortOrder = getManualSortOrderFromConfig(
|
||||
configuration,
|
||||
axis,
|
||||
);
|
||||
|
||||
const sortedOptions = sortOptionsForManualOrder(
|
||||
options,
|
||||
currentManualSortOrder,
|
||||
);
|
||||
|
||||
const handleDragEnd = (result: DropResult) => {
|
||||
if (!isDefined(result.destination)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reorderedOptions = moveArrayItem(sortedOptions, {
|
||||
fromIndex: result.source.index,
|
||||
toIndex: result.destination.index,
|
||||
});
|
||||
|
||||
const newManualSortOrder = reorderedOptions.map((option) => option.value);
|
||||
const configKey = isWidgetConfigurationOfType(
|
||||
configuration,
|
||||
'PieChartConfiguration',
|
||||
)
|
||||
? 'manualSortOrder'
|
||||
: axis === 'primary'
|
||||
? 'primaryAxisManualSortOrder'
|
||||
: 'secondaryAxisManualSortOrder';
|
||||
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: { [configKey]: newManualSortOrder },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={onBack}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{t`Reorder options`}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<DraggableList
|
||||
onDragEnd={handleDragEnd}
|
||||
draggableItems={
|
||||
<>
|
||||
{sortedOptions.map((option, index) => (
|
||||
<DraggableItem
|
||||
key={option.value}
|
||||
draggableId={option.value}
|
||||
index={index}
|
||||
isDragDisabled={sortedOptions.length === 1}
|
||||
itemComponent={
|
||||
<MenuItemDraggable
|
||||
showGrip
|
||||
isDragDisabled={sortedOptions.length === 1}
|
||||
text={
|
||||
<Tag
|
||||
preventShrink
|
||||
color={option.color}
|
||||
text={option.label}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+92
-27
@@ -1,9 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { ChartManualSortSubMenuContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartManualSortSubMenuContent';
|
||||
import { AGGREGATE_SORT_BY_OPTIONS } from '@/command-menu/pages/page-layout/constants/AggregateSortByOptions';
|
||||
import { useGraphGroupBySortOptionLabels } from '@/command-menu/pages/page-layout/hooks/useGraphGroupBySortOptionLabels';
|
||||
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 { filterSortOptionsByFieldType } from '@/command-menu/pages/page-layout/utils/filterSortOptionsByFieldType';
|
||||
import { getDefaultManualSortOrder } from '@/command-menu/pages/page-layout/utils/getDefaultManualSortOrder';
|
||||
import { getSortIconForFieldType } from '@/command-menu/pages/page-layout/utils/getSortIconForFieldType';
|
||||
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
@@ -13,13 +20,20 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
|
||||
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';
|
||||
import { type GraphOrderBy } from '~/generated/graphql';
|
||||
import {
|
||||
GraphOrderBy,
|
||||
type GraphOrderBy as GraphOrderByType,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const ChartSortByGroupByFieldDropdownContent = () => {
|
||||
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const configuration = widgetInEditMode?.configuration;
|
||||
|
||||
@@ -34,6 +48,7 @@ export const ChartSortByGroupByFieldDropdownContent = () => {
|
||||
if (!isDefined(widgetInEditMode?.objectMetadataId)) {
|
||||
throw new Error('No data source in chart');
|
||||
}
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
@@ -43,33 +58,76 @@ export const ChartSortByGroupByFieldDropdownContent = () => {
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === widgetInEditMode.objectMetadataId,
|
||||
);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const handleSelectSortOption = (orderBy: GraphOrderBy) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: { secondaryAxisOrderBy: orderBy },
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
const secondaryAxisField = objectMetadataItem?.fields.find(
|
||||
(field) => field.id === configuration.secondaryAxisGroupByFieldMetadataId,
|
||||
);
|
||||
|
||||
const { getGroupBySortOptionLabel } = useGraphGroupBySortOptionLabels({
|
||||
objectMetadataId: widgetInEditMode.objectMetadataId,
|
||||
});
|
||||
|
||||
if (!isDefined(secondaryAxisField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSelectSortOption = (orderBy: GraphOrderByType) => {
|
||||
const configToUpdate: Record<string, unknown> = {
|
||||
secondaryAxisOrderBy: orderBy,
|
||||
};
|
||||
|
||||
if (orderBy === GraphOrderBy.MANUAL) {
|
||||
const existingManualSortOrder =
|
||||
configuration.secondaryAxisManualSortOrder;
|
||||
|
||||
if (!isDefined(existingManualSortOrder)) {
|
||||
configToUpdate.secondaryAxisManualSortOrder = getDefaultManualSortOrder(
|
||||
secondaryAxisField?.options,
|
||||
);
|
||||
}
|
||||
|
||||
updateCurrentWidgetConfig({ configToUpdate });
|
||||
setIsSubMenuOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (configuration.secondaryAxisOrderBy === GraphOrderBy.MANUAL) {
|
||||
configToUpdate.secondaryAxisManualSortOrder = null;
|
||||
}
|
||||
|
||||
updateCurrentWidgetConfig({ configToUpdate });
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const availableOptions = filterSortOptionsByFieldType({
|
||||
options: AGGREGATE_SORT_BY_OPTIONS,
|
||||
fieldType: secondaryAxisField?.type,
|
||||
});
|
||||
|
||||
if (isSubMenuOpen && isDefined(secondaryAxisField)) {
|
||||
return (
|
||||
<ChartManualSortSubMenuContent
|
||||
fieldMetadataItem={secondaryAxisField}
|
||||
axis="secondary"
|
||||
onBack={() => setIsSubMenuOpen(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={AGGREGATE_SORT_BY_OPTIONS.map(
|
||||
(option) => option.value,
|
||||
)}
|
||||
>
|
||||
{AGGREGATE_SORT_BY_OPTIONS.map((sortOption) => (
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={availableOptions.map((option) => option.value)}
|
||||
>
|
||||
{availableOptions.map((sortOption) => {
|
||||
const isManualOption = sortOption.value === GraphOrderBy.MANUAL;
|
||||
|
||||
return (
|
||||
<SelectableListItem
|
||||
key={sortOption.value}
|
||||
itemId={sortOption.value}
|
||||
@@ -87,15 +145,22 @@ export const ChartSortByGroupByFieldDropdownContent = () => {
|
||||
configuration.secondaryAxisOrderBy === sortOption.value
|
||||
}
|
||||
focused={selectedItemId === sortOption.value}
|
||||
LeftIcon={sortOption.icon}
|
||||
LeftIcon={
|
||||
sortOption.icon ??
|
||||
getSortIconForFieldType({
|
||||
fieldType: secondaryAxisField?.type,
|
||||
orderBy: sortOption.value,
|
||||
})
|
||||
}
|
||||
hasSubMenu={isManualOption}
|
||||
onClick={() => {
|
||||
handleSelectSortOption(sortOption.value);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+115
-74
@@ -1,11 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { ChartManualSortSubMenuContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartManualSortSubMenuContent';
|
||||
import { X_SORT_BY_OPTIONS } from '@/command-menu/pages/page-layout/constants/XSortByOptions';
|
||||
import { useGraphXSortOptionLabels } from '@/command-menu/pages/page-layout/hooks/useGraphXSortOptionLabels';
|
||||
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 { filterSortOptionsByFieldType } from '@/command-menu/pages/page-layout/utils/filterSortOptionsByFieldType';
|
||||
import { getDefaultManualSortOrder } from '@/command-menu/pages/page-layout/utils/getDefaultManualSortOrder';
|
||||
import { getSortIconForFieldType } from '@/command-menu/pages/page-layout/utils/getSortIconForFieldType';
|
||||
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { isRelationNestedFieldDateKind } from '@/page-layout/widgets/graph/utils/isRelationNestedFieldDateKind';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
@@ -15,7 +20,7 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import {
|
||||
type BarChartConfiguration,
|
||||
@@ -24,8 +29,23 @@ import {
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const ChartSortBySelectionDropdownContent = () => {
|
||||
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const configuration = widgetInEditMode?.configuration;
|
||||
|
||||
const isPieChart = isWidgetConfigurationOfType(
|
||||
@@ -49,25 +69,10 @@ export const ChartSortBySelectionDropdownContent = () => {
|
||||
throw new Error('No data source in chart');
|
||||
}
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const { getXSortOptionLabel } = useGraphXSortOptionLabels({
|
||||
objectMetadataId: widgetInEditMode.objectMetadataId,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === widgetInEditMode.objectMetadataId,
|
||||
);
|
||||
@@ -96,44 +101,69 @@ export const ChartSortBySelectionDropdownContent = () => {
|
||||
(field) => field.id === groupByFieldMetadataId,
|
||||
);
|
||||
|
||||
const isPrimaryAxisDateField =
|
||||
isFieldMetadataDateKind(primaryAxisField?.type) ||
|
||||
(isDefined(primaryAxisField) &&
|
||||
isRelationNestedFieldDateKind({
|
||||
relationField: primaryAxisField,
|
||||
relationNestedFieldName: groupBySubFieldName ?? undefined,
|
||||
objectMetadataItems,
|
||||
}));
|
||||
if (!isDefined(primaryAxisField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const existingManualSortOrder = isPieChart
|
||||
? configuration.manualSortOrder
|
||||
: (configuration as BarChartConfiguration | LineChartConfiguration)
|
||||
.primaryAxisManualSortOrder;
|
||||
|
||||
const handleSelect = (orderBy: GraphOrderBy) => {
|
||||
if (isPieChart) {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: { orderBy },
|
||||
});
|
||||
} else {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: { primaryAxisOrderBy: orderBy },
|
||||
});
|
||||
const configToUpdate: Record<string, unknown> = {};
|
||||
|
||||
if (orderBy === GraphOrderBy.MANUAL) {
|
||||
const orderByKey = isPieChart ? 'orderBy' : 'primaryAxisOrderBy';
|
||||
const manualSortOrderKey = isPieChart
|
||||
? 'manualSortOrder'
|
||||
: 'primaryAxisManualSortOrder';
|
||||
|
||||
configToUpdate[orderByKey] = orderBy;
|
||||
|
||||
if (!isDefined(existingManualSortOrder)) {
|
||||
configToUpdate[manualSortOrderKey] = getDefaultManualSortOrder(
|
||||
primaryAxisField?.options,
|
||||
);
|
||||
}
|
||||
|
||||
updateCurrentWidgetConfig({ configToUpdate });
|
||||
setIsSubMenuOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentOrderBy === GraphOrderBy.MANUAL) {
|
||||
const manualSortOrderKey = isPieChart
|
||||
? 'manualSortOrder'
|
||||
: 'primaryAxisManualSortOrder';
|
||||
configToUpdate[manualSortOrderKey] = null;
|
||||
}
|
||||
|
||||
if (isPieChart) {
|
||||
configToUpdate.orderBy = orderBy;
|
||||
} else {
|
||||
configToUpdate.primaryAxisOrderBy = orderBy;
|
||||
}
|
||||
|
||||
updateCurrentWidgetConfig({ configToUpdate });
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const availableOptions = X_SORT_BY_OPTIONS.filter((option) => {
|
||||
const isValueSort =
|
||||
option.value === GraphOrderBy.VALUE_ASC ||
|
||||
option.value === GraphOrderBy.VALUE_DESC;
|
||||
|
||||
if (isLineChart) {
|
||||
return !isValueSort;
|
||||
}
|
||||
|
||||
if ((isBarChart || isPieChart) && isPrimaryAxisDateField) {
|
||||
return !isValueSort;
|
||||
}
|
||||
|
||||
return true;
|
||||
const availableOptions = filterSortOptionsByFieldType({
|
||||
options: X_SORT_BY_OPTIONS,
|
||||
fieldType: primaryAxisField?.type,
|
||||
});
|
||||
|
||||
if (isSubMenuOpen && isDefined(primaryAxisField)) {
|
||||
return (
|
||||
<ChartManualSortSubMenuContent
|
||||
fieldMetadataItem={primaryAxisField}
|
||||
axis={'primary'}
|
||||
onBack={() => setIsSubMenuOpen(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
@@ -141,35 +171,46 @@ export const ChartSortBySelectionDropdownContent = () => {
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={availableOptions.map((option) => option.value)}
|
||||
>
|
||||
{availableOptions.map((sortOption) => (
|
||||
<SelectableListItem
|
||||
key={sortOption.value}
|
||||
itemId={sortOption.value}
|
||||
onEnter={() => {
|
||||
handleSelect(sortOption.value);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={getXSortOptionLabel({
|
||||
graphOrderBy: sortOption.value,
|
||||
groupByFieldMetadataIdX: groupByFieldMetadataId ?? '',
|
||||
groupBySubFieldNameX: groupBySubFieldName as
|
||||
| CompositeFieldSubFieldName
|
||||
| undefined,
|
||||
aggregateFieldMetadataId:
|
||||
configuration.aggregateFieldMetadataId ?? undefined,
|
||||
aggregateOperation:
|
||||
configuration.aggregateOperation ?? undefined,
|
||||
})}
|
||||
selected={currentOrderBy === sortOption.value}
|
||||
focused={selectedItemId === sortOption.value}
|
||||
LeftIcon={sortOption.icon}
|
||||
onClick={() => {
|
||||
{availableOptions.map((sortOption) => {
|
||||
const isManualOption = sortOption.value === GraphOrderBy.MANUAL;
|
||||
|
||||
return (
|
||||
<SelectableListItem
|
||||
key={sortOption.value}
|
||||
itemId={sortOption.value}
|
||||
onEnter={() => {
|
||||
handleSelect(sortOption.value);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={getXSortOptionLabel({
|
||||
graphOrderBy: sortOption.value,
|
||||
groupByFieldMetadataIdX: groupByFieldMetadataId ?? '',
|
||||
groupBySubFieldNameX: groupBySubFieldName as
|
||||
| CompositeFieldSubFieldName
|
||||
| undefined,
|
||||
aggregateFieldMetadataId:
|
||||
configuration.aggregateFieldMetadataId ?? undefined,
|
||||
aggregateOperation:
|
||||
configuration.aggregateOperation ?? undefined,
|
||||
})}
|
||||
selected={currentOrderBy === sortOption.value}
|
||||
focused={selectedItemId === sortOption.value}
|
||||
LeftIcon={
|
||||
sortOption.icon ??
|
||||
getSortIconForFieldType({
|
||||
fieldType: primaryAxisField?.type,
|
||||
orderBy: sortOption.value,
|
||||
})
|
||||
}
|
||||
hasSubMenu={isManualOption}
|
||||
onClick={() => {
|
||||
handleSelect(sortOption.value);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
})}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
|
||||
+19
-3
@@ -1,13 +1,29 @@
|
||||
import { IconArrowDown, IconArrowUp } from 'twenty-ui/display';
|
||||
import {
|
||||
IconHandMove,
|
||||
IconSortAscending,
|
||||
IconSortDescending,
|
||||
} from 'twenty-ui/display';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const AGGREGATE_SORT_BY_OPTIONS = [
|
||||
{
|
||||
value: GraphOrderBy.FIELD_POSITION_ASC,
|
||||
icon: IconSortAscending,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.FIELD_POSITION_DESC,
|
||||
icon: IconSortDescending,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.FIELD_ASC,
|
||||
icon: IconArrowUp,
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.FIELD_DESC,
|
||||
icon: IconArrowDown,
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.MANUAL,
|
||||
icon: IconHandMove,
|
||||
},
|
||||
];
|
||||
|
||||
+24
-5
@@ -1,19 +1,34 @@
|
||||
import {
|
||||
IconArrowDown,
|
||||
IconArrowUp,
|
||||
type IconComponent,
|
||||
IconHandMove,
|
||||
IconSortAscending,
|
||||
IconSortDescending,
|
||||
IconTrendingDown,
|
||||
IconTrendingUp,
|
||||
} from 'twenty-ui/display';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const X_SORT_BY_OPTIONS = [
|
||||
type XSortByOption = {
|
||||
value: GraphOrderBy;
|
||||
icon: IconComponent | null;
|
||||
};
|
||||
|
||||
export const X_SORT_BY_OPTIONS: XSortByOption[] = [
|
||||
{
|
||||
value: GraphOrderBy.FIELD_POSITION_ASC,
|
||||
icon: IconSortAscending,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.FIELD_POSITION_DESC,
|
||||
icon: IconSortDescending,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.FIELD_ASC,
|
||||
icon: IconArrowUp,
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.FIELD_DESC,
|
||||
icon: IconArrowDown,
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.VALUE_ASC,
|
||||
@@ -23,4 +38,8 @@ export const X_SORT_BY_OPTIONS = [
|
||||
value: GraphOrderBy.VALUE_DESC,
|
||||
icon: IconTrendingDown,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.MANUAL,
|
||||
icon: IconHandMove,
|
||||
},
|
||||
];
|
||||
|
||||
+16
-3
@@ -1,8 +1,10 @@
|
||||
import { getFieldLabelWithSubField } from '@/command-menu/pages/page-layout/utils/getFieldLabelWithSubField';
|
||||
import { getSortLabelSuffixForFieldType } from '@/command-menu/pages/page-layout/utils/getSortLabelSuffixForFieldType';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const useGraphGroupBySortOptionLabels = ({
|
||||
@@ -35,13 +37,24 @@ export const useGraphGroupBySortOptionLabels = ({
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
const groupBySortLabelSuffix = getSortLabelSuffixForFieldType({
|
||||
fieldType: field?.type,
|
||||
orderBy: graphOrderBy,
|
||||
});
|
||||
|
||||
switch (graphOrderBy) {
|
||||
case GraphOrderBy.FIELD_ASC:
|
||||
return `${fieldLabel} ${t`Ascending`}`;
|
||||
case GraphOrderBy.FIELD_DESC:
|
||||
return `${fieldLabel} ${t`Descending`}`;
|
||||
default:
|
||||
case GraphOrderBy.FIELD_POSITION_ASC:
|
||||
case GraphOrderBy.FIELD_POSITION_DESC:
|
||||
return `${fieldLabel} ${groupBySortLabelSuffix}`;
|
||||
case GraphOrderBy.VALUE_ASC:
|
||||
case GraphOrderBy.VALUE_DESC:
|
||||
return '';
|
||||
case GraphOrderBy.MANUAL:
|
||||
return t`Manual`;
|
||||
default:
|
||||
assertUnreachable(graphOrderBy);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+17
-4
@@ -1,4 +1,5 @@
|
||||
import { getFieldLabelWithSubField } from '@/command-menu/pages/page-layout/utils/getFieldLabelWithSubField';
|
||||
import { getSortLabelSuffixForFieldType } from '@/command-menu/pages/page-layout/utils/getSortLabelSuffixForFieldType';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
@@ -53,15 +54,27 @@ export const useGraphXSortOptionLabels = ({
|
||||
? getAggregateOperationLabel(aggregateOperation)
|
||||
: t`Value`;
|
||||
|
||||
const groupBySortLabelSuffix = getSortLabelSuffixForFieldType({
|
||||
fieldType: groupByField?.type,
|
||||
orderBy: graphOrderBy,
|
||||
});
|
||||
|
||||
const aggregateSortLabelSuffix = getSortLabelSuffixForFieldType({
|
||||
fieldType: aggregateField?.type,
|
||||
orderBy: graphOrderBy,
|
||||
});
|
||||
|
||||
switch (graphOrderBy) {
|
||||
case GraphOrderBy.FIELD_ASC:
|
||||
return `${fieldLabel} ${t`Ascending`}`;
|
||||
case GraphOrderBy.FIELD_DESC:
|
||||
return `${fieldLabel} ${t`Descending`}`;
|
||||
case GraphOrderBy.FIELD_POSITION_ASC:
|
||||
case GraphOrderBy.FIELD_POSITION_DESC:
|
||||
return `${fieldLabel} ${groupBySortLabelSuffix}`;
|
||||
case GraphOrderBy.VALUE_ASC:
|
||||
return `${valueLabel} ${t`Ascending`}`;
|
||||
case GraphOrderBy.VALUE_DESC:
|
||||
return `${valueLabel} ${t`Descending`}`;
|
||||
return `${valueLabel} ${aggregateSortLabelSuffix}`;
|
||||
case GraphOrderBy.MANUAL:
|
||||
return t`Manual`;
|
||||
default:
|
||||
assertUnreachable(graphOrderBy);
|
||||
}
|
||||
|
||||
+19
-1
@@ -1,12 +1,12 @@
|
||||
import { type TypedBarChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedBarChartConfiguration';
|
||||
import { type TypedPieChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedPieChartConfiguration';
|
||||
import { buildChartGroupByFieldConfigUpdate } from '@/command-menu/pages/page-layout/utils/buildChartGroupByFieldConfigUpdate';
|
||||
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
|
||||
import {
|
||||
BarChartGroupMode,
|
||||
GraphOrderBy,
|
||||
WidgetConfigurationType,
|
||||
} from '~/generated/graphql';
|
||||
import { buildChartGroupByFieldConfigUpdate } from '@/command-menu/pages/page-layout/utils/buildChartGroupByFieldConfigUpdate';
|
||||
|
||||
describe('buildChartGroupByFieldConfigUpdate', () => {
|
||||
it('sets default orderBy and dateGranularity for primary axis', () => {
|
||||
@@ -79,4 +79,22 @@ describe('buildChartGroupByFieldConfigUpdate', () => {
|
||||
dateGranularity: ObjectRecordGroupByDateGranularity.DAY,
|
||||
});
|
||||
});
|
||||
|
||||
it('resets orderBy to default when field changes', () => {
|
||||
const result = buildChartGroupByFieldConfigUpdate({
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration',
|
||||
configurationType: WidgetConfigurationType.BAR_CHART,
|
||||
primaryAxisOrderBy: GraphOrderBy.VALUE_DESC,
|
||||
} as TypedBarChartConfiguration,
|
||||
fieldMetadataIdKey: 'primaryAxisGroupByFieldMetadataId',
|
||||
subFieldNameKey: 'primaryAxisGroupBySubFieldName',
|
||||
fieldId: 'new-field-id',
|
||||
subFieldName: null,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { filterSortOptionsByFieldType } from '@/command-menu/pages/page-layout/utils/filterSortOptionsByFieldType';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
describe('filterSortOptionsByFieldType', () => {
|
||||
const allOptions = [
|
||||
{ value: GraphOrderBy.FIELD_POSITION_ASC },
|
||||
{ value: GraphOrderBy.FIELD_POSITION_DESC },
|
||||
{ value: GraphOrderBy.FIELD_ASC },
|
||||
{ value: GraphOrderBy.FIELD_DESC },
|
||||
{ value: GraphOrderBy.VALUE_ASC },
|
||||
{ value: GraphOrderBy.VALUE_DESC },
|
||||
{ value: GraphOrderBy.MANUAL },
|
||||
];
|
||||
|
||||
describe('select field', () => {
|
||||
it('should include all options for select field', () => {
|
||||
const result = filterSortOptionsByFieldType({
|
||||
options: allOptions,
|
||||
fieldType: FieldMetadataType.SELECT,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('date field', () => {
|
||||
it('should exclude value sorts for date select field', () => {
|
||||
const result = filterSortOptionsByFieldType({
|
||||
options: allOptions,
|
||||
fieldType: FieldMetadataType.DATE,
|
||||
});
|
||||
|
||||
expect(result).not.toContainEqual({ value: GraphOrderBy.VALUE_ASC });
|
||||
expect(result).not.toContainEqual({ value: GraphOrderBy.VALUE_DESC });
|
||||
expect(result).not.toContainEqual({ value: GraphOrderBy.MANUAL });
|
||||
expect(result).not.toContainEqual({
|
||||
value: GraphOrderBy.FIELD_POSITION_ASC,
|
||||
});
|
||||
expect(result).not.toContainEqual({
|
||||
value: GraphOrderBy.FIELD_POSITION_DESC,
|
||||
});
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('non-select, non-date field', () => {
|
||||
it('should exclude manual and position sorts', () => {
|
||||
const result = filterSortOptionsByFieldType({
|
||||
options: allOptions,
|
||||
fieldType: FieldMetadataType.TEXT,
|
||||
});
|
||||
|
||||
expect(result).not.toContainEqual({ value: GraphOrderBy.MANUAL });
|
||||
expect(result).not.toContainEqual({
|
||||
value: GraphOrderBy.FIELD_POSITION_ASC,
|
||||
});
|
||||
expect(result).not.toContainEqual({
|
||||
value: GraphOrderBy.FIELD_POSITION_DESC,
|
||||
});
|
||||
expect(result).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { getChartDefaultOrderByForFieldType } from '@/command-menu/pages/page-layout/utils/getChartDefaultOrderByForFieldType';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
describe('getChartDefaultOrderByForFieldType', () => {
|
||||
it('should return FIELD_POSITION_ASC for SELECT field type', () => {
|
||||
expect(getChartDefaultOrderByForFieldType(FieldMetadataType.SELECT)).toBe(
|
||||
GraphOrderBy.FIELD_POSITION_ASC,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return FIELD_ASC for TEXT field type', () => {
|
||||
expect(getChartDefaultOrderByForFieldType(FieldMetadataType.TEXT)).toBe(
|
||||
GraphOrderBy.FIELD_ASC,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return FIELD_ASC for NUMBER field type', () => {
|
||||
expect(getChartDefaultOrderByForFieldType(FieldMetadataType.NUMBER)).toBe(
|
||||
GraphOrderBy.FIELD_ASC,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return FIELD_ASC for DATE field type', () => {
|
||||
expect(getChartDefaultOrderByForFieldType(FieldMetadataType.DATE)).toBe(
|
||||
GraphOrderBy.FIELD_ASC,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return FIELD_ASC for MULTI_SELECT field type', () => {
|
||||
expect(
|
||||
getChartDefaultOrderByForFieldType(FieldMetadataType.MULTI_SELECT),
|
||||
).toBe(GraphOrderBy.FIELD_ASC);
|
||||
});
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { getDefaultManualSortOrder } from '@/command-menu/pages/page-layout/utils/getDefaultManualSortOrder';
|
||||
|
||||
describe('getDefaultManualSortOrder', () => {
|
||||
it('should return empty array for null options', () => {
|
||||
expect(getDefaultManualSortOrder(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array for undefined options', () => {
|
||||
expect(getDefaultManualSortOrder(undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array for empty options', () => {
|
||||
expect(getDefaultManualSortOrder([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return values sorted by position', () => {
|
||||
const options = [
|
||||
{ value: 'third', position: 2 },
|
||||
{ value: 'first', position: 0 },
|
||||
{ value: 'second', position: 1 },
|
||||
];
|
||||
|
||||
expect(getDefaultManualSortOrder(options)).toEqual([
|
||||
'first',
|
||||
'second',
|
||||
'third',
|
||||
]);
|
||||
});
|
||||
});
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
import { getManualSortOrderFromConfig } from '@/command-menu/pages/page-layout/utils/getManualSortOrderFromConfig';
|
||||
import { expect } from '@storybook/test';
|
||||
import {
|
||||
WidgetConfigurationType,
|
||||
type BarChartConfiguration,
|
||||
type LineChartConfiguration,
|
||||
type PieChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
describe('getManualSortOrderFromConfig', () => {
|
||||
describe('pie chart configuration', () => {
|
||||
it('should return manualSortOrder for pie axis', () => {
|
||||
const config = {
|
||||
__typename: 'PieChartConfiguration' as const,
|
||||
manualSortOrder: ['a', 'b', 'c'],
|
||||
} as PieChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config)).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('should return undefined for null manualSortOrder', () => {
|
||||
const config = {
|
||||
__typename: 'PieChartConfiguration' as const,
|
||||
manualSortOrder: null,
|
||||
} as PieChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when manualSortOrder is not in config', () => {
|
||||
const config = {
|
||||
__typename: 'PieChartConfiguration',
|
||||
configurationType: WidgetConfigurationType.PIE_CHART,
|
||||
} as PieChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for wrong typename', () => {
|
||||
const config = {
|
||||
__typename: 'BarChartConfiguration',
|
||||
configurationType: WidgetConfigurationType.BAR_CHART,
|
||||
manualSortOrder: ['a', 'b', 'c'],
|
||||
} as unknown as BarChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('bar chart configuration', () => {
|
||||
it('should return primaryAxisManualSortOrder for primary axis', () => {
|
||||
const config = {
|
||||
__typename: 'BarChartConfiguration' as const,
|
||||
primaryAxisManualSortOrder: ['x', 'y', 'z'],
|
||||
} as BarChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config, 'primary')).toEqual([
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return secondaryAxisManualSortOrder for secondary axis', () => {
|
||||
const config = {
|
||||
__typename: 'BarChartConfiguration' as const,
|
||||
secondaryAxisManualSortOrder: ['1', '2', '3'],
|
||||
} as BarChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config, 'secondary')).toEqual([
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return undefined for null primaryAxisManualSortOrder', () => {
|
||||
const config = {
|
||||
__typename: 'BarChartConfiguration' as const,
|
||||
primaryAxisManualSortOrder: null,
|
||||
} as BarChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config, 'primary')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('line chart configuration', () => {
|
||||
it('should return primaryAxisManualSortOrder for primary axis', () => {
|
||||
const config = {
|
||||
__typename: 'LineChartConfiguration' as const,
|
||||
primaryAxisManualSortOrder: ['a', 'b'],
|
||||
} as LineChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config, 'primary')).toEqual([
|
||||
'a',
|
||||
'b',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return secondaryAxisManualSortOrder for secondary axis', () => {
|
||||
const config = {
|
||||
__typename: 'LineChartConfiguration' as const,
|
||||
secondaryAxisManualSortOrder: ['c', 'd'],
|
||||
} as LineChartConfiguration;
|
||||
|
||||
expect(getManualSortOrderFromConfig(config, 'secondary')).toEqual([
|
||||
'c',
|
||||
'd',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
IconSortAscending,
|
||||
IconSortAscendingLetters,
|
||||
IconSortAscendingNumbers,
|
||||
IconSortDescending,
|
||||
IconSortDescendingLetters,
|
||||
IconSortDescendingNumbers,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
import { getSortIconForFieldType } from '@/command-menu/pages/page-layout/utils/getSortIconForFieldType';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
describe('getSortIconForFieldType', () => {
|
||||
describe('position sort', () => {
|
||||
it('should return IconSortAscending for FIELD_POSITION_ASC', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.SELECT,
|
||||
orderBy: GraphOrderBy.FIELD_POSITION_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscending);
|
||||
});
|
||||
|
||||
it('should return IconSortDescending for FIELD_POSITION_DESC', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.SELECT,
|
||||
orderBy: GraphOrderBy.FIELD_POSITION_DESC,
|
||||
}),
|
||||
).toBe(IconSortDescending);
|
||||
});
|
||||
});
|
||||
|
||||
describe('text field types', () => {
|
||||
it('should return IconSortAscendingLetters for TEXT field ascending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.TEXT,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscendingLetters);
|
||||
});
|
||||
|
||||
it('should return IconSortDescendingLetters for TEXT field descending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.TEXT,
|
||||
orderBy: GraphOrderBy.FIELD_DESC,
|
||||
}),
|
||||
).toBe(IconSortDescendingLetters);
|
||||
});
|
||||
|
||||
it('should return IconSortAscendingLetters for RICH_TEXT field ascending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.RICH_TEXT,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscendingLetters);
|
||||
});
|
||||
|
||||
it('should return IconSortAscendingLetters for RICH_TEXT_V2 field ascending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.RICH_TEXT_V2,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscendingLetters);
|
||||
});
|
||||
});
|
||||
|
||||
describe('number field types', () => {
|
||||
it('should return IconSortAscendingNumbers for NUMBER field ascending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.NUMBER,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscendingNumbers);
|
||||
});
|
||||
|
||||
it('should return IconSortDescendingNumbers for NUMBER field descending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.NUMBER,
|
||||
orderBy: GraphOrderBy.FIELD_DESC,
|
||||
}),
|
||||
).toBe(IconSortDescendingNumbers);
|
||||
});
|
||||
|
||||
it('should return IconSortAscendingNumbers for CURRENCY field ascending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.CURRENCY,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscendingNumbers);
|
||||
});
|
||||
|
||||
it('should return IconSortAscendingNumbers for RATING field ascending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.RATING,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscendingNumbers);
|
||||
});
|
||||
});
|
||||
|
||||
describe('select field type', () => {
|
||||
it('should return IconSortAscendingLetters for SELECT field with FIELD_ASC', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.SELECT,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscendingLetters);
|
||||
});
|
||||
|
||||
it('should return IconSortDescendingLetters for SELECT field with FIELD_DESC', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.SELECT,
|
||||
orderBy: GraphOrderBy.FIELD_DESC,
|
||||
}),
|
||||
).toBe(IconSortDescendingLetters);
|
||||
});
|
||||
});
|
||||
|
||||
describe('undefined field type', () => {
|
||||
it('should return IconSortAscending for undefined field type ascending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: undefined,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscending);
|
||||
});
|
||||
|
||||
it('should return IconSortDescending for undefined field type descending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: undefined,
|
||||
orderBy: GraphOrderBy.FIELD_DESC,
|
||||
}),
|
||||
).toBe(IconSortDescending);
|
||||
});
|
||||
});
|
||||
|
||||
describe('value-based sort', () => {
|
||||
it('should return IconSortAscending for VALUE_ASC', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.NUMBER,
|
||||
orderBy: GraphOrderBy.VALUE_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscendingNumbers);
|
||||
});
|
||||
|
||||
it('should return IconSortDescending for VALUE_DESC', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.NUMBER,
|
||||
orderBy: GraphOrderBy.VALUE_DESC,
|
||||
}),
|
||||
).toBe(IconSortDescendingNumbers);
|
||||
});
|
||||
|
||||
it('should return IconSortAscending for VALUE_ASC with undefined field type', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: undefined,
|
||||
orderBy: GraphOrderBy.VALUE_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscending);
|
||||
});
|
||||
|
||||
it('should return IconSortDescending for VALUE_DESC with undefined field type', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: undefined,
|
||||
orderBy: GraphOrderBy.VALUE_DESC,
|
||||
}),
|
||||
).toBe(IconSortDescending);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default cases', () => {
|
||||
it('should return IconSortAscending for DATE field ascending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.DATE,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe(IconSortAscending);
|
||||
});
|
||||
|
||||
it('should return IconSortDescending for DATE field descending', () => {
|
||||
expect(
|
||||
getSortIconForFieldType({
|
||||
fieldType: FieldMetadataType.DATE,
|
||||
orderBy: GraphOrderBy.FIELD_DESC,
|
||||
}),
|
||||
).toBe(IconSortDescending);
|
||||
});
|
||||
});
|
||||
});
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { getSortLabelSuffixForFieldType } from '@/command-menu/pages/page-layout/utils/getSortLabelSuffixForFieldType';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
describe('getSortLabelSuffixForFieldType', () => {
|
||||
it('returns alphabetical for TEXT field with FIELD_ASC', () => {
|
||||
expect(
|
||||
getSortLabelSuffixForFieldType({
|
||||
fieldType: FieldMetadataType.TEXT,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe('alphabetical');
|
||||
});
|
||||
|
||||
it('returns ascending for NUMBER field with FIELD_ASC', () => {
|
||||
expect(
|
||||
getSortLabelSuffixForFieldType({
|
||||
fieldType: FieldMetadataType.NUMBER,
|
||||
orderBy: GraphOrderBy.FIELD_ASC,
|
||||
}),
|
||||
).toBe('ascending');
|
||||
});
|
||||
|
||||
it('returns position ascending for SELECT field with FIELD_POSITION_ASC', () => {
|
||||
expect(
|
||||
getSortLabelSuffixForFieldType({
|
||||
fieldType: FieldMetadataType.SELECT,
|
||||
orderBy: GraphOrderBy.FIELD_POSITION_ASC,
|
||||
}),
|
||||
).toBe('position ascending');
|
||||
});
|
||||
});
|
||||
+13
-40
@@ -1,4 +1,5 @@
|
||||
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
|
||||
import { getChartDefaultOrderByForFieldType } from '@/command-menu/pages/page-layout/utils/getChartDefaultOrderByForFieldType';
|
||||
import { isFieldOrRelationNestedFieldDateKind } from '@/command-menu/pages/page-layout/utils/isFieldOrNestedFieldDateKind';
|
||||
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
@@ -51,10 +52,15 @@ export const buildChartGroupByFieldConfigUpdate = <
|
||||
'PieChartConfiguration',
|
||||
);
|
||||
|
||||
if (isPrimaryAxis) {
|
||||
const existingOrderBy =
|
||||
isBarChart || isLineChart ? configuration.primaryAxisOrderBy : null;
|
||||
const fieldMetadataItem = objectMetadataItem?.fields?.find(
|
||||
(field) => field.id === fieldId,
|
||||
);
|
||||
|
||||
const defaultOrderBy = isDefined(fieldMetadataItem?.type)
|
||||
? getChartDefaultOrderByForFieldType(fieldMetadataItem?.type)
|
||||
: GraphOrderBy.FIELD_ASC;
|
||||
|
||||
if (isPrimaryAxis) {
|
||||
const existingDateGranularity =
|
||||
isBarChart || isLineChart
|
||||
? configuration.primaryAxisDateGranularity
|
||||
@@ -73,19 +79,9 @@ export const buildChartGroupByFieldConfigUpdate = <
|
||||
!isNewFieldDateType &&
|
||||
(isBarChart || isLineChart);
|
||||
|
||||
const isCurrentOrderByValueBased =
|
||||
existingOrderBy === GraphOrderBy.VALUE_ASC ||
|
||||
existingOrderBy === GraphOrderBy.VALUE_DESC;
|
||||
|
||||
const shouldResetOrderBy = isNewFieldDateType && isCurrentOrderByValueBased;
|
||||
|
||||
const newOrderBy = shouldResetOrderBy
|
||||
? GraphOrderBy.FIELD_ASC
|
||||
: (existingOrderBy ?? GraphOrderBy.FIELD_ASC);
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
primaryAxisOrderBy: isDefined(fieldId) ? newOrderBy : null,
|
||||
primaryAxisOrderBy: isDefined(fieldId) ? defaultOrderBy : null,
|
||||
primaryAxisDateGranularity: isDefined(fieldId)
|
||||
? (existingDateGranularity ?? ObjectRecordGroupByDateGranularity.DAY)
|
||||
: null,
|
||||
@@ -94,32 +90,13 @@ export const buildChartGroupByFieldConfigUpdate = <
|
||||
}
|
||||
|
||||
if (isPieChartGroupBy) {
|
||||
const existingOrderBy = isPieChart ? configuration.orderBy : null;
|
||||
|
||||
const existingDateGranularity = isPieChart
|
||||
? configuration.dateGranularity
|
||||
: null;
|
||||
|
||||
const isNewFieldDateType = isFieldOrRelationNestedFieldDateKind({
|
||||
fieldId,
|
||||
subFieldName,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
const isCurrentOrderByValueBased =
|
||||
existingOrderBy === GraphOrderBy.VALUE_ASC ||
|
||||
existingOrderBy === GraphOrderBy.VALUE_DESC;
|
||||
|
||||
const shouldResetOrderBy = isNewFieldDateType && isCurrentOrderByValueBased;
|
||||
|
||||
const newOrderBy = shouldResetOrderBy
|
||||
? GraphOrderBy.FIELD_ASC
|
||||
: (existingOrderBy ?? GraphOrderBy.FIELD_ASC);
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
orderBy: isDefined(fieldId) ? newOrderBy : null,
|
||||
orderBy: isDefined(fieldId) ? defaultOrderBy : null,
|
||||
dateGranularity: isDefined(fieldId)
|
||||
? (existingDateGranularity ?? ObjectRecordGroupByDateGranularity.DAY)
|
||||
: null,
|
||||
@@ -133,9 +110,7 @@ export const buildChartGroupByFieldConfigUpdate = <
|
||||
if (isBarChart) {
|
||||
return {
|
||||
...baseConfig,
|
||||
secondaryAxisOrderBy: isDefined(fieldId)
|
||||
? (configuration.secondaryAxisOrderBy ?? GraphOrderBy.FIELD_ASC)
|
||||
: null,
|
||||
secondaryAxisOrderBy: isDefined(fieldId) ? defaultOrderBy : null,
|
||||
secondaryAxisGroupByDateGranularity: isDefined(fieldId)
|
||||
? (configuration.secondaryAxisGroupByDateGranularity ??
|
||||
ObjectRecordGroupByDateGranularity.DAY)
|
||||
@@ -149,9 +124,7 @@ export const buildChartGroupByFieldConfigUpdate = <
|
||||
if (isLineChart) {
|
||||
return {
|
||||
...baseConfig,
|
||||
secondaryAxisOrderBy: isDefined(fieldId)
|
||||
? (configuration.secondaryAxisOrderBy ?? GraphOrderBy.FIELD_ASC)
|
||||
: null,
|
||||
secondaryAxisOrderBy: isDefined(fieldId) ? defaultOrderBy : null,
|
||||
secondaryAxisGroupByDateGranularity: isDefined(fieldId)
|
||||
? (configuration.secondaryAxisGroupByDateGranularity ??
|
||||
ObjectRecordGroupByDateGranularity.DAY)
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
isFieldMetadataDateKind,
|
||||
isFieldMetadataSelectKind,
|
||||
} from 'twenty-shared/utils';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export type SortOption = {
|
||||
value: GraphOrderBy;
|
||||
icon?: IconComponent | null;
|
||||
};
|
||||
|
||||
type FilterSortOptionsParams = {
|
||||
options: SortOption[];
|
||||
fieldType: FieldMetadataType;
|
||||
};
|
||||
|
||||
export const filterSortOptionsByFieldType = ({
|
||||
options,
|
||||
fieldType,
|
||||
}: FilterSortOptionsParams): SortOption[] => {
|
||||
return options.filter((option) => {
|
||||
const isValueSort =
|
||||
option.value === GraphOrderBy.VALUE_ASC ||
|
||||
option.value === GraphOrderBy.VALUE_DESC;
|
||||
|
||||
const isManualSort = option.value === GraphOrderBy.MANUAL;
|
||||
|
||||
const isPositionSort =
|
||||
option.value === GraphOrderBy.FIELD_POSITION_ASC ||
|
||||
option.value === GraphOrderBy.FIELD_POSITION_DESC;
|
||||
|
||||
const isSelectField = isFieldMetadataSelectKind(fieldType);
|
||||
|
||||
if (isManualSort && !isSelectField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPositionSort && !isSelectField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFieldMetadataDateKind(fieldType)) {
|
||||
return !isValueSort;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const getChartDefaultOrderByForFieldType = (
|
||||
fieldType: FieldMetadataType,
|
||||
): GraphOrderBy => {
|
||||
const isSelectField = fieldType === FieldMetadataType.SELECT;
|
||||
|
||||
return isSelectField
|
||||
? GraphOrderBy.FIELD_POSITION_ASC
|
||||
: GraphOrderBy.FIELD_ASC;
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
type FieldOption = {
|
||||
value: string;
|
||||
position?: number | null;
|
||||
};
|
||||
|
||||
export const getDefaultManualSortOrder = (
|
||||
options: FieldOption[] | null | undefined,
|
||||
): string[] => {
|
||||
if (!options) return [];
|
||||
|
||||
const sortedByPosition = options.toSorted(
|
||||
(a, b) => (a.position ?? 0) - (b.position ?? 0),
|
||||
);
|
||||
|
||||
return sortedByPosition.map((option) => option.value);
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type WidgetConfiguration } from '~/generated/graphql';
|
||||
|
||||
export const getManualSortOrderFromConfig = (
|
||||
configuration: WidgetConfiguration,
|
||||
axis?: 'primary' | 'secondary',
|
||||
): string[] | undefined => {
|
||||
if (isWidgetConfigurationOfType(configuration, 'PieChartConfiguration')) {
|
||||
return configuration.manualSortOrder ?? undefined;
|
||||
}
|
||||
|
||||
if (!isDefined(axis)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
|
||||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration')
|
||||
) {
|
||||
if (axis === 'primary') {
|
||||
return configuration.primaryAxisManualSortOrder ?? undefined;
|
||||
}
|
||||
|
||||
return configuration.secondaryAxisManualSortOrder ?? undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
isFieldMetadataNumericKind,
|
||||
isFieldMetadataTextKind,
|
||||
} from 'twenty-shared/utils';
|
||||
import {
|
||||
type IconComponent,
|
||||
IconSortAscending,
|
||||
IconSortAscendingLetters,
|
||||
IconSortAscendingNumbers,
|
||||
IconSortDescending,
|
||||
IconSortDescendingLetters,
|
||||
IconSortDescendingNumbers,
|
||||
} from 'twenty-ui/display';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const getSortIconForFieldType = ({
|
||||
fieldType,
|
||||
orderBy,
|
||||
}: {
|
||||
fieldType: FieldMetadataType | undefined;
|
||||
orderBy: GraphOrderBy;
|
||||
}): IconComponent => {
|
||||
const isAscending =
|
||||
orderBy === GraphOrderBy.FIELD_ASC ||
|
||||
orderBy === GraphOrderBy.FIELD_POSITION_ASC ||
|
||||
orderBy === GraphOrderBy.VALUE_ASC;
|
||||
|
||||
const isPositionSort =
|
||||
orderBy === GraphOrderBy.FIELD_POSITION_ASC ||
|
||||
orderBy === GraphOrderBy.FIELD_POSITION_DESC;
|
||||
|
||||
if (isPositionSort) {
|
||||
return isAscending ? IconSortAscending : IconSortDescending;
|
||||
}
|
||||
|
||||
if (!fieldType) {
|
||||
return isAscending ? IconSortAscending : IconSortDescending;
|
||||
}
|
||||
|
||||
if (isFieldMetadataTextKind(fieldType)) {
|
||||
return isAscending ? IconSortAscendingLetters : IconSortDescendingLetters;
|
||||
}
|
||||
|
||||
if (isFieldMetadataNumericKind(fieldType)) {
|
||||
return isAscending ? IconSortAscendingNumbers : IconSortDescendingNumbers;
|
||||
}
|
||||
|
||||
if (
|
||||
fieldType === FieldMetadataType.SELECT &&
|
||||
(orderBy === GraphOrderBy.FIELD_ASC || orderBy === GraphOrderBy.FIELD_DESC)
|
||||
) {
|
||||
return isAscending ? IconSortAscendingLetters : IconSortDescendingLetters;
|
||||
}
|
||||
|
||||
return isAscending ? IconSortAscending : IconSortDescending;
|
||||
};
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
isDefined,
|
||||
isFieldMetadataNumericKind,
|
||||
isFieldMetadataTextKind,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const getSortLabelSuffixForFieldType = ({
|
||||
fieldType,
|
||||
orderBy,
|
||||
}: {
|
||||
fieldType: FieldMetadataType | undefined;
|
||||
orderBy: GraphOrderBy;
|
||||
}): string => {
|
||||
const isAscending =
|
||||
orderBy === GraphOrderBy.FIELD_ASC ||
|
||||
orderBy === GraphOrderBy.FIELD_POSITION_ASC ||
|
||||
orderBy === GraphOrderBy.VALUE_ASC;
|
||||
|
||||
if (!isDefined(fieldType)) {
|
||||
return isAscending ? t`ascending` : t`descending`;
|
||||
}
|
||||
|
||||
if (isFieldMetadataTextKind(fieldType)) {
|
||||
return isAscending ? t`alphabetical` : t`reverse alphabetical`;
|
||||
}
|
||||
|
||||
if (isFieldMetadataNumericKind(fieldType)) {
|
||||
return isAscending ? t`ascending` : t`descending`;
|
||||
}
|
||||
|
||||
if (fieldType === FieldMetadataType.SELECT) {
|
||||
if (
|
||||
orderBy === GraphOrderBy.FIELD_ASC ||
|
||||
orderBy === GraphOrderBy.FIELD_DESC
|
||||
) {
|
||||
return isAscending ? t`alphabetical` : t`reverse alphabetical`;
|
||||
}
|
||||
|
||||
if (
|
||||
orderBy === GraphOrderBy.FIELD_POSITION_ASC ||
|
||||
orderBy === GraphOrderBy.FIELD_POSITION_DESC
|
||||
) {
|
||||
return isAscending ? t`position ascending` : t`position descending`;
|
||||
}
|
||||
|
||||
return isAscending ? t`ascending` : t`descending`;
|
||||
}
|
||||
|
||||
return isAscending ? t`ascending` : t`descending`;
|
||||
};
|
||||
Reference in New Issue
Block a user