From 777c12dd067c3db415e43d3b7e08b92d672d030d Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Thu, 6 Mar 2025 17:57:23 +0100 Subject: [PATCH] Fix advanced filter creation of group rule (#10690) This PR improves advanced filter code and fixes the bug that prevented the creation of a filter group. On the debugging side : - Adding an advanced filter rule to create a group now works On the refactoring side : - We now use AdvancedFilterRecordFilterGroupChildOptionsDropdown to clarify the code that show the option dropdown of a group. - Refacatored useCurrentViewViewFilterGroup to useChildRecordFiltersAndRecordFilterGroups. It is now using only RecordFilter and RecordFilterGroup type instead of view types. It also exports recordFilters and recordFilterGroups alone, when they are children of a group, so we don't have to extract them from the merged array that is typed RecordFilter | RecordFilterGroup, which is necessary for displaying a group. - Two typeguards have been introduced to help discern RecordFilter from RecordFilterGroup : isRecordFilterGroupChildARecordFilterGroup and isRecordFilterGroupChildARecordFilter, this allows to remove any typing on child processing. - Renaming from view to record (but there are still some left) --- .../AdvancedFilterAddFilterRuleSelect.tsx | 69 ++++---------- ...sx => AdvancedFilterRecordFilterGroup.tsx} | 44 +++++---- ...rRecordFilterGroupChildOptionsDropdown.tsx | 27 ++++++ ...FilterRecordFilterGroupOptionsDropdown.tsx | 53 +++++++++++ ...ancedFilterRecordFilterOptionsDropdown.tsx | 71 ++++++++++++++ ...AdvancedFilterRootLevelViewFilterGroup.tsx | 42 +++++---- .../AdvancedFilterRuleOptionsDropdown.tsx | 93 ------------------- ...dvancedFilterRuleOptionsDropdownButton.tsx | 25 ----- ...hildRecordFiltersAndRecordFilterGroups.ts} | 32 +++---- .../useDefaultFieldMetadataItemForFilter.ts | 42 +++++++++ .../isRecordFilterGroupChildARecordFilter.ts | 8 ++ ...ecordFilterGroupChildARecordFilterGroup.ts | 8 ++ .../AdvancedFilterDropdownButton.tsx | 2 +- 13 files changed, 293 insertions(+), 223 deletions(-) rename packages/twenty-front/src/modules/object-record/advanced-filter/components/{AdvancedFilterViewFilterGroup.tsx => AdvancedFilterRecordFilterGroup.tsx} (54%) create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown.tsx create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown.tsx create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdownButton.tsx rename packages/twenty-front/src/modules/object-record/advanced-filter/hooks/{useCurrentViewViewFilterGroup.ts => useChildRecordFiltersAndRecordFilterGroups.ts} (68%) create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter.ts create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilter.ts create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup.ts 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 e6f9e0234..6f8e6f77a 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,6 +1,5 @@ -import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; -import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { useDefaultFieldMetadataItemForFilter } from '@/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter'; import { getAdvancedFilterAddFilterRuleSelectDropdownId } from '@/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId'; import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; @@ -12,8 +11,6 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; -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,55 +38,23 @@ export const AdvancedFilterAddFilterRuleSelect = ({ const { closeDropdown } = useDropdown(dropdownId); - const objectMetadataId = currentView?.objectMetadataId; - - if (!isDefined(objectMetadataId)) { - throw new Error('Object metadata id is missing from current view'); - } - - const { objectMetadataItem } = useObjectMetadataItemById({ - objectId: objectMetadataId, - }); - - const availableFieldMetadataItemsForFilter = useRecoilValue( - availableFieldMetadataItemsForFilterFamilySelector({ - objectMetadataItemId: objectMetadataId, - }), - ); - - const getDefaultFieldMetadataItem = useCallback(() => { - const defaultFieldMetadataItem = - availableFieldMetadataItemsForFilter.find( - (fieldMetadataItem) => - fieldMetadataItem.id === - objectMetadataItem?.labelIdentifierFieldMetadataId, - ) ?? availableFieldMetadataItemsForFilter[0]; - - if (!isDefined(defaultFieldMetadataItem)) { - throw new Error( - `Could not find default field metadata item for object ${objectMetadataId}`, - ); - } - - return defaultFieldMetadataItem; - }, [ - availableFieldMetadataItemsForFilter, - objectMetadataItem, - objectMetadataId, - ]); + const { defaultFieldMetadataItemForFilter } = + useDefaultFieldMetadataItemForFilter(); const handleAddFilter = () => { + if (!isDefined(defaultFieldMetadataItemForFilter)) { + throw new Error('Missing default field metadata item for filter'); + } + closeDropdown(); - const defaultFieldMetadataItem = getDefaultFieldMetadataItem(); - const filterType = getFilterTypeFromFieldType( - defaultFieldMetadataItem.type, + defaultFieldMetadataItemForFilter.type, ); upsertRecordFilter({ id: v4(), - fieldMetadataId: defaultFieldMetadataItem.id, + fieldMetadataId: defaultFieldMetadataItemForFilter.id, type: filterType, operand: getRecordFilterOperands({ filterType, @@ -98,13 +63,17 @@ export const AdvancedFilterAddFilterRuleSelect = ({ displayValue: '', recordFilterGroupId: recordFilterGroup.id, positionInRecordFilterGroup: newPositionInRecordFilterGroup, - label: defaultFieldMetadataItem.label, + label: defaultFieldMetadataItemForFilter.label, }); }; const handleAddFilterGroup = () => { closeDropdown(); + if (!isDefined(defaultFieldMetadataItemForFilter)) { + throw new Error('Missing default field metadata item for filter'); + } + if (!isDefined(currentView)) { throw new Error('Missing view'); } @@ -120,15 +89,13 @@ export const AdvancedFilterAddFilterRuleSelect = ({ upsertRecordFilterGroup(newRecordFilterGroup); - const defaultFieldMetadataItem = getDefaultFieldMetadataItem(); - const filterType = getFilterTypeFromFieldType( - defaultFieldMetadataItem.type, + defaultFieldMetadataItemForFilter.type, ); upsertRecordFilter({ id: v4(), - fieldMetadataId: defaultFieldMetadataItem.id, + fieldMetadataId: defaultFieldMetadataItemForFilter.id, type: filterType, operand: getRecordFilterOperands({ filterType, @@ -136,8 +103,8 @@ export const AdvancedFilterAddFilterRuleSelect = ({ value: '', displayValue: '', recordFilterGroupId: newRecordFilterGroupId, - positionInRecordFilterGroup: newPositionInRecordFilterGroup, - label: defaultFieldMetadataItem.label, + positionInRecordFilterGroup: 1, + label: defaultFieldMetadataItemForFilter.label, }); }; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterGroup.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroup.tsx similarity index 54% rename from packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterGroup.tsx rename to packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroup.tsx index ef706e119..b2d8244b1 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterViewFilterGroup.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroup.tsx @@ -1,9 +1,11 @@ import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect'; import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell'; -import { AdvancedFilterRuleOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown'; +import { AdvancedFilterRecordFilterGroupChildOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown'; + import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter'; -import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup'; +import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups'; import styled from '@emotion/styled'; +import { isDefined } from 'twenty-shared'; const StyledRow = styled.div` display: flex; @@ -25,41 +27,45 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>` overflow: hidden; `; -type AdvancedFilterViewFilterGroupProps = { - viewFilterGroupId: string; +type AdvancedFilterRecordFilterGroupProps = { + recordFilterGroupId: string; }; -export const AdvancedFilterViewFilterGroup = ({ - viewFilterGroupId, -}: AdvancedFilterViewFilterGroupProps) => { +export const AdvancedFilterRecordFilterGroup = ({ + recordFilterGroupId, +}: AdvancedFilterRecordFilterGroupProps) => { const { - currentViewFilterGroup, - childViewFiltersAndViewFilterGroups, + currentRecordFilterGroup, + childRecordFiltersAndRecordFilterGroups, lastChildPosition, - } = useCurrentViewViewFilterGroup({ - recordFilterGroupId: viewFilterGroupId, + } = useChildRecordFiltersAndRecordFilterGroups({ + recordFilterGroupId, }); - if (!currentViewFilterGroup) { + if (!currentRecordFilterGroup) { return null; } + const hasParentRecordFilterGroup = isDefined( + currentRecordFilterGroup.parentRecordFilterGroupId, + ); + return ( - - {childViewFiltersAndViewFilterGroups.map((child, i) => ( + + {childRecordFiltersAndRecordFilterGroups.map((child, i) => ( - + ))} diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown.tsx new file mode 100644 index 000000000..034f96b71 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown.tsx @@ -0,0 +1,27 @@ +import { AdvancedFilterRecordFilterGroupOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown'; +import { AdvancedFilterRecordFilterOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown'; +import { isRecordFilterGroupChildARecordFilter } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilter'; +import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; + +type AdvancedFilterRecordFilterGroupChildOptionsDropdownProps = { + recordFilterGroupChild: RecordFilter | RecordFilterGroup; +}; + +export const AdvancedFilterRecordFilterGroupChildOptionsDropdown = ({ + recordFilterGroupChild, +}: AdvancedFilterRecordFilterGroupChildOptionsDropdownProps) => { + const isRecordFilter = isRecordFilterGroupChildARecordFilter( + recordFilterGroupChild, + ); + + return isRecordFilter ? ( + + ) : ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown.tsx new file mode 100644 index 000000000..18f924161 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown.tsx @@ -0,0 +1,53 @@ +import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups'; +import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup'; +import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; + +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { IconButton, IconDotsVertical, MenuItem } from 'twenty-ui'; + +type AdvancedFilterRecordFilterGroupOptionsDropdownProps = { + recordFilterGroupId: string; +}; + +export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({ + recordFilterGroupId, +}: AdvancedFilterRecordFilterGroupOptionsDropdownProps) => { + const dropdownId = `advanced-filter-record-filter-group-options-${recordFilterGroupId}`; + + const { removeRecordFilter } = useRemoveRecordFilter(); + const { removeRecordFilterGroup } = useRemoveRecordFilterGroup(); + + const { childRecordFilters } = useChildRecordFiltersAndRecordFilterGroups({ + recordFilterGroupId, + }); + + const handleRemove = () => { + for (const childRecordFilter of childRecordFilters ?? []) { + removeRecordFilter({ recordFilterId: childRecordFilter.id }); + } + + removeRecordFilterGroup(recordFilterGroupId); + }; + + return ( + + } + dropdownComponents={ + + + + } + dropdownHotkeyScope={{ scope: dropdownId }} + dropdownOffset={{ y: 8, x: 0 }} + dropdownPlacement="bottom-start" + /> + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown.tsx new file mode 100644 index 000000000..8b4c2ff2b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown.tsx @@ -0,0 +1,71 @@ +import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups'; +import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup'; + +import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; + +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { isDefined } from 'twenty-shared'; +import { IconButton, IconDotsVertical, MenuItem } from 'twenty-ui'; + +type AdvancedFilterRecordFilterOptionsDropdownProps = { + recordFilterId: string; +}; + +export const AdvancedFilterRecordFilterOptionsDropdown = ({ + recordFilterId, +}: AdvancedFilterRecordFilterOptionsDropdownProps) => { + const dropdownId = `advanced-filter-record-filter-options-${recordFilterId}`; + + const { removeRecordFilter } = useRemoveRecordFilter(); + const { removeRecordFilterGroup } = useRemoveRecordFilterGroup(); + + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const currentRecordFilter = currentRecordFilters.find( + (recordFilter) => recordFilter.id === recordFilterId, + ); + + const { childRecordFiltersAndRecordFilterGroups } = + useChildRecordFiltersAndRecordFilterGroups({ + recordFilterGroupId: currentRecordFilter?.recordFilterGroupId, + }); + + const handleRemove = async () => { + removeRecordFilter({ recordFilterId: recordFilterId }); + + if (isDefined(currentRecordFilter?.recordFilterGroupId)) { + const isOnlyViewFilterInGroup = + childRecordFiltersAndRecordFilterGroups?.length === 1; + + if (isOnlyViewFilterInGroup) { + removeRecordFilterGroup(currentRecordFilter.recordFilterGroupId); + } + } + }; + + return ( + + } + dropdownComponents={ + + + + } + dropdownHotkeyScope={{ scope: dropdownId }} + dropdownOffset={{ y: 8, x: 0 }} + dropdownPlacement="bottom-start" + /> + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx index 81bf23708..d0d292266 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx @@ -1,9 +1,11 @@ import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect'; import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell'; -import { AdvancedFilterRuleOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown'; +import { AdvancedFilterRecordFilterGroupChildOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown'; + +import { AdvancedFilterRecordFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroup'; import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter'; -import { AdvancedFilterViewFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterGroup'; -import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup'; +import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups'; +import { isRecordFilterGroupChildARecordFilterGroup } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared'; @@ -28,49 +30,53 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>` `; type AdvancedFilterRootLevelViewFilterGroupProps = { - rootLevelViewFilterGroupId: string; + rootLevelRecordFilterGroupId: string; }; export const AdvancedFilterRootLevelViewFilterGroup = ({ - rootLevelViewFilterGroupId, + rootLevelRecordFilterGroupId, }: AdvancedFilterRootLevelViewFilterGroupProps) => { const { - currentViewFilterGroup: rootLevelViewFilterGroup, - childViewFiltersAndViewFilterGroups, + currentRecordFilterGroup: rootLevelRecordFilterGroup, + childRecordFiltersAndRecordFilterGroups, lastChildPosition, - } = useCurrentViewViewFilterGroup({ - recordFilterGroupId: rootLevelViewFilterGroupId, + } = useChildRecordFiltersAndRecordFilterGroups({ + recordFilterGroupId: rootLevelRecordFilterGroupId, }); - if (!isDefined(rootLevelViewFilterGroup)) { + if (!isDefined(rootLevelRecordFilterGroup)) { return null; } return ( - {childViewFiltersAndViewFilterGroups.map((child, i) => - (child as any).__typename === 'ViewFilterGroup' ? ( + {childRecordFiltersAndRecordFilterGroups.map((child, i) => + isRecordFilterGroupChildARecordFilterGroup(child) ? ( + + - - ) : ( - + ), )} diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown.tsx deleted file mode 100644 index 3441144f5..000000000 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { AdvancedFilterRuleOptionsDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdownButton'; - -import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup'; -import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup'; -import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; -import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; - -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { isDefined } from 'twenty-shared'; -import { MenuItem } from 'twenty-ui'; - -type AdvancedFilterRuleOptionsDropdownProps = - | { - viewFilterId: string; - viewFilterGroupId?: never; - } - | { - viewFilterId?: never; - viewFilterGroupId: string; - }; - -export const AdvancedFilterRuleOptionsDropdown = ({ - viewFilterId, - viewFilterGroupId, -}: AdvancedFilterRuleOptionsDropdownProps) => { - const dropdownId = `advanced-filter-rule-options-${viewFilterId ?? viewFilterGroupId}`; - - const { removeRecordFilter } = useRemoveRecordFilter(); - const { removeRecordFilterGroup } = useRemoveRecordFilterGroup(); - - const { currentViewFilterGroup, childViewFiltersAndViewFilterGroups } = - useCurrentViewViewFilterGroup({ - recordFilterGroupId: viewFilterGroupId, - }); - - const currentRecordFilters = useRecoilComponentValueV2( - currentRecordFiltersComponentState, - ); - - const currentRecordFilter = currentRecordFilters.find( - (recordFilter) => recordFilter.id === viewFilterId, - ); - - const handleRemove = async () => { - if (isDefined(viewFilterId)) { - removeRecordFilter({ recordFilterId: viewFilterId }); - - const isOnlyViewFilterInGroup = - childViewFiltersAndViewFilterGroups.length === 1; - - if ( - isOnlyViewFilterInGroup && - isDefined(currentRecordFilter?.recordFilterGroupId) - ) { - removeRecordFilterGroup(currentRecordFilter.recordFilterGroupId); - } - } else if (isDefined(currentViewFilterGroup)) { - removeRecordFilterGroup(currentViewFilterGroup.id); - - // TODO: This is a temporary fix view filter group will be removed soon. - const childViewFilters = childViewFiltersAndViewFilterGroups.filter( - (child) => (child as any).__typename === 'ViewFilter', - ); - - for (const childViewFilter of childViewFilters) { - removeRecordFilter({ recordFilterId: childViewFilter.id }); - } - } else { - throw new Error('No view filter or view filter group to remove'); - } - }; - - const removeButtonLabel = viewFilterId ? 'Remove rule' : 'Remove rule group'; - - return ( - - } - dropdownComponents={ - - - - } - dropdownHotkeyScope={{ scope: dropdownId }} - dropdownOffset={{ y: 8, x: 0 }} - dropdownPlacement="bottom-start" - /> - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdownButton.tsx deleted file mode 100644 index 40c21f09e..000000000 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdownButton.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; -import { IconButton, IconDotsVertical } from 'twenty-ui'; - -type AdvancedFilterRuleOptionsDropdownButtonProps = { - dropdownId: string; -}; - -export const AdvancedFilterRuleOptionsDropdownButton = ({ - dropdownId, -}: AdvancedFilterRuleOptionsDropdownButtonProps) => { - const { toggleDropdown } = useDropdown(dropdownId); - - const handleClick = () => { - toggleDropdown(); - }; - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups.ts similarity index 68% rename from packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup.ts rename to packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups.ts index e5c4e6069..13552ab83 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup.ts +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups.ts @@ -1,11 +1,9 @@ import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; import { isDefined } from 'twenty-shared'; -export const useCurrentViewViewFilterGroup = ({ +export const useChildRecordFiltersAndRecordFilterGroups = ({ recordFilterGroupId, }: { recordFilterGroupId?: string; @@ -24,11 +22,11 @@ export const useCurrentViewViewFilterGroup = ({ if (!isDefined(currentRecordFilterGroup)) { return { - currentViewFilterGroup: undefined, - childViewFiltersAndViewFilterGroups: [] as ( - | ViewFilter - | ViewFilterGroup - )[], + currentRecordFilterGroup: undefined, + childRecordFiltersAndRecordFilterGroups: [], + childRecordFilters: [], + childRecordFilterGroups: [], + lastChildPosition: 0, }; } @@ -37,15 +35,15 @@ export const useCurrentViewViewFilterGroup = ({ recordFilterToFilter.recordFilterGroupId === currentRecordFilterGroup.id, ); - const childViewFilterGroups = currentRecordFilterGroups.filter( + const childRecordFilterGroups = currentRecordFilterGroups.filter( (currentRecordGroupToFilter) => currentRecordGroupToFilter.parentRecordFilterGroupId === currentRecordFilterGroup.id, ); - const childViewFiltersAndViewFilterGroups = [ - ...(childViewFilterGroups ?? []), - ...(childRecordFilters ?? []), + const childRecordFiltersAndRecordFilterGroups = [ + ...childRecordFilterGroups, + ...childRecordFilters, ].sort((a, b) => { const positionA = a.positionInRecordFilterGroup ?? 0; const positionB = b.positionInRecordFilterGroup ?? 0; @@ -53,13 +51,15 @@ export const useCurrentViewViewFilterGroup = ({ }); const lastChildPosition = - childViewFiltersAndViewFilterGroups[ - childViewFiltersAndViewFilterGroups.length - 1 + childRecordFiltersAndRecordFilterGroups[ + childRecordFiltersAndRecordFilterGroups.length - 1 ]?.positionInRecordFilterGroup ?? 0; return { - currentViewFilterGroup: currentRecordFilterGroup, - childViewFiltersAndViewFilterGroups, + currentRecordFilterGroup, + childRecordFiltersAndRecordFilterGroups, + childRecordFilters, + childRecordFilterGroups, lastChildPosition, }; }; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter.ts new file mode 100644 index 000000000..daa243ef8 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter.ts @@ -0,0 +1,42 @@ +import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared'; + +export const useDefaultFieldMetadataItemForFilter = () => { + const { currentView } = useGetCurrentViewOnly(); + + const objectMetadataId = currentView?.objectMetadataId; + + if (!isDefined(objectMetadataId)) { + throw new Error('Object metadata id is missing from current view'); + } + + const { objectMetadataItem } = useObjectMetadataItemById({ + objectId: objectMetadataId, + }); + + const availableFieldMetadataItemsForFilter = useRecoilValue( + availableFieldMetadataItemsForFilterFamilySelector({ + objectMetadataItemId: objectMetadataId, + }), + ); + + const fieldMetadataItemForLabelIdentifier = + availableFieldMetadataItemsForFilter.find( + (fieldMetadataItem) => + fieldMetadataItem.id === + objectMetadataItem?.labelIdentifierFieldMetadataId, + ); + + const firstFieldMetadataItem = availableFieldMetadataItemsForFilter?.[0] as + | FieldMetadataItem + | undefined; + + const defaultFieldMetadataItemForFilter = + fieldMetadataItemForLabelIdentifier ?? firstFieldMetadataItem; + + return { defaultFieldMetadataItemForFilter }; +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilter.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilter.ts new file mode 100644 index 000000000..e35009880 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilter.ts @@ -0,0 +1,8 @@ +import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; + +export const isRecordFilterGroupChildARecordFilter = ( + child: RecordFilter | RecordFilterGroup, +): child is RecordFilter => { + return ('fieldMetadataId' satisfies keyof RecordFilter) in child; +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup.ts new file mode 100644 index 000000000..6d167cd53 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup.ts @@ -0,0 +1,8 @@ +import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; + +export const isRecordFilterGroupChildARecordFilterGroup = ( + child: RecordFilter | RecordFilterGroup, +): child is RecordFilterGroup => { + return ('logicalOperator' satisfies keyof RecordFilterGroup) in child; +}; diff --git a/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx index d40f7ba1e..2173d7cd0 100644 --- a/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx @@ -72,7 +72,7 @@ export const AdvancedFilterDropdownButton = () => { } dropdownComponents={ } dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}