Refactored table filters to consume new currentRecordFilters component state (#9652)

This PR implements a first real use case, now currentRecordFilters
component state acts as the global record filter reference.

It is set by the view initially and can be reset to view filters state
at any point.

This new state is also modified by two new upsertRecordFilter /
removeRecordFilter hooks that will be drop-in replacement of the actual
upsertCombinedViewFilter and removeCombinediewFilter hooks.

This PR implements the logic to manipulate record filters but only reads
it to make the table find many request, all other features are still
relying on the old view filter implementation.

Advanced filters are ignored because they are hidden and because this
effort is made precisely to allow the completion of the advanced filters
feature.
This commit is contained in:
Lucas Bordeau
2025-01-23 11:09:44 +01:00
committed by GitHub
parent 3ab193f298
commit bddca09451
42 changed files with 1303 additions and 302 deletions

View File

@ -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;
}

View File

@ -5,6 +5,7 @@ import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlur
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
@ -107,17 +108,21 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
recordIndexId: instanceId,
}}
>
<ObjectFilterDropdownComponentInstanceContext.Provider
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId }}
>
<RecordTableComponentInstanceContext.Provider
value={{ instanceId: instanceId, onColumnsChange: () => {} }}
<ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId }}
>
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
<Story />
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</ObjectFilterDropdownComponentInstanceContext.Provider>
<RecordTableComponentInstanceContext.Provider
value={{ instanceId: instanceId, onColumnsChange: () => {} }}
>
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
<Story />
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</ObjectFilterDropdownComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</RecordIndexContextProvider>
);
},

View File

@ -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<

View File

@ -7,6 +7,7 @@ import { ObjectOptionsDropdownContent } from '@/object-record/object-options-dro
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@ -37,22 +38,26 @@ const meta: Meta<typeof ObjectOptionsDropdownContent> = {
}, [setObjectMetadataItems]);
return (
<RecordTableComponentInstanceContext.Provider
value={{ instanceId, onColumnsChange: () => {} }}
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: 'object-options-dropdown' }}
>
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId }}
>
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
<RecordTableComponentInstanceContext.Provider
value={{ instanceId, onColumnsChange: () => {} }}
>
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId }}
>
<Story />
</MemoryRouter>
</ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
>
<Story />
</MemoryRouter>
</ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
);
},
ObjectMetadataItemsDecorator,

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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 {

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -0,0 +1,4 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const RecordFiltersComponentInstanceContext =
createComponentInstanceContext();

View File

@ -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,
});

View File

@ -0,0 +1,128 @@
import { isMatchingArrayFilter } from '../isMatchingArrayFilter';
describe('isMatchingArrayFilter', () => {
describe('is filter', () => {
it('should return true when checking for NULL and value is null', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { is: 'NULL' },
value: null,
}),
).toBe(true);
});
it('should return false when checking for NULL and value is not null', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { is: 'NULL' },
value: ['test'],
}),
).toBe(false);
});
it('should return true when checking for NOT_NULL and value is not null', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { is: 'NOT_NULL' },
value: ['test'],
}),
).toBe(true);
});
it('should return false when checking for NOT_NULL and value is null', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { is: 'NOT_NULL' },
value: null,
}),
).toBe(false);
});
});
describe('isEmptyArray filter', () => {
it('should return true when array is empty and checking for empty array', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { isEmptyArray: true },
value: [],
}),
).toBe(true);
});
it('should return false when array is not empty and checking for empty array', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { isEmptyArray: true },
value: ['test'],
}),
).toBe(false);
});
it('should return false when value is null and checking for empty array', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { isEmptyArray: true },
value: null,
}),
).toBe(false);
});
});
describe('containsIlike filter', () => {
it('should return true when array contains item matching case-insensitive search', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { containsIlike: 'TEST' },
value: ['test item'],
}),
).toBe(true);
});
it('should return false when array does not contain item matching search', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { containsIlike: 'missing' },
value: ['test item'],
}),
).toBe(false);
});
it('should return false when value is null and using containsIlike', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { containsIlike: 'test' },
value: null,
}),
).toBe(false);
});
it('should match partial strings case-insensitively', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { containsIlike: 'TE' },
value: ['Test Item', 'Another Item'],
}),
).toBe(true);
});
});
describe('error handling', () => {
it('should throw error for invalid filter', () => {
expect(() =>
isMatchingArrayFilter({
arrayFilter: {},
value: [],
}),
).toThrow('Unexpected value for array filter');
});
it('should throw error for unknown filter type', () => {
expect(() =>
isMatchingArrayFilter({
arrayFilter: { unknownFilter: 'test' } as any,
value: [],
}),
).toThrow('Unexpected value for array filter');
});
});
});

View File

@ -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,
);

View File

@ -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,
],
);

View File

@ -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 }) =>

View File

@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
@ -42,29 +43,33 @@ export const RightDrawerRecord = () => {
);
return (
<ContextStoreComponentInstanceContext.Provider
value={{
instanceId: `record-show-${objectRecordId}`,
}}
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }}
<ContextStoreComponentInstanceContext.Provider
value={{
instanceId: `record-show-${objectRecordId}`,
}}
>
<StyledRightDrawerRecord isMobile={isMobile}>
<RecordFieldValueSelectorContextProvider>
{!isNewViewableRecordLoading && (
<RecordValueSetterEffect recordId={objectRecordId} />
)}
<RecordShowContainer
objectNameSingular={objectNameSingular}
objectRecordId={objectRecordId}
loading={false}
isInRightDrawer={true}
isNewRightDrawerItemLoading={isNewViewableRecordLoading}
/>
</RecordFieldValueSelectorContextProvider>
</StyledRightDrawerRecord>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }}
>
<StyledRightDrawerRecord isMobile={isMobile}>
<RecordFieldValueSelectorContextProvider>
{!isNewViewableRecordLoading && (
<RecordValueSetterEffect recordId={objectRecordId} />
)}
<RecordShowContainer
objectNameSingular={objectNameSingular}
objectRecordId={objectRecordId}
loading={false}
isInRightDrawer={true}
isNewRightDrawerItemLoading={isNewViewableRecordLoading}
/>
</RecordFieldValueSelectorContextProvider>
</StyledRightDrawerRecord>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
);
};

View File

@ -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);
};