Make filters and sorts work on record page pagination (#12460)

Fixes #7929 

This PR implements a system to capture and preserve the filters and
sorts when navigating from an index view to a record show page. This
information is stored in a context store component state.

This allows users to navigate between records inside the record page
while maintaining context from the index view.
This commit is contained in:
Raphaël Bosi
2025-06-11 18:01:03 +02:00
committed by GitHub
parent 23cbeec227
commit 27d0a3766f
18 changed files with 296 additions and 195 deletions

View File

@ -1,27 +0,0 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews';
import { useQueryVariablesFromView } from './useQueryVariablesFromView';
export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const { view } = useViewOrDefaultViewFromPrefetchedViews({
objectMetadataItemId: objectMetadataItem.id,
});
const { filterValueDependencies } = useFilterValueDependencies();
const { filter, orderBy } = useQueryVariablesFromView({
objectMetadataItem,
view,
filterValueDependencies,
});
return {
filter,
orderBy,
};
};

View File

@ -0,0 +1,32 @@
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreRecordShowParentViewComponentState } from '@/context-store/states/contextStoreRecordShowParentViewComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { getQueryVariablesFromFiltersAndSorts } from '../utils/getQueryVariablesFromFiltersAndSorts';
export const useQueryVariablesFromParentView = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const recordShowParentView = useRecoilComponentValueV2(
contextStoreRecordShowParentViewComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const { filterValueDependencies } = useFilterValueDependencies();
const { filter, orderBy } = getQueryVariablesFromFiltersAndSorts({
recordFilterGroups: recordShowParentView?.parentViewFilterGroups ?? [],
recordFilters: recordShowParentView?.parentViewFilters ?? [],
recordSorts: recordShowParentView?.parentViewSorts ?? [],
objectMetadataItem,
filterValueDependencies,
});
return {
filter,
orderBy,
};
};

View File

@ -1,58 +0,0 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
import { View } from '@/views/types/View';
import { getFilterableFieldsWithVectorSearch } from '@/views/utils/getFilterableFieldsWithVectorSearch';
import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { isDefined } from 'twenty-shared/utils';
export const useQueryVariablesFromView = ({
view,
objectMetadataItem,
filterValueDependencies,
}: {
view: View | null | undefined;
objectMetadataItem: ObjectMetadataItem;
filterValueDependencies: RecordFilterValueDependencies;
}) => {
if (!isDefined(view)) {
return {
filter: undefined,
orderBy: undefined,
};
}
const { viewFilterGroups, viewFilters, viewSorts } = view;
const recordFilterGroups = mapViewFilterGroupsToRecordFilterGroups(
viewFilterGroups ?? [],
);
const filterableFieldMetadataItems =
getFilterableFieldsWithVectorSearch(objectMetadataItem);
const recordFilters = mapViewFiltersToFilters(
viewFilters,
filterableFieldMetadataItems,
);
const filter = computeRecordGqlOperationFilter({
fields: objectMetadataItem?.fields ?? [],
filterValueDependencies,
recordFilterGroups,
recordFilters,
});
const orderBy = turnSortsIntoOrderBy(
objectMetadataItem,
mapViewSortsToSorts(viewSorts),
);
return {
filter,
orderBy,
};
};

View File

@ -0,0 +1,35 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
import { RecordSort } from '@/object-record/record-sort/types/RecordSort';
export const getQueryVariablesFromFiltersAndSorts = ({
recordFilterGroups,
recordFilters,
recordSorts,
objectMetadataItem,
filterValueDependencies,
}: {
recordFilterGroups: RecordFilterGroup[];
recordFilters: RecordFilter[];
recordSorts: RecordSort[];
objectMetadataItem: ObjectMetadataItem;
filterValueDependencies: RecordFilterValueDependencies;
}) => {
const filter = computeRecordGqlOperationFilter({
fields: objectMetadataItem?.fields ?? [],
filterValueDependencies,
recordFilterGroups,
recordFilters,
});
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, recordSorts);
return {
filter,
orderBy,
};
};