From b29ff9b4e6d2129ddcd9fd781a3d9105f62c4e47 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Mon, 3 Feb 2025 17:29:57 +0100 Subject: [PATCH] Removed availableFilterDefinitions as a state but kept its usage as a derived state of objectMetadataItems (#9972) The global record filter refactor will derive everything at runtime from objectMetadataItemsState, thus removing the need for a filter definition concept. Here we don't yet remove available filter definition usage but we replace the available filter definitions states, we now derive the same value from objectMetadataItemsState. This will allow us to progressively remove the usage of the concept of filter definition, at the end it will then be easy to just remove from the codebase because nothing will use it anymore. --- ...eldMetadataItemsForFilterFamilySelector.ts | 41 ++++++++ .../getFilterFilterableFieldMetadataItems.ts | 52 ++++++++++ .../AdvancedFilterAddFilterRuleSelect.tsx | 55 +++++++---- .../hooks/useCurrentViewFilter.ts | 13 +-- .../components/AdvancedFilterButton.tsx | 37 +++++--- .../components/ObjectFilterDropdownButton.tsx | 14 +-- .../ObjectFilterDropdownFilterSelect.tsx | 67 ++++++------- ...jectFilterDropdownFilterSelectMenuItem.tsx | 44 +++++---- ...SingleEntityObjectFilterDropdownButton.tsx | 95 ++++++------------- ...EntityObjectFilterDropdownButtonEffect.tsx | 47 +++++++++ .../MultipleFiltersDropdownButton.stories.tsx | 15 --- ...useSelectFilterDefinitionUsedInDropdown.ts | 7 -- ...vailableFilterDefinitionsComponentState.ts | 12 --- ...nitionsFromFilterableFieldMetadataItems.ts | 23 +++++ .../hooks/useFilterableFieldMetadataItems.ts | 15 +++ .../components/RecordIndexContainer.tsx | 6 +- .../components/RecordIndexViewBarEffect.tsx | 6 +- .../hooks/useHandleToggleColumnFilter.ts | 27 ++++-- .../SignInBackgroundMockContainerEffect.tsx | 4 - .../EditableFilterDropdownButton.tsx | 94 +++++------------- .../EditableFilterDropdownButtonEffect.tsx | 74 +++++++++++++++ .../views/components/ViewBarEffect.tsx | 7 -- .../views/components/ViewBarFilterEffect.tsx | 17 ---- .../components/ViewBarRecordFilterEffect.tsx | 21 ++-- ...ViewFiltersToCurrentRecordFilters.test.tsx | 52 +++++----- ...ViewFiltersToCurrentRecordFilters.test.tsx | 50 +++++----- ...urrentViewFiltersToCurrentRecordFilters.ts | 12 +-- ...eApplyViewFiltersToCurrentRecordFilters.ts | 10 +- .../src/modules/views/hooks/useInitViewBar.ts | 7 -- ...vailableFilterDefinitionsComponentState.ts | 11 --- .../checkIfFeatureFlagIsEnabledOnWorkspace.ts | 22 +++++ .../getJestMetadataAndApolloMocksWrapper.tsx | 32 +++++-- 32 files changed, 566 insertions(+), 423 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/getFilterFilterableFieldMetadataItems.ts create mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButtonEffect.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/availableFilterDefinitionsComponentState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterDefinitionsFromFilterableFieldMetadataItems.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterableFieldMetadataItems.ts create mode 100644 packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx delete mode 100644 packages/twenty-front/src/modules/views/states/availableFilterDefinitionsComponentState.ts create mode 100644 packages/twenty-front/src/modules/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace.ts 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 0000000000..f3d6a9bdbe --- /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 0000000000..a704b49074 --- /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 1c259f4667..76c136c5f8 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 40bd802ba2..fced8cd204 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 d7e2d84445..fc43e1607c 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 74f37a6806..3ebd9b2d66 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 fe0dbd6401..8dcf9ab7e0 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 fcccd4b6e0..9ecf90ce9f 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 4be9678ab5..9048d8668c 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 0000000000..c2632dde83 --- /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 ad980c6204..5f67343a98 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 ddc529f000..0000000000 --- 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 0000000000..df831fd08e --- /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 0000000000..80cfad8c76 --- /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 b3cec7e8f6..a99c1946a8 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 44ae887962..1d64d9cb61 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 00c4b2cd38..4ed6a73381 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 04b9d9205e..75929819a6 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 146bcc41a3..35d5eddd44 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 0000000000..f834b3cc52 --- /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 8137fb6d1e..662b4ed49f 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 4fc803b86a..34fbaebd03 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 344fd07535..1f9c5cb9fb 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 07f8074cc1..0f8435c836 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 d4b74ac83e..4a7e6540bf 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 c1ad5920f3..22fb0581bb 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 ceba489a83..558dfcd6c1 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 b7b85cc239..7303c4f15e 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 3f534d2b03..0000000000 --- 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 0000000000..e72e12394e --- /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 7665d4d056..c38c486b8c 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} + + + +