[Dashboards] Relation fields groupby (#16093)
This commit is contained in:
+3
-8
@@ -25,12 +25,9 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FieldMetadataType,
|
||||
GraphType,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated/graphql';
|
||||
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
const StyledCommandMenuContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -136,9 +133,7 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
|
||||
(field) => field.id === primaryAxisFieldMetadataId,
|
||||
);
|
||||
|
||||
const isPrimaryAxisDate =
|
||||
primaryAxisField?.type === FieldMetadataType.DATE ||
|
||||
primaryAxisField?.type === FieldMetadataType.DATE_TIME;
|
||||
const isPrimaryAxisDate = isFieldMetadataDateKind(primaryAxisField?.type);
|
||||
|
||||
const primaryAxisDateGranularity =
|
||||
configuration.__typename === 'BarChartConfiguration' ||
|
||||
|
||||
+72
-18
@@ -1,4 +1,5 @@
|
||||
import { ChartGroupByFieldSelectionCompositeFieldView } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionCompositeFieldView';
|
||||
import { ChartGroupByFieldSelectionRelationFieldView } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionRelationFieldView';
|
||||
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';
|
||||
@@ -23,6 +24,7 @@ import { useMemo, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { RelationType } from '~/generated/graphql';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
type ChartGroupByFieldSelectionDropdownContentBaseProps<
|
||||
@@ -43,6 +45,9 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
const [selectedCompositeField, setSelectedCompositeField] =
|
||||
useState<FieldMetadataItem | null>(null);
|
||||
|
||||
const [selectedRelationField, setSelectedRelationField] =
|
||||
useState<FieldMetadataItem | null>(null);
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
@@ -77,8 +82,15 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
items: sourceObjectMetadataItem?.fields || [],
|
||||
searchQuery,
|
||||
getSearchableValues: (item) => [item.label, item.name],
|
||||
// TODO: remove the relation filter once group by is supported for relation fields
|
||||
}).filter((field) => !isFieldRelation(field) && !field.isSystem),
|
||||
}).filter((field) => {
|
||||
if (field.isSystem === true) {
|
||||
return false;
|
||||
}
|
||||
if (isFieldRelation(field)) {
|
||||
return field.relation?.type === RelationType.MANY_TO_ONE;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
[sourceObjectMetadataItem?.fields, searchQuery],
|
||||
);
|
||||
|
||||
@@ -94,20 +106,26 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
}
|
||||
|
||||
const handleSelectField = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
if (isFieldRelation(fieldMetadataItem)) {
|
||||
setSelectedRelationField(fieldMetadataItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCompositeFieldType(fieldMetadataItem.type)) {
|
||||
setSelectedCompositeField(fieldMetadataItem);
|
||||
} else {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: buildChartGroupByFieldConfigUpdate({
|
||||
configuration,
|
||||
fieldMetadataIdKey,
|
||||
subFieldNameKey,
|
||||
fieldId: fieldMetadataItem.id,
|
||||
subFieldName: null,
|
||||
}),
|
||||
});
|
||||
closeDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: buildChartGroupByFieldConfigUpdate({
|
||||
configuration,
|
||||
fieldMetadataIdKey,
|
||||
subFieldNameKey,
|
||||
fieldId: fieldMetadataItem.id,
|
||||
subFieldName: null,
|
||||
}),
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleSelectNone = () => {
|
||||
@@ -123,11 +141,15 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
const handleBackFromComposite = () => {
|
||||
setSelectedCompositeField(null);
|
||||
};
|
||||
|
||||
const handleSelectSubField = (subFieldName: string) => {
|
||||
const handleBackFromRelation = () => {
|
||||
setSelectedRelationField(null);
|
||||
};
|
||||
|
||||
const handleSelectCompositeSubField = (subFieldName: string) => {
|
||||
if (!isDefined(selectedCompositeField)) {
|
||||
return;
|
||||
}
|
||||
@@ -144,13 +166,41 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleSelectRelationSubField = (subFieldName: string) => {
|
||||
if (!isDefined(selectedRelationField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: buildChartGroupByFieldConfigUpdate({
|
||||
configuration,
|
||||
fieldMetadataIdKey,
|
||||
subFieldNameKey,
|
||||
fieldId: selectedRelationField.id,
|
||||
subFieldName,
|
||||
}),
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
if (isDefined(selectedRelationField)) {
|
||||
return (
|
||||
<ChartGroupByFieldSelectionRelationFieldView
|
||||
relationField={selectedRelationField}
|
||||
currentSubFieldName={currentSubFieldName}
|
||||
onBack={handleBackFromRelation}
|
||||
onSelectSubField={handleSelectRelationSubField}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isDefined(selectedCompositeField)) {
|
||||
return (
|
||||
<ChartGroupByFieldSelectionCompositeFieldView
|
||||
compositeField={selectedCompositeField}
|
||||
currentSubFieldName={currentSubFieldName}
|
||||
onBack={handleBack}
|
||||
onSelectSubField={handleSelectSubField}
|
||||
onBack={handleBackFromComposite}
|
||||
onSelectSubField={handleSelectCompositeSubField}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -195,11 +245,15 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
text={fieldMetadataItem.label}
|
||||
selected={
|
||||
!isCompositeFieldType(fieldMetadataItem.type) &&
|
||||
!isFieldRelation(fieldMetadataItem) &&
|
||||
currentGroupByFieldMetadataId === fieldMetadataItem.id
|
||||
}
|
||||
focused={selectedItemId === fieldMetadataItem.id}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
hasSubMenu={isCompositeFieldType(fieldMetadataItem.type)}
|
||||
hasSubMenu={
|
||||
isCompositeFieldType(fieldMetadataItem.type) ||
|
||||
isFieldRelation(fieldMetadataItem)
|
||||
}
|
||||
onClick={() => {
|
||||
handleSelectField(fieldMetadataItem);
|
||||
}}
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
import { ChartGroupByFieldSelectionCompositeFieldView } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionCompositeFieldView';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
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 { 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 { useMemo, useState } from 'react';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { IconChevronLeft, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
type ChartGroupByFieldSelectionRelationFieldViewProps = {
|
||||
relationField: FieldMetadataItem;
|
||||
currentSubFieldName: string | undefined;
|
||||
onBack: () => void;
|
||||
onSelectSubField: (subFieldName: string) => void;
|
||||
};
|
||||
|
||||
export const ChartGroupByFieldSelectionRelationFieldView = ({
|
||||
relationField,
|
||||
currentSubFieldName,
|
||||
onBack,
|
||||
onSelectSubField,
|
||||
}: ChartGroupByFieldSelectionRelationFieldViewProps) => {
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const [selectedCompositeField, setSelectedCompositeField] =
|
||||
useState<FieldMetadataItem | null>(null);
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const targetObjectNameSingular =
|
||||
relationField.relation?.targetObjectMetadata?.nameSingular;
|
||||
|
||||
const targetObjectMetadataItem = useMemo(
|
||||
() =>
|
||||
objectMetadataItems.find(
|
||||
(item) => item.nameSingular === targetObjectNameSingular,
|
||||
),
|
||||
[objectMetadataItems, targetObjectNameSingular],
|
||||
);
|
||||
|
||||
const availableFields = useMemo(() => {
|
||||
if (!isDefined(targetObjectMetadataItem)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return filterBySearchQuery({
|
||||
items: targetObjectMetadataItem.fields.filter(
|
||||
(field) =>
|
||||
!field.isSystem &&
|
||||
!isFieldRelation(field) &&
|
||||
// TODO: Backend doesn't fully support date fields for relation fields yet so we hide them for now. https://github.com/twentyhq/core-team-issues/issues/1935
|
||||
!isFieldMetadataDateKind(field.type),
|
||||
),
|
||||
searchQuery,
|
||||
getSearchableValues: (field) => [field.label, field.name],
|
||||
});
|
||||
}, [targetObjectMetadataItem, searchQuery]);
|
||||
|
||||
const handleSelectField = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
if (isCompositeFieldType(fieldMetadataItem.type)) {
|
||||
setSelectedCompositeField(fieldMetadataItem);
|
||||
} else {
|
||||
onSelectSubField(fieldMetadataItem.name);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectCompositeSubField = (compositeSubFieldName: string) => {
|
||||
if (!isDefined(selectedCompositeField)) {
|
||||
return;
|
||||
}
|
||||
onSelectSubField(`${selectedCompositeField.name}.${compositeSubFieldName}`);
|
||||
};
|
||||
|
||||
const handleBackFromComposite = () => {
|
||||
setSelectedCompositeField(null);
|
||||
};
|
||||
|
||||
const [currentNestedFieldName, currentNestedSubFieldName] =
|
||||
currentSubFieldName?.split('.') ?? [];
|
||||
|
||||
if (isDefined(selectedCompositeField)) {
|
||||
return (
|
||||
<ChartGroupByFieldSelectionCompositeFieldView
|
||||
compositeField={selectedCompositeField}
|
||||
currentSubFieldName={currentNestedSubFieldName}
|
||||
onBack={handleBackFromComposite}
|
||||
onSelectSubField={handleSelectCompositeSubField}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={onBack}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{relationField.label}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
{availableFields.length === 0 ? (
|
||||
<MenuItem text={t`No fields available`} />
|
||||
) : (
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={availableFields.map((field) => field.id)}
|
||||
>
|
||||
{availableFields.map((fieldMetadataItem) => (
|
||||
<SelectableListItem
|
||||
key={fieldMetadataItem.id}
|
||||
itemId={fieldMetadataItem.id}
|
||||
onEnter={() => {
|
||||
handleSelectField(fieldMetadataItem);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={fieldMetadataItem.label}
|
||||
selected={
|
||||
!isCompositeFieldType(fieldMetadataItem.type) &&
|
||||
currentNestedFieldName === fieldMetadataItem.name
|
||||
}
|
||||
focused={selectedItemId === fieldMetadataItem.id}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
hasSubMenu={isCompositeFieldType(fieldMetadataItem.type)}
|
||||
onClick={() => {
|
||||
handleSelectField(fieldMetadataItem);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+2
@@ -84,6 +84,7 @@ export const useChartSettingsValues = ({
|
||||
? getFieldLabelWithSubField({
|
||||
field: groupByFieldX,
|
||||
subFieldName: groupBySubFieldNameX,
|
||||
objectMetadataItems,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
@@ -181,6 +182,7 @@ export const useChartSettingsValues = ({
|
||||
? getFieldLabelWithSubField({
|
||||
field: pieChartGroupByField,
|
||||
subFieldName: finalGroupBySubFieldNameY,
|
||||
objectMetadataItems,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ export const useGraphGroupBySortOptionLabels = ({
|
||||
const fieldLabel = getFieldLabelWithSubField({
|
||||
field,
|
||||
subFieldName: groupBySubFieldName,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
switch (graphOrderBy) {
|
||||
|
||||
+1
@@ -39,6 +39,7 @@ export const useGraphXSortOptionLabels = ({
|
||||
const fieldLabel = getFieldLabelWithSubField({
|
||||
field: groupByField,
|
||||
subFieldName: groupBySubFieldNameX,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
const aggregateField = objectMetadataItem?.fields.find(
|
||||
|
||||
+26
-15
@@ -1,5 +1,9 @@
|
||||
import { getRelationFieldLabel } from '@/command-menu/pages/page-layout/utils/getRelationFieldLabel';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { type CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
|
||||
@@ -8,24 +12,31 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
export const getFieldLabelWithSubField = ({
|
||||
field,
|
||||
subFieldName,
|
||||
objectMetadataItems,
|
||||
}: {
|
||||
field: FieldMetadataItem | undefined;
|
||||
subFieldName?: CompositeFieldSubFieldName;
|
||||
subFieldName?: CompositeFieldSubFieldName | string;
|
||||
objectMetadataItems?: ObjectMetadataItem[];
|
||||
}): string => {
|
||||
const subFieldNameLabel =
|
||||
isDefined(subFieldName) && isDefined(field)
|
||||
? getCompositeSubFieldLabel(
|
||||
field.type as CompositeFieldType,
|
||||
subFieldName,
|
||||
)
|
||||
: undefined;
|
||||
if (!isDefined(field?.label)) {
|
||||
return t`Field`;
|
||||
}
|
||||
|
||||
const fieldLabel =
|
||||
isDefined(subFieldNameLabel) && isDefined(field?.label)
|
||||
? `${field.label} ${subFieldNameLabel}`
|
||||
: isDefined(field?.label)
|
||||
? field.label
|
||||
: t`Field`;
|
||||
if (!isDefined(subFieldName)) {
|
||||
return field.label;
|
||||
}
|
||||
|
||||
return fieldLabel;
|
||||
if (isFieldRelation(field)) {
|
||||
return getRelationFieldLabel(field, subFieldName, objectMetadataItems);
|
||||
}
|
||||
|
||||
if (isCompositeFieldType(field.type)) {
|
||||
const subFieldLabel = getCompositeSubFieldLabel(
|
||||
field.type as CompositeFieldType,
|
||||
subFieldName as CompositeFieldSubFieldName,
|
||||
);
|
||||
return subFieldLabel ? `${field.label} ${subFieldLabel}` : field.label;
|
||||
}
|
||||
|
||||
return field.label;
|
||||
};
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { type CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getRelationFieldLabel = (
|
||||
field: FieldMetadataItem,
|
||||
subFieldName: string,
|
||||
objectMetadataItems: ObjectMetadataItem[] | undefined,
|
||||
): string => {
|
||||
if (!isDefined(objectMetadataItems)) {
|
||||
return field.label;
|
||||
}
|
||||
|
||||
const targetObjectNameSingular =
|
||||
field.relation?.targetObjectMetadata?.nameSingular;
|
||||
const targetObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === targetObjectNameSingular,
|
||||
);
|
||||
|
||||
if (!isDefined(targetObjectMetadataItem)) {
|
||||
return field.label;
|
||||
}
|
||||
|
||||
const [nestedFieldName, nestedSubFieldName] = subFieldName.split('.');
|
||||
const nestedField = targetObjectMetadataItem.fields.find(
|
||||
(f) => f.name === nestedFieldName,
|
||||
);
|
||||
|
||||
if (!isDefined(nestedField)) {
|
||||
return field.label;
|
||||
}
|
||||
|
||||
if (
|
||||
!isDefined(nestedSubFieldName) ||
|
||||
!isCompositeFieldType(nestedField.type)
|
||||
) {
|
||||
return `${field.label} ${nestedField.label}`;
|
||||
}
|
||||
|
||||
const compositeSubFieldLabel = getCompositeSubFieldLabel(
|
||||
nestedField.type as CompositeFieldType,
|
||||
nestedSubFieldName as CompositeFieldSubFieldName,
|
||||
);
|
||||
|
||||
return compositeSubFieldLabel
|
||||
? `${field.label} ${nestedField.label} ${compositeSubFieldLabel}`
|
||||
: `${field.label} ${nestedField.label}`;
|
||||
};
|
||||
+5
@@ -2,6 +2,7 @@ import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
|
||||
@@ -21,6 +22,10 @@ const shouldHideDateGranularityBasedOnFieldType = (
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isFieldRelation(field)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !isFieldMetadataDateKind(field.type);
|
||||
};
|
||||
|
||||
|
||||
+3
-5
@@ -13,9 +13,9 @@ import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useG
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { IconChevronLeft, IconSettings, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const ObjectOptionsDropdownCalendarFieldsContent = () => {
|
||||
const { t } = useLingui();
|
||||
@@ -32,10 +32,8 @@ export const ObjectOptionsDropdownCalendarFieldsContent = () => {
|
||||
const setRecordIndexCalendarFieldMetadataId = useSetRecoilState(
|
||||
recordIndexCalendarFieldMetadataIdState,
|
||||
);
|
||||
const availableFieldsForCalendar = objectMetadataItem.fields.filter(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.DATE ||
|
||||
field.type === FieldMetadataType.DATE_TIME,
|
||||
const availableFieldsForCalendar = objectMetadataItem.fields.filter((field) =>
|
||||
isFieldMetadataDateKind(field.type),
|
||||
);
|
||||
|
||||
const calendarFieldMetadata = currentView?.calendarFieldMetadataId
|
||||
|
||||
+2
-5
@@ -14,11 +14,10 @@ import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDim
|
||||
import { filterGroupByResults } from '@/page-layout/widgets/graph/utils/filterGroupByResults';
|
||||
import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey';
|
||||
import { type BarDatum } from '@nivo/bar';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
AxisNameDisplay,
|
||||
FieldMetadataType,
|
||||
type BarChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
@@ -150,9 +149,7 @@ export const transformGroupByDataToBarChartData = ({
|
||||
const showDataLabels = configuration.displayDataLabel ?? false;
|
||||
const showLegend = configuration.displayLegend ?? true;
|
||||
|
||||
const isDateField =
|
||||
groupByFieldX.type === FieldMetadataType.DATE ||
|
||||
groupByFieldX.type === FieldMetadataType.DATE_TIME;
|
||||
const isDateField = isFieldMetadataDateKind(groupByFieldX.type);
|
||||
|
||||
const omitNullValues = configuration.omitNullValues ?? false;
|
||||
|
||||
|
||||
+5
-1
@@ -105,10 +105,14 @@ export const GraphWidgetPieChart = ({
|
||||
const tooltipData = createTooltipData(datum);
|
||||
if (!isDefined(tooltipData)) return null;
|
||||
|
||||
const handleTooltipClick: (() => void) | undefined = isDefined(onSliceClick)
|
||||
? () => handleSliceClick(datum)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<GraphWidgetTooltip
|
||||
items={[tooltipData.tooltipItem]}
|
||||
onGraphWidgetTooltipClick={() => handleSliceClick(datum)}
|
||||
onGraphWidgetTooltipClick={handleTooltipClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+28
-1
@@ -4,7 +4,7 @@ import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
describe('buildGroupByFieldObject', () => {
|
||||
it('should return field with Id suffix for relation fields', () => {
|
||||
it('should return field with Id suffix for relation fields without subFieldName', () => {
|
||||
const field = {
|
||||
name: 'company',
|
||||
type: FieldMetadataType.RELATION,
|
||||
@@ -15,6 +15,33 @@ describe('buildGroupByFieldObject', () => {
|
||||
expect(result).toEqual({ companyId: true });
|
||||
});
|
||||
|
||||
it('should return nested object for relation field with subFieldName', () => {
|
||||
const field = {
|
||||
name: 'company',
|
||||
type: FieldMetadataType.RELATION,
|
||||
} as any;
|
||||
|
||||
const result = buildGroupByFieldObject({ field, subFieldName: 'name' });
|
||||
|
||||
expect(result).toEqual({ company: { name: true } });
|
||||
});
|
||||
|
||||
it('should return deeply nested object for relation with composite subfield', () => {
|
||||
const field = {
|
||||
name: 'company',
|
||||
type: FieldMetadataType.RELATION,
|
||||
} as any;
|
||||
|
||||
const result = buildGroupByFieldObject({
|
||||
field,
|
||||
subFieldName: 'address.addressCity',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
company: { address: { addressCity: true } },
|
||||
});
|
||||
});
|
||||
|
||||
it('should return nested object for composite fields with subfield', () => {
|
||||
const field = {
|
||||
name: 'name',
|
||||
|
||||
+9
@@ -1,4 +1,6 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { buildDateFilterForDayGranularity } from '@/page-layout/widgets/graph/utils/buildDateFilterForDayGranularity';
|
||||
import { buildDateRangeFiltersForGranularity } from '@/page-layout/widgets/graph/utils/buildDateRangeFiltersForGranularity';
|
||||
@@ -69,6 +71,13 @@ export const buildFilterFromChartBucket = ({
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
isFieldRelation(fieldMetadataItem) ||
|
||||
isFieldMorphRelation(fieldMetadataItem)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isFieldMetadataDateKind(fieldMetadataItem.type)) {
|
||||
const parsedBucketDate = new Date(String(bucketRawValue));
|
||||
|
||||
|
||||
+30
-7
@@ -8,9 +8,12 @@ import {
|
||||
type FirstDayOfTheWeek,
|
||||
ObjectRecordGroupByDateGranularity,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
export type GroupByFieldObject = Record<
|
||||
string,
|
||||
boolean | Record<string, boolean | string | Record<string, boolean | string>>
|
||||
>;
|
||||
|
||||
export const buildGroupByFieldObject = ({
|
||||
field,
|
||||
@@ -22,15 +25,35 @@ export const buildGroupByFieldObject = ({
|
||||
subFieldName?: string | null;
|
||||
dateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
firstDayOfTheWeek?: number | null;
|
||||
}): Record<string, boolean | Record<string, boolean | string>> => {
|
||||
}): GroupByFieldObject => {
|
||||
const isRelation = isFieldRelation(field) || isFieldMorphRelation(field);
|
||||
const isComposite = isCompositeFieldType(field.type);
|
||||
const isDateField =
|
||||
field.type === FieldMetadataType.DATE ||
|
||||
field.type === FieldMetadataType.DATE_TIME;
|
||||
const isDateField = isFieldMetadataDateKind(field.type);
|
||||
|
||||
if (isRelation) {
|
||||
return { [`${field.name}Id`]: true };
|
||||
if (!isDefined(subFieldName)) {
|
||||
return { [`${field.name}Id`]: true };
|
||||
}
|
||||
|
||||
const parts = subFieldName.split('.');
|
||||
const nestedFieldName = parts[0];
|
||||
const nestedSubFieldName = parts[1];
|
||||
|
||||
if (isDefined(nestedSubFieldName)) {
|
||||
return {
|
||||
[field.name]: {
|
||||
[nestedFieldName]: {
|
||||
[nestedSubFieldName]: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
[field.name]: {
|
||||
[nestedFieldName]: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (isComposite) {
|
||||
|
||||
+32
-13
@@ -1,17 +1,22 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
|
||||
import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy';
|
||||
import {
|
||||
type AggregateOrderByWithGroupByField,
|
||||
type ObjectRecordOrderByForCompositeField,
|
||||
type ObjectRecordOrderByForRelationField,
|
||||
type ObjectRecordOrderByForScalarField,
|
||||
type ObjectRecordOrderByWithGroupByDateField,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import {
|
||||
type BarChartConfiguration,
|
||||
type LineChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
import { buildGroupByFieldObject } from './buildGroupByFieldObject';
|
||||
import {
|
||||
buildGroupByFieldObject,
|
||||
type GroupByFieldObject,
|
||||
} from './buildGroupByFieldObject';
|
||||
|
||||
export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
|
||||
objectMetadataItem,
|
||||
@@ -51,27 +56,34 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
|
||||
);
|
||||
}
|
||||
|
||||
const groupBy: Array<
|
||||
Record<string, boolean | Record<string, boolean | string>>
|
||||
> = [];
|
||||
const isFieldXDate = isFieldMetadataDateKind(groupByFieldX.type);
|
||||
|
||||
const groupBy: Array<GroupByFieldObject> = [];
|
||||
|
||||
groupBy.push(
|
||||
buildGroupByFieldObject({
|
||||
field: groupByFieldX,
|
||||
subFieldName: groupBySubFieldNameX,
|
||||
dateGranularity:
|
||||
chartConfiguration.primaryAxisDateGranularity ?? undefined,
|
||||
dateGranularity: isFieldXDate
|
||||
? (chartConfiguration.primaryAxisDateGranularity ??
|
||||
GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
|
||||
firstDayOfTheWeek,
|
||||
}),
|
||||
);
|
||||
|
||||
if (isDefined(groupByFieldY)) {
|
||||
const isFieldYDate = isFieldMetadataDateKind(groupByFieldY.type);
|
||||
|
||||
groupBy.push(
|
||||
buildGroupByFieldObject({
|
||||
field: groupByFieldY,
|
||||
subFieldName: groupBySubFieldNameY,
|
||||
dateGranularity:
|
||||
chartConfiguration.secondaryAxisGroupByDateGranularity ?? undefined,
|
||||
dateGranularity: isFieldYDate
|
||||
? (chartConfiguration.secondaryAxisGroupByDateGranularity ??
|
||||
GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
firstDayOfTheWeek,
|
||||
}),
|
||||
);
|
||||
@@ -82,6 +94,7 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
|
||||
| ObjectRecordOrderByForScalarField
|
||||
| ObjectRecordOrderByWithGroupByDateField
|
||||
| ObjectRecordOrderByForCompositeField
|
||||
| ObjectRecordOrderByForRelationField
|
||||
> = [];
|
||||
|
||||
if (isDefined(chartConfiguration.primaryAxisOrderBy)) {
|
||||
@@ -91,8 +104,10 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
|
||||
groupByField: groupByFieldX,
|
||||
groupBySubFieldName: chartConfiguration.primaryAxisGroupBySubFieldName,
|
||||
aggregateOperation,
|
||||
dateGranularity:
|
||||
chartConfiguration.primaryAxisDateGranularity ?? undefined,
|
||||
dateGranularity: isFieldXDate
|
||||
? (chartConfiguration.primaryAxisDateGranularity ??
|
||||
GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -100,6 +115,8 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
|
||||
isDefined(groupByFieldY) &&
|
||||
isDefined(chartConfiguration.secondaryAxisOrderBy)
|
||||
) {
|
||||
const isFieldYDateForOrderBy = isFieldMetadataDateKind(groupByFieldY.type);
|
||||
|
||||
orderBy.push(
|
||||
getGroupByOrderBy({
|
||||
graphOrderBy: chartConfiguration.secondaryAxisOrderBy,
|
||||
@@ -107,8 +124,10 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
|
||||
groupBySubFieldName:
|
||||
chartConfiguration.secondaryAxisGroupBySubFieldName,
|
||||
aggregateOperation,
|
||||
dateGranularity:
|
||||
chartConfiguration.secondaryAxisGroupByDateGranularity ?? undefined,
|
||||
dateGranularity: isFieldYDateForOrderBy
|
||||
? (chartConfiguration.secondaryAxisGroupByDateGranularity ??
|
||||
GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
+18
-7
@@ -1,14 +1,19 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
|
||||
import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy';
|
||||
import {
|
||||
type AggregateOrderByWithGroupByField,
|
||||
type ObjectRecordOrderByForCompositeField,
|
||||
type ObjectRecordOrderByForRelationField,
|
||||
type ObjectRecordOrderByForScalarField,
|
||||
type ObjectRecordOrderByWithGroupByDateField,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { type PieChartConfiguration } from '~/generated/graphql';
|
||||
import { buildGroupByFieldObject } from './buildGroupByFieldObject';
|
||||
import {
|
||||
buildGroupByFieldObject,
|
||||
type GroupByFieldObject,
|
||||
} from './buildGroupByFieldObject';
|
||||
|
||||
export const generateGroupByQueryVariablesFromPieChartConfiguration = ({
|
||||
objectMetadataItem,
|
||||
@@ -38,13 +43,16 @@ export const generateGroupByQueryVariablesFromPieChartConfiguration = ({
|
||||
);
|
||||
}
|
||||
|
||||
const groupBy: Array<
|
||||
Record<string, boolean | Record<string, boolean | string>>
|
||||
> = [
|
||||
const isFieldDate = isFieldMetadataDateKind(groupByField.type);
|
||||
|
||||
const groupBy: Array<GroupByFieldObject> = [
|
||||
buildGroupByFieldObject({
|
||||
field: groupByField,
|
||||
subFieldName: groupBySubFieldName,
|
||||
dateGranularity,
|
||||
|
||||
dateGranularity: isFieldDate
|
||||
? (dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
firstDayOfTheWeek,
|
||||
}),
|
||||
];
|
||||
@@ -54,6 +62,7 @@ export const generateGroupByQueryVariablesFromPieChartConfiguration = ({
|
||||
| ObjectRecordOrderByForScalarField
|
||||
| ObjectRecordOrderByWithGroupByDateField
|
||||
| ObjectRecordOrderByForCompositeField
|
||||
| ObjectRecordOrderByForRelationField
|
||||
> = [];
|
||||
|
||||
if (isDefined(chartConfiguration.orderBy)) {
|
||||
@@ -63,7 +72,9 @@ export const generateGroupByQueryVariablesFromPieChartConfiguration = ({
|
||||
groupByField,
|
||||
groupBySubFieldName,
|
||||
aggregateOperation,
|
||||
dateGranularity,
|
||||
dateGranularity: isFieldDate
|
||||
? (dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
|
||||
import { getRelationFieldOrderBy } from '@/page-layout/widgets/graph/utils/getRelationFieldOrderBy';
|
||||
import {
|
||||
type ObjectRecordGroupByDateGranularity,
|
||||
type ObjectRecordOrderByForCompositeField,
|
||||
type ObjectRecordOrderByForRelationField,
|
||||
type ObjectRecordOrderByForScalarField,
|
||||
type ObjectRecordOrderByWithGroupByDateField,
|
||||
type OrderByDirection,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
|
||||
export const getFieldOrderBy = (
|
||||
groupByField: FieldMetadataItem,
|
||||
groupBySubFieldName: string | null | undefined,
|
||||
dateGranularity: ObjectRecordGroupByDateGranularity | undefined,
|
||||
direction: OrderByDirection,
|
||||
):
|
||||
| ObjectRecordOrderByForScalarField
|
||||
| ObjectRecordOrderByWithGroupByDateField
|
||||
| ObjectRecordOrderByForCompositeField
|
||||
| ObjectRecordOrderByForRelationField => {
|
||||
if (isCompositeFieldType(groupByField.type)) {
|
||||
if (!isDefined(groupBySubFieldName)) {
|
||||
throw new Error(
|
||||
`Group by subFieldName is required for composite fields (field: ${groupByField.name})`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
[groupByField.name]: {
|
||||
[groupBySubFieldName]: direction,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (isFieldMetadataDateKind(groupByField.type)) {
|
||||
return {
|
||||
[groupByField.name]: {
|
||||
orderBy: direction,
|
||||
granularity: dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (isFieldRelation(groupByField) || isFieldMorphRelation(groupByField)) {
|
||||
return getRelationFieldOrderBy(
|
||||
groupByField,
|
||||
groupBySubFieldName,
|
||||
direction,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
[groupByField.name]: direction,
|
||||
};
|
||||
};
|
||||
+13
-49
@@ -1,36 +1,17 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
|
||||
import { getFieldOrderBy } from '@/page-layout/widgets/graph/utils/getFieldOrderBy';
|
||||
import { mapOrderByToDirection } from '@/page-layout/widgets/graph/utils/mapOrderByToDirection';
|
||||
import {
|
||||
OrderByDirection,
|
||||
type AggregateOrderByWithGroupByField,
|
||||
type ObjectRecordGroupByDateGranularity,
|
||||
type ObjectRecordOrderByForCompositeField,
|
||||
type ObjectRecordOrderByForRelationField,
|
||||
type ObjectRecordOrderByForScalarField,
|
||||
type ObjectRecordOrderByWithGroupByDateField,
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
assertUnreachable,
|
||||
isDefined,
|
||||
isFieldMetadataDateKind,
|
||||
} from 'twenty-shared/utils';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
const mapOrderByToDirection = (orderByEnum: GraphOrderBy): OrderByDirection => {
|
||||
switch (orderByEnum) {
|
||||
case GraphOrderBy.FIELD_ASC:
|
||||
return OrderByDirection.AscNullsLast;
|
||||
case GraphOrderBy.FIELD_DESC:
|
||||
return OrderByDirection.DescNullsLast;
|
||||
case GraphOrderBy.VALUE_ASC:
|
||||
return OrderByDirection.AscNullsLast;
|
||||
case GraphOrderBy.VALUE_DESC:
|
||||
return OrderByDirection.DescNullsLast;
|
||||
default:
|
||||
assertUnreachable(orderByEnum);
|
||||
}
|
||||
};
|
||||
|
||||
export const getGroupByOrderBy = ({
|
||||
graphOrderBy,
|
||||
groupByField,
|
||||
@@ -47,34 +28,17 @@ export const getGroupByOrderBy = ({
|
||||
| AggregateOrderByWithGroupByField
|
||||
| ObjectRecordOrderByForScalarField
|
||||
| ObjectRecordOrderByWithGroupByDateField
|
||||
| ObjectRecordOrderByForCompositeField => {
|
||||
| ObjectRecordOrderByForCompositeField
|
||||
| ObjectRecordOrderByForRelationField => {
|
||||
switch (graphOrderBy) {
|
||||
case GraphOrderBy.FIELD_ASC:
|
||||
case GraphOrderBy.FIELD_DESC: {
|
||||
if (isCompositeFieldType(groupByField.type)) {
|
||||
if (!isDefined(groupBySubFieldName)) {
|
||||
throw new Error(
|
||||
`Group by subFieldName is required for composite fields (field: ${groupByField.name})`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
[groupByField.name]: {
|
||||
[groupBySubFieldName]: mapOrderByToDirection(graphOrderBy),
|
||||
},
|
||||
};
|
||||
} else if (isFieldMetadataDateKind(groupByField.type)) {
|
||||
return {
|
||||
[groupByField.name]: {
|
||||
orderBy: mapOrderByToDirection(graphOrderBy),
|
||||
granularity: dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
[groupByField.name]: mapOrderByToDirection(graphOrderBy),
|
||||
};
|
||||
}
|
||||
}
|
||||
case GraphOrderBy.FIELD_DESC:
|
||||
return getFieldOrderBy(
|
||||
groupByField,
|
||||
groupBySubFieldName,
|
||||
dateGranularity,
|
||||
mapOrderByToDirection(graphOrderBy),
|
||||
);
|
||||
case GraphOrderBy.VALUE_ASC:
|
||||
case GraphOrderBy.VALUE_DESC: {
|
||||
if (!isDefined(aggregateOperation)) {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import {
|
||||
type ObjectRecordOrderByForRelationField,
|
||||
type ObjectRecordOrderByForScalarField,
|
||||
type OrderByDirection,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getRelationFieldOrderBy = (
|
||||
groupByField: FieldMetadataItem,
|
||||
groupBySubFieldName: string | null | undefined,
|
||||
direction: OrderByDirection,
|
||||
): ObjectRecordOrderByForScalarField | ObjectRecordOrderByForRelationField => {
|
||||
if (!isDefined(groupBySubFieldName)) {
|
||||
return {
|
||||
[`${groupByField.name}Id`]: direction,
|
||||
};
|
||||
}
|
||||
|
||||
const [nestedFieldName, nestedSubFieldName] = groupBySubFieldName.split('.');
|
||||
|
||||
if (!isDefined(nestedSubFieldName)) {
|
||||
return {
|
||||
[groupByField.name]: {
|
||||
[nestedFieldName]: direction,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
[groupByField.name]: {
|
||||
[nestedFieldName]: {
|
||||
[nestedSubFieldName]: direction,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { OrderByDirection } from 'twenty-shared/types';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const mapOrderByToDirection = (
|
||||
orderByEnum: GraphOrderBy,
|
||||
): OrderByDirection => {
|
||||
switch (orderByEnum) {
|
||||
case GraphOrderBy.FIELD_ASC:
|
||||
return OrderByDirection.AscNullsLast;
|
||||
case GraphOrderBy.FIELD_DESC:
|
||||
return OrderByDirection.DescNullsLast;
|
||||
case GraphOrderBy.VALUE_ASC:
|
||||
return OrderByDirection.AscNullsLast;
|
||||
case GraphOrderBy.VALUE_DESC:
|
||||
return OrderByDirection.DescNullsLast;
|
||||
default:
|
||||
assertUnreachable(orderByEnum);
|
||||
}
|
||||
};
|
||||
+4
-7
@@ -6,9 +6,8 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType, SettingsPath } from 'twenty-shared/types';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
export const useGetAvailableFieldsForCalendar = () => {
|
||||
@@ -26,10 +25,8 @@ export const useGetAvailableFieldsForCalendar = () => {
|
||||
);
|
||||
|
||||
const availableFieldsForCalendar =
|
||||
objectMetadataItem?.readableFields.filter(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.DATE ||
|
||||
field.type === FieldMetadataType.DATE_TIME,
|
||||
objectMetadataItem?.readableFields.filter((field) =>
|
||||
isFieldMetadataDateKind(field.type),
|
||||
) ?? [];
|
||||
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
Reference in New Issue
Block a user