From 21259b4c672aa7e6b4994eae46d196327a8d0fa7 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Wed, 15 Jan 2025 17:38:36 +0100 Subject: [PATCH] - Added tests for upsert and remove record filters hooks - Created and applied upsert and remove record filter everywhere old upsert and remove are used - Added RecordFilter context - Renamed usePrefetchRunQuery to useUpsertRecordsInCacheForPrefetchKey --- .../hooks/useDeleteFavoriteFolder.ts | 9 +- .../hooks/usePrefetchedFavoritesData.ts | 4 +- .../usePrefetchedFavoritesFoldersData.ts | 4 +- .../hooks/useCombinedFindManyRecords.ts | 26 +++- .../ObjectFilterDropdownSourceSelect.tsx | 9 ++ ...vailableFilterDefinitionsComponentState.ts | 1 + .../__tests__/useRemoveRecordFilter.test.tsx | 117 ++++++++++++++++++ .../__tests__/useUpsertRecordFilter.test.tsx | 112 +++++++++++++++++ .../hooks/useApplyRecordFilter.ts | 24 +--- .../hooks/useRemoveRecordFilter.ts | 46 +++++++ .../hooks/useUpsertRecordFilter.ts | 54 ++++++++ .../RecordFiltersComponentInstanceContext.ts | 4 + .../currentRecordFiltersComponentState.ts | 11 ++ .../useFindManyRecordIndexTableParams.ts | 13 +- .../hooks/useHandleToggleColumnFilter.ts | 5 + .../hooks/useHandleToggleTrashColumnFilter.ts | 11 +- .../RecordTableEmptyStateSoftDelete.tsx | 21 +++- .../components/PrefetchRunQueriesEffect.tsx | 8 +- ... useUpsertRecordsInCacheForPrefetchKey.ts} | 3 +- .../EditableFilterDropdownButton.tsx | 9 +- .../views/components/VariantFilterChip.tsx | 5 + .../src/modules/views/components/ViewBar.tsx | 2 + .../views/components/ViewBarDetails.tsx | 5 + .../views/components/ViewBarFilterEffect.tsx | 22 +--- .../components/ViewBarRecordFilterEffect.tsx | 50 ++++++++ ...eApplyViewFiltersToCurrentRecordFilters.ts | 42 +++++++ .../pages/object-record/RecordIndexPage.tsx | 39 +++--- ...taAndApolloMocksAndContextStoreWrapper.tsx | 31 +++-- .../getJestMetadataAndApolloMocksWrapper.tsx | 11 +- 29 files changed, 592 insertions(+), 106 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/states/currentRecordFiltersComponentState.ts rename packages/twenty-front/src/modules/prefetch/hooks/internal/{usePrefetchRunQuery.ts => useUpsertRecordsInCacheForPrefetchKey.ts} (94%) create mode 100644 packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx create mode 100644 packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts diff --git a/packages/twenty-front/src/modules/favorites/hooks/useDeleteFavoriteFolder.ts b/packages/twenty-front/src/modules/favorites/hooks/useDeleteFavoriteFolder.ts index 4c0925ca622a..c10adccb88f3 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useDeleteFavoriteFolder.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useDeleteFavoriteFolder.ts @@ -4,7 +4,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig'; -import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; +import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; export const useDeleteFavoriteFolder = () => { @@ -12,9 +12,10 @@ export const useDeleteFavoriteFolder = () => { objectNameSingular: CoreObjectNameSingular.FavoriteFolder, }); - const { upsertRecordsInCache } = usePrefetchRunQuery({ - prefetchKey: PrefetchKey.AllFavorites, - }); + const { upsertRecordsInCache } = + useUpsertRecordsInCacheForPrefetchKey({ + prefetchKey: PrefetchKey.AllFavorites, + }); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular: diff --git a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts index 7eae9cf3a95c..ece3d2bdf3f3 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts @@ -1,6 +1,6 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { Favorite } from '@/favorites/types/Favorite'; -import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; +import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useRecoilValue } from 'recoil'; @@ -33,7 +33,7 @@ export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => { ); const { upsertRecordsInCache: upsertFavorites } = - usePrefetchRunQuery({ + useUpsertRecordsInCacheForPrefetchKey({ prefetchKey: PrefetchKey.AllFavorites, }); diff --git a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts index b794dc2ec494..32972ed05697 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts @@ -1,6 +1,6 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { FavoriteFolder } from '@/favorites/types/FavoriteFolder'; -import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; +import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useRecoilValue } from 'recoil'; @@ -26,7 +26,7 @@ export const usePrefetchedFavoritesFoldersData = ); const { upsertRecordsInCache: upsertFavoriteFolders } = - usePrefetchRunQuery({ + useUpsertRecordsInCacheForPrefetchKey({ prefetchKey: PrefetchKey.AllFavoritesFolders, }); diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecords.ts index b147b5535f71..6c07c7afdc53 100644 --- a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecords.ts @@ -5,24 +5,28 @@ import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; import { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; +import { useEffect, useMemo } from 'react'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; export const useCombinedFindManyRecords = ({ operationSignatures, skip = false, + onDataChange, }: { operationSignatures: RecordGqlOperationSignature[]; skip?: boolean; + onDataChange?: ( + data: MultiObjectRecordQueryResult | null | undefined, + ) => void; }) => { const findManyQuery = useGenerateCombinedFindManyRecordsQuery({ operationSignatures, }); - const { data, loading } = useQuery( - findManyQuery ?? EMPTY_QUERY, - { + const { data, loading, previousData } = + useQuery(findManyQuery ?? EMPTY_QUERY, { skip, - }, - ); + }); const resultWithoutConnection = Object.fromEntries( Object.entries(data ?? {}).map(([namePlural, objectRecordConnection]) => [ @@ -33,8 +37,20 @@ export const useCombinedFindManyRecords = ({ ]), ); + const isSameDataAsPreviousData = useMemo( + () => isDeeplyEqual(previousData, data), + [previousData, data], + ); + + useEffect(() => { + if (!isSameDataAsPreviousData) { + onDataChange?.(data); + } + }, [isSameDataAsPreviousData, onDataChange, data]); + return { result: resultWithoutConnection, loading, + isSameDataAsPreviousData, }; }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx index 92b77bd0830b..5639d82ea1f9 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx @@ -9,6 +9,7 @@ import { selectedFilterComponentState } from '@/object-record/object-filter-drop import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions'; import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; +import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown'; import { SelectableItem } from '@/object-record/select/types/SelectableItem'; @@ -61,6 +62,7 @@ export const ObjectFilterDropdownSourceSelect = ({ const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(viewComponentId); + // TODO: this should be removed as it is not consistent across re-renders const [fieldId] = useState(v4()); const sourceTypes = getActorSourceMultiSelectOptions( @@ -73,6 +75,8 @@ export const ObjectFilterDropdownSourceSelect = ({ const { emptyRecordFilter } = useEmptyRecordFilter(); + const { removeRecordFilter } = useRemoveRecordFilter(); + const handleMultipleItemSelectChange = ( itemToSelect: SelectableItem, newSelectedValue: boolean, @@ -83,8 +87,13 @@ export const ObjectFilterDropdownSourceSelect = ({ (id) => id !== itemToSelect.id, ); + if (!filterDefinitionUsedInDropdown) { + throw new Error('Filter definition used in dropdown should be defined'); + } + if (newSelectedItemIds.length === 0) { emptyRecordFilter(); + removeRecordFilter(filterDefinitionUsedInDropdown.fieldMetadataId); deleteCombinedViewFilter(fieldId); return; } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/availableFilterDefinitionsComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/availableFilterDefinitionsComponentState.ts index 52ae7db07f25..ddc529f0002a 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/availableFilterDefinitionsComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/availableFilterDefinitionsComponentState.ts @@ -1,5 +1,6 @@ import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; + import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; export const availableFilterDefinitionsComponentState = createComponentStateV2< diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx new file mode 100644 index 000000000000..9c8d1f0217fc --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx @@ -0,0 +1,117 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; + +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { useRemoveRecordFilter } from '../useRemoveRecordFilter'; +import { useUpsertRecordFilter } from '../useUpsertRecordFilter'; + +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + +describe('useRemoveRecordFilter', () => { + it('should remove an existing filter', () => { + const { result } = renderHook( + () => { + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const { upsertRecordFilter } = useUpsertRecordFilter(); + const { removeRecordFilter } = useRemoveRecordFilter(); + + return { + upsertRecordFilter, + removeRecordFilter, + currentRecordFilters, + }; + }, + { + wrapper: Wrapper, + }, + ); + + const filter: RecordFilter = { + id: 'filter-1', + fieldMetadataId: 'field-1', + value: 'test-value', + operand: ViewFilterOperand.Contains, + displayValue: 'test-value', + definition: { + type: 'TEXT', + fieldMetadataId: 'field-1', + label: 'Test Field', + iconName: 'IconText', + }, + }; + + // First add a filter + act(() => { + result.current.upsertRecordFilter(filter); + }); + + expect(result.current.currentRecordFilters).toHaveLength(1); + expect(result.current.currentRecordFilters[0]).toEqual(filter); + + // Then remove it + act(() => { + result.current.removeRecordFilter(filter.fieldMetadataId); + }); + + expect(result.current.currentRecordFilters).toHaveLength(0); + }); + + it('should not modify filters when trying to remove a non-existent filter', () => { + const { result } = renderHook( + () => { + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + const { upsertRecordFilter } = useUpsertRecordFilter(); + const { removeRecordFilter } = useRemoveRecordFilter(); + return { + upsertRecordFilter, + removeRecordFilter, + currentRecordFilters, + }; + }, + { + wrapper: Wrapper, + }, + ); + + const filter: RecordFilter = { + id: 'filter-1', + fieldMetadataId: 'field-1', + value: 'test-value', + operand: ViewFilterOperand.Contains, + displayValue: 'test-value', + definition: { + type: 'TEXT', + fieldMetadataId: 'field-1', + label: 'Test Field', + iconName: 'IconText', + }, + }; + + // Add a filter + act(() => { + result.current.upsertRecordFilter(filter); + }); + + expect(result.current.currentRecordFilters).toHaveLength(1); + + // Try to remove a non-existent filter + act(() => { + result.current.removeRecordFilter('non-existent-field'); + }); + + // Filter list should remain unchanged + expect(result.current.currentRecordFilters).toHaveLength(1); + expect(result.current.currentRecordFilters[0]).toEqual(filter); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx new file mode 100644 index 000000000000..3b25ff2f0745 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx @@ -0,0 +1,112 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; + +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { useUpsertRecordFilter } from '../useUpsertRecordFilter'; + +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + +describe('useUpsertRecordFilter', () => { + it('should add a new filter when fieldMetadataId does not exist', () => { + const { result } = renderHook( + () => { + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const { upsertRecordFilter } = useUpsertRecordFilter(); + + return { upsertRecordFilter, currentRecordFilters }; + }, + { + wrapper: Wrapper, + }, + ); + + const newFilter: RecordFilter = { + id: 'filter-1', + fieldMetadataId: 'field-1', + value: 'test-value', + operand: ViewFilterOperand.Contains, + displayValue: 'test-value', + definition: { + type: 'TEXT', + fieldMetadataId: 'field-1', + label: 'Test Field', + iconName: 'IconText', + }, + }; + + act(() => { + result.current.upsertRecordFilter(newFilter); + }); + + expect(result.current.currentRecordFilters).toHaveLength(1); + expect(result.current.currentRecordFilters[0]).toEqual(newFilter); + }); + + it('should update an existing filter when fieldMetadataId exists', () => { + const { result } = renderHook( + () => { + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const { upsertRecordFilter } = useUpsertRecordFilter(); + + return { upsertRecordFilter, currentRecordFilters }; + }, + { + wrapper: Wrapper, + }, + ); + + const initialFilter: RecordFilter = { + id: 'filter-1', + fieldMetadataId: 'field-1', + value: 'initial-value', + operand: ViewFilterOperand.Contains, + displayValue: 'initial-value', + definition: { + type: 'TEXT', + fieldMetadataId: 'field-1', + label: 'Test Field', + iconName: 'IconText', + }, + }; + + const updatedFilter: RecordFilter = { + id: 'filter-1', + fieldMetadataId: 'field-1', + value: 'updated-value', + operand: ViewFilterOperand.Contains, + displayValue: 'updated-value', + definition: { + type: 'TEXT', + fieldMetadataId: 'field-1', + label: 'Test Field', + iconName: 'IconText', + }, + }; + + act(() => { + result.current.upsertRecordFilter(initialFilter); + }); + + expect(result.current.currentRecordFilters).toHaveLength(1); + expect(result.current.currentRecordFilters[0]).toEqual(initialFilter); + + act(() => { + result.current.upsertRecordFilter(updatedFilter); + }); + + expect(result.current.currentRecordFilters).toHaveLength(1); + expect(result.current.currentRecordFilters[0]).toEqual(updatedFilter); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useApplyRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useApplyRecordFilter.ts index ad0edea7e2cd..ba82b14c57be 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useApplyRecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useApplyRecordFilter.ts @@ -1,7 +1,6 @@ -import { onFilterSelectComponentState } from '@/object-record/object-filter-dropdown/states/onFilterSelectComponentState'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; +import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; -import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; import { useRecoilCallback } from 'recoil'; @@ -14,32 +13,19 @@ export const useApplyRecordFilter = (componentInstanceId?: string) => { componentInstanceId, ); - const onFilterSelectCallbackState = useRecoilComponentCallbackStateV2( - onFilterSelectComponentState, - componentInstanceId, - ); + const { upsertRecordFilter } = useUpsertRecordFilter(); const applyRecordFilter = useRecoilCallback( - ({ set, snapshot }) => + ({ set }) => (filter: RecordFilter | null) => { set(selectedFilterCallbackState, filter); - const onFilterSelect = getSnapshotValue( - snapshot, - onFilterSelectCallbackState, - ); - if (isDefined(filter)) { upsertCombinedViewFilter(filter); + upsertRecordFilter(filter); } - - onFilterSelect?.(filter); }, - [ - selectedFilterCallbackState, - onFilterSelectCallbackState, - upsertCombinedViewFilter, - ], + [selectedFilterCallbackState, upsertCombinedViewFilter, upsertRecordFilter], ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts new file mode 100644 index 000000000000..a41f5b3f87cd --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts @@ -0,0 +1,46 @@ +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { useRecoilCallback } from 'recoil'; + +export const useRemoveRecordFilter = () => { + const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2( + currentRecordFiltersComponentState, + ); + + const removeRecordFilter = useRecoilCallback( + ({ set, snapshot }) => + (fieldMetadataId: string) => { + const currentRecordFilters = getSnapshotValue( + snapshot, + currentRecordFiltersCallbackState, + ); + + const foundRecordFilterInCurrentRecordFilters = + currentRecordFilters.some( + (existingFilter) => + existingFilter.fieldMetadataId === fieldMetadataId, + ); + + if (foundRecordFilterInCurrentRecordFilters) { + set(currentRecordFiltersCallbackState, (currentRecordFilters) => { + const newCurrentRecordFilters = [...currentRecordFilters]; + + const indexOfFilterToRemove = newCurrentRecordFilters.findIndex( + (existingFilter) => + existingFilter.fieldMetadataId === fieldMetadataId, + ); + + newCurrentRecordFilters.splice(indexOfFilterToRemove, 1); + + return newCurrentRecordFilters; + }); + } + }, + [currentRecordFiltersCallbackState], + ); + + return { + removeRecordFilter, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts new file mode 100644 index 000000000000..3e7291f577c5 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts @@ -0,0 +1,54 @@ +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { useRecoilCallback } from 'recoil'; + +export const useUpsertRecordFilter = () => { + const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2( + currentRecordFiltersComponentState, + ); + + const upsertRecordFilter = useRecoilCallback( + ({ set, snapshot }) => + (filter: RecordFilter) => { + const currentRecordFilters = getSnapshotValue( + snapshot, + currentRecordFiltersCallbackState, + ); + + const foundRecordFilterInCurrentRecordFilters = + currentRecordFilters.some( + (existingFilter) => + existingFilter.fieldMetadataId === filter.fieldMetadataId, + ); + + if (!foundRecordFilterInCurrentRecordFilters) { + set(currentRecordFiltersCallbackState, [ + ...currentRecordFilters, + filter, + ]); + } else { + set(currentRecordFiltersCallbackState, (currentRecordFilters) => { + const newCurrentRecordFilters = [...currentRecordFilters]; + + const indexOfFilterToUpdate = newCurrentRecordFilters.findIndex( + (existingFilter) => + existingFilter.fieldMetadataId === filter.fieldMetadataId, + ); + + newCurrentRecordFilters[indexOfFilterToUpdate] = { + ...filter, + }; + + return newCurrentRecordFilters; + }); + } + }, + [currentRecordFiltersCallbackState], + ); + + return { + upsertRecordFilter, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext.ts b/packages/twenty-front/src/modules/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext.ts new file mode 100644 index 000000000000..e8a6200d6a14 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext.ts @@ -0,0 +1,4 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const RecordFiltersComponentInstanceContext = + createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/states/currentRecordFiltersComponentState.ts b/packages/twenty-front/src/modules/object-record/record-filter/states/currentRecordFiltersComponentState.ts new file mode 100644 index 000000000000..a2b6d80595b3 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/states/currentRecordFiltersComponentState.ts @@ -0,0 +1,11 @@ +import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { RecordFilter } from '../../record-filter/types/RecordFilter'; + +export const currentRecordFiltersComponentState = createComponentStateV2< + RecordFilter[] +>({ + key: 'currentRecordFiltersComponentState', + defaultValue: [], + componentInstanceContext: RecordFiltersComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts index df15ba3715c5..4e69c157aba1 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts @@ -1,10 +1,10 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition'; import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter'; -import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState'; import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState'; import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -27,20 +27,21 @@ export const useFindManyRecordIndexTableParams = ( tableViewFilterGroupsComponentState, recordTableId, ); - const tableFilters = useRecoilComponentValueV2( - tableFiltersComponentState, - recordTableId, - ); + const tableSorts = useRecoilComponentValueV2( tableSortsComponentState, recordTableId, ); + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + const { filterValueDependencies } = useFilterValueDependencies(); const stateFilter = computeViewRecordGqlOperationFilter( filterValueDependencies, - tableFilters, + currentRecordFilters, objectMetadataItem?.fields ?? [], tableViewFilterGroups, ); 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 e573c4cfd7cc..daa0bb001a61 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 @@ -5,6 +5,7 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown'; +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'; @@ -33,6 +34,7 @@ export const useHandleToggleColumnFilter = ({ useColumnDefinitionsFromFieldMetadata(objectMetadataItem); const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(viewBarId); + const { upsertRecordFilter } = useUpsertRecordFilter(); const openDropdown = useRecoilCallback(({ set }) => { return (dropdownId: string) => { @@ -93,6 +95,8 @@ export const useHandleToggleColumnFilter = ({ value: '', }; + upsertRecordFilter(newFilter); + await upsertCombinedViewFilter(newFilter); selectFilterDefinitionUsedInDropdown({ filterDefinition }); @@ -107,6 +111,7 @@ export const useHandleToggleColumnFilter = ({ selectFilterDefinitionUsedInDropdown, currentViewWithCombinedFiltersAndSorts, availableFilterDefinitions, + upsertRecordFilter, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts index 4964a26e4ae0..f3471ea3ae1a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; @@ -36,6 +37,8 @@ export const useHandleToggleTrashColumnFilter = ({ viewBarId, ); + const { upsertRecordFilter } = useUpsertRecordFilter(); + const handleToggleTrashColumnFilter = useCallback(() => { const trashFieldMetadata = objectMetadataItem.fields.find( (field: { name: string }) => field.name === 'deletedAt', @@ -69,8 +72,14 @@ export const useHandleToggleTrashColumnFilter = ({ value: '', }; + upsertRecordFilter(newFilter); upsertCombinedViewFilter(newFilter); - }, [columnDefinitions, objectMetadataItem, upsertCombinedViewFilter]); + }, [ + columnDefinitions, + objectMetadataItem, + upsertCombinedViewFilter, + upsertRecordFilter, + ]); const toggleSoftDeleteFilterState = useRecoilCallback( ({ set }) => diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx index fd05f3a28b1f..055c9a129ab7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx @@ -1,6 +1,7 @@ import { IconFilterOff } from 'twenty-ui'; import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel'; +import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; @@ -25,14 +26,22 @@ export const RecordTableEmptyStateSoftDelete = () => { viewBarId: recordTableId, }); + const { removeRecordFilter } = useRemoveRecordFilter(); + const handleButtonClick = async () => { - deleteCombinedViewFilter( - tableFilters.find( - (filter) => - filter.definition.label === 'Deleted' && - filter.operand === 'isNotEmpty', - )?.id ?? '', + const deletedFilter = tableFilters.find( + (filter) => + filter.definition.label === 'Deleted' && + filter.operand === 'isNotEmpty', ); + + if (!deletedFilter) { + throw new Error('Deleted filter not found'); + } + + removeRecordFilter(deletedFilter.fieldMetadataId); + deleteCombinedViewFilter(deletedFilter.id); + toggleSoftDeleteFilterState(false); }; diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx index 2c9d4d581f2d..0fd7d9db6809 100644 --- a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx +++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx @@ -7,7 +7,7 @@ import { FavoriteFolder } from '@/favorites/types/FavoriteFolder'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords'; import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig'; -import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; +import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { View } from '@/views/types/View'; import { isDefined } from '~/utils/isDefined'; @@ -16,16 +16,16 @@ export const PrefetchRunQueriesEffect = () => { const currentUser = useRecoilValue(currentUserState); const { upsertRecordsInCache: upsertViewsInCache } = - usePrefetchRunQuery({ + useUpsertRecordsInCacheForPrefetchKey({ prefetchKey: PrefetchKey.AllViews, }); const { upsertRecordsInCache: upsertFavoritesInCache } = - usePrefetchRunQuery({ + useUpsertRecordsInCacheForPrefetchKey({ prefetchKey: PrefetchKey.AllFavorites, }); const { upsertRecordsInCache: upsertFavoritesFoldersInCache } = - usePrefetchRunQuery({ + useUpsertRecordsInCacheForPrefetchKey({ prefetchKey: PrefetchKey.AllFavoritesFolders, }); const { objectMetadataItems } = useObjectMetadataItems(); diff --git a/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts b/packages/twenty-front/src/modules/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey.ts similarity index 94% rename from packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts rename to packages/twenty-front/src/modules/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey.ts index a2b18f3ca139..2e1da0cb8967 100644 --- a/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts +++ b/packages/twenty-front/src/modules/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey.ts @@ -11,7 +11,7 @@ export type UsePrefetchRunQuery = { prefetchKey: PrefetchKey; }; -export const usePrefetchRunQuery = ({ +export const useUpsertRecordsInCacheForPrefetchKey = ({ prefetchKey, }: UsePrefetchRunQuery) => { const setPrefetchDataIsLoaded = useSetRecoilState( @@ -45,7 +45,6 @@ export const usePrefetchRunQuery = ({ return { objectMetadataItem, - setPrefetchDataIsLoaded, upsertRecordsInCache, }; }; diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index 7f6769cb26f9..de1211f3e006 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -11,6 +11,7 @@ import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; @@ -73,14 +74,17 @@ export const EditableFilterDropdownButton = ({ viewFilterDropdownId, ]); + const { removeRecordFilter } = useRemoveRecordFilter(); + const handleRemove = () => { closeDropdown(); deleteCombinedViewFilter(viewFilter.id); + removeRecordFilter(viewFilter.fieldMetadataId); }; const handleDropdownClickOutside = useCallback(() => { - const { id: fieldId, value, operand } = viewFilter; + const { id: fieldId, value, operand, fieldMetadataId } = viewFilter; if ( !value && ![ @@ -91,9 +95,10 @@ export const EditableFilterDropdownButton = ({ RecordFilterOperand.IsToday, ].includes(operand) ) { + removeRecordFilter(fieldMetadataId); deleteCombinedViewFilter(fieldId); } - }, [viewFilter, deleteCombinedViewFilter]); + }, [viewFilter, deleteCombinedViewFilter, removeRecordFilter]); return ( { deleteCombinedViewFilter(viewFilter.id); + removeRecordFilter(viewFilter.fieldMetadataId); + if ( viewFilter.definition.label === 'Deleted' && viewFilter.operand === 'isNotEmpty' diff --git a/packages/twenty-front/src/modules/views/components/ViewBar.tsx b/packages/twenty-front/src/modules/views/components/ViewBar.tsx index 00a80b9183ed..73f84fdb77df 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBar.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBar.tsx @@ -21,6 +21,7 @@ import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope'; import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; import { VIEW_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ViewSortDropdownId'; import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext'; +import { ViewBarRecordFilterEffect } from '@/views/components/ViewBarRecordFilterEffect'; import { ViewEventContext } from '@/views/events/contexts/ViewEventContext'; import { UpdateViewButtonGroup } from './UpdateViewButtonGroup'; import { ViewBarDetails } from './ViewBarDetails'; @@ -53,6 +54,7 @@ export const ViewBar = ({ value={{ instanceId: VIEW_SORT_DROPDOWN_ID }} > + diff --git a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx index 3f9cfc17fece..157d315c1896 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx @@ -14,6 +14,7 @@ import { EditableFilterDropdownButton } from '@/views/components/EditableFilterD import { EditableSortChip } from '@/views/components/EditableSortChip'; import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; +import { useApplyViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyViewFiltersToCurrentRecordFilters'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; @@ -167,9 +168,13 @@ export const ViewBarDetails = ({ }; }, [currentViewWithCombinedFiltersAndSorts]); + const { applyViewFiltersToCurrentRecordFilters } = + useApplyViewFiltersToCurrentRecordFilters(); + const handleCancelClick = () => { if (isDefined(viewId)) { resetUnsavedViewStates(viewId); + applyViewFiltersToCurrentRecordFilters(); toggleSoftDeleteFilterState(false); } }; diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx index d30be6932dc4..61f69af4961c 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterEffect.tsx @@ -1,16 +1,13 @@ import { isNonEmptyString } from '@sniptt/guards'; import { useEffect } from 'react'; -import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState'; import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState'; -import { onFilterSelectComponentState } from '@/object-record/object-filter-dropdown/states/onFilterSelectComponentState'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; -import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema'; import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema'; @@ -23,19 +20,12 @@ type ViewBarFilterEffectProps = { export const ViewBarFilterEffect = ({ filterDropdownId, }: ViewBarFilterEffectProps) => { - const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(); - const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); const availableFilterDefinitions = useRecoilComponentValueV2( availableFilterDefinitionsComponentState, ); - const setOnFilterSelect = useSetRecoilComponentStateV2( - onFilterSelectComponentState, - filterDropdownId, - ); - const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( filterDefinitionUsedInDropdownComponentState, filterDropdownId, @@ -62,17 +52,7 @@ export const ViewBarFilterEffect = ({ if (isDefined(availableFilterDefinitions)) { setAvailableFilterDefinitions(availableFilterDefinitions); } - setOnFilterSelect(() => (filter: RecordFilter | null) => { - if (isDefined(filter)) { - upsertCombinedViewFilter(filter); - } - }); - }, [ - availableFilterDefinitions, - setAvailableFilterDefinitions, - setOnFilterSelect, - upsertCombinedViewFilter, - ]); + }, [availableFilterDefinitions, setAvailableFilterDefinitions]); useEffect(() => { if (filterDefinitionUsedInDropdown?.type === 'RELATION') { diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx new file mode 100644 index 000000000000..e5b781432075 --- /dev/null +++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx @@ -0,0 +1,50 @@ +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; +import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; +import { View } from '@/views/types/View'; +import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; +import { useEffect } from 'react'; +import { isDefined } from 'twenty-ui'; + +export const ViewBarRecordFilterEffect = () => { + const { records: views, isDataPrefetched } = usePrefetchedData( + PrefetchKey.AllViews, + ); + + const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); + + const setCurrentRecordFilters = useSetRecoilComponentStateV2( + currentRecordFiltersComponentState, + ); + + const availableFilterDefinitions = useRecoilComponentValueV2( + availableFilterDefinitionsComponentState, + ); + + useEffect(() => { + if (isDataPrefetched) { + const currentView = views.find((view) => view.id === currentViewId); + + if (isDefined(currentView)) { + setCurrentRecordFilters( + mapViewFiltersToFilters( + currentView.viewFilters, + availableFilterDefinitions, + ), + ); + } + } + }, [ + isDataPrefetched, + views, + availableFilterDefinitions, + currentViewId, + setCurrentRecordFilters, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts b/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts new file mode 100644 index 000000000000..ccdc69cb606b --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts @@ -0,0 +1,42 @@ +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; +import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; +import { View } from '@/views/types/View'; +import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; + +import { isDefined } from 'twenty-ui'; + +export const useApplyViewFiltersToCurrentRecordFilters = () => { + const { records: views } = usePrefetchedData(PrefetchKey.AllViews); + + const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); + + const setCurrentRecordFilters = useSetRecoilComponentStateV2( + currentRecordFiltersComponentState, + ); + + const availableFilterDefinitions = useRecoilComponentValueV2( + availableFilterDefinitionsComponentState, + ); + + const applyViewFiltersToCurrentRecordFilters = () => { + const currentView = views.find((view) => view.id === currentViewId); + + if (isDefined(currentView)) { + setCurrentRecordFilters( + mapViewFiltersToFilters( + currentView.viewFilters, + availableFilterDefinitions, + ), + ); + } + }; + + return { + applyViewFiltersToCurrentRecordFilters, + }; +}; diff --git a/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx b/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx index 269e515f18c4..86ee6b581a33 100644 --- a/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx @@ -8,6 +8,7 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId'; +import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer'; import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect'; import { RecordIndexContainerContextStoreObjectMetadataEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreObjectMetadataEffect'; @@ -69,28 +70,32 @@ export const RecordIndexPage = () => { - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx index 1946010092bd..c500eaeb58d8 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx @@ -1,6 +1,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { MockedResponse } from '@apollo/client/testing'; import { ReactNode } from 'react'; import { MutableSnapshot } from 'recoil'; @@ -38,23 +39,29 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({ instanceId: componentInstanceId, }} > - - - {children} - - + + {children} + + + ); diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx index ea0e6528f0bc..b07d02a65a40 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx @@ -2,6 +2,7 @@ import { MockedProvider, MockedResponse } from '@apollo/client/testing'; import { ReactNode } from 'react'; import { MutableSnapshot, RecoilRoot } from 'recoil'; +import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; @@ -18,9 +19,13 @@ export const getJestMetadataAndApolloMocksWrapper = ({ - - {children} - + + + {children} + +