Fixed sub-field filter dropdown content and icons (#12516)

This PR improves the sub-field selection UX in advanced filters.

- Now using the icon of the field instead of the field type
- Now using the label of the field instead of the field type
- Removed now useless constant ICON_NAME_BY_ANY_SUB_FIELD
- Now selects a default value (any or default sub-field for type) when
clicking on the field, instead of waiting for the user to select the
sub-field

Fixes https://github.com/twentyhq/core-team-issues/issues/1005
This commit is contained in:
Lucas Bordeau
2025-06-10 14:00:12 +02:00
committed by GitHub
parent 78d63a3fe8
commit f0f0d85380
5 changed files with 44 additions and 40 deletions

View File

@ -106,17 +106,17 @@ export const AdvancedFilterFieldSelectMenu = ({
selectedFieldMetadataItem.type,
);
selectFieldUsedInAdvancedFilterDropdown({
fieldMetadataItemId: selectedFieldMetadataItem.id,
recordFilterId,
});
if (isCompositeFieldType(filterType)) {
setObjectFilterDropdownSubMenuFieldType(filterType);
setFieldMetadataItemIdUsedInDropdown(selectedFieldMetadataItem.id);
setObjectFilterDropdownIsSelectingCompositeField(true);
} else {
selectFieldUsedInAdvancedFilterDropdown({
fieldMetadataItemId: selectedFieldMetadataItem.id,
recordFilterId,
});
closeAdvancedFilterFieldSelectDropdown();
}
};

View File

@ -10,12 +10,9 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel';
import { DEFAULT_ANY_SUB_FIELD_ICON_NAME } from '@/object-record/record-filter/constants/DefaultAnySubFieldIconName';
import { ICON_NAME_BY_ANY_SUB_FIELD } from '@/object-record/record-filter/constants/IconNameByAnySubField';
import { ICON_NAME_BY_SUB_FIELD } from '@/object-record/record-filter/constants/IconNameBySubField';
import { areCompositeTypeSubFieldsFilterable } from '@/object-record/record-filter/utils/areCompositeTypeSubFieldsFilterable';
import { isCompositeTypeFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeFilterableByAnySubField';
import { isCompositeTypeNonFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeNonFilterableByAnySubField';
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
@ -103,7 +100,7 @@ export const AdvancedFilterSubFieldSelectMenu = ({
const compositeFieldTypeIsFilterableByAnySubField =
isDefined(fieldMetadataItemUsedInDropdown) &&
isCompositeTypeFilterableByAnySubField(
!isCompositeTypeNonFilterableByAnySubField(
fieldMetadataItemUsedInDropdown.type,
);
@ -112,16 +109,6 @@ export const AdvancedFilterSubFieldSelectMenu = ({
...subFieldNames.map((subFieldName) => subFieldName),
];
const iconNameForAnySubField = isDefined(fieldMetadataItemUsedInDropdown)
? ICON_NAME_BY_ANY_SUB_FIELD[fieldMetadataItemUsedInDropdown?.type]
: (null ?? null);
const anySubFieldIcon = getIcon(
iconNameForAnySubField ??
fieldMetadataItemUsedInDropdown?.icon ??
DEFAULT_ANY_SUB_FIELD_ICON_NAME,
);
return (
<DropdownContent>
<DropdownMenuHeader
@ -132,7 +119,7 @@ export const AdvancedFilterSubFieldSelectMenu = ({
/>
}
>
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
{fieldMetadataItemUsedInDropdown?.label}
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<SelectableList
@ -155,7 +142,7 @@ export const AdvancedFilterSubFieldSelectMenu = ({
onClick={() => {
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
LeftIcon={anySubFieldIcon}
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown.icon)}
text={`Any ${fieldMetadataItemUsedInDropdown.label} field`}
/>
</SelectableListItem>

View File

@ -6,10 +6,13 @@ import { objectFilterDropdownSearchInputComponentState } from '@/object-record/o
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { isCompositeTypeNonFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeNonFilterableByAnySubField';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
@ -81,7 +84,13 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
const firstOperand = getRecordFilterOperands({
filterType,
subFieldName,
})[0];
})?.[0];
if (!isDefined(firstOperand)) {
throw new Error(
`No valid operand found for filter type: ${filterType} and subFieldName: ${subFieldName}`,
);
}
setSelectedOperandInDropdown(firstOperand);
@ -94,6 +103,27 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
(recordFilter) => recordFilter.id === recordFilterId,
);
const isCompositeFilterOnAnySubField =
isCompositeFieldType(filterType) && !isDefined(subFieldName);
const compositeFilterNonFilterableByAnySubField =
isCompositeTypeNonFilterableByAnySubField(filterType);
let subFieldNameForNonFilterableWithAny:
| CompositeFieldSubFieldName
| undefined
| null = subFieldName;
if (
isCompositeFilterOnAnySubField &&
compositeFilterNonFilterableByAnySubField
) {
subFieldNameForNonFilterableWithAny =
getDefaultSubFieldNameForCompositeFilterableFieldType(filterType);
}
const subFieldNameToUse =
subFieldName ?? subFieldNameForNonFilterableWithAny;
const newAdvancedFilter = {
id: recordFilterId,
fieldMetadataId: fieldMetadataItem.id,
@ -105,10 +135,10 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
existingRecordFilter?.positionInRecordFilterGroup,
type: filterType,
label: fieldMetadataItem.label,
subFieldName,
subFieldName: subFieldNameToUse,
} satisfies RecordFilter;
setSubFieldNameUsedInDropdown(subFieldName);
setSubFieldNameUsedInDropdown(subFieldNameToUse);
setObjectFilterDropdownSearchInput('');

View File

@ -1,13 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
export const ICON_NAME_BY_ANY_SUB_FIELD: Partial<
Record<FieldMetadataType, string>
> = {
[FieldMetadataType.LINKS]: 'IconLink',
[FieldMetadataType.EMAILS]: 'IconMail',
[FieldMetadataType.PHONES]: 'IconPhone',
[FieldMetadataType.ADDRESS]: 'IconMap',
[FieldMetadataType.ACTOR]: 'IconCreativeCommonsSa',
[FieldMetadataType.FULL_NAME]: 'IconUser',
[FieldMetadataType.POSITION]: 'IconBriefcase',
};

View File

@ -8,8 +8,8 @@ const COMPOSITE_TYPES_NON_FILTERABLE_WITH_ANY = [
type CompositeTypeNonFilterableWithAny =
(typeof COMPOSITE_TYPES_NON_FILTERABLE_WITH_ANY)[number];
export const isCompositeTypeFilterableByAnySubField = (
export const isCompositeTypeNonFilterableByAnySubField = (
fieldType: FieldType,
): fieldType is CompositeTypeNonFilterableWithAny => {
return !COMPOSITE_TYPES_NON_FILTERABLE_WITH_ANY.includes(fieldType as any);
return COMPOSITE_TYPES_NON_FILTERABLE_WITH_ANY.includes(fieldType as any);
};