Add ability to save any field filter to view (#13401)
This PR adds the ability to save an any field filter to a view. It adds a new `anyFieldFilterValue` on both core view and workspace view entities. It also introduces the necessary utils that mimic the logic that manages the save of record filters and record sorts on views.
This commit is contained in:
@ -25,5 +25,6 @@ export const findAllViewsOperationSignatureFactory: RecordGqlOperationSignatureF
|
||||
viewSorts: true,
|
||||
viewFields: true,
|
||||
viewGroups: true,
|
||||
anyFieldFilterValue: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -14,6 +14,7 @@ import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/h
|
||||
import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAreViewFiltersDifferentFromRecordFilters';
|
||||
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } from '@/views/hooks/useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter';
|
||||
import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
@ -84,10 +85,14 @@ export const UpdateViewButtonGroup = () => {
|
||||
const { viewSortsAreDifferentFromRecordSorts } =
|
||||
useAreViewSortsDifferentFromRecordSorts();
|
||||
|
||||
const { viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } =
|
||||
useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter();
|
||||
|
||||
const canShowButton =
|
||||
(viewFiltersAreDifferentFromRecordFilters ||
|
||||
viewSortsAreDifferentFromRecordSorts ||
|
||||
viewFilterGroupsAreDifferentFromRecordFilterGroups) &&
|
||||
viewFilterGroupsAreDifferentFromRecordFilterGroups ||
|
||||
viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter) &&
|
||||
!hasFiltersQueryParams;
|
||||
|
||||
if (!canShowButton) {
|
||||
|
||||
@ -12,6 +12,7 @@ import { ViewPickerDropdown } from '@/views/view-picker/components/ViewPickerDro
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
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 { ViewBarAnyFieldFilterEffect } from '@/views/components/ViewBarAnyFieldFilterEffect';
|
||||
import { ViewBarFilterDropdown } from '@/views/components/ViewBarFilterDropdown';
|
||||
import { ViewBarRecordFilterEffect } from '@/views/components/ViewBarRecordFilterEffect';
|
||||
import { ViewBarRecordFilterGroupEffect } from '@/views/components/ViewBarRecordFilterGroupEffect';
|
||||
@ -43,6 +44,7 @@ export const ViewBar = ({
|
||||
value={{ instanceId: VIEW_SORT_DROPDOWN_ID }}
|
||||
>
|
||||
<ViewBarRecordFilterGroupEffect />
|
||||
<ViewBarAnyFieldFilterEffect />
|
||||
<ViewBarRecordFilterEffect />
|
||||
<ViewBarRecordSortEffect />
|
||||
<QueryParamsFiltersEffect />
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
|
||||
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { hasInitializedAnyFieldFilterComponentFamilyState } from '@/views/states/hasInitializedAnyFieldFilterComponentFamilyState';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const ViewBarAnyFieldFilterEffect = () => {
|
||||
const currentViewId = useRecoilComponentValueV2(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const currentView = useRecoilValue(
|
||||
prefetchViewFromViewIdFamilySelector({
|
||||
viewId: currentViewId ?? '',
|
||||
}),
|
||||
);
|
||||
|
||||
const [hasInitializedAnyFieldFilter, setHasInitializedAnyFieldFilter] =
|
||||
useRecoilComponentFamilyStateV2(
|
||||
hasInitializedAnyFieldFilterComponentFamilyState,
|
||||
{
|
||||
viewId: currentViewId ?? undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const setAnyFieldFilterValue = useSetRecoilComponentStateV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasInitializedAnyFieldFilter && isDefined(currentView)) {
|
||||
if (currentView.objectMetadataId !== objectMetadataItem.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAnyFieldFilterValue(currentView.anyFieldFilterValue ?? '');
|
||||
|
||||
setHasInitializedAnyFieldFilter(true);
|
||||
}
|
||||
}, [
|
||||
setAnyFieldFilterValue,
|
||||
currentViewId,
|
||||
hasInitializedAnyFieldFilter,
|
||||
setHasInitializedAnyFieldFilter,
|
||||
currentView,
|
||||
objectMetadataItem,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -27,8 +27,10 @@ import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDrop
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { AnyFieldSearchDropdownButton } from '@/views/components/AnyFieldSearchDropdownButton';
|
||||
import { ANY_FIELD_SEARCH_DROPDOWN_ID } from '@/views/constants/AnyFieldSearchDropdownId';
|
||||
import { useApplyCurrentViewAnyFieldFilterToAnyFieldFilter } from '@/views/hooks/useApplyCurrentViewAnyFieldFilterToAnyFieldFilter';
|
||||
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
|
||||
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
|
||||
import { useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } from '@/views/hooks/useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter';
|
||||
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||
@ -144,11 +146,8 @@ export const ViewBarDetails = ({
|
||||
const { viewSortsAreDifferentFromRecordSorts } =
|
||||
useAreViewSortsDifferentFromRecordSorts();
|
||||
|
||||
const canResetView =
|
||||
(viewFiltersAreDifferentFromRecordFilters ||
|
||||
viewSortsAreDifferentFromRecordSorts ||
|
||||
viewFilterGroupsAreDifferentFromRecordFilterGroups) &&
|
||||
!hasFiltersQueryParams;
|
||||
const { viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } =
|
||||
useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter();
|
||||
|
||||
const { checkIsSoftDeleteFilter } = useCheckIsSoftDeleteFilter();
|
||||
|
||||
@ -170,6 +169,9 @@ export const ViewBarDetails = ({
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
useApplyCurrentViewFiltersToCurrentRecordFilters();
|
||||
|
||||
const { applyCurrentViewAnyFieldFilterToAnyFieldFilter } =
|
||||
useApplyCurrentViewAnyFieldFilterToAnyFieldFilter();
|
||||
|
||||
const { applyCurrentViewSortsToCurrentRecordSorts } =
|
||||
useApplyCurrentViewSortsToCurrentRecordSorts();
|
||||
|
||||
@ -177,6 +179,7 @@ export const ViewBarDetails = ({
|
||||
applyCurrentViewFilterGroupsToCurrentRecordFilterGroups();
|
||||
applyCurrentViewFiltersToCurrentRecordFilters();
|
||||
applyCurrentViewSortsToCurrentRecordSorts();
|
||||
applyCurrentViewAnyFieldFilterToAnyFieldFilter();
|
||||
toggleSoftDeleteFilterState(false);
|
||||
};
|
||||
|
||||
@ -188,6 +191,13 @@ export const ViewBarDetails = ({
|
||||
ANY_FIELD_SEARCH_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const canResetView =
|
||||
(viewFiltersAreDifferentFromRecordFilters ||
|
||||
viewSortsAreDifferentFromRecordSorts ||
|
||||
viewFilterGroupsAreDifferentFromRecordFilterGroups ||
|
||||
viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter) &&
|
||||
!hasFiltersQueryParams;
|
||||
|
||||
const shouldShowAnyFieldSearchChip =
|
||||
isNonEmptyString(anyFieldFilterValue) || isAnyFieldSearchDropdownOpen;
|
||||
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useApplyCurrentViewAnyFieldFilterToAnyFieldFilter = () => {
|
||||
const currentViewId = useRecoilComponentValueV2(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
const setAnyFieldFilterValue = useSetRecoilComponentStateV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const applyCurrentViewAnyFieldFilterToAnyFieldFilter = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const currentView = snapshot
|
||||
.getLoadable(
|
||||
prefetchViewFromViewIdFamilySelector({
|
||||
viewId: currentViewId ?? '',
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (isDefined(currentView)) {
|
||||
setAnyFieldFilterValue(currentView.anyFieldFilterValue ?? '');
|
||||
}
|
||||
},
|
||||
[currentViewId, setAnyFieldFilterValue],
|
||||
);
|
||||
|
||||
return {
|
||||
applyCurrentViewAnyFieldFilterToAnyFieldFilter,
|
||||
};
|
||||
};
|
||||
@ -3,6 +3,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
@ -40,6 +41,10 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
objectNameSingular: CoreObjectNameSingular.View,
|
||||
});
|
||||
|
||||
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { createViewFieldRecords } = usePersistViewFieldRecords();
|
||||
|
||||
const { createViewSortRecords } = usePersistViewSortRecords();
|
||||
@ -126,6 +131,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
type: type ?? sourceView.type,
|
||||
objectMetadataId: sourceView.objectMetadataId,
|
||||
openRecordIn: sourceView.openRecordIn,
|
||||
anyFieldFilterValue: anyFieldFilterValue,
|
||||
});
|
||||
|
||||
if (isUndefinedOrNull(newView)) {
|
||||
@ -209,6 +215,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
},
|
||||
[
|
||||
anyFieldFilterValue,
|
||||
currentViewIdCallbackState,
|
||||
createOneRecord,
|
||||
createViewFieldRecords,
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { compareNonEmptyStrings } from '~/utils/compareNonEmptyStrings';
|
||||
|
||||
export const useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter = () => {
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const viewAnyFieldFilterValue = currentView?.anyFieldFilterValue;
|
||||
|
||||
const viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter =
|
||||
!compareNonEmptyStrings(viewAnyFieldFilterValue, anyFieldFilterValue);
|
||||
|
||||
return { viewAnyFieldFilterDifferentFromCurrentAnyFieldFilter };
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { useUpdateView } from '@/views/hooks/useUpdateView';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useSaveAnyFieldFilterToView = () => {
|
||||
const { updateView } = useUpdateView();
|
||||
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
|
||||
const anyFieldFilterValueCallbackState = useRecoilComponentCallbackStateV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const saveAnyFieldFilterToView = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async () => {
|
||||
if (!isDefined(currentView)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentViewAnyFieldFilterValue = currentView?.anyFieldFilterValue;
|
||||
|
||||
const currentAnyFieldFilterValue = snapshot
|
||||
.getLoadable(anyFieldFilterValueCallbackState)
|
||||
.getValue();
|
||||
|
||||
if (currentAnyFieldFilterValue !== currentViewAnyFieldFilterValue) {
|
||||
await updateView({
|
||||
...currentView,
|
||||
anyFieldFilterValue: currentAnyFieldFilterValue,
|
||||
});
|
||||
}
|
||||
},
|
||||
[updateView, anyFieldFilterValueCallbackState, currentView],
|
||||
);
|
||||
|
||||
return {
|
||||
saveAnyFieldFilterToView,
|
||||
};
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveAnyFieldFilterToView } from '@/views/hooks/useSaveAnyFieldFilterToView';
|
||||
import { useSaveRecordFilterGroupsToViewFilterGroups } from '@/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups';
|
||||
import { useSaveRecordFiltersToViewFilters } from '@/views/hooks/useSaveRecordFiltersToViewFilters';
|
||||
import { useSaveRecordSortsToViewSorts } from '@/views/hooks/useSaveRecordSortsToViewSorts';
|
||||
@ -11,10 +12,13 @@ export const useSaveCurrentViewFiltersAndSorts = () => {
|
||||
|
||||
const { saveRecordSortsToViewSorts } = useSaveRecordSortsToViewSorts();
|
||||
|
||||
const { saveAnyFieldFilterToView } = useSaveAnyFieldFilterToView();
|
||||
|
||||
const saveCurrentViewFilterAndSorts = async () => {
|
||||
await saveRecordSortsToViewSorts();
|
||||
await saveRecordFiltersToViewFilters();
|
||||
await saveRecordFilterGroupsToViewFilterGroups();
|
||||
await saveAnyFieldFilterToView();
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||
|
||||
export const hasInitializedAnyFieldFilterComponentFamilyState =
|
||||
createComponentFamilyStateV2<boolean, { viewId?: string }>({
|
||||
key: 'hasInitializedAnyFieldFilterComponentFamilyState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: RecordFiltersComponentInstanceContext,
|
||||
});
|
||||
@ -29,4 +29,5 @@ export type GraphQLView = {
|
||||
viewGroups: ViewGroup[];
|
||||
position: number;
|
||||
icon: string;
|
||||
anyFieldFilterValue?: string | null;
|
||||
};
|
||||
|
||||
@ -29,5 +29,6 @@ export type View = {
|
||||
position: number;
|
||||
icon: string;
|
||||
openRecordIn: ViewOpenRecordInType;
|
||||
anyFieldFilterValue?: string | null;
|
||||
__typename: 'View';
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user