From 570b2e353093b2bd95c2e0c90a24c82265cee3b0 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Fri, 24 Jan 2025 18:48:59 +0100 Subject: [PATCH] Refactored record filter saving to view filters (#9844) This PR refactors the record filter saving to view filters. Before we used states to track the change of view filters, now we just check if there's a difference between the current record filters and the current view filters before saving. We also use this check to show the reset and save buttons. CRUD operations to perform on view filters are computed by utils , and . Also added unit tests on those utils. --- .../components/UpdateViewButtonGroup.tsx | 20 +-- .../views/components/ViewBarDetails.tsx | 83 +++++------ ...reViewFiltersDifferentFromRecordFilters.ts | 46 ++++++ ...useAreViewSortsDifferentFromRecordSorts.ts | 14 ++ .../views/hooks/useGetCurrentViewOnly.ts | 22 +++ .../useSaveCurrentViewFiltersAndSorts.ts | 77 +--------- .../useSaveRecordFiltersToViewFilters.ts | 79 +++++++++++ ...DifferentFromRecordSortsFamilySelector.ts} | 18 +-- .../__tests__/areViewFiltersEqual.test.ts | 89 ++++++++++++ .../__tests__/getViewFiltersToCreate.test.ts | 117 +++++++++++++++ .../__tests__/getViewFiltersToDelete.test.ts | 99 +++++++++++++ .../__tests__/getViewFiltersToUpdate.test.ts | 133 ++++++++++++++++++ .../views/utils/areViewFiltersEqual.ts | 19 +++ .../views/utils/getViewFiltersToCreate.ts | 21 +++ .../views/utils/getViewFiltersToDelete.ts | 16 +++ .../views/utils/getViewFiltersToUpdate.ts | 27 ++++ .../utils/mapRecordFilterToViewFilter.ts | 11 ++ 17 files changed, 748 insertions(+), 143 deletions(-) create mode 100644 packages/twenty-front/src/modules/views/hooks/useAreViewFiltersDifferentFromRecordFilters.ts create mode 100644 packages/twenty-front/src/modules/views/hooks/useAreViewSortsDifferentFromRecordSorts.ts create mode 100644 packages/twenty-front/src/modules/views/hooks/useGetCurrentViewOnly.ts create mode 100644 packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts rename packages/twenty-front/src/modules/views/states/selectors/{canPersistViewComponentFamilySelector.ts => areViewSortsDifferentFromRecordSortsFamilySelector.ts} (59%) create mode 100644 packages/twenty-front/src/modules/views/utils/__tests__/areViewFiltersEqual.test.ts create mode 100644 packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts create mode 100644 packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts create mode 100644 packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts create mode 100644 packages/twenty-front/src/modules/views/utils/areViewFiltersEqual.ts create mode 100644 packages/twenty-front/src/modules/views/utils/getViewFiltersToCreate.ts create mode 100644 packages/twenty-front/src/modules/views/utils/getViewFiltersToDelete.ts create mode 100644 packages/twenty-front/src/modules/views/utils/getViewFiltersToUpdate.ts create mode 100644 packages/twenty-front/src/modules/views/utils/mapRecordFilterToViewFilter.ts diff --git a/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx b/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx index 4d0e22b10..4cc33451e 100644 --- a/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx +++ b/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx @@ -11,15 +11,15 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { UPDATE_VIEW_BUTTON_DROPDOWN_ID } from '@/views/constants/UpdateViewButtonDropdownId'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; +import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAreViewFiltersDifferentFromRecordFilters'; +import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts'; import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; -import { canPersistViewComponentFamilySelector } from '@/views/states/selectors/canPersistViewComponentFamilySelector'; import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId'; import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode'; import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState'; @@ -46,11 +46,6 @@ export const UpdateViewButtonGroup = ({ const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); - const canPersistView = useRecoilComponentFamilyValueV2( - canPersistViewComponentFamilySelector, - { viewId: currentViewId }, - ); - const { closeDropdown: closeUpdateViewButtonDropdown } = useDropdown( UPDATE_VIEW_BUTTON_DROPDOWN_ID, ); @@ -89,7 +84,16 @@ export const UpdateViewButtonGroup = ({ const { hasFiltersQueryParams } = useViewFromQueryParams(); - const canShowButton = canPersistView && !hasFiltersQueryParams; + const { viewFiltersAreDifferentFromRecordFilters } = + useAreViewFiltersDifferentFromRecordFilters(); + + const { viewSortsAreDifferentFromRecordSorts } = + useAreViewSortsDifferentFromRecordSorts(); + + const canShowButton = + (viewFiltersAreDifferentFromRecordFilters || + viewSortsAreDifferentFromRecordSorts) && + !hasFiltersQueryParams; if (!canShowButton) { return <>; diff --git a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx index d81bacc00..1a915825e 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx @@ -4,10 +4,8 @@ import { ReactNode, useMemo } from 'react'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { AddObjectFilterFromDetailsButton } from '@/object-record/object-filter-dropdown/components/AddObjectFilterFromDetailsButton'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; -import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; -import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { AdvancedFilterDropdownButton } from '@/views/components/AdvancedFilterDropdownButton'; import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton'; @@ -15,14 +13,14 @@ import { EditableSortChip } from '@/views/components/EditableSortChip'; import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters'; +import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAreViewFiltersDifferentFromRecordFilters'; +import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates'; -import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState'; import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState'; -import { canPersistViewComponentFamilySelector } from '@/views/states/selectors/canPersistViewComponentFamilySelector'; -import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; import { isDefined } from 'twenty-ui'; import { VariantFilterChip } from './VariantFilterChip'; @@ -118,13 +116,8 @@ export const ViewBarDetails = ({ const { hasFiltersQueryParams } = useViewFromQueryParams(); - const canPersistView = useRecoilComponentFamilyValueV2( - canPersistViewComponentFamilySelector, - { viewId }, - ); - - const availableFilterDefinitions = useRecoilComponentValueV2( - availableFilterDefinitionsComponentState, + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, ); const availableSortDefinitions = useRecoilComponentValueV2( @@ -139,35 +132,34 @@ export const ViewBarDetails = ({ viewBarId: viewBarId, }); const { resetUnsavedViewStates } = useResetUnsavedViewStates(); - const canResetView = canPersistView && !hasFiltersQueryParams; - const { otherViewFilters, defaultViewFilters } = useMemo(() => { - if (!currentViewWithCombinedFiltersAndSorts) { - return { - otherViewFilters: [], - defaultViewFilters: [], - }; - } + const { viewFiltersAreDifferentFromRecordFilters } = + useAreViewFiltersDifferentFromRecordFilters(); - const otherViewFilters = - currentViewWithCombinedFiltersAndSorts.viewFilters.filter( - (viewFilter) => - viewFilter.variant && - viewFilter.variant !== 'default' && - !viewFilter.viewFilterGroupId, - ); - const defaultViewFilters = - currentViewWithCombinedFiltersAndSorts.viewFilters.filter( - (viewFilter) => - (!viewFilter.variant || viewFilter.variant === 'default') && - !viewFilter.viewFilterGroupId, - ); + const { viewSortsAreDifferentFromRecordSorts } = + useAreViewSortsDifferentFromRecordSorts(); - return { - otherViewFilters, - defaultViewFilters, - }; - }, [currentViewWithCombinedFiltersAndSorts]); + const canResetView = + (viewFiltersAreDifferentFromRecordFilters || + viewSortsAreDifferentFromRecordSorts) && + !hasFiltersQueryParams; + + const otherViewFilters = useMemo(() => { + return currentRecordFilters.filter( + (viewFilter) => + viewFilter.variant && + viewFilter.variant !== 'default' && + !viewFilter.viewFilterGroupId, + ); + }, [currentRecordFilters]); + + const defaultViewFilters = useMemo(() => { + return currentRecordFilters.filter( + (viewFilter) => + (!viewFilter.variant || viewFilter.variant === 'default') && + !viewFilter.viewFilterGroupId, + ); + }, [currentRecordFilters]); const { applyCurrentViewFiltersToCurrentRecordFilters } = useApplyCurrentViewFiltersToCurrentRecordFilters(); @@ -181,9 +173,9 @@ export const ViewBarDetails = ({ }; const shouldExpandViewBar = - canPersistView || + viewFiltersAreDifferentFromRecordFilters || ((currentViewWithCombinedFiltersAndSorts?.viewSorts?.length || - currentViewWithCombinedFiltersAndSorts?.viewFilters?.length) && + currentRecordFilters?.length) && isViewBarExpanded); if (!shouldExpandViewBar) { @@ -201,11 +193,7 @@ export const ViewBarDetails = ({ {otherViewFilters.map((viewFilter) => ( ))} @@ -228,10 +216,7 @@ export const ViewBarDetails = ({ )} {showAdvancedFilterDropdownButton && } - {mapViewFiltersToFilters( - defaultViewFilters, - availableFilterDefinitions, - ).map((viewFilter) => ( + {defaultViewFilters.map((viewFilter) => ( { + const { currentView } = useGetCurrentViewOnly(); + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const viewFiltersAreDifferentFromRecordFilters = useMemo(() => { + const currentViewFilters = currentView?.viewFilters ?? []; + const viewFiltersFromCurrentRecordFilters = currentRecordFilters.map( + mapRecordFilterToViewFilter, + ); + + const viewFiltersToCreate = getViewFiltersToCreate( + currentViewFilters, + viewFiltersFromCurrentRecordFilters, + ); + + const viewFiltersToDelete = getViewFiltersToDelete( + currentViewFilters, + viewFiltersFromCurrentRecordFilters, + ); + + const viewFiltersToUpdate = getViewFiltersToUpdate( + currentViewFilters, + viewFiltersFromCurrentRecordFilters, + ); + + const filtersHaveChanged = + viewFiltersToCreate.length > 0 || + viewFiltersToDelete.length > 0 || + viewFiltersToUpdate.length > 0; + + return filtersHaveChanged; + }, [currentRecordFilters, currentView]); + + return { viewFiltersAreDifferentFromRecordFilters }; +}; diff --git a/packages/twenty-front/src/modules/views/hooks/useAreViewSortsDifferentFromRecordSorts.ts b/packages/twenty-front/src/modules/views/hooks/useAreViewSortsDifferentFromRecordSorts.ts new file mode 100644 index 000000000..b19896b0e --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useAreViewSortsDifferentFromRecordSorts.ts @@ -0,0 +1,14 @@ +import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; +import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; +import { areViewSortsDifferentFromRecordSortsSelector } from '@/views/states/selectors/areViewSortsDifferentFromRecordSortsFamilySelector'; + +export const useAreViewSortsDifferentFromRecordSorts = () => { + const { currentView } = useGetCurrentViewOnly(); + + const viewSortsAreDifferentFromRecordSorts = useRecoilComponentFamilyValueV2( + areViewSortsDifferentFromRecordSortsSelector, + { viewId: currentView?.id }, + ); + + return { viewSortsAreDifferentFromRecordSorts }; +}; diff --git a/packages/twenty-front/src/modules/views/hooks/useGetCurrentViewOnly.ts b/packages/twenty-front/src/modules/views/hooks/useGetCurrentViewOnly.ts new file mode 100644 index 000000000..e6fb9bed9 --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useGetCurrentViewOnly.ts @@ -0,0 +1,22 @@ +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; +import { View } from '@/views/types/View'; + +import { useMemo } from 'react'; + +export const useGetCurrentViewOnly = () => { + const { records: views } = usePrefetchedData(PrefetchKey.AllViews); + + const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); + + const currentView = useMemo( + () => views.find((view) => view.id === currentViewId), + [views, currentViewId], + ); + + return { + currentView, + }; +}; diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewFiltersAndSorts.ts b/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewFiltersAndSorts.ts index f2f94e240..aa04c5533 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewFiltersAndSorts.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewFiltersAndSorts.ts @@ -3,16 +3,14 @@ import { useRecoilCallback } from 'recoil'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroupRecords'; -import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords'; import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords'; import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache'; import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates'; +import { useSaveRecordFiltersToViewFilters } from '@/views/hooks/useSaveRecordFiltersToViewFilters'; import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState'; -import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState'; import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState'; import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState'; -import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState'; import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState'; import { isDefined } from '~/utils/isDefined'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; @@ -39,18 +37,6 @@ export const useSaveCurrentViewFiltersAndSorts = ( viewBarComponentId, ); - const unsavedToDeleteViewFilterIdsCallbackState = - useRecoilComponentCallbackStateV2( - unsavedToDeleteViewFilterIdsComponentFamilyState, - viewBarComponentId, - ); - - const unsavedToUpsertViewFiltersCallbackState = - useRecoilComponentCallbackStateV2( - unsavedToUpsertViewFiltersComponentFamilyState, - viewBarComponentId, - ); - const unsavedToUpsertViewFilterGroupsCallbackState = useRecoilComponentCallbackStateV2( unsavedToUpsertViewFilterGroupsComponentFamilyState, @@ -69,12 +55,6 @@ export const useSaveCurrentViewFiltersAndSorts = ( deleteViewSortRecords, } = usePersistViewSortRecords(); - const { - createViewFilterRecords, - updateViewFilterRecords, - deleteViewFilterRecords, - } = usePersistViewFilterRecords(); - const { createViewFilterGroupRecords, deleteViewFilterGroupRecords, @@ -130,53 +110,6 @@ export const useSaveCurrentViewFiltersAndSorts = ( ], ); - const saveViewFilters = useRecoilCallback( - ({ snapshot }) => - async (viewId: string) => { - const unsavedToDeleteViewFilterIds = getSnapshotValue( - snapshot, - unsavedToDeleteViewFilterIdsCallbackState({ viewId }), - ); - - const unsavedToUpsertViewFilters = getSnapshotValue( - snapshot, - unsavedToUpsertViewFiltersCallbackState({ viewId }), - ); - - const view = await getViewFromCache(viewId); - - if (isUndefinedOrNull(view)) { - return; - } - - const viewFiltersToCreate = unsavedToUpsertViewFilters.filter( - (viewFilter) => - !view.viewFilters.some( - (viewFilterToFilter) => viewFilterToFilter.id === viewFilter.id, - ), - ); - - const viewFiltersToUpdate = unsavedToUpsertViewFilters.filter( - (viewFilter) => - view.viewFilters.some( - (viewFilterToFilter) => viewFilterToFilter.id === viewFilter.id, - ), - ); - - await createViewFilterRecords(viewFiltersToCreate, view); - await updateViewFilterRecords(viewFiltersToUpdate); - await deleteViewFilterRecords(unsavedToDeleteViewFilterIds); - }, - [ - createViewFilterRecords, - deleteViewFilterRecords, - getViewFromCache, - unsavedToDeleteViewFilterIdsCallbackState, - unsavedToUpsertViewFiltersCallbackState, - updateViewFilterRecords, - ], - ); - const saveViewFilterGroups = useRecoilCallback( ({ snapshot }) => async (viewId: string) => { @@ -226,6 +159,9 @@ export const useSaveCurrentViewFiltersAndSorts = ( ], ); + const { saveRecordFiltersToViewFilters } = + useSaveRecordFiltersToViewFilters(); + const saveCurrentViewFilterAndSorts = useRecoilCallback( ({ snapshot }) => async (viewIdFromProps?: string) => { @@ -240,17 +176,18 @@ export const useSaveCurrentViewFiltersAndSorts = ( const viewId = viewIdFromProps ?? currentViewId; await saveViewFilterGroups(viewId); - await saveViewFilters(viewId); await saveViewSorts(viewId); + await saveRecordFiltersToViewFilters(); + resetUnsavedViewStates(viewId); }, [ currentViewIdCallbackState, resetUnsavedViewStates, - saveViewFilters, saveViewSorts, saveViewFilterGroups, + saveRecordFiltersToViewFilters, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts new file mode 100644 index 000000000..3a3e8b903 --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts @@ -0,0 +1,79 @@ +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 { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords'; +import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; +import { getViewFiltersToCreate } from '@/views/utils/getViewFiltersToCreate'; +import { getViewFiltersToDelete } from '@/views/utils/getViewFiltersToDelete'; +import { getViewFiltersToUpdate } from '@/views/utils/getViewFiltersToUpdate'; +import { mapRecordFilterToViewFilter } from '@/views/utils/mapRecordFilterToViewFilter'; +import { useRecoilCallback } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const useSaveRecordFiltersToViewFilters = () => { + const { + createViewFilterRecords, + updateViewFilterRecords, + deleteViewFilterRecords, + } = usePersistViewFilterRecords(); + + const { currentView } = useGetCurrentViewOnly(); + + const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2( + currentRecordFiltersComponentState, + ); + + const saveRecordFiltersToViewFilters = useRecoilCallback( + ({ snapshot }) => + async () => { + if (!isDefined(currentView)) { + return; + } + + const currentViewFilters = currentView?.viewFilters ?? []; + + const currentRecordFilters = getSnapshotValue( + snapshot, + currentRecordFiltersCallbackState, + ); + + const newViewFilters = currentRecordFilters.map( + mapRecordFilterToViewFilter, + ); + + const viewFiltersToCreate = getViewFiltersToCreate( + currentViewFilters, + newViewFilters, + ); + + const viewFiltersToDelete = getViewFiltersToDelete( + currentViewFilters, + newViewFilters, + ); + + const viewFiltersToUpdate = getViewFiltersToUpdate( + currentViewFilters, + newViewFilters, + ); + + const viewFilterIdsToDelete = viewFiltersToDelete.map( + (viewFilter) => viewFilter.id, + ); + + await createViewFilterRecords(viewFiltersToCreate, currentView); + await updateViewFilterRecords(viewFiltersToUpdate); + await deleteViewFilterRecords(viewFilterIdsToDelete); + }, + [ + createViewFilterRecords, + deleteViewFilterRecords, + updateViewFilterRecords, + currentRecordFiltersCallbackState, + currentView, + ], + ); + + return { + saveRecordFiltersToViewFilters, + }; +}; diff --git a/packages/twenty-front/src/modules/views/states/selectors/canPersistViewComponentFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/areViewSortsDifferentFromRecordSortsFamilySelector.ts similarity index 59% rename from packages/twenty-front/src/modules/views/states/selectors/canPersistViewComponentFamilySelector.ts rename to packages/twenty-front/src/modules/views/states/selectors/areViewSortsDifferentFromRecordSortsFamilySelector.ts index 51cc6d230..a25d1b397 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/canPersistViewComponentFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/areViewSortsDifferentFromRecordSortsFamilySelector.ts @@ -1,35 +1,21 @@ import { createComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; -import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState'; import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState'; -import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState'; import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState'; -export const canPersistViewComponentFamilySelector = +export const areViewSortsDifferentFromRecordSortsSelector = createComponentFamilySelectorV2({ - key: 'canPersistViewComponentFamilySelector', + key: 'areViewSortsDifferentFromRecordSortsSelector', get: ({ familyKey, instanceId }) => ({ get }) => { return ( - get( - unsavedToUpsertViewFiltersComponentFamilyState.atomFamily({ - familyKey, - instanceId, - }), - ).length > 0 || get( unsavedToUpsertViewSortsComponentFamilyState.atomFamily({ familyKey, instanceId, }), ).length > 0 || - get( - unsavedToDeleteViewFilterIdsComponentFamilyState.atomFamily({ - familyKey, - instanceId, - }), - ).length > 0 || get( unsavedToDeleteViewSortIdsComponentFamilyState.atomFamily({ familyKey, diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/areViewFiltersEqual.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/areViewFiltersEqual.test.ts new file mode 100644 index 000000000..28db1a0c0 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/__tests__/areViewFiltersEqual.test.ts @@ -0,0 +1,89 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { areViewFiltersEqual } from '../areViewFiltersEqual'; + +describe('areViewFiltersEqual', () => { + const baseFilter: ViewFilter = { + __typename: 'ViewFilter', + id: 'filter-1', + fieldMetadataId: 'field-1', + operand: ViewFilterOperand.Contains, + value: 'test', + displayValue: 'test', + viewFilterGroupId: 'group-1', + positionInViewFilterGroup: 0, + }; + + it('should return true when all comparable properties are equal', () => { + const filterA = { ...baseFilter }; + const filterB = { ...baseFilter }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(true); + }); + + it('should return false when displayValue is different', () => { + const filterA = { ...baseFilter }; + const filterB = { ...baseFilter, displayValue: 'different' }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(false); + }); + + it('should return false when fieldMetadataId is different', () => { + const filterA = { ...baseFilter }; + const filterB = { ...baseFilter, fieldMetadataId: 'field-2' }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(false); + }); + + it('should return false when viewFilterGroupId is different', () => { + const filterA = { ...baseFilter }; + const filterB = { ...baseFilter, viewFilterGroupId: 'group-2' }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(false); + }); + + it('should return false when operand is different', () => { + const filterA = { ...baseFilter }; + const filterB = { + ...baseFilter, + operand: ViewFilterOperand.DoesNotContain, + }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(false); + }); + + it('should return false when positionInViewFilterGroup is different', () => { + const filterA = { ...baseFilter }; + const filterB = { ...baseFilter, positionInViewFilterGroup: 1 }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(false); + }); + + it('should return false when value is different', () => { + const filterA = { ...baseFilter }; + const filterB = { ...baseFilter, value: 'different' }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(false); + }); + + it('should ignore non-comparable properties', () => { + const filterA = { ...baseFilter, id: 'id-1', createdAt: '2023-01-01' }; + const filterB = { ...baseFilter, id: 'id-2', createdAt: '2023-01-02' }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(true); + }); + + it('should handle undefined optional properties', () => { + const filterA = { ...baseFilter, viewFilterGroupId: undefined }; + const filterB = { ...baseFilter, viewFilterGroupId: undefined }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(true); + }); + + it('should handle one filter having optional property and other not', () => { + const filterA = { ...baseFilter, viewFilterGroupId: 'group-1' }; + const filterB = { ...baseFilter, viewFilterGroupId: undefined }; + + expect(areViewFiltersEqual(filterA, filterB)).toBe(false); + }); +}); diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts new file mode 100644 index 000000000..5b87e142e --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts @@ -0,0 +1,117 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { getViewFiltersToCreate } from '../getViewFiltersToCreate'; + +describe('getViewFiltersToCreate', () => { + const baseFilter: ViewFilter = { + __typename: 'ViewFilter', + id: 'filter-1', + fieldMetadataId: 'field-1', + operand: ViewFilterOperand.Contains, + value: 'test', + displayValue: 'test', + viewFilterGroupId: 'group-1', + positionInViewFilterGroup: 0, + }; + + it('should return all filters when current filters array is empty', () => { + const currentViewFilters: ViewFilter[] = []; + const newViewFilters: ViewFilter[] = [ + { ...baseFilter }, + { ...baseFilter, id: 'filter-2', fieldMetadataId: 'field-2' }, + ]; + + const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); + + expect(result).toEqual(newViewFilters); + }); + + it('should return empty array when new filters array is empty', () => { + const currentViewFilters: ViewFilter[] = [baseFilter]; + const newViewFilters: ViewFilter[] = []; + + const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should return only filters that do not exist in current filters', () => { + const existingFilter = { ...baseFilter }; + const newFilterWithDifferentFieldMetadata = { + ...baseFilter, + id: 'filter-2', + fieldMetadataId: 'field-2', + }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + + const newViewFilters: ViewFilter[] = [ + existingFilter, + newFilterWithDifferentFieldMetadata, + ]; + + const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); + + expect(result).toEqual([newFilterWithDifferentFieldMetadata]); + }); + + it('should handle filters with different viewFilterGroupIds', () => { + const existingFilter = { ...baseFilter }; + const filterWithDifferentGroup = { + ...baseFilter, + viewFilterGroupId: 'group-2', + }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + + const newViewFilters: ViewFilter[] = [ + existingFilter, + filterWithDifferentGroup, + ]; + + const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); + + expect(result).toEqual([filterWithDifferentGroup]); + }); + + it('should handle empty arrays for both inputs', () => { + const currentViewFilters: ViewFilter[] = []; + const newViewFilters: ViewFilter[] = []; + + const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should consider filters with same fieldMetadataId but different viewFilterGroupId as new', () => { + const currentViewFilters: ViewFilter[] = [baseFilter]; + const filterWithSameFieldMetadataIdButDifferentGroup = { + ...baseFilter, + id: 'filter-2', + viewFilterGroupId: 'group-2', + }; + const newViewFilters: ViewFilter[] = [ + filterWithSameFieldMetadataIdButDifferentGroup, + ]; + + const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); + + expect(result).toEqual([filterWithSameFieldMetadataIdButDifferentGroup]); + }); + + it('should consider filters with same viewFilterGroupId but different fieldMetadataId as new', () => { + const currentViewFilters: ViewFilter[] = [baseFilter]; + const filterWithSameGroupButDifferentFieldMetadata = { + ...baseFilter, + id: 'filter-2', + fieldMetadataId: 'field-2', + }; + const newViewFilters: ViewFilter[] = [ + filterWithSameGroupButDifferentFieldMetadata, + ]; + + const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); + + expect(result).toEqual([filterWithSameGroupButDifferentFieldMetadata]); + }); +}); diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts new file mode 100644 index 000000000..0440b5236 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts @@ -0,0 +1,99 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { getViewFiltersToDelete } from '../getViewFiltersToDelete'; + +describe('getViewFiltersToDelete', () => { + const baseFilter: ViewFilter = { + __typename: 'ViewFilter', + id: 'filter-1', + fieldMetadataId: 'field-1', + operand: ViewFilterOperand.Contains, + value: 'test', + displayValue: 'test', + viewFilterGroupId: 'group-1', + positionInViewFilterGroup: 0, + }; + + it('should return empty array when current filters array is empty', () => { + const currentViewFilters: ViewFilter[] = []; + const newViewFilters: ViewFilter[] = [baseFilter]; + + const result = getViewFiltersToDelete(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should return all current filters when new filters array is empty', () => { + const existingFilter = { ...baseFilter }; + const currentViewFilters: ViewFilter[] = [existingFilter]; + const newViewFilters: ViewFilter[] = []; + + const result = getViewFiltersToDelete(currentViewFilters, newViewFilters); + + expect(result).toEqual([existingFilter]); + }); + + it('should return filters that exist in current but not in new filters', () => { + const filterToDelete = { ...baseFilter }; + const filterToKeep = { + ...baseFilter, + id: 'filter-2', + fieldMetadataId: 'field-2', + }; + + const currentViewFilters: ViewFilter[] = [filterToDelete, filterToKeep]; + const newViewFilters: ViewFilter[] = [filterToKeep]; + + const result = getViewFiltersToDelete(currentViewFilters, newViewFilters); + + expect(result).toEqual([filterToDelete]); + }); + + it('should handle empty arrays for both inputs', () => { + const currentViewFilters: ViewFilter[] = []; + const newViewFilters: ViewFilter[] = []; + + const result = getViewFiltersToDelete(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should identify filters to delete based on fieldMetadataId and viewFilterGroupId', () => { + const filterInGroup1 = { ...baseFilter }; + const filterInGroup2 = { + ...baseFilter, + viewFilterGroupId: 'group-2', + }; + const filterWithDifferentField = { + ...baseFilter, + fieldMetadataId: 'field-2', + }; + + const currentViewFilters: ViewFilter[] = [ + filterInGroup1, + filterInGroup2, + filterWithDifferentField, + ]; + const newViewFilters: ViewFilter[] = [filterInGroup1]; + + const result = getViewFiltersToDelete(currentViewFilters, newViewFilters); + + expect(result).toEqual([filterInGroup2, filterWithDifferentField]); + }); + + it('should not delete filters that match in both fieldMetadataId and viewFilterGroupId', () => { + const existingFilter = { ...baseFilter }; + const matchingFilter = { + ...baseFilter, + value: 'different-value', + displayValue: 'different-value', + }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + const newViewFilters: ViewFilter[] = [matchingFilter]; + + const result = getViewFiltersToDelete(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); +}); diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts new file mode 100644 index 000000000..275f0d6c0 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts @@ -0,0 +1,133 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { getViewFiltersToUpdate } from '../getViewFiltersToUpdate'; + +describe('getViewFiltersToUpdate', () => { + const baseFilter: ViewFilter = { + __typename: 'ViewFilter', + id: 'filter-1', + fieldMetadataId: 'field-1', + operand: ViewFilterOperand.Contains, + value: 'test', + displayValue: 'test', + viewFilterGroupId: 'group-1', + positionInViewFilterGroup: 0, + }; + + it('should return empty array when current filters array is empty', () => { + const currentViewFilters: ViewFilter[] = []; + const newViewFilters: ViewFilter[] = [baseFilter]; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should return empty array when new filters array is empty', () => { + const currentViewFilters: ViewFilter[] = [baseFilter]; + const newViewFilters: ViewFilter[] = []; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should return filters that exist in both arrays but have different values', () => { + const existingFilter = { ...baseFilter }; + const updatedFilter = { + ...baseFilter, + value: 'updated-value', + displayValue: 'updated-value', + }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + const newViewFilters: ViewFilter[] = [updatedFilter]; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([updatedFilter]); + }); + + it('should not return filters that exist in both arrays with same values', () => { + const existingFilter = { ...baseFilter }; + const sameFilter = { ...baseFilter }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + const newViewFilters: ViewFilter[] = [sameFilter]; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should handle empty arrays for both inputs', () => { + const currentViewFilters: ViewFilter[] = []; + const newViewFilters: ViewFilter[] = []; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should not update filters with same fieldMetadataId but different viewFilterGroupId', () => { + const existingFilter = { ...baseFilter }; + const filterInDifferentGroup = { + ...baseFilter, + viewFilterGroupId: 'group-2', + value: 'updated-value', + }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + const newViewFilters: ViewFilter[] = [filterInDifferentGroup]; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should not update filters with same viewFilterGroupId but different fieldMetadataId', () => { + const existingFilter = { ...baseFilter }; + const filterWithDifferentField = { + ...baseFilter, + fieldMetadataId: 'field-2', + value: 'updated-value', + }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + const newViewFilters: ViewFilter[] = [filterWithDifferentField]; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([]); + }); + + it('should update filter when operand changes', () => { + const existingFilter = { ...baseFilter }; + const filterWithNewOperand = { + ...baseFilter, + operand: ViewFilterOperand.DoesNotContain, + }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + const newViewFilters: ViewFilter[] = [filterWithNewOperand]; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([filterWithNewOperand]); + }); + + it('should update filter when position changes', () => { + const existingFilter = { ...baseFilter }; + const filterWithNewPosition = { + ...baseFilter, + positionInViewFilterGroup: 1, + }; + + const currentViewFilters: ViewFilter[] = [existingFilter]; + const newViewFilters: ViewFilter[] = [filterWithNewPosition]; + + const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); + + expect(result).toEqual([filterWithNewPosition]); + }); +}); diff --git a/packages/twenty-front/src/modules/views/utils/areViewFiltersEqual.ts b/packages/twenty-front/src/modules/views/utils/areViewFiltersEqual.ts new file mode 100644 index 000000000..5087bf44a --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/areViewFiltersEqual.ts @@ -0,0 +1,19 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; + +export const areViewFiltersEqual = ( + viewFilterA: ViewFilter, + viewFilterB: ViewFilter, +) => { + const propertiesToCompare: (keyof ViewFilter)[] = [ + 'fieldMetadataId', + 'viewFilterGroupId', + 'positionInViewFilterGroup', + 'value', + 'displayValue', + 'operand', + ]; + + return propertiesToCompare.every( + (property) => viewFilterA[property] === viewFilterB[property], + ); +}; diff --git a/packages/twenty-front/src/modules/views/utils/getViewFiltersToCreate.ts b/packages/twenty-front/src/modules/views/utils/getViewFiltersToCreate.ts new file mode 100644 index 000000000..a222a1daf --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/getViewFiltersToCreate.ts @@ -0,0 +1,21 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; +import { isDefined } from 'twenty-ui'; + +export const getViewFiltersToCreate = ( + currentViewFilters: ViewFilter[], + newViewFilters: ViewFilter[], +) => { + return newViewFilters.filter((newViewFilter) => { + const correspondingViewFilter = currentViewFilters.find( + (currentViewFilter) => + currentViewFilter.fieldMetadataId === newViewFilter.fieldMetadataId && + currentViewFilter.viewFilterGroupId === newViewFilter.viewFilterGroupId, + ); + + const shouldCreateBecauseViewFilterIsNew = !isDefined( + correspondingViewFilter, + ); + + return shouldCreateBecauseViewFilterIsNew; + }); +}; diff --git a/packages/twenty-front/src/modules/views/utils/getViewFiltersToDelete.ts b/packages/twenty-front/src/modules/views/utils/getViewFiltersToDelete.ts new file mode 100644 index 000000000..d6ee34cff --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/getViewFiltersToDelete.ts @@ -0,0 +1,16 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; + +export const getViewFiltersToDelete = ( + currentViewFilters: ViewFilter[], + newViewFilters: ViewFilter[], +) => { + return currentViewFilters.filter( + (currentViewFilter) => + !newViewFilters.some( + (newViewFilter) => + newViewFilter.fieldMetadataId === currentViewFilter.fieldMetadataId && + newViewFilter.viewFilterGroupId === + currentViewFilter.viewFilterGroupId, + ), + ); +}; diff --git a/packages/twenty-front/src/modules/views/utils/getViewFiltersToUpdate.ts b/packages/twenty-front/src/modules/views/utils/getViewFiltersToUpdate.ts new file mode 100644 index 000000000..d30ee10b2 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/getViewFiltersToUpdate.ts @@ -0,0 +1,27 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; +import { areViewFiltersEqual } from '@/views/utils/areViewFiltersEqual'; +import { isDefined } from 'twenty-ui'; + +export const getViewFiltersToUpdate = ( + currentViewFilters: ViewFilter[], + newViewFilters: ViewFilter[], +) => { + return newViewFilters.filter((newViewFilter) => { + const correspondingViewFilter = currentViewFilters.find( + (currentViewFilter) => + currentViewFilter.fieldMetadataId === newViewFilter.fieldMetadataId && + currentViewFilter.viewFilterGroupId === newViewFilter.viewFilterGroupId, + ); + + if (!isDefined(correspondingViewFilter)) { + return false; + } + + const shouldUpdateBecauseViewFilterIsDifferent = !areViewFiltersEqual( + newViewFilter, + correspondingViewFilter, + ); + + return shouldUpdateBecauseViewFilterIsDifferent; + }); +}; diff --git a/packages/twenty-front/src/modules/views/utils/mapRecordFilterToViewFilter.ts b/packages/twenty-front/src/modules/views/utils/mapRecordFilterToViewFilter.ts new file mode 100644 index 000000000..64847493d --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/mapRecordFilterToViewFilter.ts @@ -0,0 +1,11 @@ +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { ViewFilter } from '@/views/types/ViewFilter'; + +export const mapRecordFilterToViewFilter = ( + recordFilter: RecordFilter, +): ViewFilter => { + return { + __typename: 'ViewFilter', + ...recordFilter, + } satisfies ViewFilter; +};