diff --git a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts new file mode 100644 index 000000000..f3d6a9bdb --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts @@ -0,0 +1,41 @@ +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { getFilterFilterableFieldMetadataItems } from '@/object-metadata/utils/getFilterFilterableFieldMetadataItems'; +import { checkIfFeatureFlagIsEnabledOnWorkspace } from '@/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace'; +import { selectorFamily } from 'recoil'; +import { isDefined } from 'twenty-shared'; +import { FeatureFlagKey } from '~/generated-metadata/graphql'; + +export const availableFieldMetadataItemsForFilterFamilySelector = + selectorFamily({ + key: 'availableFieldMetadataItemsForFilterFamilySelector', + get: + ({ objectMetadataItemId }: { objectMetadataItemId: string }) => + ({ get }) => { + const currentWorkspace = get(currentWorkspaceState); + const objectMetadataItems = get(objectMetadataItemsState); + + const objectMetadataItem = objectMetadataItems.find( + (item) => item.id === objectMetadataItemId, + ); + + if (!isDefined(objectMetadataItem)) { + return []; + } + + const isJsonFeatureFlagEnabled = checkIfFeatureFlagIsEnabledOnWorkspace( + FeatureFlagKey.IsJsonFilterEnabled, + currentWorkspace, + ); + + const filterFilterableFieldMetadataItems = + getFilterFilterableFieldMetadataItems({ + isJsonFilterEnabled: isJsonFeatureFlagEnabled, + }); + + const availableFieldMetadataItemsForFilter = + objectMetadataItem.fields.filter(filterFilterableFieldMetadataItems); + + return availableFieldMetadataItemsForFilter; + }, + }); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getFilterFilterableFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/utils/getFilterFilterableFieldMetadataItems.ts new file mode 100644 index 000000000..a704b4907 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/getFilterFilterableFieldMetadataItems.ts @@ -0,0 +1,52 @@ +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { + FieldMetadataType, + RelationDefinitionType, +} from '~/generated-metadata/graphql'; + +export const getFilterFilterableFieldMetadataItems = ({ + isJsonFilterEnabled, +}: { + isJsonFilterEnabled: boolean; +}) => { + return (field: FieldMetadataItem) => { + const isSystemField = field.isSystem; + const isFieldActive = field.isActive; + + const isRelationFieldHandled = !( + field.type === FieldMetadataType.RELATION && + field.relationDefinition?.direction !== + RelationDefinitionType.MANY_TO_ONE && + field.relationDefinition?.direction !== RelationDefinitionType.ONE_TO_ONE + ); + + const isFieldTypeFilterable = [ + FieldMetadataType.BOOLEAN, + FieldMetadataType.DATE_TIME, + FieldMetadataType.DATE, + FieldMetadataType.TEXT, + FieldMetadataType.EMAILS, + FieldMetadataType.NUMBER, + FieldMetadataType.LINKS, + FieldMetadataType.FULL_NAME, + FieldMetadataType.ADDRESS, + FieldMetadataType.RELATION, + FieldMetadataType.SELECT, + FieldMetadataType.MULTI_SELECT, + FieldMetadataType.CURRENCY, + FieldMetadataType.RATING, + FieldMetadataType.ACTOR, + FieldMetadataType.PHONES, + FieldMetadataType.ARRAY, + ...(isJsonFilterEnabled ? [FieldMetadataType.RAW_JSON] : []), + ].includes(field.type); + + const isFieldFilterable = + !isSystemField && + isFieldActive && + isRelationFieldHandled && + isFieldTypeFilterable; + + return isFieldFilterable; + }; +}; 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 1c259f466..76c136c5f 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,17 +1,18 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup'; import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; 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'; import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator'; import { useCallback } from 'react'; +import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared'; import { IconLibraryPlus, IconPlus, LightButton, MenuItem } from 'twenty-ui'; import { v4 } from 'uuid'; @@ -41,7 +42,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({ const objectMetadataId = currentViewWithCombinedFiltersAndSorts?.objectMetadataId; - if (!objectMetadataId) { + if (!isDefined(objectMetadataId)) { throw new Error('Object metadata id is missing from current view'); } @@ -49,33 +50,45 @@ export const AdvancedFilterAddFilterRuleSelect = ({ objectId: objectMetadataId, }); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, + const availableFieldMetadataItemsForFilter = useRecoilValue( + availableFieldMetadataItemsForFilterFamilySelector({ + objectMetadataItemId: objectMetadataId, + }), ); - const getDefaultFilterDefinition = useCallback(() => { - const defaultFilterDefinition = - availableFilterDefinitions.find( - (filterDefinition) => - filterDefinition.fieldMetadataId === + const getDefaultFieldMetadataItem = useCallback(() => { + const defaultFieldMetadataItem = + availableFieldMetadataItemsForFilter.find( + (fieldMetadataItem) => + fieldMetadataItem.id === objectMetadataItem?.labelIdentifierFieldMetadataId, - ) ?? availableFilterDefinitions?.[0]; + ) ?? availableFieldMetadataItemsForFilter[0]; - if (!defaultFilterDefinition) { - throw new Error('Missing default filter definition'); + if (!isDefined(defaultFieldMetadataItem)) { + throw new Error( + `Could not find default field metadata item for object ${objectMetadataId}`, + ); } - return defaultFilterDefinition; - }, [availableFilterDefinitions, objectMetadataItem]); + return defaultFieldMetadataItem; + }, [ + availableFieldMetadataItemsForFilter, + objectMetadataItem, + objectMetadataId, + ]); const handleAddFilter = () => { closeDropdown(); - const defaultFilterDefinition = getDefaultFilterDefinition(); + const defaultFieldMetadataItem = getDefaultFieldMetadataItem(); + + const defaultFilterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: defaultFieldMetadataItem, + }); upsertCombinedViewFilter({ id: v4(), - fieldMetadataId: defaultFilterDefinition.fieldMetadataId, + fieldMetadataId: defaultFieldMetadataItem.id, operand: getRecordFilterOperandsForRecordFilterDefinition( defaultFilterDefinition, )[0], @@ -104,11 +117,15 @@ export const AdvancedFilterAddFilterRuleSelect = ({ upsertCombinedViewFilterGroup(newViewFilterGroup); - const defaultFilterDefinition = getDefaultFilterDefinition(); + const defaultFieldMetadataItem = getDefaultFieldMetadataItem(); + + const defaultFilterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: defaultFieldMetadataItem, + }); upsertCombinedViewFilter({ id: v4(), - fieldMetadataId: defaultFilterDefinition.fieldMetadataId, + fieldMetadataId: defaultFieldMetadataItem.id, operand: getRecordFilterOperandsForRecordFilterDefinition( defaultFilterDefinition, )[0], diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useCurrentViewFilter.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useCurrentViewFilter.ts index 40bd802ba..fced8cd20 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useCurrentViewFilter.ts +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useCurrentViewFilter.ts @@ -1,6 +1,5 @@ -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useFilterDefinitionsFromFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterDefinitionsFromFilterableFieldMetadataItems'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; export const useCurrentViewFilter = ({ @@ -8,9 +7,8 @@ export const useCurrentViewFilter = ({ }: { viewFilterId?: string; }) => { - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - ); + const { filterDefinitions } = + useFilterDefinitionsFromFilterableFieldMetadataItems(); const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); @@ -22,10 +20,7 @@ export const useCurrentViewFilter = ({ return undefined; } - const [filter] = mapViewFiltersToFilters( - [viewFilter], - availableFilterDefinitions, - ); + const [filter] = mapViewFiltersToFilters([viewFilter], filterDefinitions); return filter; }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx index d7e2d8444..fc43e1607 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx @@ -1,15 +1,17 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup'; import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator'; import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared'; import { IconFilter, MenuItemLeftContent, @@ -66,8 +68,10 @@ export const AdvancedFilterButton = () => { objectId: objectMetadataId ?? null, }); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, + const availableFieldMetadataItemsForFilter = useRecoilValue( + availableFieldMetadataItemsForFilterFamilySelector({ + objectMetadataItemId: objectMetadataItem.id, + }), ); const handleClick = () => { @@ -88,24 +92,27 @@ export const AdvancedFilterButton = () => { upsertCombinedViewFilterGroup(newViewFilterGroup); - const defaultFilterDefinition = - availableFilterDefinitions.find( - (filterDefinition) => - filterDefinition.fieldMetadataId === + const defaultFieldMetadataItem = + availableFieldMetadataItemsForFilter.find( + (fieldMetadataItem) => + fieldMetadataItem.id === objectMetadataItem?.labelIdentifierFieldMetadataId, - ) ?? availableFilterDefinitions?.[0]; + ) ?? availableFieldMetadataItemsForFilter[0]; - if (!defaultFilterDefinition) { + if (!isDefined(defaultFieldMetadataItem)) { throw new Error('Missing default filter definition'); } + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: defaultFieldMetadataItem, + }); + upsertCombinedViewFilter({ id: v4(), - fieldMetadataId: defaultFilterDefinition.fieldMetadataId, - operand: getRecordFilterOperandsForRecordFilterDefinition( - defaultFilterDefinition, - )[0], - definition: defaultFilterDefinition, + fieldMetadataId: defaultFieldMetadataItem.id, + operand: + getRecordFilterOperandsForRecordFilterDefinition(filterDefinition)[0], + definition: filterDefinition, value: '', displayValue: '', viewFilterGroupId: newViewFilterGroup.id, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton.tsx index 74f37a680..3ebd9b2d6 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton.tsx @@ -1,8 +1,7 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; +import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems'; import { MultipleFiltersDropdownButton } from './MultipleFiltersDropdownButton'; import { SingleEntityObjectFilterDropdownButton } from './SingleEntityObjectFilterDropdownButton'; @@ -15,16 +14,13 @@ export const ObjectFilterDropdownButton = ({ filterDropdownId, hotkeyScope, }: ObjectFilterDropdownButtonProps) => { - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - filterDropdownId, - ); + const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(); const hasOnlyOneEntityFilter = - availableFilterDefinitions.length === 1 && - availableFilterDefinitions[0].type === 'RELATION'; + filterableFieldMetadataItems.length === 1 && + filterableFieldMetadataItems[0].type === 'RELATION'; - if (!availableFilterDefinitions.length) { + if (!filterableFieldMetadataItems.length) { return <>; } 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 fe0dbd640..8dcf9ab7e 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 @@ -19,14 +19,15 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { isDefined } from 'twenty-shared'; import { FeatureFlagKey } from '~/generated/graphql'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState'; import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; +import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems'; import { useLingui } from '@lingui/react/macro'; export const StyledInput = styled.input` @@ -80,9 +81,7 @@ export const ObjectFilterDropdownFilterSelect = ({ advancedFilterViewFilterId, ); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - ); + const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(); const visibleTableColumns = useRecoilComponentValueV2( visibleTableColumnsComponentSelector, @@ -99,29 +98,29 @@ export const ObjectFilterDropdownFilterSelect = ({ (column) => column.fieldMetadataId, ); - const filteredSearchInputFilterDefinitions = - availableFilterDefinitions.filter((item) => - item.label + const filteredSearchInputFieldMetadataItems = + filterableFieldMetadataItems.filter((fieldMetadataItem) => + fieldMetadataItem.label .toLocaleLowerCase() .includes(objectFilterDropdownSearchInput.toLocaleLowerCase()), ); - const visibleColumnsFilterDefinitions = filteredSearchInputFilterDefinitions - + const visibleColumnsFieldMetadataItems = filteredSearchInputFieldMetadataItems .sort((a, b) => { - return ( - visibleColumnsIds.indexOf(a.fieldMetadataId) - - visibleColumnsIds.indexOf(b.fieldMetadataId) - ); + return visibleColumnsIds.indexOf(a.id) - visibleColumnsIds.indexOf(b.id); }) - .filter((item) => visibleColumnsIds.includes(item.fieldMetadataId)); + .filter((fieldMetadataItem) => + visibleColumnsIds.includes(fieldMetadataItem.id), + ); - const hiddenColumnsFilterDefinitions = filteredSearchInputFilterDefinitions + const hiddenColumnsFieldMetadataItems = filteredSearchInputFieldMetadataItems .sort((a, b) => a.label.localeCompare(b.label)) - .filter((item) => hiddenColumnIds.includes(item.fieldMetadataId)); + .filter((fieldMetadataItem) => + hiddenColumnIds.includes(fieldMetadataItem.id), + ); - const selectableListItemIds = availableFilterDefinitions.map( - (item) => item.fieldMetadataId, + const selectableFieldMetadataItemIds = filterableFieldMetadataItems.map( + (fieldMetadataItem) => fieldMetadataItem.id, ); const { selectFilterDefinitionUsedInDropdown } = @@ -134,16 +133,20 @@ export const ObjectFilterDropdownFilterSelect = ({ const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID); const handleEnter = (fieldMetadataItemId: string) => { - const selectedFilterDefinition = availableFilterDefinitions.find( - (item) => item.fieldMetadataId === fieldMetadataItemId, + const selectedFieldMetadataItem = filterableFieldMetadataItems.find( + (fieldMetadataItem) => fieldMetadataItem.id === fieldMetadataItemId, ); - if (!isDefined(selectedFilterDefinition)) { + if (!isDefined(selectedFieldMetadataItem)) { return; } resetSelectedItem(); + const selectedFilterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: selectedFieldMetadataItem, + }); + selectFilterDefinitionUsedInDropdown({ filterDefinition: selectedFilterDefinition, }); @@ -156,8 +159,8 @@ export const ObjectFilterDropdownFilterSelect = ({ }; const shoudShowSeparator = - visibleColumnsFilterDefinitions.length > 0 && - hiddenColumnsFilterDefinitions.length > 0; + visibleColumnsFieldMetadataItems.length > 0 && + hiddenColumnsFieldMetadataItems.length > 0; const { currentViewId, currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); @@ -186,32 +189,32 @@ export const ObjectFilterDropdownFilterSelect = ({ /> - {visibleColumnsFilterDefinitions.map( - (visibleFilterDefinition, index) => ( + {visibleColumnsFieldMetadataItems.map( + (visibleFieldMetadataItem, index) => ( ), )} {shoudShowSeparator && } - {hiddenColumnsFilterDefinitions.map( - (hiddenFilterDefinition, index) => ( + {hiddenColumnsFieldMetadataItems.map( + (hiddenFieldMetadataItem, index) => ( ), 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 fcccd4b6e..9ecf90ce9 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 @@ -1,7 +1,5 @@ import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown'; import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; - -import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown'; import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState'; import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; @@ -9,8 +7,12 @@ 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 { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField'; import { CompositeFilterableFieldType } from '@/object-record/record-filter/types/CompositeFilterableFieldType'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { formatFieldMetadataItemAsFilterDefinition } 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 { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; @@ -23,15 +25,12 @@ import { useRecoilValue } from 'recoil'; import { MenuItemSelect, useIcons } from 'twenty-ui'; export type ObjectFilterDropdownFilterSelectMenuItemProps = { - filterDefinition: RecordFilterDefinition; + fieldMetadataItemToSelect: FieldMetadataItem; }; export const ObjectFilterDropdownFilterSelectMenuItem = ({ - filterDefinition, + fieldMetadataItemToSelect, }: ObjectFilterDropdownFilterSelectMenuItemProps) => { - const { selectFilterDefinitionUsedInDropdown } = - useSelectFilterDefinitionUsedInDropdown(); - const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( fieldMetadataItemIdUsedInDropdownComponentState, ); @@ -58,16 +57,24 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ OBJECT_FILTER_DROPDOWN_ID, ); + const filterDefinitionToSelect = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItemToSelect, + }); + const isSelectedItem = useRecoilValue( - isSelectedItemIdSelector(filterDefinition.fieldMetadataId), + isSelectedItemIdSelector(fieldMetadataItemToSelect.id), ); - const isACompositeField = isCompositeField(filterDefinition.type); + const isACompositeField = isCompositeField(fieldMetadataItemToSelect.type); const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( selectedOperandInDropdownComponentState, ); + const setFilterDefinitionUsedInDropdown = useSetRecoilComponentStateV2( + filterDefinitionUsedInDropdownComponentState, + ); + const advancedFilterViewFilterId = useRecoilComponentValueV2( advancedFilterViewFilterIdComponentState, ); @@ -83,13 +90,10 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ ) => { closeAdvancedFilterDropdown(); - selectFilterDefinitionUsedInDropdown({ - filterDefinition: availableFilterDefinition, - }); - setFieldMetadataItemIdUsedInDropdown( availableFilterDefinition.fieldMetadataId, ); + setFilterDefinitionUsedInDropdown(availableFilterDefinition); if ( availableFilterDefinition.type === 'RELATION' || @@ -115,12 +119,14 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ if (isACompositeField) { // TODO: create isCompositeFilterableFieldType type guard setObjectFilterDropdownSubMenuFieldType( - filterDefinition.type as CompositeFilterableFieldType, + filterDefinitionToSelect.type as CompositeFilterableFieldType, + ); + setObjectFilterDropdownFirstLevelFilterDefinition( + filterDefinitionToSelect, ); - setObjectFilterDropdownFirstLevelFilterDefinition(filterDefinition); setObjectFilterDropdownIsSelectingCompositeField(true); } else { - handleSelectFilterDefinition(filterDefinition); + handleSelectFilterDefinition(filterDefinitionToSelect); } }; @@ -129,8 +135,8 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ selected={false} hovered={isSelectedItem} onClick={handleClick} - LeftIcon={getIcon(filterDefinition.iconName)} - text={filterDefinition.label} + LeftIcon={getIcon(filterDefinitionToSelect.iconName)} + text={filterDefinitionToSelect.label} hasSubMenu={isACompositeField} /> ); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx index 4be9678ab..9048d8668 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx @@ -1,5 +1,4 @@ import { useTheme } from '@emotion/react'; -import React from 'react'; import { IconChevronDown } from 'twenty-ui'; import { ObjectFilterDropdownRecordRemoveFilterMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordRemoveFilterMenuItem'; @@ -8,15 +7,10 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { SingleEntityObjectFilterDropdownButtonEffect } from '@/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButtonEffect'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; -import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { useLingui } from '@lingui/react/macro'; -import { getRecordFilterOperandsForRecordFilterDefinition } from '../../record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; import { GenericEntityFilterChip } from './GenericEntityFilterChip'; import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect'; import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput'; @@ -32,68 +26,37 @@ export const SingleEntityObjectFilterDropdownButton = ({ selectedFilterComponentState, ); - const setFilterDefinitionUsedInDropdown = useSetRecoilComponentStateV2( - filterDefinitionUsedInDropdownComponentState, - ); - - const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( - fieldMetadataItemIdUsedInDropdownComponentState, - ); - - const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( - selectedOperandInDropdownComponentState, - ); - - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - ); - - const availableFilterDefinition = availableFilterDefinitions[0]; - - React.useEffect(() => { - setFieldMetadataItemIdUsedInDropdown( - availableFilterDefinition.fieldMetadataId, - ); - setFilterDefinitionUsedInDropdown(availableFilterDefinition); - const defaultOperand = getRecordFilterOperandsForRecordFilterDefinition( - availableFilterDefinition, - )[0]; - setSelectedOperandInDropdown(defaultOperand); - }, [ - availableFilterDefinition, - setFilterDefinitionUsedInDropdown, - setSelectedOperandInDropdown, - setFieldMetadataItemIdUsedInDropdown, - ]); - const theme = useTheme(); const { t } = useLingui(); return ( - - {selectedFilter ? ( - - ) : ( - t`Filter` - )} - - - } - dropdownComponents={ - <> - - - - - - } - /> + <> + + + {selectedFilter ? ( + + ) : ( + t`Filter` + )} + + + } + dropdownComponents={ + <> + + + + + + } + /> + ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButtonEffect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButtonEffect.tsx new file mode 100644 index 000000000..c2632dde8 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButtonEffect.tsx @@ -0,0 +1,47 @@ +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; +import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems'; +import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { useEffect } from 'react'; + +export const SingleEntityObjectFilterDropdownButtonEffect = () => { + const setFilterDefinitionUsedInDropdown = useSetRecoilComponentStateV2( + filterDefinitionUsedInDropdownComponentState, + ); + + const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( + fieldMetadataItemIdUsedInDropdownComponentState, + ); + + const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( + selectedOperandInDropdownComponentState, + ); + + const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(); + + const firstFieldMetadataItem = filterableFieldMetadataItems[0]; + + const firstFieldDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: firstFieldMetadataItem, + }); + + useEffect(() => { + setFieldMetadataItemIdUsedInDropdown(firstFieldDefinition.fieldMetadataId); + setFilterDefinitionUsedInDropdown(firstFieldDefinition); + + const defaultOperand = + getRecordFilterOperandsForRecordFilterDefinition(firstFieldDefinition)[0]; + + setSelectedOperandInDropdown(defaultOperand); + }, [ + firstFieldDefinition, + setFilterDefinitionUsedInDropdown, + setSelectedOperandInDropdown, + setFieldMetadataItemIdUsedInDropdown, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/__stories__/MultipleFiltersDropdownButton.stories.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/__stories__/MultipleFiltersDropdownButton.stories.tsx index ad980c620..5f67343a9 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/__stories__/MultipleFiltersDropdownButton.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/__stories__/MultipleFiltersDropdownButton.stories.tsx @@ -4,7 +4,6 @@ import { TaskGroups } from '@/activities/tasks/components/TaskGroups'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; -import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; @@ -12,7 +11,6 @@ import { RecordIndexContextProvider } from '@/object-record/record-index/context import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { within } from '@storybook/test'; import { @@ -35,10 +33,6 @@ const meta: Meta = { (item) => item.nameSingular === CoreObjectNameSingular.Company, )!; const instanceId = 'entity-tasks-filter-scope'; - const setAvailableFilterDefinitions = useSetRecoilComponentStateV2( - availableFilterDefinitionsComponentState, - instanceId, - ); const setTableColumns = useSetRecoilComponentStateV2( tableColumnsComponentState, @@ -54,17 +48,8 @@ const meta: Meta = { }), ); - const filterDefinitions = companyObjectMetadataItem.fields.map( - (fieldMetadataItem) => - formatFieldMetadataItemAsFilterDefinition({ - field: fieldMetadataItem, - }), - ); - setTableColumns(columns); - setAvailableFilterDefinitions(filterDefinitions); - return ( { setFilterDefinitionUsedInDropdown(filterDefinition); - setFieldMetadataItemIdUsedInDropdown(filterDefinition.fieldMetadataId); if ( filterDefinition.type === 'RELATION' || diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/availableFilterDefinitionsComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/availableFilterDefinitionsComponentState.ts deleted file mode 100644 index ddc529f00..000000000 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/availableFilterDefinitionsComponentState.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; -import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; - -import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; - -export const availableFilterDefinitionsComponentState = createComponentStateV2< - RecordFilterDefinition[] ->({ - key: 'availableFilterDefinitionsComponentState', - defaultValue: [], - componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterDefinitionsFromFilterableFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterDefinitionsFromFilterableFieldMetadataItems.ts new file mode 100644 index 000000000..df831fd08 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterDefinitionsFromFilterableFieldMetadataItems.ts @@ -0,0 +1,23 @@ +import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { useRecoilValue } from 'recoil'; + +export const useFilterDefinitionsFromFilterableFieldMetadataItems = () => { + const { objectMetadataItem } = useRecordIndexContextOrThrow(); + + const availableFieldMetadataItemsForFilter = useRecoilValue( + availableFieldMetadataItemsForFilterFamilySelector({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); + + const filterDefinitions = availableFieldMetadataItemsForFilter.map( + (fieldMetadataItem) => + formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItem, + }), + ); + + return { filterDefinitions }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterableFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterableFieldMetadataItems.ts new file mode 100644 index 000000000..80cfad8c7 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterableFieldMetadataItems.ts @@ -0,0 +1,15 @@ +import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; +import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { useRecoilValue } from 'recoil'; + +export const useFilterableFieldMetadataItems = () => { + const { objectMetadataItem } = useRecordIndexContextOrThrow(); + + const filterableFieldMetadataItems = useRecoilValue( + availableFieldMetadataItemsForFilterFamilySelector({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); + + return { filterableFieldMetadataItems }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index b3cec7e8f..a99c1946a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -26,6 +26,7 @@ import { RecordIndexActionMenu } from '@/action-menu/components/RecordIndexActio import { ContextStoreCurrentViewTypeEffect } from '@/context-store/components/ContextStoreCurrentViewTypeEffect'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; +import { useFilterDefinitionsFromFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterDefinitionsFromFilterableFieldMetadataItems'; import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup'; import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect'; import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect'; @@ -76,7 +77,7 @@ export const RecordIndexContainer = () => { const setRecordGroup = useSetRecordGroup(recordIndexId); - const { columnDefinitions, filterDefinitions, sortDefinitions } = + const { columnDefinitions, sortDefinitions } = useColumnDefinitionsFromFieldMetadata(objectMetadataItem); const setRecordIndexViewFilterGroups = useSetRecoilState( @@ -179,6 +180,9 @@ export const RecordIndexContainer = () => { contextStoreTargetedRecordsRuleComponentState, ); + const { filterDefinitions } = + useFilterDefinitionsFromFilterableFieldMetadataItems(); + const isCommandMenuV2Enabled = useIsFeatureEnabled( FeatureFlagKey.IsCommandMenuV2Enabled, ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx index 44ae88796..1d64d9cb6 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx @@ -23,13 +23,12 @@ export const RecordIndexViewBarEffect = ({ objectNameSingular, }); - const { columnDefinitions, filterDefinitions, sortDefinitions } = + const { columnDefinitions, sortDefinitions } = useColumnDefinitionsFromFieldMetadata(objectMetadataItem); const { setViewObjectMetadataId, setAvailableSortDefinitions, - setAvailableFilterDefinitions, setAvailableFieldDefinitions, } = useInitViewBar(viewBarId); @@ -39,15 +38,12 @@ export const RecordIndexViewBarEffect = ({ } setViewObjectMetadataId?.(objectMetadataItem.id); setAvailableSortDefinitions?.(sortDefinitions); - setAvailableFilterDefinitions?.(filterDefinitions); setAvailableFieldDefinitions?.(columnDefinitions); }, [ setViewObjectMetadataId, objectMetadataItem, setAvailableSortDefinitions, sortDefinitions, - setAvailableFilterDefinitions, - filterDefinitions, setAvailableFieldDefinitions, columnDefinitions, ]); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts index 00c4b2cd3..4ed6a7338 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts @@ -4,19 +4,19 @@ import { v4 } from 'uuid'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown'; import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; -import { useRecoilCallback } from 'recoil'; +import { useRecoilCallback, useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared'; type UseHandleToggleColumnFilterProps = { @@ -49,8 +49,10 @@ export const useHandleToggleColumnFilter = ({ }; }, []); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, + const availableFieldMetadataItemsForFilter = useRecoilValue( + availableFieldMetadataItemsForFilterFamilySelector({ + objectMetadataItemId: objectMetadataItem.id, + }), ); const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); @@ -80,10 +82,19 @@ export const useHandleToggleColumnFilter = ({ ); if (!existingViewFilter) { - const filterDefinition = availableFilterDefinitions.find( - (fd) => fd.fieldMetadataId === fieldMetadataId, + const fieldMetadataItem = availableFieldMetadataItemsForFilter.find( + (fieldMetadataItemToFind) => + fieldMetadataItemToFind.id === fieldMetadataId, ); + if (!isDefined(fieldMetadataItem)) { + throw new Error('Field metadata item not found'); + } + + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItem, + }); + if (!isDefined(filterDefinition)) { throw new Error('Filter definition not found'); } @@ -118,7 +129,7 @@ export const useHandleToggleColumnFilter = ({ upsertCombinedViewFilter, selectFilterDefinitionUsedInDropdown, currentViewWithCombinedFiltersAndSorts, - availableFilterDefinitions, + availableFieldMetadataItemsForFilter, upsertRecordFilter, setFieldMetadataItemIdUsedInDropdown, ], diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainerEffect.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainerEffect.tsx index 04b9d9205..75929819a 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainerEffect.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainerEffect.tsx @@ -5,7 +5,6 @@ import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObje import { useSetRecordIndexEntityCount } from '@/object-record/record-index/hooks/useSetRecordIndexEntityCount'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions'; -import { SIGN_IN_BACKGROUND_MOCK_FILTER_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockFilterDefinitions'; import { SIGN_IN_BACKGROUND_MOCK_SORT_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockSortDefinitions'; import { SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS } from '@/sign-in-background-mock/constants/SignInBackgroundMockViewFields'; import { useInitViewBar } from '@/views/hooks/useInitViewBar'; @@ -37,7 +36,6 @@ export const SignInBackgroundMockContainerEffect = ({ const { setAvailableSortDefinitions, - setAvailableFilterDefinitions, setAvailableFieldDefinitions, setViewObjectMetadataId, } = useInitViewBar(viewId); @@ -48,7 +46,6 @@ export const SignInBackgroundMockContainerEffect = ({ setViewObjectMetadataId?.(objectMetadataItem.id); setAvailableSortDefinitions?.(SIGN_IN_BACKGROUND_MOCK_SORT_DEFINITIONS); - setAvailableFilterDefinitions?.(SIGN_IN_BACKGROUND_MOCK_FILTER_DEFINITIONS); setAvailableFieldDefinitions?.(SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS); setAvailableTableColumns(SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS); @@ -62,7 +59,6 @@ export const SignInBackgroundMockContainerEffect = ({ }, [ setViewObjectMetadataId, setAvailableSortDefinitions, - setAvailableFilterDefinitions, setAvailableFieldDefinitions, objectMetadataItem, setAvailableTableColumns, diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index 146bcc41a..35d5eddd4 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -1,23 +1,16 @@ -import { useCallback, useEffect } from 'react'; +import { useCallback } from 'react'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { EditableFilterChip } from '@/views/components/EditableFilterChip'; import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput'; -import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; -import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; -import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; -import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { EditableFilterDropdownButtonEffect } from '@/views/components/EditableFilterDropdownButtonEffect'; import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; -import { isDefined } from 'twenty-shared'; type EditableFilterDropdownButtonProps = { viewFilterDropdownId: string; @@ -30,57 +23,10 @@ export const EditableFilterDropdownButton = ({ viewFilter, hotkeyScope, }: EditableFilterDropdownButtonProps) => { - const setFilterDefinitionUsedInDropdown = useSetRecoilComponentStateV2( - filterDefinitionUsedInDropdownComponentState, - viewFilterDropdownId, - ); - - const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( - fieldMetadataItemIdUsedInDropdownComponentState, - ); - - const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( - selectedOperandInDropdownComponentState, - viewFilterDropdownId, - ); - - const setSelectedFilter = useSetRecoilComponentStateV2( - selectedFilterComponentState, - viewFilterDropdownId, - ); - - // TODO: verify this instance id works - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - viewFilterDropdownId, - ); - const { closeDropdown } = useDropdown(viewFilterDropdownId); const { deleteCombinedViewFilter } = useDeleteCombinedViewFilters(); - useEffect(() => { - const filterDefinition = availableFilterDefinitions.find( - (filterDefinition) => - filterDefinition.fieldMetadataId === viewFilter.fieldMetadataId, - ); - - if (isDefined(filterDefinition)) { - setFilterDefinitionUsedInDropdown(filterDefinition); - setFieldMetadataItemIdUsedInDropdown(filterDefinition.fieldMetadataId); - setSelectedOperandInDropdown(viewFilter.operand); - setSelectedFilter(viewFilter); - } - }, [ - availableFilterDefinitions, - setFilterDefinitionUsedInDropdown, - setFieldMetadataItemIdUsedInDropdown, - viewFilter, - setSelectedOperandInDropdown, - setSelectedFilter, - viewFilterDropdownId, - ]); - const { removeRecordFilter } = useRemoveRecordFilter(); const handleRemove = () => { @@ -108,20 +54,26 @@ export const EditableFilterDropdownButton = ({ }, [viewFilter, deleteCombinedViewFilter, removeRecordFilter]); return ( - - } - dropdownComponents={ - - } - dropdownHotkeyScope={hotkeyScope} - dropdownOffset={{ y: 8, x: 0 }} - dropdownPlacement="bottom-start" - onClickOutside={handleDropdownClickOutside} - /> + <> + + + } + dropdownComponents={ + + } + dropdownHotkeyScope={hotkeyScope} + dropdownOffset={{ y: 8, x: 0 }} + dropdownPlacement="bottom-start" + onClickOutside={handleDropdownClickOutside} + /> + ); }; diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx new file mode 100644 index 000000000..f834b3cc5 --- /dev/null +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx @@ -0,0 +1,74 @@ +import { useEffect } from 'react'; + +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; +import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; +import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { isDefined } from 'twenty-shared'; + +type EditableFilterDropdownButtonEffectProps = { + viewFilterDropdownId: string; + viewFilter: RecordFilter; +}; + +export const EditableFilterDropdownButtonEffect = ({ + viewFilterDropdownId, + viewFilter, +}: EditableFilterDropdownButtonEffectProps) => { + const setFilterDefinitionUsedInDropdown = useSetRecoilComponentStateV2( + filterDefinitionUsedInDropdownComponentState, + viewFilterDropdownId, + ); + + const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( + fieldMetadataItemIdUsedInDropdownComponentState, + ); + + const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( + selectedOperandInDropdownComponentState, + viewFilterDropdownId, + ); + + const setSelectedFilter = useSetRecoilComponentStateV2( + selectedFilterComponentState, + viewFilterDropdownId, + ); + + const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(); + + useEffect(() => { + const fieldMetadataItem = filterableFieldMetadataItems.find( + (fieldMetadataItem) => + fieldMetadataItem.id === viewFilter.fieldMetadataId, + ); + + if (!isDefined(fieldMetadataItem)) { + return; + } + + const filterDefinition = formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItem, + }); + + if (isDefined(filterDefinition)) { + setFilterDefinitionUsedInDropdown(filterDefinition); + setFieldMetadataItemIdUsedInDropdown(filterDefinition.fieldMetadataId); + setSelectedOperandInDropdown(viewFilter.operand); + setSelectedFilter(viewFilter); + } + }, [ + filterableFieldMetadataItems, + setFilterDefinitionUsedInDropdown, + setFieldMetadataItemIdUsedInDropdown, + viewFilter, + setSelectedOperandInDropdown, + setSelectedFilter, + viewFilterDropdownId, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx index 8137fb6d1..662b4ed49 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx @@ -4,7 +4,6 @@ import { useContext, useEffect, useState } from 'react'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { ViewEventContext } from '@/views/events/contexts/ViewEventContext'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { isPersistingViewFieldsComponentState } from '@/views/states/isPersistingViewFieldsComponentState'; import { View } from '@/views/types/View'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -23,11 +22,6 @@ export const ViewBarEffect = ({ viewBarId }: ViewBarEffectProps) => { View | undefined >(undefined); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - viewBarId, - ); - const isPersistingViewFields = useRecoilComponentValueV2( isPersistingViewFieldsComponentState, viewBarId, @@ -52,7 +46,6 @@ export const ViewBarEffect = ({ viewBarId }: ViewBarEffectProps) => { } } }, [ - availableFilterDefinitions, currentViewSnapshot, currentViewWithCombinedFiltersAndSorts, isPersistingViewFields, diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx index 4fc803b86..34fbaebd0 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx @@ -8,7 +8,6 @@ import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/ob 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'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema'; import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema'; import { isDefined } from 'twenty-shared'; @@ -22,10 +21,6 @@ export const ViewBarFilterEffect = ({ }: ViewBarFilterEffectProps) => { const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - ); - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( filterDefinitionUsedInDropdownComponentState, filterDropdownId, @@ -42,18 +37,6 @@ export const ViewBarFilterEffect = ({ filterDropdownId, ); - // TODO: verify this instance id works - const setAvailableFilterDefinitions = useSetRecoilComponentStateV2( - availableFilterDefinitionsComponentState, - filterDropdownId, - ); - - useEffect(() => { - if (isDefined(availableFilterDefinitions)) { - setAvailableFilterDefinitions(availableFilterDefinitions); - } - }, [availableFilterDefinitions, setAvailableFilterDefinitions]); - useEffect(() => { if (filterDefinitionUsedInDropdown?.type === 'RELATION') { const viewFilterUsedInDropdown = diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx index 344fd0753..1f9c5cb9f 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx @@ -1,9 +1,10 @@ +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { View } from '@/views/types/View'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; @@ -21,29 +22,31 @@ export const ViewBarRecordFilterEffect = () => { currentRecordFiltersComponentState, ); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - ); + const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(); useEffect(() => { if (isDataPrefetched) { const currentView = views.find((view) => view.id === currentViewId); + const filterDefinitions = filterableFieldMetadataItems.map( + (fieldMetadataItem) => + formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItem, + }), + ); + if (isDefined(currentView)) { setCurrentRecordFilters( - mapViewFiltersToFilters( - currentView.viewFilters, - availableFilterDefinitions, - ), + mapViewFiltersToFilters(currentView.viewFilters, filterDefinitions), ); } } }, [ isDataPrefetched, views, - availableFilterDefinitions, currentViewId, setCurrentRecordFilters, + filterableFieldMetadataItems, ]); return null; 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 07f8074cc..0f8435c83 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,30 +1,42 @@ import { act, renderHook } from '@testing-library/react'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; 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'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { ViewFilter } from '@/views/types/ViewFilter'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { isDefined } from 'twenty-shared'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '../useApplyCurrentViewFiltersToCurrentRecordFilters'; jest.mock('@/prefetch/hooks/usePrefetchedData'); describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { - const mockFilterDefinition: RecordFilterDefinition = { - fieldMetadataId: 'field-1', - label: 'Test Field', - type: 'TEXT', - iconName: 'IconText', - }; + const mockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ); + + if (!isDefined(mockObjectMetadataItem)) { + throw new Error( + 'Missing mock object metadata item with name singular "company"', + ); + } + + const mockFieldMetadataItem = mockObjectMetadataItem.fields[0]; + + const mockFilterDefinition: RecordFilterDefinition = + formatFieldMetadataItemAsFilterDefinition({ + field: mockFieldMetadataItem, + }); const mockViewFilter: ViewFilter = { __typename: 'ViewFilter', id: 'filter-1', - fieldMetadataId: 'field-1', + fieldMetadataId: mockFieldMetadataItem.id, operand: ViewFilterOperand.Contains, value: 'test', displayValue: 'test', @@ -36,17 +48,15 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { const mockView = { id: 'view-1', name: 'Test View', - objectMetadataId: 'object-1', + objectMetadataId: mockObjectMetadataItem.id, viewFilters: [mockViewFilter], }; - beforeEach(() => { + it('should apply filters from current view', () => { (usePrefetchedData as jest.Mock).mockReturnValue({ records: [mockView], }); - }); - it('should apply filters from current view', () => { const { result } = renderHook( () => { const { applyCurrentViewFiltersToCurrentRecordFilters } = @@ -70,12 +80,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { }), mockView.id, ); - snapshot.set( - availableFilterDefinitionsComponentState.atomFamily({ - instanceId: 'instanceId', - }), - [mockFilterDefinition], - ); }, }), }, @@ -127,12 +131,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { }), mockView.id, ); - snapshot.set( - availableFilterDefinitionsComponentState.atomFamily({ - instanceId: 'instanceId', - }), - [mockFilterDefinition], - ); }, }), }, @@ -178,12 +176,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { }), mockView.id, ); - snapshot.set( - availableFilterDefinitionsComponentState.atomFamily({ - instanceId: 'instanceId', - }), - [mockFilterDefinition], - ); }, }), }, 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 d4b74ac83..4a7e6540b 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,26 +1,38 @@ import { act, renderHook } from '@testing-library/react'; +import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { ViewFilter } from '@/views/types/ViewFilter'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { isDefined } from 'twenty-shared'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { useApplyViewFiltersToCurrentRecordFilters } from '../useApplyViewFiltersToCurrentRecordFilters'; describe('useApplyViewFiltersToCurrentRecordFilters', () => { - const mockAvailableFilterDefinition: RecordFilterDefinition = { - fieldMetadataId: 'field-1', - label: 'Test Field', - type: 'TEXT', - iconName: 'IconText', - }; + const mockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ); + + if (!isDefined(mockObjectMetadataItem)) { + throw new Error( + 'Missing mock object metadata item with name singular "company"', + ); + } + + const mockFieldMetadataItem = mockObjectMetadataItem.fields[0]; + + const mockAvailableFilterDefinition: RecordFilterDefinition = + formatFieldMetadataItemAsFilterDefinition({ + field: mockFieldMetadataItem, + }); const mockViewFilter: ViewFilter = { __typename: 'ViewFilter', id: 'filter-1', - fieldMetadataId: 'field-1', + fieldMetadataId: mockFieldMetadataItem.id, operand: ViewFilterOperand.Contains, value: 'test', displayValue: 'test', @@ -42,16 +54,7 @@ describe('useApplyViewFiltersToCurrentRecordFilters', () => { return { applyViewFiltersToCurrentRecordFilters, currentFilters }; }, { - wrapper: getJestMetadataAndApolloMocksWrapper({ - onInitializeRecoilSnapshot: (snapshot) => { - snapshot.set( - availableFilterDefinitionsComponentState.atomFamily({ - instanceId: 'instanceId', - }), - [mockAvailableFilterDefinition], - ); - }, - }), + wrapper: getJestMetadataAndApolloMocksWrapper({}), }, ); @@ -86,16 +89,7 @@ describe('useApplyViewFiltersToCurrentRecordFilters', () => { return { applyViewFiltersToCurrentRecordFilters, currentFilters }; }, { - wrapper: getJestMetadataAndApolloMocksWrapper({ - onInitializeRecoilSnapshot: (snapshot) => { - snapshot.set( - availableFilterDefinitionsComponentState.atomFamily({ - instanceId: 'instanceId', - }), - [mockAvailableFilterDefinition], - ); - }, - }), + wrapper: getJestMetadataAndApolloMocksWrapper({}), }, ); diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts index c1ad5920f..22fb0581b 100644 --- a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts +++ b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts @@ -1,9 +1,9 @@ +import { useFilterDefinitionsFromFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterDefinitionsFromFilterableFieldMetadataItems'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { View } from '@/views/types/View'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; @@ -19,19 +19,15 @@ export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => { currentRecordFiltersComponentState, ); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - ); + const { filterDefinitions } = + useFilterDefinitionsFromFilterableFieldMetadataItems(); const applyCurrentViewFiltersToCurrentRecordFilters = () => { const currentView = views.find((view) => view.id === currentViewId); if (isDefined(currentView)) { setCurrentRecordFilters( - mapViewFiltersToFilters( - currentView.viewFilters, - availableFilterDefinitions, - ), + mapViewFiltersToFilters(currentView.viewFilters, filterDefinitions), ); } }; diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts b/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts index ceba489a8..558dfcd6c 100644 --- a/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts +++ b/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts @@ -1,7 +1,6 @@ +import { useFilterDefinitionsFromFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterDefinitionsFromFilterableFieldMetadataItems'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { ViewFilter } from '@/views/types/ViewFilter'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; @@ -10,16 +9,15 @@ export const useApplyViewFiltersToCurrentRecordFilters = () => { currentRecordFiltersComponentState, ); - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, - ); + const { filterDefinitions } = + useFilterDefinitionsFromFilterableFieldMetadataItems(); const applyViewFiltersToCurrentRecordFilters = ( viewFilters: ViewFilter[], ) => { const recordFiltersToApply = mapViewFiltersToFilters( viewFilters, - availableFilterDefinitions, + filterDefinitions, ); setCurrentRecordFilters(recordFiltersToApply); diff --git a/packages/twenty-front/src/modules/views/hooks/useInitViewBar.ts b/packages/twenty-front/src/modules/views/hooks/useInitViewBar.ts index b7b85cc23..7303c4f15 100644 --- a/packages/twenty-front/src/modules/views/hooks/useInitViewBar.ts +++ b/packages/twenty-front/src/modules/views/hooks/useInitViewBar.ts @@ -1,6 +1,5 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { availableFieldDefinitionsComponentState } from '@/views/states/availableFieldDefinitionsComponentState'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; @@ -15,11 +14,6 @@ export const useInitViewBar = (viewBarInstanceId?: string) => { viewBarInstanceId, ); - const setAvailableFilterDefinitions = useSetRecoilComponentStateV2( - availableFilterDefinitionsComponentState, - viewBarInstanceId, - ); - const setViewObjectMetadataId = useSetRecoilComponentStateV2( viewObjectMetadataIdComponentState, viewBarInstanceId, @@ -28,7 +22,6 @@ export const useInitViewBar = (viewBarInstanceId?: string) => { return { setAvailableFieldDefinitions, setAvailableSortDefinitions, - setAvailableFilterDefinitions, setViewObjectMetadataId, }; }; diff --git a/packages/twenty-front/src/modules/views/states/availableFilterDefinitionsComponentState.ts b/packages/twenty-front/src/modules/views/states/availableFilterDefinitionsComponentState.ts deleted file mode 100644 index 3f534d2b0..000000000 --- a/packages/twenty-front/src/modules/views/states/availableFilterDefinitionsComponentState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; -import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; - -export const availableFilterDefinitionsComponentState = createComponentStateV2< - RecordFilterDefinition[] ->({ - key: 'availableFilterDefinitionsComponentState', - defaultValue: [], - componentInstanceContext: ViewComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace.ts b/packages/twenty-front/src/modules/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace.ts new file mode 100644 index 000000000..e72e12394 --- /dev/null +++ b/packages/twenty-front/src/modules/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace.ts @@ -0,0 +1,22 @@ +import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState'; +import { isDefined } from 'twenty-shared'; +import { FeatureFlagKey } from '~/generated-metadata/graphql'; + +export const checkIfFeatureFlagIsEnabledOnWorkspace = ( + featureKey: FeatureFlagKey | null | undefined, + workspace: CurrentWorkspace | null | undefined, +) => { + if ( + !isDefined(featureKey) || + !isDefined(workspace) || + !isDefined(workspace.featureFlags) + ) { + return false; + } + + const featureFlag = workspace.featureFlags.find( + (flag) => flag.key === featureKey, + ); + + return featureFlag?.value === true; +}; diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx index 7665d4d05..c38c486b8 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx @@ -3,9 +3,11 @@ import { ReactNode } from 'react'; import { MutableSnapshot, RecoilRoot } from 'recoil'; import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; +import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; export const getJestMetadataAndApolloMocksWrapper = ({ apolloMocks, @@ -20,17 +22,31 @@ export const getJestMetadataAndApolloMocksWrapper = ({ - 'indexIdentifierUrl', + onIndexRecordsLoaded: () => {}, + objectNamePlural: 'objectNamePlural', + objectNameSingular: 'objectNameSingular', + objectMetadataItem: + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ) ?? generatedMockObjectMetadataItems[0], + recordIndexId: 'recordIndexId', + }} > - - - {children} - - - + + + {children} + + + +