Implemented view filter group CRUD hooks and utils (#10551)

This PR implements hooks and utils logic for handling CRUD and view
filter group comparison.

The main hook is useAreViewFilterGroupsDifferentFromRecordFilterGroups,
like view filters and view sorts.

Inside this hook we implement getViewFilterGroupsToCreate,
getViewFilterGroupsToDelete and getViewFilterGroupsToUpdate.

All of those come with their unit tests.

In this PR we also introduce a new util to prevent nasty bugs happening
when we compare undefined === null,

This util is called compareStrictlyExceptForNullAndUndefined and it
should replace every strict equality comparison between values that can
be null or undefined (which we have a lot)

This could be enforced by a custom ESLint rule, the autofix may also be
implemented (maybe the util should be put in twenty-shared ?)
This commit is contained in:
Lucas Bordeau
2025-02-28 13:32:54 +01:00
committed by GitHub
parent b7abaa242c
commit ea1ac3708c
13 changed files with 518 additions and 7 deletions

View File

@ -24,6 +24,9 @@ import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreVie
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
import { t } from '@lingui/core/macro';
import { isNonEmptyArray } from '@sniptt/guards';
@ -122,6 +125,10 @@ export const ViewBarDetails = ({
const { hasFiltersQueryParams } = useViewFromQueryParams();
const currentRecordFilterGroups = useRecoilComponentValueV2(
currentRecordFilterGroupsComponentState,
);
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
@ -139,6 +146,9 @@ export const ViewBarDetails = ({
});
const { resetUnsavedViewStates } = useResetUnsavedViewStates();
const { viewFilterGroupsAreDifferentFromRecordFilterGroups } =
useAreViewFilterGroupsDifferentFromRecordFilterGroups();
const { viewFiltersAreDifferentFromRecordFilters } =
useAreViewFiltersDifferentFromRecordFilters();
@ -147,7 +157,8 @@ export const ViewBarDetails = ({
const canResetView =
(viewFiltersAreDifferentFromRecordFilters ||
viewSortsAreDifferentFromRecordSorts) &&
viewSortsAreDifferentFromRecordSorts ||
viewFilterGroupsAreDifferentFromRecordFilterGroups) &&
!hasFiltersQueryParams;
const { checkIsSoftDeleteFilter } = useCheckIsSoftDeleteFilter();
@ -164,6 +175,9 @@ export const ViewBarDetails = ({
);
}, [currentRecordFilters, checkIsSoftDeleteFilter]);
const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } =
useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups();
const { applyCurrentViewFiltersToCurrentRecordFilters } =
useApplyCurrentViewFiltersToCurrentRecordFilters();
@ -173,6 +187,7 @@ export const ViewBarDetails = ({
const handleCancelClick = () => {
if (isDefined(viewId)) {
resetUnsavedViewStates(viewId);
applyCurrentViewFilterGroupsToCurrentRecordFilterGroups();
applyCurrentViewFiltersToCurrentRecordFilters();
applyCurrentViewSortsToCurrentRecordSorts();
toggleSoftDeleteFilterState(false);
@ -182,7 +197,10 @@ export const ViewBarDetails = ({
const shouldExpandViewBar =
viewFiltersAreDifferentFromRecordFilters ||
viewSortsAreDifferentFromRecordSorts ||
((currentRecordSorts?.length || currentRecordFilters?.length) &&
viewFilterGroupsAreDifferentFromRecordFilterGroups ||
((currentRecordSorts.length > 0 ||
currentRecordFilters.length > 0 ||
currentRecordFilterGroups.length > 0) &&
isViewBarExpanded);
if (!shouldExpandViewBar) {