From e7ed9530ca32584dda44fd96683abee630b86e70 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Thu, 30 Jan 2025 17:48:38 +0100 Subject: [PATCH] Implement parallel code path for fieldMetadataItem instead of filterDefinition (#9931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR doesn't remove or change the current behavior of the filter definition used in filter dropdown, but adds a parallel code path where we set the field metadata item used in filter dropdown, which is enough to replace the filter definition. The goal at the end is to compute dynamically the equivalent of filter definition where needed, by deriving from objectMetadataItems global state + fieldMetadataItemId used in dropdown, that way we don't create any other source of truth for the concept of filter definition and everything is easier to work with, especially with advanced filters. The general spirit is that it's always better to derive everywhere from a unique state as much as possible, and only create the equivalent of selectors where needed that will only take the relevant chunk of state for the small zone of the code operating some reading/writing. - Added utils and hooks to get a FieldMetadataItem more easily - Removed some properties from RecordFilterDefinition (the easiest to remove) and replaced them with a dynamic logic, deriving what's needed where it is needed - Added a new fieldMetadataItemIdUsedInDropdownComponentState that is set in parallel of filterDefinitionUsedInDropdown (to prepare the removal of filter definition used in dropdown) - Fixed some stories --------- Co-authored-by: Raphaƫl Bosi <71827178+bosiraphael@users.noreply.github.com> --- .../__stories__/PrefetchLoading.stories.tsx | 2 +- .../hooks/useFieldMetadataItemById.ts | 17 ++++ .../hooks/useGetFieldMetadataItemById.ts | 23 ++++++ ...atFieldMetadataItemsAsFilterDefinitions.ts | 20 ++++- .../AdvancedFilterViewFilterValueInput.tsx | 6 ++ .../ObjectFilterDropdownFilterSelect.tsx | 13 ++- ...pdownFilterSelectCompositeFieldSubMenu.tsx | 7 ++ ...jectFilterDropdownFilterSelectMenuItem.tsx | 13 ++- .../ObjectFilterDropdownRecordSelect.tsx | 17 +++- ...SingleEntityObjectFilterDropdownButton.tsx | 19 ++--- .../MultipleFiltersDropdownButton.stories.tsx | 81 ++++++------------- ...useSelectFilterDefinitionUsedInDropdown.ts | 7 ++ ...adataItemIdUsedInDropdownComponentState.ts | 9 +++ ...dataItemUsedInDropdownComponentSelector.ts | 31 +++++++ ...ubFieldNameUsedInDropdownComponentState.ts | 10 +++ .../RelationToOneFieldInput.stories.tsx | 5 +- .../record-filter/types/RecordFilter.ts | 2 + .../types/RecordFilterDefinition.ts | 6 -- .../hooks/useHandleToggleColumnFilter.ts | 11 ++- .../SingleRecordSelectMenuItems.tsx | 43 +--------- .../EditableFilterDropdownButton.tsx | 7 ++ 21 files changed, 221 insertions(+), 128 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts create mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState.ts create mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector.ts create mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState.ts diff --git a/packages/twenty-front/src/loading/components/__stories__/PrefetchLoading.stories.tsx b/packages/twenty-front/src/loading/components/__stories__/PrefetchLoading.stories.tsx index 3e84058d31..0f94fc9a0d 100644 --- a/packages/twenty-front/src/loading/components/__stories__/PrefetchLoading.stories.tsx +++ b/packages/twenty-front/src/loading/components/__stories__/PrefetchLoading.stories.tsx @@ -36,6 +36,6 @@ export const Default: Story = { await canvas.findByText('Search'); await canvas.findByText('Settings'); await canvas.findByText('Linkedin'); - await canvas.findByText('All'); + await canvas.findByText('All companies'); }, }; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts new file mode 100644 index 0000000000..1daffe5f9e --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts @@ -0,0 +1,17 @@ +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const useFieldMetadataItemById = (fieldMetadataId: string) => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const fieldMetadataItem = objectMetadataItems + .flatMap((objectMetadataItem) => objectMetadataItem.fields) + .find((field) => field.id === fieldMetadataId); + + if (!isDefined(fieldMetadataItem)) { + throw new Error(`Field metadata item not found for id ${fieldMetadataId}`); + } + + return { fieldMetadataItem }; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts new file mode 100644 index 0000000000..c1827d2847 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts @@ -0,0 +1,23 @@ +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const useGetFieldMetadataItemById = () => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const getFieldMetadataItemById = (fieldMetadataId: string) => { + const fieldMetadataItem = objectMetadataItems + .flatMap((objectMetadataItem) => objectMetadataItem.fields) + .find((field) => field.id === fieldMetadataId); + + if (!isDefined(fieldMetadataItem)) { + throw new Error( + `Field metadata item not found for id ${fieldMetadataId}`, + ); + } + + return fieldMetadataItem; + }; + + return { getFieldMetadataItemById }; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts index b817bdf537..35eaa9130c 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts @@ -60,13 +60,25 @@ export const formatFieldMetadataItemAsFilterDefinition = ({ fieldMetadataId: field.id, label: field.label, iconName: field.icon ?? 'Icon123', - relationObjectMetadataNamePlural: - field.relationDefinition?.targetObjectMetadata.namePlural, - relationObjectMetadataNameSingular: - field.relationDefinition?.targetObjectMetadata.nameSingular, type: getFilterTypeFromFieldType(field.type), }); +export const getRelationObjectMetadataNameSingular = ({ + field, +}: { + field: ObjectMetadataItem['fields'][0]; +}): string | undefined => { + return field.relationDefinition?.targetObjectMetadata.nameSingular; +}; + +export const getRelationObjectMetadataNamePlural = ({ + field, +}: { + field: ObjectMetadataItem['fields'][0]; +}): string | undefined => { + return field.relationDefinition?.targetObjectMetadata.namePlural; +}; + export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => { switch (fieldType) { case FieldMetadataType.DATE_TIME: diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterValueInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterValueInput.tsx index e401914186..48c8c0a7b9 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterValueInput.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterValueInput.tsx @@ -1,5 +1,6 @@ import { useCurrentViewFilter } from '@/object-record/advanced-filter/hooks/useCurrentViewFilter'; import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput'; +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'; @@ -26,6 +27,10 @@ export const AdvancedFilterViewFilterValueInput = ({ filterDefinitionUsedInDropdownComponentState, ); + const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( + fieldMetadataItemIdUsedInDropdownComponentState, + ); + const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( selectedOperandInDropdownComponentState, ); @@ -58,6 +63,7 @@ export const AdvancedFilterViewFilterValueInput = ({ /> } onOpen={() => { + setFieldMetadataItemIdUsedInDropdown(filter.fieldMetadataId); setFilterDefinitionUsedInDropdown(filter.definition); setSelectedOperandInDropdown(filter.operand); setSelectedFilter(filter); 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 69a87b395a..efcc83ba5f 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 @@ -25,6 +25,7 @@ import { isDefined } from 'twenty-ui'; import { FeatureFlagKey } from '~/generated/graphql'; 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 { useLingui } from '@lingui/react/macro'; @@ -126,11 +127,15 @@ export const ObjectFilterDropdownFilterSelect = ({ const { selectFilterDefinitionUsedInDropdown } = useSelectFilterDefinitionUsedInDropdown(); + const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( + fieldMetadataItemIdUsedInDropdownComponentState, + ); + const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID); - const handleEnter = (itemId: string) => { + const handleEnter = (fieldMetadataItemId: string) => { const selectedFilterDefinition = availableFilterDefinitions.find( - (item) => item.fieldMetadataId === itemId, + (item) => item.fieldMetadataId === fieldMetadataItemId, ); if (!isDefined(selectedFilterDefinition)) { @@ -143,6 +148,10 @@ export const ObjectFilterDropdownFilterSelect = ({ filterDefinition: selectedFilterDefinition, }); + setFieldMetadataItemIdUsedInDropdown( + selectedFilterDefinition.fieldMetadataId, + ); + closeAdvancedFilterDropdown(); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx index 5f1fa512db..71b3b356cb 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx @@ -1,6 +1,7 @@ import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown'; import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState'; import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState'; +import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState'; @@ -61,6 +62,10 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { filterDefinitionUsedInDropdownComponentState, ); + const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( + fieldMetadataItemIdUsedInDropdownComponentState, + ); + const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( selectedOperandInDropdownComponentState, ); @@ -110,6 +115,7 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { } setFilterDefinitionUsedInDropdown(definition); + setFieldMetadataItemIdUsedInDropdown(definition.fieldMetadataId); setSelectedOperandInDropdown( getRecordFilterOperandsForRecordFilterDefinition(definition)[0], @@ -122,6 +128,7 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { }; const handleSubMenuBack = () => { + setFieldMetadataItemIdUsedInDropdown(null); setFilterDefinitionUsedInDropdown(null); setObjectFilterDropdownSubMenuFieldType(null); setObjectFilterDropdownFirstLevelFilterDefinition(null); 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 6e5ef654df..fcccd4b6e0 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,15 +1,16 @@ 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'; import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState'; import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; -import { CompositeFilterableFieldType } from '@/object-record/record-filter/types/CompositeFilterableFieldType'; - import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField'; +import { CompositeFilterableFieldType } from '@/object-record/record-filter/types/CompositeFilterableFieldType'; 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'; @@ -31,6 +32,10 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ const { selectFilterDefinitionUsedInDropdown } = useSelectFilterDefinitionUsedInDropdown(); + const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( + fieldMetadataItemIdUsedInDropdownComponentState, + ); + const [, setObjectFilterDropdownFirstLevelFilterDefinition] = useRecoilComponentStateV2( objectFilterDropdownFirstLevelFilterDefinitionComponentState, @@ -82,6 +87,10 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ filterDefinition: availableFilterDefinition, }); + setFieldMetadataItemIdUsedInDropdown( + availableFilterDefinition.fieldMetadataId, + ); + if ( availableFilterDefinition.type === 'RELATION' || availableFilterDefinition.type === 'SELECT' diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx index d8cdad5539..b63a8817e3 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx @@ -2,8 +2,10 @@ import { useState } from 'react'; import { v4 } from 'uuid'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { getRelationObjectMetadataNameSingular } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { ObjectFilterDropdownRecordPinnedItems } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems'; import { CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID } from '@/object-record/object-filter-dropdown/constants/CurrentWorkspaceMemberSelectableItemId'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState'; @@ -41,6 +43,10 @@ export const ObjectFilterDropdownRecordSelect = ({ filterDefinitionUsedInDropdownComponentState, ); + const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + ); + const selectedOperandInDropdown = useRecoilComponentValueV2( selectedOperandInDropdownComponentState, ); @@ -73,15 +79,20 @@ export const ObjectFilterDropdownRecordSelect = ({ }) .parse(selectedFilter?.value); - const objectNameSingular = - filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular; + if (!isDefined(fieldMetadataItemUsedInFilterDropdown)) { + throw new Error('fieldMetadataItemUsedInFilterDropdown is not defined'); + } + + const objectNameSingular = getRelationObjectMetadataNameSingular({ + field: fieldMetadataItemUsedInFilterDropdown, + }); if (!isDefined(objectNameSingular)) { throw new Error('relationObjectMetadataNameSingular is not defined'); } const { objectMetadataItem } = useObjectMetadataItem({ - objectNameSingular: objectNameSingular, + objectNameSingular, }); const objectLabelPlural = objectMetadataItem?.labelPlural; 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 20b57d9b0e..4be9678ab5 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 @@ -7,8 +7,8 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +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'; @@ -36,6 +36,10 @@ export const SingleEntityObjectFilterDropdownButton = ({ filterDefinitionUsedInDropdownComponentState, ); + const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( + fieldMetadataItemIdUsedInDropdownComponentState, + ); + const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( selectedOperandInDropdownComponentState, ); @@ -47,6 +51,9 @@ export const SingleEntityObjectFilterDropdownButton = ({ const availableFilterDefinition = availableFilterDefinitions[0]; React.useEffect(() => { + setFieldMetadataItemIdUsedInDropdown( + availableFilterDefinition.fieldMetadataId, + ); setFilterDefinitionUsedInDropdown(availableFilterDefinition); const defaultOperand = getRecordFilterOperandsForRecordFilterDefinition( availableFilterDefinition, @@ -56,6 +63,7 @@ export const SingleEntityObjectFilterDropdownButton = ({ availableFilterDefinition, setFilterDefinitionUsedInDropdown, setSelectedOperandInDropdown, + setFieldMetadataItemIdUsedInDropdown, ]); const theme = useTheme(); @@ -69,14 +77,7 @@ export const SingleEntityObjectFilterDropdownButton = ({ clickableComponent={ {selectedFilter ? ( - + ) : ( t`Filter` )} 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 b209520ec3..ad980c6204 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 @@ -3,13 +3,14 @@ import { Meta, StoryObj } from '@storybook/react'; 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'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; -import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; @@ -18,7 +19,6 @@ import { ComponentDecorator, getCanvasElementForDropdownTesting, } from 'twenty-ui'; -import { FieldMetadataType } from '~/generated/graphql'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; @@ -45,59 +45,26 @@ const meta: Meta = { instanceId, ); - setTableColumns([ - { - fieldMetadataId: '1', - iconName: 'IconUser', - label: 'Text', - type: FieldMetadataType.TEXT, - isVisible: true, - metadata: { - fieldName: 'text', - }, - } as ColumnDefinition, - { - fieldMetadataId: '3', - iconName: 'IconNumber', - label: 'Number', - type: FieldMetadataType.NUMBER, - isVisible: true, - metadata: { - fieldName: 'number', - }, - } as ColumnDefinition, - { - fieldMetadataId: '4', - iconName: 'IconCalendar', - label: 'Date', - type: FieldMetadataType.DATE_TIME, - isVisible: true, - metadata: { - fieldName: 'date', - }, - } as ColumnDefinition, - ]); + const columns = companyObjectMetadataItem.fields.map( + (fieldMetadataItem, index) => + formatFieldMetadataItemAsColumnDefinition({ + field: fieldMetadataItem, + objectMetadataItem: companyObjectMetadataItem, + position: index, + }), + ); + + const filterDefinitions = companyObjectMetadataItem.fields.map( + (fieldMetadataItem) => + formatFieldMetadataItemAsFilterDefinition({ + field: fieldMetadataItem, + }), + ); + + setTableColumns(columns); + + setAvailableFilterDefinitions(filterDefinitions); - setAvailableFilterDefinitions([ - { - fieldMetadataId: '1', - iconName: 'IconUser', - label: 'Text', - type: FieldMetadataType.TEXT, - }, - { - fieldMetadataId: '3', - iconName: 'IconNumber', - label: 'Number', - type: FieldMetadataType.NUMBER, - }, - { - fieldMetadataId: '3', - iconName: 'IconCalendar', - label: 'Date', - type: FieldMetadataType.DATE_TIME, - }, - ]); return ( { setFilterDefinitionUsedInDropdown(filterDefinition); + setFieldMetadataItemIdUsedInDropdown(filterDefinition.fieldMetadataId); if ( filterDefinition.type === 'RELATION' || diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState.ts new file mode 100644 index 0000000000..cefeb2b739 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState.ts @@ -0,0 +1,9 @@ +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const fieldMetadataItemIdUsedInDropdownComponentState = + createComponentStateV2({ + key: 'fieldMetadataItemIdUsedInDropdownComponentState', + defaultValue: null, + componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector.ts new file mode 100644 index 0000000000..bf3e6622e4 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector.ts @@ -0,0 +1,31 @@ +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; +import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; +import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2'; + +export const fieldMetadataItemUsedInDropdownComponentSelector = + createComponentSelectorV2({ + key: 'fieldMetadataItemUsedInDropdownComponentSelector', + get: + ({ instanceId }) => + ({ get }) => { + const fieldMetadataItemIdUsedInDropdown = get( + fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({ + instanceId, + }), + ); + + const objectMetadataItems = get(objectMetadataItemsState); + + const correspondingFieldMetadataItem = objectMetadataItems + .flatMap((objectMetadataItem) => objectMetadataItem.fields) + .find( + (fieldMetadataItem) => + fieldMetadataItem.id === fieldMetadataItemIdUsedInDropdown, + ); + + return correspondingFieldMetadataItem; + }, + componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState.ts new file mode 100644 index 0000000000..db286d7397 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState.ts @@ -0,0 +1,10 @@ +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const subFieldNameUsedInDropdownComponentState = createComponentStateV2< + string | null +>({ + key: 'subFieldNameUsedInDropdownComponentState', + defaultValue: null, + componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RelationToOneFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RelationToOneFieldInput.stories.tsx index 262d00fca9..ce70f5d51f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RelationToOneFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/RelationToOneFieldInput.stories.tsx @@ -140,7 +140,10 @@ export const Submit: Story = { }); await userEvent.click(item); - await waitFor(() => expect(submitJestFn).toHaveBeenCalledTimes(1)); + + await waitFor(() => { + expect(submitJestFn).toHaveBeenCalledTimes(1); + }); }, }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts index b993ea318b..d6760f92f2 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts @@ -12,4 +12,6 @@ export type RecordFilter = { operand: ViewFilterOperand; positionInViewFilterGroup?: number | null; definition: RecordFilterDefinition; + label?: string; + subFieldName?: string; }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilterDefinition.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilterDefinition.ts index d549f1038b..820195f68a 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilterDefinition.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilterDefinition.ts @@ -1,5 +1,3 @@ -import { IconComponent } from 'twenty-ui'; - import { FilterableFieldType } from './FilterableFieldType'; export type RecordFilterDefinition = { @@ -7,9 +5,5 @@ export type RecordFilterDefinition = { label: string; iconName: string; type: FilterableFieldType; - relationObjectMetadataNamePlural?: string; - relationObjectMetadataNameSingular?: string; - selectAllLabel?: string; - SelectAllIcon?: IconComponent; compositeFieldName?: string; }; 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 daa0bb001a..bcc7d25be2 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 @@ -3,13 +3,15 @@ import { v4 } from 'uuid'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown'; +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'; @@ -56,6 +58,11 @@ export const useHandleToggleColumnFilter = ({ const { selectFilterDefinitionUsedInDropdown } = useSelectFilterDefinitionUsedInDropdown(viewBarId); + const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( + fieldMetadataItemIdUsedInDropdownComponentState, + viewBarId, + ); + const handleToggleColumnFilter = useCallback( async (fieldMetadataId: string) => { const correspondingColumnDefinition = columnDefinitions.find( @@ -100,6 +107,7 @@ export const useHandleToggleColumnFilter = ({ await upsertCombinedViewFilter(newFilter); selectFilterDefinitionUsedInDropdown({ filterDefinition }); + setFieldMetadataItemIdUsedInDropdown(fieldMetadataId); } openDropdown(existingViewFilter?.id ?? newFilterId); @@ -112,6 +120,7 @@ export const useHandleToggleColumnFilter = ({ currentViewWithCombinedFiltersAndSorts, availableFilterDefinitions, upsertRecordFilter, + setFieldMetadataItemIdUsedInDropdown, ], ); diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleRecordSelectMenuItems.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleRecordSelectMenuItems.tsx index c657ba9a96..77782e5507 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleRecordSelectMenuItems.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleRecordSelectMenuItems.tsx @@ -24,11 +24,6 @@ export type SingleRecordSelectMenuItemsProps = { onCancel?: () => void; onRecordSelected: (entity?: RecordForSelect) => void; selectedRecord?: RecordForSelect; - SelectAllIcon?: IconComponent; - selectAllLabel?: string; - isAllRecordsSelected?: boolean; - isAllRecordsSelectShown?: boolean; - onAllRecordsSelected?: () => void; hotkeyScope?: string; isFiltered: boolean; shouldSelectEmptyOption?: boolean; @@ -42,11 +37,6 @@ export const SingleRecordSelectMenuItems = ({ onCancel, onRecordSelected, selectedRecord, - SelectAllIcon, - selectAllLabel, - isAllRecordsSelected, - isAllRecordsSelectShown, - onAllRecordsSelected, hotkeyScope = RelationPickerHotkeyScope.RelationPicker, isFiltered, shouldSelectEmptyOption, @@ -61,16 +51,7 @@ export const SingleRecordSelectMenuItems = ({ } : null; - const selectAll = isAllRecordsSelectShown - ? { - __typename: '', - id: 'select-all', - name: selectAllLabel, - } - : null; - const recordsInDropdown = [ - selectAll, selectNone, selectedRecord, ...recordsToSelect, @@ -87,10 +68,6 @@ export const SingleRecordSelectMenuItems = ({ isSelectedItemIdSelector('select-none'), ); - const isSelectedSelectAllButton = useRecoilValue( - isSelectedItemIdSelector('select-all'), - ); - useScopedHotkeys( [Key.Escape], () => { @@ -120,9 +97,7 @@ export const SingleRecordSelectMenuItems = ({ {loading && !isFiltered ? ( - ) : recordsInDropdown.length === 0 && - !isAllRecordsSelectShown && - !loading ? ( + ) : recordsInDropdown.length === 0 && !loading ? ( <> ) : ( recordsInDropdown?.map((record) => { @@ -141,22 +116,6 @@ export const SingleRecordSelectMenuItems = ({ ) ); } - case 'select-all': { - return ( - isAllRecordsSelectShown && - selectAllLabel && - onAllRecordsSelected && ( - onAllRecordsSelected()} - LeftIcon={SelectAllIcon} - text={selectAllLabel} - selected={!!isAllRecordsSelected} - hovered={isSelectedSelectAllButton} - /> - ) - ); - } default: { return (