From bad7ad464be61f68b71781206187ba7633cfa559 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Sun, 24 Nov 2024 09:43:44 +0100 Subject: [PATCH] Add boolean filtering (#7190) (#8700) filter-icp-true Link to issue: https://github.com/twentyhq/twenty/issues/7190 --- ...ColumnDefinitionsFromFieldMetadata.test.ts | 2 +- ...atFieldMetadataItemsAsFilterDefinitions.ts | 3 + .../ObjectFilterDropdownBooleanSelect.tsx | 113 ++++++++++++++++++ .../ObjectFilterDropdownFilterInput.tsx | 4 + .../types/FilterableFieldType.ts | 1 + .../utils/getOperandsForFilterType.ts | 2 + .../computeViewRecordGqlOperationFilter.ts | 8 ++ .../utils/resolveBooleanViewFilterValue.ts | 7 ++ .../utils/resolveFilterValue.ts | 7 +- 9 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect.tsx create mode 100644 packages/twenty-front/src/modules/views/view-filter-value/utils/resolveBooleanViewFilterValue.ts diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts index bbe5122ef..2d26b7665 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts @@ -61,7 +61,7 @@ describe('useColumnDefinitionsFromFieldMetadata', () => { result.current; expect(columnDefinitions.length).toBe(21); - expect(filterDefinitions.length).toBe(15); + expect(filterDefinitions.length).toBe(17); expect(sortDefinitions.length).toBe(14); }); }); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts index 33726b908..92ea3e0d6 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts @@ -25,6 +25,7 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({ if ( ![ + FieldMetadataType.Boolean, FieldMetadataType.DateTime, FieldMetadataType.Date, FieldMetadataType.Text, @@ -100,6 +101,8 @@ export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => { return 'ARRAY'; case FieldMetadataType.RawJson: return 'RAW_JSON'; + case FieldMetadataType.Boolean: + return 'BOOLEAN'; default: return 'TEXT'; } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect.tsx new file mode 100644 index 000000000..9a550ea9d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect.tsx @@ -0,0 +1,113 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { v4 } from 'uuid'; + +import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; +import { BooleanDisplay } from '@/ui/field/display/components/BooleanDisplay'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { IconCheck } from 'twenty-ui'; +import { isDefined } from '~/utils/isDefined'; + +const StyledBooleanSelectContainer = styled.div<{ selected?: boolean }>` + align-items: center; + cursor: pointer; + display: flex; + padding: ${({ theme }) => + `${theme.spacing(2)} ${theme.spacing(2)} ${theme.spacing(2)} ${theme.spacing(1)}`}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + + &:hover { + background: ${({ theme }) => theme.background.transparent.light}; + } +`; + +const StyledIconCheckContainer = styled.div` + flex: 1; + display: flex; + justify-content: flex-end; +`; + +export const ObjectFilterDropdownBooleanSelect = () => { + const theme = useTheme(); + const options = [true, false]; + + const { + filterDefinitionUsedInDropdownState, + selectedOperandInDropdownState, + selectedFilterState, + selectFilter, + } = useFilterDropdown(); + + const { closeDropdown } = useDropdown(); + + const filterDefinitionUsedInDropdown = useRecoilValue( + filterDefinitionUsedInDropdownState, + ); + const selectedOperandInDropdown = useRecoilValue( + selectedOperandInDropdownState, + ); + const selectedFilter = useRecoilValue(selectedFilterState); + + const [selectedValue, setSelectedValue] = useState( + selectedFilter?.value === 'true', + ); + + useEffect(() => { + setSelectedValue(selectedFilter?.value === 'true'); + }, [selectedFilter?.value]); + + const handleOptionSelect = (value: boolean) => { + if ( + !isDefined(filterDefinitionUsedInDropdown) || + !isDefined(selectedOperandInDropdown) + ) { + return; + } + + selectFilter({ + id: selectedFilter?.id ?? v4(), + definition: filterDefinitionUsedInDropdown, + operand: selectedOperandInDropdown, + displayValue: value ? 'True' : 'False', + fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + value: value.toString(), + viewFilterGroupId: selectedFilter?.viewFilterGroupId, + }); + + setSelectedValue(value); + closeDropdown(); + }; + + return ( + option.toString())} + hotkeyScope={RelationPickerHotkeyScope.RelationPicker} + onEnter={(itemId) => { + handleOptionSelect(itemId === 'true'); + }} + > + + {options.map((option) => ( + handleOptionSelect(option)} + selected={selectedValue === option} + > + + {selectedFilter?.value === option.toString() && ( + + + + )} + + ))} + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx index c795f4ee5..7664504bb 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx @@ -14,6 +14,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { isDefined } from 'twenty-ui'; +import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect'; import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes'; import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes'; import { TEXT_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/TextFilterTypes'; @@ -96,6 +97,9 @@ export const ObjectFilterDropdownFilterInput = ({ )} + {filterDefinitionUsedInDropdown.type === 'BOOLEAN' && ( + + )} )} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterableFieldType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterableFieldType.ts index b2bf87102..9fc65b90e 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterableFieldType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterableFieldType.ts @@ -20,4 +20,5 @@ export type FilterableFieldType = PickLiteral< | 'ACTOR' | 'ARRAY' | 'RAW_JSON' + | 'BOOLEAN' >; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index 897139af9..5ddd7a374 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -87,6 +87,8 @@ export const getOperandsForFilterDefinition = ( ViewFilterOperand.DoesNotContain, ...emptyOperands, ]; + case 'BOOLEAN': + return [ViewFilterOperand.Is]; default: return []; } diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts index 10abe1029..3a3427044 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts @@ -4,6 +4,7 @@ import { ActorFilter, AddressFilter, ArrayFilter, + BooleanFilter, CurrencyFilter, DateFilter, EmailsFilter, @@ -855,6 +856,13 @@ const computeFilterRecordGqlOperationFilter = ( ); } } + case 'BOOLEAN': { + return { + [correspondingField.name]: { + eq: filter.value === 'true', + } as BooleanFilter, + }; + } default: throw new Error('Unknown filter type'); } diff --git a/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveBooleanViewFilterValue.ts b/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveBooleanViewFilterValue.ts new file mode 100644 index 000000000..0112db9e8 --- /dev/null +++ b/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveBooleanViewFilterValue.ts @@ -0,0 +1,7 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; + +export const resolveBooleanViewFilterValue = ( + viewFilter: Pick, +) => { + return viewFilter.value === 'true'; +}; diff --git a/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveFilterValue.ts b/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveFilterValue.ts index 47a160cbe..38fd27439 100644 --- a/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveFilterValue.ts +++ b/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveFilterValue.ts @@ -7,6 +7,7 @@ import { resolveDateViewFilterValue, ResolvedDateViewFilterValue, } from './resolveDateViewFilterValue'; +import { resolveBooleanViewFilterValue } from '@/views/view-filter-value/utils/resolveBooleanViewFilterValue'; type ResolvedFilterValue< T extends FilterableFieldType, @@ -17,7 +18,9 @@ type ResolvedFilterValue< ? ReturnType : T extends 'SELECT' | 'MULTI_SELECT' ? string[] - : string; + : T extends 'BOOLEAN' + ? boolean + : string; type PartialFilter< T extends FilterableFieldType, @@ -42,6 +45,8 @@ export const resolveFilterValue = < case 'SELECT': case 'MULTI_SELECT': return resolveSelectViewFilterValue(filter) as ResolvedFilterValue; + case 'BOOLEAN': + return resolveBooleanViewFilterValue(filter) as ResolvedFilterValue; default: return filter.value as ResolvedFilterValue; }