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 9bdedea1d..b1e75599e 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 @@ -8,6 +8,7 @@ import { RecordFilterGroup } from '@/object-record/record-filter-group/types/Rec import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator'; import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -61,6 +62,9 @@ export const AdvancedFilterAddFilterRuleSelect = ({ defaultFieldMetadataItemForFilter.type, ); + const defaultSubFieldName = + getDefaultSubFieldNameForCompositeFilterableFieldType(filterType); + const newRecordFilter: RecordFilter = { id: v4(), fieldMetadataId: defaultFieldMetadataItemForFilter.id, @@ -73,6 +77,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({ recordFilterGroupId: recordFilterGroup.id, positionInRecordFilterGroup: newPositionInRecordFilterGroup, label: defaultFieldMetadataItemForFilter.label, + subFieldName: defaultSubFieldName, }; upsertRecordFilter(newRecordFilter); 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 4fde4314e..aeb598099 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,20 +1,17 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; -import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup'; import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; -import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator'; import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; -import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow'; -import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator'; +import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem'; 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 { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; -import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; @@ -85,6 +82,9 @@ export const AdvancedFilterButton = () => { const { setRecordFilterUsedInAdvancedFilterDropdownRow } = useSetRecordFilterUsedInAdvancedFilterDropdownRow(); + const { createEmptyRecordFilterFromFieldMetadataItem } = + useCreateEmptyRecordFilterFromFieldMetadataItem(); + const handleClick = () => { if (!isDefined(currentView)) { throw new Error('Missing current view id'); @@ -96,13 +96,10 @@ export const AdvancedFilterButton = () => { const newRecordFilterGroup = { id: v4(), viewId: currentView.id, - logicalOperator: ViewFilterGroupLogicalOperator.AND, + logicalOperator: RecordFilterGroupLogicalOperator.AND, }; - upsertRecordFilterGroup({ - id: newRecordFilterGroup.id, - logicalOperator: RecordFilterGroupLogicalOperator.AND, - }); + upsertRecordFilterGroup(newRecordFilterGroup); const defaultFieldMetadataItem = availableFieldMetadataItemsForFilter.find( @@ -115,25 +112,11 @@ export const AdvancedFilterButton = () => { throw new Error('Missing default filter definition'); } - const filterType = getFilterTypeFromFieldType( - defaultFieldMetadataItem.type, + const { newRecordFilter } = createEmptyRecordFilterFromFieldMetadataItem( + defaultFieldMetadataItem, ); - const firstOperand = getRecordFilterOperands({ - filterType, - })[0]; - - const newRecordFilter: RecordFilter = { - id: v4(), - fieldMetadataId: defaultFieldMetadataItem.id, - operand: firstOperand, - value: '', - displayValue: '', - recordFilterGroupId: newRecordFilterGroup.id, - type: getFilterTypeFromFieldType(defaultFieldMetadataItem.type), - label: defaultFieldMetadataItem.label, - positionInRecordFilterGroup: 1, - }; + newRecordFilter.recordFilterGroupId = newRecordFilterGroup.id; upsertRecordFilter(newRecordFilter); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts index fc7c63e35..eaec15bd8 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts @@ -64,16 +64,9 @@ describe('getOperandsForFilterType', () => { ['DATE', [...dateOperands, ...emptyOperands]], ['DATE_TIME', [...dateOperands, ...emptyOperands]], ['RELATION', [...relationOperand, ...emptyOperands]], - [undefined, []], - [null, []], - ['UNKNOWN_TYPE', []], ] satisfies ( - | [ - FieldType | null | undefined | 'UNKNOWN_TYPE', - RecordFilterOperand[], - CompositeFieldSubFieldName, - ] - | [FieldType | null | undefined | 'UNKNOWN_TYPE', RecordFilterOperand[]] + | [FieldType, RecordFilterOperand[], CompositeFieldSubFieldName] + | [FieldType, RecordFilterOperand[]] )[]; testCases.forEach(([filterType, expectedOperands, subFieldName]) => { diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem.ts new file mode 100644 index 000000000..f3bf9d98d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem.ts @@ -0,0 +1,40 @@ +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType'; +import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; +import { v4 } from 'uuid'; + +export const useCreateEmptyRecordFilterFromFieldMetadataItem = () => { + const createEmptyRecordFilterFromFieldMetadataItem = ( + fieldMetadataItem: FieldMetadataItem, + ) => { + const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type); + + const availableOperandsForFilter = getRecordFilterOperands({ + filterType, + }); + + const defaultOperand = availableOperandsForFilter[0]; + + const defaultSubFieldName = + getDefaultSubFieldNameForCompositeFilterableFieldType(filterType); + + const newRecordFilter: RecordFilter = { + id: v4(), + fieldMetadataId: fieldMetadataItem.id, + operand: defaultOperand, + displayValue: '', + label: fieldMetadataItem.label, + type: filterType, + value: '', + subFieldName: defaultSubFieldName, + }; + + return { newRecordFilter }; + }; + + return { + createEmptyRecordFilterFromFieldMetadataItem, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts index f7186e1e4..236ab6f35 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts @@ -4,6 +4,7 @@ import { FilterableFieldType } from '@/object-record/record-filter/types/Filtera import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName'; import { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand'; import { FieldMetadataType } from 'twenty-shared/types'; +import { assertUnreachable } from 'twenty-shared/utils'; export type GetRecordFilterOperandsParams = { filterType: FilterableFieldType; @@ -208,6 +209,6 @@ export const getRecordFilterOperands = ({ case 'BOOLEAN': return FILTER_OPERANDS_MAP.BOOLEAN; default: - return []; + assertUnreachable(filterType, `Unknown filter type ${filterType}`); } }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index 2a9a80d38..b5d3d902e 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -3,7 +3,6 @@ import { useEffect } from 'react'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter'; import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; @@ -16,13 +15,7 @@ import { isDefined } from 'twenty-shared/utils'; export const RecordIndexTableContainerEffect = () => { const { recordIndexId, objectNameSingular } = useRecordIndexContextOrThrow(); - const viewBarId = recordIndexId; - - const { - setAvailableTableColumns, - setOnToggleColumnFilter, - setOnToggleColumnSort, - } = useRecordTable({ + const { setAvailableTableColumns, setOnToggleColumnSort } = useRecordTable({ recordTableId: recordIndexId, }); @@ -37,24 +30,12 @@ export const RecordIndexTableContainerEffect = () => { setAvailableTableColumns(columnDefinitions); }, [columnDefinitions, setAvailableTableColumns]); - const handleToggleColumnFilter = useHandleToggleColumnFilter({ - objectNameSingular, - viewBarId, - }); - const handleToggleColumnSort = useHandleToggleColumnSort({ objectNameSingular, }); const { currentView } = useGetCurrentViewOnly(); - useEffect(() => { - setOnToggleColumnFilter( - () => (fieldMetadataId: string) => - handleToggleColumnFilter(fieldMetadataId), - ); - }, [setOnToggleColumnFilter, handleToggleColumnFilter]); - useEffect(() => { setOnToggleColumnSort( () => (fieldMetadataId: 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 deleted file mode 100644 index 1c41a3ae0..000000000 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { useCallback } from 'react'; -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 { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; - -import { useSelectFilterUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterUsedInDropdown'; -import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; -import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; -import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType'; -import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; -import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; -import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; -import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; -import { useRecoilCallback, useRecoilValue } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; - -type UseHandleToggleColumnFilterProps = { - objectNameSingular: string; - viewBarId: string; -}; - -export const useHandleToggleColumnFilter = ({ - objectNameSingular, - viewBarId, -}: UseHandleToggleColumnFilterProps) => { - const { objectMetadataItem } = useObjectMetadataItem({ - objectNameSingular, - }); - - const { columnDefinitions } = - useColumnDefinitionsFromFieldMetadata(objectMetadataItem); - - const { upsertRecordFilter } = useUpsertRecordFilter(); - - const { setActiveDropdownFocusIdAndMemorizePrevious } = - useSetActiveDropdownFocusIdAndMemorizePrevious(); - - const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); - - const openDropdown = useRecoilCallback( - ({ set }) => { - return (dropdownId: string) => { - const dropdownOpenState = extractComponentState( - isDropdownOpenComponentState, - dropdownId, - ); - - setActiveDropdownFocusIdAndMemorizePrevious(dropdownId); - setHotkeyScopeAndMemorizePreviousScope(dropdownId); - - set(dropdownOpenState, true); - }; - }, - [ - setActiveDropdownFocusIdAndMemorizePrevious, - setHotkeyScopeAndMemorizePreviousScope, - ], - ); - - const availableFieldMetadataItemsForFilter = useRecoilValue( - availableFieldMetadataItemsForFilterFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), - ); - - const { selectFilterUsedInDropdown } = - useSelectFilterUsedInDropdown(viewBarId); - - const currentRecordFilters = useRecoilComponentValueV2( - currentRecordFiltersComponentState, - ); - - const handleToggleColumnFilter = useCallback( - async (fieldMetadataId: string) => { - const correspondingColumnDefinition = columnDefinitions.find( - (columnDefinition) => - columnDefinition.fieldMetadataId === fieldMetadataId, - ); - - if (!isDefined(correspondingColumnDefinition)) return; - - const newFilterId = v4(); - - const existingRecordFilter = currentRecordFilters.find( - (recordFilter) => recordFilter.fieldMetadataId === fieldMetadataId, - ); - - if (!isDefined(existingRecordFilter)) { - const fieldMetadataItem = availableFieldMetadataItemsForFilter.find( - (fieldMetadataItemToFind) => - fieldMetadataItemToFind.id === fieldMetadataId, - ); - - if (!isDefined(fieldMetadataItem)) { - throw new Error('Field metadata item not found'); - } - - const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type); - - const defaultSubFieldName = - getDefaultSubFieldNameForCompositeFilterableFieldType( - fieldMetadataItem.type, - ); - - const availableOperandsForFilter = getRecordFilterOperands({ - filterType, - subFieldName: defaultSubFieldName, - }); - - const defaultOperand = availableOperandsForFilter[0]; - - const newFilter: RecordFilter = { - id: newFilterId, - fieldMetadataId, - operand: defaultOperand, - displayValue: '', - label: fieldMetadataItem.label, - type: filterType, - value: '', - subFieldName: defaultSubFieldName, - }; - - upsertRecordFilter(newFilter); - - selectFilterUsedInDropdown({ fieldMetadataItemId: fieldMetadataId }); - } - - openDropdown(existingRecordFilter?.id ?? newFilterId); - }, - [ - openDropdown, - columnDefinitions, - selectFilterUsedInDropdown, - currentRecordFilters, - availableFieldMetadataItemsForFilter, - upsertRecordFilter, - ], - ); - - return handleToggleColumnFilter; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts index 941b47c0a..5d307d279 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts @@ -19,7 +19,7 @@ import { RecordTableComponentInstanceContext } from '@/object-record/record-tabl import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { onColumnsChangeComponentState } from '@/object-record/record-table/states/onColumnsChangeComponentState'; import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState'; -import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState'; + import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState'; import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -73,10 +73,6 @@ export const useRecordTable = (props?: useRecordTableProps) => { recordTableId, ); - const setOnToggleColumnFilter = useSetRecoilComponentStateV2( - onToggleColumnFilterComponentState, - recordTableId, - ); const setOnToggleColumnSort = useSetRecoilComponentStateV2( onToggleColumnSortComponentState, recordTableId, @@ -226,7 +222,6 @@ export const useRecordTable = (props?: useRecordTableProps) => { setRecordTableLastRowVisible, setFocusPosition, setHasUserSelectedAllRows, - setOnToggleColumnFilter, setOnToggleColumnSort, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx index 083467c79..cf134538f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx @@ -3,7 +3,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; -import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState'; +import { useOpenRecordFilterChipFromTableHeader } from '@/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader'; import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper'; @@ -76,9 +76,6 @@ export const RecordTableColumnHeadDropdownMenu = ({ handleColumnVisibilityChange(column); }; - const onToggleColumnFilter = useRecoilComponentValueV2( - onToggleColumnFilterComponentState, - ); const onToggleColumnSort = useRecoilComponentValueV2( onToggleColumnSortComponentState, ); @@ -89,10 +86,13 @@ export const RecordTableColumnHeadDropdownMenu = ({ onToggleColumnSort?.(column.fieldMetadataId); }; + const { openRecordFilterChipFromTableHeader } = + useOpenRecordFilterChipFromTableHeader(); + const handleFilterClick = () => { closeDropdownAndToggleScroll(); - onToggleColumnFilter?.(column.fieldMetadataId); + openRecordFilterChipFromTableHeader(column.fieldMetadataId); }; const isSortable = column.isSortable === true; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts new file mode 100644 index 000000000..9aef27c50 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts @@ -0,0 +1,70 @@ +import { useSelectFilterUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterUsedInDropdown'; +import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem'; +import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext'; +import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { useOpenDropdownFromOutside } from '@/ui/layout/dropdown/hooks/useOpenDropdownFromOutside'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates'; +import { isDefined } from 'twenty-shared/utils'; + +export const useOpenRecordFilterChipFromTableHeader = () => { + const { recordIndexId } = useRecordIndexContextOrThrow(); + + const { filterableFieldMetadataItems } = + useFilterableFieldMetadataItemsInRecordIndexContext(); + + const { selectFilterUsedInDropdown } = + useSelectFilterUsedInDropdown(recordIndexId); + + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const { createEmptyRecordFilterFromFieldMetadataItem } = + useCreateEmptyRecordFilterFromFieldMetadataItem(); + + const { upsertRecordFilter } = useUpsertRecordFilter(); + + const { openDropdownFromOutside } = useOpenDropdownFromOutside(); + + const { setEditableFilterChipDropdownStates } = + useSetEditableFilterChipDropdownStates(); + + const openRecordFilterChipFromTableHeader = (fieldMetadataItemId: string) => { + const correspondingFieldMetadataItem = filterableFieldMetadataItems.find( + (fieldMetadataItemToFind) => + fieldMetadataItemToFind.id === fieldMetadataItemId, + ); + + if (!isDefined(correspondingFieldMetadataItem)) { + throw new Error( + `Cannot find field metadata item with id : ${fieldMetadataItemId}`, + ); + } + + const existingRecordFilter = currentRecordFilters.find( + (recordFilter) => recordFilter.fieldMetadataId === fieldMetadataItemId, + ); + + if (isDefined(existingRecordFilter)) { + setEditableFilterChipDropdownStates(existingRecordFilter); + openDropdownFromOutside(existingRecordFilter.id); + return; + } + + const { newRecordFilter } = createEmptyRecordFilterFromFieldMetadataItem( + correspondingFieldMetadataItem, + ); + + upsertRecordFilter(newRecordFilter); + + selectFilterUsedInDropdown({ fieldMetadataItemId }); + + setEditableFilterChipDropdownStates(newRecordFilter); + openDropdownFromOutside(newRecordFilter.id); + }; + + return { openRecordFilterChipFromTableHeader }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/onToggleColumnFilterComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/onToggleColumnFilterComponentState.ts deleted file mode 100644 index c0e0a972b..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/states/onToggleColumnFilterComponentState.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; - -export const onToggleColumnFilterComponentState = createComponentStateV2< - ((fieldMetadataId: string) => void) | undefined ->({ - key: 'onToggleColumnFilterComponentState', - defaultValue: undefined, - componentInstanceContext: RecordTableComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts new file mode 100644 index 000000000..8ea2bae6d --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts @@ -0,0 +1,34 @@ +import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; +import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; +import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; +import { useRecoilCallback } from 'recoil'; + +export const useOpenDropdownFromOutside = () => { + const { setActiveDropdownFocusIdAndMemorizePrevious } = + useSetActiveDropdownFocusIdAndMemorizePrevious(); + + const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); + + const openDropdownFromOutside = useRecoilCallback( + ({ set }) => { + return (dropdownId: string) => { + const dropdownOpenState = extractComponentState( + isDropdownOpenComponentState, + dropdownId, + ); + + setActiveDropdownFocusIdAndMemorizePrevious(dropdownId); + setHotkeyScopeAndMemorizePreviousScope(dropdownId); + + set(dropdownOpenState, true); + }; + }, + [ + setActiveDropdownFocusIdAndMemorizePrevious, + setHotkeyScopeAndMemorizePreviousScope, + ], + ); + + return { openDropdownFromOutside }; +}; diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx index a4a6606eb..1f8116448 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx @@ -13,11 +13,13 @@ import { useIcons } from 'twenty-ui/display'; type EditableFilterChipProps = { recordFilter: RecordFilter; onRemove: () => void; + onClick?: () => void; }; export const EditableFilterChip = ({ recordFilter, onRemove, + onClick, }: EditableFilterChipProps) => { const { getIcon } = useIcons(); @@ -58,6 +60,7 @@ export const EditableFilterChip = ({ labelValue={recordFilter.displayValue} Icon={FieldMetadataItemIcon} onRemove={onRemove} + onClick={onClick} /> ); }; diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index e33d38c54..b7225195e 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -9,7 +9,7 @@ import { EditableFilterChip } from '@/views/components/EditableFilterChip'; import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput'; import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty'; -import { EditableFilterDropdownButtonEffect } from '@/views/components/EditableFilterDropdownButtonEffect'; +import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates'; type EditableFilterDropdownButtonProps = { recordFilter: RecordFilter; @@ -38,15 +38,22 @@ export const EditableFilterDropdownButton = ({ } }, [recordFilter, removeRecordFilter]); + const { setEditableFilterChipDropdownStates } = + useSetEditableFilterChipDropdownStates(); + + const handleFilterChipClick = () => { + setEditableFilterChipDropdownStates(recordFilter); + }; + return ( <> - } dropdownComponents={ diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx deleted file mode 100644 index 272f2628e..000000000 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useEffect } from 'react'; - -import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; - -import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState'; -import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; -import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; -import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; -import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext'; -import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { isDefined } from 'twenty-shared/utils'; - -type EditableFilterDropdownButtonEffectProps = { - recordFilter: RecordFilter; -}; - -export const EditableFilterDropdownButtonEffect = ({ - recordFilter, -}: EditableFilterDropdownButtonEffectProps) => { - const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( - fieldMetadataItemIdUsedInDropdownComponentState, - ); - - const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( - selectedOperandInDropdownComponentState, - recordFilter.id, - ); - - const setSubFieldNameUsedInDropdown = useSetRecoilComponentStateV2( - subFieldNameUsedInDropdownComponentState, - recordFilter.id, - ); - - const setSelectedFilter = useSetRecoilComponentStateV2( - selectedFilterComponentState, - recordFilter.id, - ); - - const setObjectFilterDropdownSelectedRecordIds = useSetRecoilComponentStateV2( - objectFilterDropdownSelectedRecordIdsComponentState, - recordFilter.id, - ); - - const { filterableFieldMetadataItems } = - useFilterableFieldMetadataItemsInRecordIndexContext(); - - useEffect(() => { - const fieldMetadataItem = filterableFieldMetadataItems.find( - (fieldMetadataItem) => - fieldMetadataItem.id === recordFilter.fieldMetadataId, - ); - - if (!isDefined(fieldMetadataItem)) { - return; - } - - setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id); - setSelectedOperandInDropdown(recordFilter.operand); - setSelectedFilter(recordFilter); - setSubFieldNameUsedInDropdown(recordFilter.subFieldName); - - try { - const selectedOptions = JSON.parse(recordFilter.value); - - setObjectFilterDropdownSelectedRecordIds(selectedOptions); - } catch { - setObjectFilterDropdownSelectedRecordIds([]); - } - }, [ - filterableFieldMetadataItems, - setFieldMetadataItemIdUsedInDropdown, - recordFilter, - setSelectedOperandInDropdown, - setSelectedFilter, - setSubFieldNameUsedInDropdown, - setObjectFilterDropdownSelectedRecordIds, - ]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx index 9cf6bb322..449601e3f 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx @@ -10,7 +10,6 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { AdvancedFilterDropdownButton } from '@/views/components/AdvancedFilterDropdownButton'; import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton'; import { EditableSortChip } from '@/views/components/EditableSortChip'; -import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter'; @@ -232,7 +231,6 @@ export const ViewBarDetails = ({ value={{ instanceId: recordFilter.id }} > - { + const { filterableFieldMetadataItems } = + useFilterableFieldMetadataItemsInRecordIndexContext(); + + const setEditableFilterChipDropdownStates = useRecoilCallback( + ({ set }) => + (recordFilter: RecordFilter) => { + const fieldMetadataItem = filterableFieldMetadataItems.find( + (fieldMetadataItem) => + fieldMetadataItem.id === recordFilter.fieldMetadataId, + ); + + if (!isDefined(fieldMetadataItem)) { + return; + } + + set( + fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({ + instanceId: recordFilter.id, + }), + fieldMetadataItem.id, + ); + + set( + selectedOperandInDropdownComponentState.atomFamily({ + instanceId: recordFilter.id, + }), + recordFilter.operand, + ); + + set( + selectedFilterComponentState.atomFamily({ + instanceId: recordFilter.id, + }), + recordFilter, + ); + + set( + subFieldNameUsedInDropdownComponentState.atomFamily({ + instanceId: recordFilter.id, + }), + recordFilter.subFieldName, + ); + + if (recordFilter.type === 'RELATION') { + const { selectedRecordIds } = jsonRelationFilterValueSchema + .catch({ + isCurrentWorkspaceMemberSelected: false, + selectedRecordIds: simpleRelationFilterValueSchema.parse( + recordFilter.value, + ), + }) + .parse(recordFilter.value); + + set( + objectFilterDropdownSelectedRecordIdsComponentState.atomFamily({ + instanceId: recordFilter.id, + }), + selectedRecordIds, + ); + } else if (['SELECT', 'MULTI_SELECT'].includes(recordFilter.type)) { + try { + const selectedOptions = JSON.parse(recordFilter.value); + + set( + objectFilterDropdownSelectedOptionValuesComponentState.atomFamily( + { + instanceId: recordFilter.id, + }, + ), + selectedOptions, + ); + } catch { + set( + objectFilterDropdownSelectedOptionValuesComponentState.atomFamily( + { + instanceId: recordFilter.id, + }, + ), + [], + ); + } + } + }, + [filterableFieldMetadataItems], + ); + + return { + setEditableFilterChipDropdownStates, + }; +}; diff --git a/packages/twenty-front/src/modules/views/utils/areViewFilterGroupsEqual.ts b/packages/twenty-front/src/modules/views/utils/areViewFilterGroupsEqual.ts index 9f47fac16..752370d76 100644 --- a/packages/twenty-front/src/modules/views/utils/areViewFilterGroupsEqual.ts +++ b/packages/twenty-front/src/modules/views/utils/areViewFilterGroupsEqual.ts @@ -9,7 +9,7 @@ export const areViewFilterGroupsEqual = ( 'positionInViewFilterGroup', 'logicalOperator', 'parentViewFilterGroupId', - 'viewId', + 'id', ]; return propertiesToCompare.every((property) =>