diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts index eed084061..42b12ed46 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts @@ -54,10 +54,8 @@ describe('useColumnDefinitionsFromFieldMetadata', () => { ); expect(Array.isArray(result.current.columnDefinitions)).toBe(true); - expect(Array.isArray(result.current.filterDefinitions)).toBe(true); expect(Array.isArray(result.current.sortDefinitions)).toBe(true); expect(result.current.columnDefinitions.length).toBe(0); - expect(result.current.filterDefinitions.length).toBe(0); expect(result.current.sortDefinitions.length).toBe(0); }); @@ -76,11 +74,9 @@ describe('useColumnDefinitionsFromFieldMetadata', () => { }, ); - const { columnDefinitions, filterDefinitions, sortDefinitions } = - result.current; + const { columnDefinitions, sortDefinitions } = result.current; expect(columnDefinitions.length).toBe(21); - expect(filterDefinitions.length).toBe(17); expect(sortDefinitions.length).toBe(14); }); }); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts index 35eaa9130..d0793cbe7 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts @@ -4,6 +4,7 @@ import { RelationDefinitionType, } from '~/generated-metadata/graphql'; +import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType'; import { ObjectMetadataItem } from '../types/ObjectMetadataItem'; export const formatFieldMetadataItemsAsFilterDefinitions = ({ @@ -79,7 +80,9 @@ export const getRelationObjectMetadataNamePlural = ({ return field.relationDefinition?.targetObjectMetadata.namePlural; }; -export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => { +export const getFilterTypeFromFieldType = ( + fieldType: FieldMetadataType, +): FilterableFieldType => { switch (fieldType) { case FieldMetadataType.DATE_TIME: return 'DATE_TIME'; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx index 76c136c5f..dea3a49e0 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx @@ -1,8 +1,11 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; -import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { + formatFieldMetadataItemAsFilterDefinition, + getFilterTypeFromFieldType, +} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup'; -import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; +import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; @@ -86,12 +89,17 @@ export const AdvancedFilterAddFilterRuleSelect = ({ field: defaultFieldMetadataItem, }); + const filterType = getFilterTypeFromFieldType( + defaultFieldMetadataItem.type, + ); + upsertCombinedViewFilter({ id: v4(), fieldMetadataId: defaultFieldMetadataItem.id, - operand: getRecordFilterOperandsForRecordFilterDefinition( - defaultFilterDefinition, - )[0], + type: filterType, + operand: getRecordFilterOperands({ + filterType, + })[0], definition: defaultFilterDefinition, value: '', displayValue: '', @@ -123,12 +131,17 @@ export const AdvancedFilterAddFilterRuleSelect = ({ field: defaultFieldMetadataItem, }); + const filterType = getFilterTypeFromFieldType( + defaultFieldMetadataItem.type, + ); + upsertCombinedViewFilter({ id: v4(), fieldMetadataId: defaultFieldMetadataItem.id, - operand: getRecordFilterOperandsForRecordFilterDefinition( - defaultFilterDefinition, - )[0], + type: filterType, + operand: getRecordFilterOperands({ + filterType, + })[0], definition: defaultFilterDefinition, value: '', displayValue: '', diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterFieldSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterFieldSelect.tsx index 290aa037d..93265a6fc 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterFieldSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterFieldSelect.tsx @@ -25,9 +25,9 @@ export const AdvancedFilterViewFilterFieldSelect = ({ }: AdvancedFilterViewFilterFieldSelectProps) => { const { advancedFilterDropdownId } = useAdvancedFilterDropdown(viewFilterId); - const filter = useCurrentViewFilter({ viewFilterId }); + const recordFilter = useCurrentViewFilter({ viewFilterId }); - const selectedFieldLabel = filter?.definition.label ?? ''; + const selectedFieldLabel = recordFilter?.label ?? ''; const setAdvancedFilterViewFilterId = useSetRecoilComponentStateV2( advancedFilterViewFilterIdComponentState, @@ -58,8 +58,8 @@ export const AdvancedFilterViewFilterFieldSelect = ({ /> } onOpen={() => { - setAdvancedFilterViewFilterId(filter?.id); - setAdvancedFilterViewFilterGroupId(filter?.viewFilterGroupId); + setAdvancedFilterViewFilterId(recordFilter?.id); + setAdvancedFilterViewFilterGroupId(recordFilter?.viewFilterGroupId); }} dropdownComponents={ shouldShowCompositeSelectionSubMenu ? ( diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect.tsx index fe8295eec..ea79da58c 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect.tsx @@ -3,7 +3,8 @@ import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; import { v4 } from 'uuid'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; @@ -39,8 +40,8 @@ export const ObjectFilterDropdownBooleanSelect = () => { const theme = useTheme(); const options = [true, false]; - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const selectedOperandInDropdown = useRecoilComponentValueV2( @@ -65,18 +66,22 @@ export const ObjectFilterDropdownBooleanSelect = () => { const handleOptionSelect = (value: boolean) => { if ( - !isDefined(filterDefinitionUsedInDropdown) || + !isDefined(fieldMetadataItemUsedInDropdown) || !isDefined(selectedOperandInDropdown) ) { return; } + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); + applyRecordFilter({ id: selectedFilter?.id ?? v4(), - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, operand: selectedOperandInDropdown, displayValue: value ? 'True' : 'False', - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, value: value.toString(), viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index 77b980e6f..31580d3c9 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -1,6 +1,7 @@ import { v4 } from 'uuid'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue'; @@ -19,8 +20,8 @@ import { isDefined } from 'twenty-shared'; import { FieldMetadataType } from '~/generated-metadata/graphql'; export const ObjectFilterDropdownDateInput = () => { - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const selectedOperandInDropdown = useRecoilComponentValueV2( @@ -41,16 +42,21 @@ export const ObjectFilterDropdownDateInput = () => { ); const isDateTimeInput = - filterDefinitionUsedInDropdown?.type === FieldMetadataType.DATE_TIME; + fieldMetadataItemUsedInDropdown?.type === FieldMetadataType.DATE_TIME; const handleAbsoluteDateChange = (newDate: Date | null) => { setInternalDate(newDate); - if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return; + if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return; + + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); applyRecordFilter({ id: selectedFilter?.id ? selectedFilter.id : v4(), - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + definition: filterDefinition, + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, value: newDate?.toISOString() ?? '', operand: selectedOperandInDropdown, displayValue: isDefined(newDate) @@ -58,7 +64,6 @@ export const ObjectFilterDropdownDateInput = () => { ? newDate.toLocaleString() : newDate.toLocaleDateString() : '', - definition: filterDefinitionUsedInDropdown, viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); }; @@ -70,7 +75,11 @@ export const ObjectFilterDropdownDateInput = () => { unit: VariableDateViewFilterValueUnit; } | null, ) => { - if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return; + if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return; + + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); const value = relativeDate ? computeVariableDateViewFilterValue( @@ -82,11 +91,11 @@ export const ObjectFilterDropdownDateInput = () => { applyRecordFilter({ id: selectedFilter?.id ? selectedFilter.id : v4(), - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, value, operand: selectedOperandInDropdown, displayValue: getRelativeDateDisplayValue(relativeDate), - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx index 8dcf9ab7e..309dda68c 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx @@ -151,9 +151,7 @@ export const ObjectFilterDropdownFilterSelect = ({ filterDefinition: selectedFilterDefinition, }); - setFieldMetadataItemIdUsedInDropdown( - selectedFilterDefinition.fieldMetadataId, - ); + setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemId); closeAdvancedFilterDropdown(); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx index b87f88caf..42b5aada6 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx @@ -1,7 +1,10 @@ +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown'; import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState'; import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState'; import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState'; @@ -9,12 +12,13 @@ import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/o import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel'; import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel'; import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue'; import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; -import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; +import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -37,6 +41,14 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { objectFilterDropdownFirstLevelFilterDefinitionComponentState, ); + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + ); + + const setSubFieldNameUsedInDropdown = useSetRecoilComponentStateV2( + subFieldNameUsedInDropdownComponentState, + ); + const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2( objectFilterDropdownFilterIsSelectedComponentState, ); @@ -83,39 +95,60 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { advancedFilterViewFilterId, ); - const handleSelectFilter = (definition: RecordFilterDefinition | null) => { - if (definition !== null) { + const handleSelectFilter = ( + fieldMetadataItem: FieldMetadataItem | null | undefined, + subFieldName?: string | null | undefined, + ) => { + if (isDefined(fieldMetadataItem)) { + const filterDefinition: RecordFilterDefinition = { + fieldMetadataId: fieldMetadataItem.id, + type: getFilterTypeFromFieldType(fieldMetadataItem.type), + label: fieldMetadataItem.label, + iconName: fieldMetadataItem.icon ?? '', + compositeFieldName: subFieldName ?? undefined, + }; + if ( isDefined(advancedFilterViewFilterId) && isDefined(advancedFilterViewFilterGroupId) ) { closeAdvancedFilterDropdown(); - const operand = - getRecordFilterOperandsForRecordFilterDefinition(definition)[0]; - const { value, displayValue } = getInitialFilterValue( - definition.type, - operand, - ); + const type = getFilterTypeFromFieldType(fieldMetadataItem.type); + + const operand = getRecordFilterOperands({ + filterType: type, + subFieldName: subFieldName, + })[0]; + + const { value, displayValue } = getInitialFilterValue(type, operand); applyRecordFilter({ id: advancedFilterViewFilterId, - fieldMetadataId: definition.fieldMetadataId, + fieldMetadataId: fieldMetadataItem.id, value, operand, displayValue, - definition, + definition: filterDefinition, viewFilterGroupId: advancedFilterViewFilterGroupId, + subFieldName: subFieldName, }); } - setFilterDefinitionUsedInDropdown(definition); - setFieldMetadataItemIdUsedInDropdown(definition.fieldMetadataId); + setFilterDefinitionUsedInDropdown(filterDefinition); + setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id); + + const type = getFilterTypeFromFieldType(fieldMetadataItem.type); setSelectedOperandInDropdown( - getRecordFilterOperandsForRecordFilterDefinition(definition)[0], + getRecordFilterOperands({ + filterType: type, + subFieldName: subFieldName, + })[0], ); + setSubFieldNameUsedInDropdown(subFieldName); + setObjectFilterDropdownSearchInput(''); setObjectFilterDropdownFilterIsSelected(true); @@ -167,36 +200,30 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { key={`select-filter-${-1}`} testId={`select-filter-${-1}`} onClick={() => { - handleSelectFilter(objectFilterDropdownFirstLevelFilterDefinition); + handleSelectFilter(fieldMetadataItemUsedInDropdown); }} LeftIcon={IconApps} text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`} /> {/* TODO: fix this with a backend field on ViewFilter for composite field filter */} - {objectFilterDropdownFirstLevelFilterDefinition.type === 'ACTOR' && + {fieldMetadataItemUsedInDropdown?.type === 'ACTOR' && options.map((subFieldName, index) => ( { - if (isDefined(objectFilterDropdownFirstLevelFilterDefinition)) { - handleSelectFilter({ - ...objectFilterDropdownFirstLevelFilterDefinition, - label: getCompositeSubFieldLabel( - objectFilterDropdownSubMenuFieldType, - subFieldName, - ), - compositeFieldName: subFieldName, - }); + if (isDefined(fieldMetadataItemUsedInDropdown)) { + handleSelectFilter( + fieldMetadataItemUsedInDropdown, + subFieldName, + ); } }} text={getCompositeSubFieldLabel( objectFilterDropdownSubMenuFieldType, subFieldName, )} - LeftIcon={getIcon( - objectFilterDropdownFirstLevelFilterDefinition?.iconName, - )} + LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)} /> ))} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx index 9ecf90ce9..6e4603772 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx @@ -7,14 +7,15 @@ import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/ import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; -import { CompositeFilterableFieldType } from '@/object-record/record-filter/types/CompositeFilterableFieldType'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { + formatFieldMetadataItemAsFilterDefinition, + getFilterTypeFromFieldType, +} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField'; -import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; -import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; +import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; @@ -57,16 +58,10 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ OBJECT_FILTER_DROPDOWN_ID, ); - const filterDefinitionToSelect = formatFieldMetadataItemAsFilterDefinition({ - field: fieldMetadataItemToSelect, - }); - const isSelectedItem = useRecoilValue( isSelectedItemIdSelector(fieldMetadataItemToSelect.id), ); - const isACompositeField = isCompositeField(fieldMetadataItemToSelect.type); - const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( selectedOperandInDropdownComponentState, ); @@ -85,27 +80,28 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ advancedFilterViewFilterId, ); - const handleSelectFilterDefinition = ( - availableFilterDefinition: RecordFilterDefinition, - ) => { + const handleSelectFilter = (fieldMetadataItem: FieldMetadataItem) => { closeAdvancedFilterDropdown(); - setFieldMetadataItemIdUsedInDropdown( - availableFilterDefinition.fieldMetadataId, - ); - setFilterDefinitionUsedInDropdown(availableFilterDefinition); + setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id); + + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItem, + }); + + setFilterDefinitionUsedInDropdown(filterDefinition); if ( - availableFilterDefinition.type === 'RELATION' || - availableFilterDefinition.type === 'SELECT' + filterDefinition.type === 'RELATION' || + filterDefinition.type === 'SELECT' ) { setHotkeyScope(RelationPickerHotkeyScope.RelationPicker); } setSelectedOperandInDropdown( - getRecordFilterOperandsForRecordFilterDefinition( - availableFilterDefinition, - )[0], + getRecordFilterOperands({ + filterType: filterDefinition.type, + })[0], ); setObjectFilterDropdownFilterIsSelected(true); @@ -113,20 +109,29 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ const { getIcon } = useIcons(); + const Icon = getIcon(fieldMetadataItemToSelect.icon); + + const shouldShowSubMenu = isCompositeField(fieldMetadataItemToSelect.type); + const handleClick = () => { resetSelectedItem(); - if (isACompositeField) { - // TODO: create isCompositeFilterableFieldType type guard - setObjectFilterDropdownSubMenuFieldType( - filterDefinitionToSelect.type as CompositeFilterableFieldType, - ); - setObjectFilterDropdownFirstLevelFilterDefinition( - filterDefinitionToSelect, - ); + const filterType = getFilterTypeFromFieldType( + fieldMetadataItemToSelect.type, + ); + + if (isCompositeField(filterType)) { + setObjectFilterDropdownSubMenuFieldType(filterType); + + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemToSelect, + }); + + setObjectFilterDropdownFirstLevelFilterDefinition(filterDefinition); + setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemToSelect.id); setObjectFilterDropdownIsSelectingCompositeField(true); } else { - handleSelectFilterDefinition(filterDefinitionToSelect); + handleSelectFilter(fieldMetadataItemToSelect); } }; @@ -135,9 +140,9 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ selected={false} hovered={isSelectedItem} onClick={handleClick} - LeftIcon={getIcon(filterDefinitionToSelect.iconName)} - text={filterDefinitionToSelect.label} - hasSubMenu={isACompositeField} + LeftIcon={Icon} + text={fieldMetadataItemToSelect.label} + hasSubMenu={shouldShowSubMenu} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput.tsx index 58f1bfc4c..26f7794d0 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput.tsx @@ -1,7 +1,8 @@ import { ChangeEvent, useCallback, useState } from 'react'; import { v4 } from 'uuid'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; @@ -13,8 +14,8 @@ export const ObjectFilterDropdownNumberInput = () => { selectedOperandInDropdownComponentState, ); - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const selectedFilter = useRecoilComponentValueV2( @@ -39,27 +40,32 @@ export const ObjectFilterDropdownNumberInput = () => { }, [hasFocused], ); + return ( - filterDefinitionUsedInDropdown && + fieldMetadataItemUsedInDropdown && selectedOperandInDropdown && ( ) => { const newValue = event.target.value; setInputValue(newValue); + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); + applyRecordFilter({ id: selectedFilter?.id ? selectedFilter.id : v4(), - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '', value: newValue, operand: selectedOperandInDropdown, displayValue: newValue, - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); }} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx index 48bf47878..2db46230a 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx @@ -1,10 +1,16 @@ import { v4 } from 'uuid'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { + formatFieldMetadataItemAsFilterDefinition, + getFilterTypeFromFieldType, +} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; 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 { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; +import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -13,7 +19,6 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared'; import { MenuItem } from 'twenty-ui'; -import { getRecordFilterOperandsForRecordFilterDefinition } from '../../record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; import { getOperandLabel } from '../utils/getOperandLabel'; const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)` @@ -22,8 +27,8 @@ const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)` `; export const ObjectFilterDropdownOperandSelect = () => { - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( @@ -34,14 +39,21 @@ export const ObjectFilterDropdownOperandSelect = () => { selectedFilterComponentState, ); + const subFieldNameUsedInDropdown = useRecoilComponentValueV2( + subFieldNameUsedInDropdownComponentState, + ); + const { applyRecordFilter } = useApplyRecordFilter(); const { closeDropdown } = useDropdown(); - const operandsForFilterType = isDefined(filterDefinitionUsedInDropdown) - ? getRecordFilterOperandsForRecordFilterDefinition( - filterDefinitionUsedInDropdown, - ) + const operandsForFilterType = isDefined(fieldMetadataItemUsedInDropdown) + ? getRecordFilterOperands({ + filterType: getFilterTypeFromFieldType( + fieldMetadataItemUsedInDropdown.type, + ), + subFieldName: subFieldNameUsedInDropdown, + }) : []; const handleOperandChange = (newOperand: ViewFilterOperand) => { @@ -55,36 +67,48 @@ export const ObjectFilterDropdownOperandSelect = () => { setSelectedOperandInDropdown(newOperand); - if (isValuelessOperand && isDefined(filterDefinitionUsedInDropdown)) { + if (isValuelessOperand && isDefined(fieldMetadataItemUsedInDropdown)) { + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); + applyRecordFilter({ id: v4(), - fieldMetadataId: filterDefinitionUsedInDropdown?.fieldMetadataId ?? '', + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, displayValue: '', operand: newOperand, value: '', - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, }); return; } if ( - isDefined(filterDefinitionUsedInDropdown) && + isDefined(fieldMetadataItemUsedInDropdown) && isDefined(selectedFilter) ) { + const filterType = getFilterTypeFromFieldType( + fieldMetadataItemUsedInDropdown.type, + ); + const { value, displayValue } = getInitialFilterValue( - filterDefinitionUsedInDropdown.type, + filterType, newOperand, selectedFilter.value, selectedFilter.displayValue, ); + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); + applyRecordFilter({ id: selectedFilter.id ? selectedFilter.id : v4(), fieldMetadataId: selectedFilter.fieldMetadataId, displayValue, operand: newOperand, value, - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, }); } }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx index 0056a8ad8..da6089462 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx @@ -13,7 +13,8 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; @@ -32,8 +33,8 @@ type SelectOptionForFilter = FieldMetadataItemOption & { }; export const ObjectFilterDropdownOptionSelect = () => { - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const objectFilterDropdownSelectedOptionValues = useRecoilComponentValueV2( @@ -66,7 +67,7 @@ export const ObjectFilterDropdownOptionSelect = () => { const selectedItemId = useRecoilValue(selectedItemIdState); - const fieldMetaDataId = filterDefinitionUsedInDropdown?.fieldMetadataId ?? ''; + const fieldMetaDataId = fieldMetadataItemUsedInDropdown?.id ?? ''; const { selectOptions } = useOptionsForSelect(fieldMetaDataId); @@ -125,7 +126,7 @@ export const ObjectFilterDropdownOptionSelect = () => { : selectedOptions.map((option) => option.label).join(', '); if ( - isDefined(filterDefinitionUsedInDropdown) && + isDefined(fieldMetadataItemUsedInDropdown) && isDefined(selectedOperandInDropdown) ) { const newFilterValue = @@ -133,12 +134,16 @@ export const ObjectFilterDropdownOptionSelect = () => { ? JSON.stringify(selectedOptions.map((option) => option.value)) : EMPTY_FILTER_VALUE; + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); + applyRecordFilter({ id: selectedFilter?.id ? selectedFilter.id : v4(), - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, operand: selectedOperandInDropdown, displayValue: filterDisplayValue, - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, value: newFilterValue, viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput.tsx index 0a71e6cc9..5021a4b91 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput.tsx @@ -1,6 +1,5 @@ import { v4 } from 'uuid'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues'; import { FieldRatingValue } from '@/object-record/record-field/types/FieldMetadata'; @@ -9,6 +8,8 @@ import { RatingInput } from '@/ui/field/input/components/RatingInput'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; const convertFieldRatingValueToNumber = ( rating: Exclude, @@ -37,8 +38,8 @@ export const ObjectFilterDropdownRatingInput = () => { selectedOperandInDropdownComponentState, ); - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const selectedFilter = useRecoilComponentValueV2( @@ -48,7 +49,7 @@ export const ObjectFilterDropdownRatingInput = () => { const { applyRecordFilter } = useApplyRecordFilter(); return ( - filterDefinitionUsedInDropdown && + fieldMetadataItemUsedInDropdown && selectedOperandInDropdown && ( { return; } + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); + applyRecordFilter?.({ id: selectedFilter?.id ? selectedFilter.id : v4(), - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, value: convertFieldRatingValueToNumber(newValue), operand: selectedOperandInDropdown, displayValue: convertFieldRatingValueToNumber(newValue), - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); }} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx index dd2299a03..dadd03621 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx @@ -1,8 +1,8 @@ -import { useState } from 'react'; -import { v4 } from 'uuid'; - import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { getRelationObjectMetadataNameSingular } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { + formatFieldMetadataItemAsFilterDefinition, + getRelationObjectMetadataNameSingular, +} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { ObjectFilterDropdownRecordPinnedItems } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems'; import { CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID } from '@/object-record/object-filter-dropdown/constants/CurrentWorkspaceMemberSelectableItemId'; import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; @@ -24,6 +24,7 @@ import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validat import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema'; import { isDefined } from 'twenty-shared'; import { IconUserCircle } from 'twenty-ui'; +import { v4 } from 'uuid'; export const EMPTY_FILTER_VALUE: string = JSON.stringify({ isCurrentWorkspaceMemberSelected: false, @@ -68,8 +69,6 @@ export const ObjectFilterDropdownRecordSelect = ({ const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(viewComponentId); - const [fieldId] = useState(v4()); - const { isCurrentWorkspaceMemberSelected } = jsonRelationFilterValueSchema .catch({ isCurrentWorkspaceMemberSelected: false, @@ -200,17 +199,21 @@ export const ObjectFilterDropdownRecordSelect = ({ currentViewWithCombinedFiltersAndSorts?.viewFilters.find( (viewFilter) => viewFilter.fieldMetadataId === - filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataItemUsedInFilterDropdown.id, ); - const filterId = viewFilter?.id ?? fieldId; + const filterId = viewFilter?.id ?? v4(); + + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInFilterDropdown, + }); applyRecordFilter({ id: selectedFilter?.id ? selectedFilter.id : filterId, - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, operand: selectedOperandInDropdown, displayValue: filterDisplayValue, - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataId: fieldMetadataItemUsedInFilterDropdown.id, value: newFilterValue, viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput.tsx index 6f0c8b19a..9a4a7b3b9 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput.tsx @@ -1,6 +1,6 @@ import { ChangeEvent, useCallback, useState } from 'react'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; @@ -8,8 +8,8 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; export const ObjectFilterDropdownSearchInput = () => { - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const selectedOperandInDropdown = useRecoilComponentValueV2( @@ -37,14 +37,14 @@ export const ObjectFilterDropdownSearchInput = () => { [hasFocused], ); return ( - filterDefinitionUsedInDropdown && + fieldMetadataItemUsedInDropdown && selectedOperandInDropdown && ( ) => { setObjectFilterDropdownSearchInput(event.target.value); }} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx index cf61eda46..870c3bc7e 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx @@ -1,8 +1,9 @@ import { useState } from 'react'; import { v4 } from 'uuid'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { useEmptyRecordFilter } from '@/object-record/object-filter-dropdown/hooks/useEmptyRecordFilter'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; @@ -50,8 +51,8 @@ export const ObjectFilterDropdownSourceSelect = ({ selectedOperandInDropdownComponentState, ); - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const { applyRecordFilter } = useApplyRecordFilter(viewComponentId); @@ -87,13 +88,15 @@ export const ObjectFilterDropdownSourceSelect = ({ (id) => id !== itemToSelect.id, ); - if (!filterDefinitionUsedInDropdown) { - throw new Error('Filter definition used in dropdown should be defined'); + if (!isDefined(fieldMetadataItemUsedInFilterDropdown)) { + throw new Error( + 'Field metadata item used in filter dropdown should be defined', + ); } if (newSelectedItemIds.length === 0) { emptyRecordFilter(); - removeRecordFilter(filterDefinitionUsedInDropdown.fieldMetadataId); + removeRecordFilter(fieldMetadataItemUsedInFilterDropdown.id); deleteCombinedViewFilter(fieldId); return; } @@ -110,7 +113,7 @@ export const ObjectFilterDropdownSourceSelect = ({ : selectedItemNames.join(', '); if ( - isDefined(filterDefinitionUsedInDropdown) && + isDefined(fieldMetadataItemUsedInFilterDropdown) && isDefined(selectedOperandInDropdown) ) { const newFilterValue = @@ -122,17 +125,21 @@ export const ObjectFilterDropdownSourceSelect = ({ currentViewWithCombinedFiltersAndSorts?.viewFilters.find( (viewFilter) => viewFilter.fieldMetadataId === - filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataItemUsedInFilterDropdown.id, ); const filterId = viewFilter?.id ?? fieldId; + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInFilterDropdown, + }); + applyRecordFilter({ id: selectedFilter?.id ? selectedFilter.id : filterId, - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, operand: selectedOperandInDropdown || ViewFilterOperand.Is, displayValue: filterDisplayValue, - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + fieldMetadataId: fieldMetadataItemUsedInFilterDropdown.id, value: newFilterValue, viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput.tsx index b3977d230..ab5f38d9f 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput.tsx @@ -1,7 +1,7 @@ import { ChangeEvent, useCallback, useState } from 'react'; -import { v4 } from 'uuid'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; @@ -9,13 +9,13 @@ import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApp import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { v4 } from 'uuid'; export const ObjectFilterDropdownTextSearchInput = () => { - const [filterId] = useState(v4()); const [hasFocused, setHasFocused] = useState(false); - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, ); const selectedOperandInDropdown = useRecoilComponentValueV2( @@ -47,24 +47,28 @@ export const ObjectFilterDropdownTextSearchInput = () => { [hasFocused], ); return ( - filterDefinitionUsedInDropdown && + fieldMetadataItemUsedInDropdown && selectedOperandInDropdown && ( ) => { setObjectFilterDropdownSearchInput(event.target.value); + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemUsedInDropdown, + }); + applyRecordFilter({ - id: selectedFilter?.id ?? filterId, - fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + id: selectedFilter?.id ?? v4(), + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, value: event.target.value, operand: selectedOperandInDropdown, displayValue: event.target.value, - definition: filterDefinitionUsedInDropdown, + definition: filterDefinition, viewFilterGroupId: selectedFilter?.viewFilterGroupId, }); }} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState.ts index db286d739..4a5429b05 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState.ts @@ -2,7 +2,7 @@ import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/ob import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; export const subFieldNameUsedInDropdownComponentState = createComponentStateV2< - string | null + string | null | undefined >({ key: 'subFieldNameUsedInDropdownComponentState', defaultValue: null, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField.ts new file mode 100644 index 000000000..e2ed3909a --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField.ts @@ -0,0 +1,7 @@ +import { FieldActorValue } from '@/object-record/record-field/types/FieldMetadata'; + +export const isFilterOnActorSourceSubField = ( + subFieldName?: string | null | undefined, +) => { + return subFieldName === ('source' satisfies keyof FieldActorValue); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx index 9c8d1f021..490ae3345 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx @@ -47,6 +47,8 @@ describe('useRemoveRecordFilter', () => { label: 'Test Field', iconName: 'IconText', }, + label: 'Test Field', + type: 'TEXT', }; // First add a filter @@ -96,6 +98,8 @@ describe('useRemoveRecordFilter', () => { label: 'Test Field', iconName: 'IconText', }, + label: 'Test Field', + type: 'TEXT', }; // Add a filter diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx index 3b25ff2f0..d8725e5a0 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx @@ -41,6 +41,8 @@ describe('useUpsertRecordFilter', () => { label: 'Test Field', iconName: 'IconText', }, + label: 'Test Field', + type: 'TEXT', }; act(() => { @@ -79,6 +81,8 @@ describe('useUpsertRecordFilter', () => { label: 'Test Field', iconName: 'IconText', }, + label: 'Test Field', + type: 'TEXT', }; const updatedFilter: RecordFilter = { @@ -93,6 +97,8 @@ describe('useUpsertRecordFilter', () => { label: 'Test Field', iconName: 'IconText', }, + label: 'Test Field', + type: 'TEXT', }; act(() => { diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts index 3e7291f57..a6bc5a85a 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts @@ -17,16 +17,25 @@ export const useUpsertRecordFilter = () => { currentRecordFiltersCallbackState, ); + // TODO: This is a temporary solution to ensure that the record filter is compatible with filter definitions + // Label and type will be set without definition + const recordFilterToSet: RecordFilter = { + ...filter, + label: filter.definition.label, + type: filter.definition.type, + }; + const foundRecordFilterInCurrentRecordFilters = currentRecordFilters.some( (existingFilter) => - existingFilter.fieldMetadataId === filter.fieldMetadataId, + existingFilter.fieldMetadataId === + recordFilterToSet.fieldMetadataId, ); if (!foundRecordFilterInCurrentRecordFilters) { set(currentRecordFiltersCallbackState, [ ...currentRecordFilters, - filter, + recordFilterToSet, ]); } else { set(currentRecordFiltersCallbackState, (currentRecordFilters) => { @@ -34,11 +43,12 @@ export const useUpsertRecordFilter = () => { const indexOfFilterToUpdate = newCurrentRecordFilters.findIndex( (existingFilter) => - existingFilter.fieldMetadataId === filter.fieldMetadataId, + existingFilter.fieldMetadataId === + recordFilterToSet.fieldMetadataId, ); newCurrentRecordFilters[indexOfFilterToUpdate] = { - ...filter, + ...recordFilterToSet, }; return newCurrentRecordFilters; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts index d6760f92f..1065310be 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts @@ -1,3 +1,4 @@ +import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType'; import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; @@ -7,11 +8,12 @@ export type RecordFilter = { fieldMetadataId: string; value: string; displayValue: string; + type?: FilterableFieldType; viewFilterGroupId?: string; displayAvatarUrl?: string; operand: ViewFilterOperand; positionInViewFilterGroup?: number | null; definition: RecordFilterDefinition; label?: string; - subFieldName?: string; + subFieldName?: string | null | undefined; }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts index c6b30384b..5e2200182 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts @@ -795,7 +795,7 @@ const computeFilterRecordGqlOperationFilter = ( ); default: throw new Error( - `Unknown operand ${filter.operand} for ${filter.definition.label} filter`, + `Unknown operand ${filter.operand} for ${filter.label} filter`, ); } } diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts new file mode 100644 index 000000000..a94c9a5cc --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts @@ -0,0 +1,105 @@ +import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField'; +import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType'; +import { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand'; + +export type GetRecordFilterOperandsParams = { + filterType: FilterableFieldType; + subFieldName?: string | null | undefined; +}; + +export const getRecordFilterOperands = ({ + filterType, + subFieldName, +}: GetRecordFilterOperandsParams): RecordFilterOperand[] => { + const emptyOperands = [ + RecordFilterOperand.IsEmpty, + RecordFilterOperand.IsNotEmpty, + ]; + + const relationOperands = [RecordFilterOperand.Is, RecordFilterOperand.IsNot]; + + switch (filterType) { + case 'TEXT': + case 'EMAILS': + case 'FULL_NAME': + case 'ADDRESS': + case 'LINKS': + case 'PHONES': + return [ + RecordFilterOperand.Contains, + RecordFilterOperand.DoesNotContain, + ...emptyOperands, + ]; + case 'CURRENCY': + case 'NUMBER': + return [ + RecordFilterOperand.GreaterThan, + RecordFilterOperand.LessThan, + ...emptyOperands, + ]; + case 'RAW_JSON': + return [ + RecordFilterOperand.Contains, + RecordFilterOperand.DoesNotContain, + ...emptyOperands, + ]; + case 'DATE_TIME': + case 'DATE': + return [ + RecordFilterOperand.Is, + RecordFilterOperand.IsRelative, + RecordFilterOperand.IsInPast, + RecordFilterOperand.IsInFuture, + RecordFilterOperand.IsToday, + RecordFilterOperand.IsBefore, + RecordFilterOperand.IsAfter, + ...emptyOperands, + ]; + case 'RATING': + return [ + RecordFilterOperand.Is, + RecordFilterOperand.GreaterThan, + RecordFilterOperand.LessThan, + ...emptyOperands, + ]; + case 'RELATION': + return [...relationOperands, ...emptyOperands]; + case 'MULTI_SELECT': + return [ + RecordFilterOperand.Contains, + RecordFilterOperand.DoesNotContain, + ...emptyOperands, + ]; + case 'SELECT': + return [ + RecordFilterOperand.Is, + RecordFilterOperand.IsNot, + ...emptyOperands, + ]; + case 'ACTOR': { + if (isFilterOnActorSourceSubField(subFieldName)) { + return [ + RecordFilterOperand.Is, + RecordFilterOperand.IsNot, + ...emptyOperands, + ]; + } + + return [ + RecordFilterOperand.Contains, + RecordFilterOperand.DoesNotContain, + ...emptyOperands, + ]; + } + case 'ARRAY': + return [ + RecordFilterOperand.Contains, + RecordFilterOperand.DoesNotContain, + ...emptyOperands, + ]; + case 'BOOLEAN': + return [RecordFilterOperand.Is]; + default: + return []; + } +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx index 055c9a129..6a31da197 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx @@ -30,9 +30,7 @@ export const RecordTableEmptyStateSoftDelete = () => { const handleButtonClick = async () => { const deletedFilter = tableFilters.find( - (filter) => - filter.definition.label === 'Deleted' && - filter.operand === 'isNotEmpty', + (filter) => filter.label === 'Deleted' && filter.operand === 'isNotEmpty', ); if (!deletedFilter) { diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx index 09ff2ef4e..ff74e221c 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx @@ -1,5 +1,6 @@ import { useIcons } from 'twenty-ui'; +import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById'; import { getOperandLabelShort } from '@/object-record/object-filter-dropdown/utils/getOperandLabel'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { SortOrFilterChip } from '@/views/components/SortOrFilterChip'; @@ -14,13 +15,20 @@ export const EditableFilterChip = ({ onRemove, }: EditableFilterChipProps) => { const { getIcon } = useIcons(); + + const { fieldMetadataItem } = useFieldMetadataItemById( + viewFilter.fieldMetadataId, + ); + + const FieldMetadataItemIcon = getIcon(fieldMetadataItem.icon); + return ( ); diff --git a/packages/twenty-front/src/modules/views/components/VariantFilterChip.tsx b/packages/twenty-front/src/modules/views/components/VariantFilterChip.tsx index 71f4fe02d..32077b7f8 100644 --- a/packages/twenty-front/src/modules/views/components/VariantFilterChip.tsx +++ b/packages/twenty-front/src/modules/views/components/VariantFilterChip.tsx @@ -1,5 +1,6 @@ import { useIcons } from 'twenty-ui'; +import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; @@ -9,12 +10,12 @@ import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedVie import { useParams } from 'react-router-dom'; type VariantFilterChipProps = { - viewFilter: RecordFilter; + recordFilter: RecordFilter; viewBarId: string; }; export const VariantFilterChip = ({ - viewFilter, + recordFilter, viewBarId, }: VariantFilterChipProps) => { const { deleteCombinedViewFilter } = useDeleteCombinedViewFilters(); @@ -30,17 +31,23 @@ export const VariantFilterChip = ({ viewBarId, }); + const { fieldMetadataItem } = useFieldMetadataItemById( + recordFilter.fieldMetadataId, + ); + const { removeRecordFilter } = useRemoveRecordFilter(); const { getIcon } = useIcons(); + const FieldMetadataItemIcon = getIcon(fieldMetadataItem.icon); + const handleRemoveClick = () => { - deleteCombinedViewFilter(viewFilter.id); - removeRecordFilter(viewFilter.fieldMetadataId); + deleteCombinedViewFilter(recordFilter.id); + removeRecordFilter(recordFilter.fieldMetadataId); if ( - viewFilter.definition.label === 'Deleted' && - viewFilter.operand === 'isNotEmpty' + recordFilter.label === 'Deleted' && + recordFilter.operand === 'isNotEmpty' ) { toggleSoftDeleteFilterState(false); } @@ -48,11 +55,11 @@ export const VariantFilterChip = ({ return ( ); diff --git a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx index 21e9ac782..2fbfb10ff 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx @@ -193,7 +193,7 @@ export const ViewBarDetails = ({ {otherViewFilters.map((viewFilter) => ( ))} diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx index 34fbaebd0..cfbb95aca 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState'; import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; @@ -21,8 +21,8 @@ export const ViewBarFilterEffect = ({ }: ViewBarFilterEffectProps) => { const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( - filterDefinitionUsedInDropdownComponentState, + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, filterDropdownId, ); @@ -38,12 +38,11 @@ export const ViewBarFilterEffect = ({ ); useEffect(() => { - if (filterDefinitionUsedInDropdown?.type === 'RELATION') { + if (fieldMetadataItemUsedInDropdown?.type === 'RELATION') { const viewFilterUsedInDropdown = currentViewWithCombinedFiltersAndSorts?.viewFilters.find( (filter) => - filter.fieldMetadataId === - filterDefinitionUsedInDropdown?.fieldMetadataId, + filter.fieldMetadataId === fieldMetadataItemUsedInDropdown?.id, ); const { selectedRecordIds } = jsonRelationFilterValueSchema @@ -57,14 +56,13 @@ export const ViewBarFilterEffect = ({ setObjectFilterDropdownSelectedRecordIds(selectedRecordIds); } else if ( - isDefined(filterDefinitionUsedInDropdown) && - ['SELECT', 'MULTI_SELECT'].includes(filterDefinitionUsedInDropdown.type) + isDefined(fieldMetadataItemUsedInDropdown) && + ['SELECT', 'MULTI_SELECT'].includes(fieldMetadataItemUsedInDropdown.type) ) { const viewFilterUsedInDropdown = currentViewWithCombinedFiltersAndSorts?.viewFilters.find( (filter) => - filter.fieldMetadataId === - filterDefinitionUsedInDropdown?.fieldMetadataId, + filter.fieldMetadataId === fieldMetadataItemUsedInDropdown?.id, ); const viewFilterSelectedRecords = isNonEmptyString( @@ -75,7 +73,7 @@ export const ViewBarFilterEffect = ({ setObjectFilterDropdownSelectedOptionValues(viewFilterSelectedRecords); } }, [ - filterDefinitionUsedInDropdown, + fieldMetadataItemUsedInDropdown, setObjectFilterDropdownSelectedRecordIds, setObjectFilterDropdownSelectedOptionValues, currentViewWithCombinedFiltersAndSorts, diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx index 0f8435c83..327d3a000 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx @@ -1,7 +1,11 @@ import { act, renderHook } from '@testing-library/react'; -import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { + formatFieldMetadataItemAsFilterDefinition, + getFilterTypeFromFieldType, +} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -39,7 +43,7 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { fieldMetadataId: mockFieldMetadataItem.id, operand: ViewFilterOperand.Contains, value: 'test', - displayValue: 'test', + displayValue: mockFieldMetadataItem.label, viewFilterGroupId: 'group-1', positionInViewFilterGroup: 0, definition: mockFilterDefinition, @@ -99,7 +103,9 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { viewFilterGroupId: mockViewFilter.viewFilterGroupId, positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup, definition: mockFilterDefinition, - }, + label: mockViewFilter.displayValue, + type: getFilterTypeFromFieldType(mockFieldMetadataItem.type), + } satisfies RecordFilter, ]); }); diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyViewFiltersToCurrentRecordFilters.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyViewFiltersToCurrentRecordFilters.test.tsx index 4a7e6540b..b17767c93 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyViewFiltersToCurrentRecordFilters.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyViewFiltersToCurrentRecordFilters.test.tsx @@ -1,7 +1,11 @@ import { act, renderHook } from '@testing-library/react'; -import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { + formatFieldMetadataItemAsFilterDefinition, + getFilterTypeFromFieldType, +} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { ViewFilter } from '@/views/types/ViewFilter'; @@ -35,7 +39,7 @@ describe('useApplyViewFiltersToCurrentRecordFilters', () => { fieldMetadataId: mockFieldMetadataItem.id, operand: ViewFilterOperand.Contains, value: 'test', - displayValue: 'test', + displayValue: mockFieldMetadataItem.label, viewFilterGroupId: 'group-1', positionInViewFilterGroup: 0, definition: mockAvailableFilterDefinition, @@ -72,7 +76,9 @@ describe('useApplyViewFiltersToCurrentRecordFilters', () => { viewFilterGroupId: mockViewFilter.viewFilterGroupId, positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup, definition: mockAvailableFilterDefinition, - }, + label: mockViewFilter.displayValue, + type: getFilterTypeFromFieldType(mockFieldMetadataItem.type), + } satisfies RecordFilter, ]); }); diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts index dfa4efab8..0e8ca1d72 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts @@ -10,6 +10,7 @@ import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinit import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; + import { FieldMetadataType } from '~/generated/graphql'; const baseDefinition = { @@ -65,6 +66,10 @@ describe('mapViewFiltersToFilters', () => { ...baseDefinition, type: 'FULL_NAME', }, + label: baseDefinition.label, + type: 'FULL_NAME', + positionInViewFilterGroup: undefined, + viewFilterGroupId: undefined, }, ]; expect( diff --git a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts index b4da197ea..2cce9412e 100644 --- a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts +++ b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts @@ -26,6 +26,8 @@ export const mapViewFiltersToFilters = ( viewFilterGroupId: viewFilter.viewFilterGroupId, positionInViewFilterGroup: viewFilter.positionInViewFilterGroup, definition: viewFilter.definition ?? availableFilterDefinition, + label: viewFilter.definition?.label ?? availableFilterDefinition.label, + type: viewFilter.definition?.type ?? availableFilterDefinition.type, }; }) .filter(isDefined);