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:
@ -10,6 +10,7 @@ import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBar
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { expect, jest } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
@ -25,58 +26,63 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
||||
decorators: [
|
||||
RouterDecorator,
|
||||
(Story) => (
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
>
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: 'story-action-menu',
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: ['1', '2', '3'],
|
||||
},
|
||||
);
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: 'story-action-menu',
|
||||
}),
|
||||
3,
|
||||
);
|
||||
const map = new Map<string, ActionMenuEntry>();
|
||||
map.set('delete', {
|
||||
isPinned: true,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
position: 0,
|
||||
Icon: IconTrash,
|
||||
onClick: deleteMock,
|
||||
});
|
||||
set(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId: 'story-action-menu',
|
||||
}),
|
||||
map,
|
||||
);
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: getActionBarIdFromActionMenuId('story-action-menu'),
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}}
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: 'story-action-menu',
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: ['1', '2', '3'],
|
||||
},
|
||||
);
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: 'story-action-menu',
|
||||
}),
|
||||
3,
|
||||
);
|
||||
const map = new Map<string, ActionMenuEntry>();
|
||||
map.set('delete', {
|
||||
isPinned: true,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
position: 0,
|
||||
Icon: IconTrash,
|
||||
onClick: deleteMock,
|
||||
});
|
||||
set(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId: 'story-action-menu',
|
||||
}),
|
||||
map,
|
||||
);
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId:
|
||||
getActionBarIdFromActionMenuId('story-action-menu'),
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</RecoilRoot>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
>
|
||||
<Story />
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</RecoilRoot>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
|
||||
@ -9,6 +9,7 @@ import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKey
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
|
||||
@ -74,37 +75,41 @@ export const CommandMenuContainer = ({
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'command-menu' }}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'command-menu' }}
|
||||
>
|
||||
<ActionMenuContext.Provider
|
||||
value={{
|
||||
isInRightDrawer: false,
|
||||
onActionExecutedCallback: toggleCommandMenu,
|
||||
}}
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'command-menu' }}
|
||||
>
|
||||
<RecordActionMenuEntriesSetter />
|
||||
{isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />}
|
||||
<ActionMenuConfirmationModals />
|
||||
{isCommandMenuOpened && (
|
||||
<StyledCommandMenu
|
||||
data-testid="command-menu"
|
||||
ref={commandMenuRef}
|
||||
className="command-menu"
|
||||
animate={targetVariantForAnimation}
|
||||
initial="closed"
|
||||
exit="closed"
|
||||
variants={COMMAND_MENU_ANIMATION_VARIANTS}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
>
|
||||
{children}
|
||||
</StyledCommandMenu>
|
||||
)}
|
||||
</ActionMenuContext.Provider>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
<ActionMenuContext.Provider
|
||||
value={{
|
||||
isInRightDrawer: false,
|
||||
onActionExecutedCallback: toggleCommandMenu,
|
||||
}}
|
||||
>
|
||||
<RecordActionMenuEntriesSetter />
|
||||
{isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />}
|
||||
<ActionMenuConfirmationModals />
|
||||
{isCommandMenuOpened && (
|
||||
<StyledCommandMenu
|
||||
data-testid="command-menu"
|
||||
ref={commandMenuRef}
|
||||
className="command-menu"
|
||||
animate={targetVariantForAnimation}
|
||||
initial="closed"
|
||||
exit="closed"
|
||||
variants={COMMAND_MENU_ANIMATION_VARIANTS}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
>
|
||||
{children}
|
||||
</StyledCommandMenu>
|
||||
)}
|
||||
</ActionMenuContext.Provider>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -19,6 +19,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
|
||||
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
|
||||
import { CommandMenu } from '../CommandMenu';
|
||||
@ -29,17 +30,21 @@ const openTimeout = 50;
|
||||
|
||||
const ContextStoreDecorator: Decorator = (Story) => {
|
||||
return (
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'command-menu' }}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'command-menu' }}
|
||||
>
|
||||
<JestContextStoreSetter contextStoreCurrentObjectMetadataNameSingular="company">
|
||||
<Story />
|
||||
</JestContextStoreSetter>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'command-menu' }}
|
||||
>
|
||||
<JestContextStoreSetter contextStoreCurrentObjectMetadataNameSingular="company">
|
||||
<Story />
|
||||
</JestContextStoreSetter>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
|
||||
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
|
||||
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
|
||||
export const useDeleteFavoriteFolder = () => {
|
||||
@ -12,9 +12,10 @@ export const useDeleteFavoriteFolder = () => {
|
||||
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
|
||||
});
|
||||
|
||||
const { upsertRecordsInCache } = usePrefetchRunQuery<Favorite>({
|
||||
prefetchKey: PrefetchKey.AllFavorites,
|
||||
});
|
||||
const { upsertRecordsInCache } =
|
||||
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
|
||||
prefetchKey: PrefetchKey.AllFavorites,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
|
||||
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -33,7 +33,7 @@ export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => {
|
||||
);
|
||||
|
||||
const { upsertRecordsInCache: upsertFavorites } =
|
||||
usePrefetchRunQuery<Favorite>({
|
||||
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
|
||||
prefetchKey: PrefetchKey.AllFavorites,
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
|
||||
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
|
||||
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -26,7 +26,7 @@ export const usePrefetchedFavoritesFoldersData =
|
||||
);
|
||||
|
||||
const { upsertRecordsInCache: upsertFavoriteFolders } =
|
||||
usePrefetchRunQuery<FavoriteFolder>({
|
||||
useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
|
||||
prefetchKey: PrefetchKey.AllFavoritesFolders,
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
},
|
||||
|
||||
@ -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<
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||
|
||||
export const RecordFiltersComponentInstanceContext =
|
||||
createComponentInstanceContext();
|
||||
@ -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,
|
||||
});
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -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 }) =>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
|
||||
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
|
||||
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
|
||||
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { View } from '@/views/types/View';
|
||||
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
|
||||
@ -19,16 +19,16 @@ export const PrefetchRunQueriesEffect = () => {
|
||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
|
||||
|
||||
const { upsertRecordsInCache: upsertViewsInCache } =
|
||||
usePrefetchRunQuery<View>({
|
||||
useUpsertRecordsInCacheForPrefetchKey<View>({
|
||||
prefetchKey: PrefetchKey.AllViews,
|
||||
});
|
||||
|
||||
const { upsertRecordsInCache: upsertFavoritesInCache } =
|
||||
usePrefetchRunQuery<Favorite>({
|
||||
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
|
||||
prefetchKey: PrefetchKey.AllFavorites,
|
||||
});
|
||||
const { upsertRecordsInCache: upsertFavoritesFoldersInCache } =
|
||||
usePrefetchRunQuery<FavoriteFolder>({
|
||||
useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
|
||||
prefetchKey: PrefetchKey.AllFavoritesFolders,
|
||||
});
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
@ -11,7 +11,7 @@ export type UsePrefetchRunQuery = {
|
||||
prefetchKey: PrefetchKey;
|
||||
};
|
||||
|
||||
export const usePrefetchRunQuery = <T extends ObjectRecord>({
|
||||
export const useUpsertRecordsInCacheForPrefetchKey = <T extends ObjectRecord>({
|
||||
prefetchKey,
|
||||
}: UsePrefetchRunQuery) => {
|
||||
const setPrefetchDataIsLoaded = useSetRecoilState(
|
||||
@ -45,7 +45,6 @@ export const usePrefetchRunQuery = <T extends ObjectRecord>({
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
setPrefetchDataIsLoaded,
|
||||
upsertRecordsInCache,
|
||||
};
|
||||
};
|
||||
@ -3,6 +3,7 @@ import styled from '@emotion/styled';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
||||
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
|
||||
@ -41,30 +42,36 @@ export const SignInBackgroundMockContainer = () => {
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: recordIndexId,
|
||||
}}
|
||||
>
|
||||
<ViewBar
|
||||
viewBarId={viewBarId}
|
||||
onCurrentViewChange={() => {}}
|
||||
optionsDropdownButton={<></>}
|
||||
/>
|
||||
<SignInBackgroundMockContainerEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
recordTableId={recordIndexId}
|
||||
viewId={viewBarId}
|
||||
/>
|
||||
<RecordTableWithWrappers
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={viewBarId}
|
||||
updateRecordMutation={() => {}}
|
||||
/>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
>
|
||||
<ViewBar
|
||||
viewBarId={viewBarId}
|
||||
onCurrentViewChange={() => {}}
|
||||
optionsDropdownButton={<></>}
|
||||
/>
|
||||
<SignInBackgroundMockContainerEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
recordTableId={recordIndexId}
|
||||
viewId={viewBarId}
|
||||
/>
|
||||
<RecordTableWithWrappers
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={viewBarId}
|
||||
updateRecordMutation={() => {}}
|
||||
/>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</RecordIndexContextProvider>
|
||||
</StyledContainer>
|
||||
|
||||
@ -11,6 +11,7 @@ import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
|
||||
@ -73,14 +74,17 @@ export const EditableFilterDropdownButton = ({
|
||||
viewFilterDropdownId,
|
||||
]);
|
||||
|
||||
const { removeRecordFilter } = useRemoveRecordFilter();
|
||||
|
||||
const handleRemove = () => {
|
||||
closeDropdown();
|
||||
|
||||
deleteCombinedViewFilter(viewFilter.id);
|
||||
removeRecordFilter(viewFilter.fieldMetadataId);
|
||||
};
|
||||
|
||||
const handleDropdownClickOutside = useCallback(() => {
|
||||
const { id: fieldId, value, operand } = viewFilter;
|
||||
const { id: fieldId, value, operand, fieldMetadataId } = viewFilter;
|
||||
if (
|
||||
!value &&
|
||||
![
|
||||
@ -91,9 +95,10 @@ export const EditableFilterDropdownButton = ({
|
||||
RecordFilterOperand.IsToday,
|
||||
].includes(operand)
|
||||
) {
|
||||
removeRecordFilter(fieldMetadataId);
|
||||
deleteCombinedViewFilter(fieldId);
|
||||
}
|
||||
}, [viewFilter, deleteCombinedViewFilter]);
|
||||
}, [viewFilter, deleteCombinedViewFilter, removeRecordFilter]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
|
||||
@ -3,6 +3,7 @@ import { useEffect } from 'react';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
|
||||
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { useApplyViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyViewFiltersToCurrentRecordFilters';
|
||||
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
|
||||
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
|
||||
@ -20,6 +21,9 @@ export const QueryParamsFiltersEffect = () => {
|
||||
|
||||
const { resetUnsavedViewStates } = useResetUnsavedViewStates();
|
||||
|
||||
const { applyViewFiltersToCurrentRecordFilters } =
|
||||
useApplyViewFiltersToCurrentRecordFilters();
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasFiltersQueryParams) {
|
||||
return;
|
||||
@ -27,10 +31,12 @@ export const QueryParamsFiltersEffect = () => {
|
||||
|
||||
getFiltersFromQueryParams().then((filtersFromParams) => {
|
||||
if (Array.isArray(filtersFromParams)) {
|
||||
applyViewFiltersToCurrentRecordFilters(filtersFromParams);
|
||||
setUnsavedViewFilter(filtersFromParams);
|
||||
}
|
||||
});
|
||||
}, [
|
||||
applyViewFiltersToCurrentRecordFilters,
|
||||
getFiltersFromQueryParams,
|
||||
hasFiltersQueryParams,
|
||||
resetUnsavedViewStates,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useIcons } from 'twenty-ui';
|
||||
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
|
||||
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
|
||||
@ -29,10 +30,14 @@ export const VariantFilterChip = ({
|
||||
viewBarId,
|
||||
});
|
||||
|
||||
const { removeRecordFilter } = useRemoveRecordFilter();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const handleRemoveClick = () => {
|
||||
deleteCombinedViewFilter(viewFilter.id);
|
||||
removeRecordFilter(viewFilter.fieldMetadataId);
|
||||
|
||||
if (
|
||||
viewFilter.definition.label === 'Deleted' &&
|
||||
viewFilter.operand === 'isNotEmpty'
|
||||
|
||||
@ -21,6 +21,7 @@ import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
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 { ViewBarRecordFilterEffect } from '@/views/components/ViewBarRecordFilterEffect';
|
||||
import { ViewEventContext } from '@/views/events/contexts/ViewEventContext';
|
||||
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
|
||||
import { ViewBarDetails } from './ViewBarDetails';
|
||||
@ -53,6 +54,7 @@ export const ViewBar = ({
|
||||
value={{ instanceId: VIEW_SORT_DROPDOWN_ID }}
|
||||
>
|
||||
<ViewEventContext.Provider value={{ onCurrentViewChange }}>
|
||||
<ViewBarRecordFilterEffect />
|
||||
<ViewBarEffect viewBarId={viewBarId} />
|
||||
<ViewBarFilterEffect filterDropdownId={filterDropdownId} />
|
||||
<ViewBarSortEffect />
|
||||
|
||||
@ -14,6 +14,8 @@ import { EditableFilterDropdownButton } from '@/views/components/EditableFilterD
|
||||
import { EditableSortChip } from '@/views/components/EditableSortChip';
|
||||
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
|
||||
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
|
||||
import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
@ -167,9 +169,13 @@ export const ViewBarDetails = ({
|
||||
};
|
||||
}, [currentViewWithCombinedFiltersAndSorts]);
|
||||
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
useApplyCurrentViewFiltersToCurrentRecordFilters();
|
||||
|
||||
const handleCancelClick = () => {
|
||||
if (isDefined(viewId)) {
|
||||
resetUnsavedViewStates(viewId);
|
||||
applyCurrentViewFiltersToCurrentRecordFilters();
|
||||
toggleSoftDeleteFilterState(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState';
|
||||
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
|
||||
import { onFilterSelectComponentState } from '@/object-record/object-filter-dropdown/states/onFilterSelectComponentState';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema';
|
||||
import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema';
|
||||
@ -23,19 +20,12 @@ type ViewBarFilterEffectProps = {
|
||||
export const ViewBarFilterEffect = ({
|
||||
filterDropdownId,
|
||||
}: ViewBarFilterEffectProps) => {
|
||||
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters();
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
);
|
||||
|
||||
const setOnFilterSelect = useSetRecoilComponentStateV2(
|
||||
onFilterSelectComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
filterDropdownId,
|
||||
@ -62,17 +52,7 @@ export const ViewBarFilterEffect = ({
|
||||
if (isDefined(availableFilterDefinitions)) {
|
||||
setAvailableFilterDefinitions(availableFilterDefinitions);
|
||||
}
|
||||
setOnFilterSelect(() => (filter: RecordFilter | null) => {
|
||||
if (isDefined(filter)) {
|
||||
upsertCombinedViewFilter(filter);
|
||||
}
|
||||
});
|
||||
}, [
|
||||
availableFilterDefinitions,
|
||||
setAvailableFilterDefinitions,
|
||||
setOnFilterSelect,
|
||||
upsertCombinedViewFilter,
|
||||
]);
|
||||
}, [availableFilterDefinitions, setAvailableFilterDefinitions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (filterDefinitionUsedInDropdown?.type === 'RELATION') {
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||
import { View } from '@/views/types/View';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const ViewBarRecordFilterEffect = () => {
|
||||
const { records: views, isDataPrefetched } = usePrefetchedData<View>(
|
||||
PrefetchKey.AllViews,
|
||||
);
|
||||
|
||||
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState);
|
||||
|
||||
const setCurrentRecordFilters = useSetRecoilComponentStateV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDataPrefetched) {
|
||||
const currentView = views.find((view) => view.id === currentViewId);
|
||||
|
||||
if (isDefined(currentView)) {
|
||||
setCurrentRecordFilters(
|
||||
mapViewFiltersToFilters(
|
||||
currentView.viewFilters,
|
||||
availableFilterDefinitions,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
isDataPrefetched,
|
||||
views,
|
||||
availableFilterDefinitions,
|
||||
currentViewId,
|
||||
setCurrentRecordFilters,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -0,0 +1,198 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '../useApplyCurrentViewFiltersToCurrentRecordFilters';
|
||||
|
||||
jest.mock('@/prefetch/hooks/usePrefetchedData');
|
||||
|
||||
describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
const mockFilterDefinition: RecordFilterDefinition = {
|
||||
fieldMetadataId: 'field-1',
|
||||
label: 'Test Field',
|
||||
type: 'TEXT',
|
||||
iconName: 'IconText',
|
||||
};
|
||||
|
||||
const mockViewFilter: ViewFilter = {
|
||||
__typename: 'ViewFilter',
|
||||
id: 'filter-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'test',
|
||||
displayValue: 'test',
|
||||
viewFilterGroupId: 'group-1',
|
||||
positionInViewFilterGroup: 0,
|
||||
definition: mockFilterDefinition,
|
||||
};
|
||||
|
||||
const mockView = {
|
||||
id: 'view-1',
|
||||
name: 'Test View',
|
||||
objectMetadataId: 'object-1',
|
||||
viewFilters: [mockViewFilter],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(usePrefetchedData as jest.Mock).mockReturnValue({
|
||||
records: [mockView],
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply filters from current view', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
useApplyCurrentViewFiltersToCurrentRecordFilters();
|
||||
|
||||
const currentFilters = useRecoilComponentValueV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
applyCurrentViewFiltersToCurrentRecordFilters,
|
||||
currentFilters,
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
currentViewIdComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
}),
|
||||
mockView.id,
|
||||
);
|
||||
snapshot.set(
|
||||
availableFilterDefinitionsComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
}),
|
||||
[mockFilterDefinition],
|
||||
);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.applyCurrentViewFiltersToCurrentRecordFilters();
|
||||
});
|
||||
|
||||
expect(result.current.currentFilters).toEqual([
|
||||
{
|
||||
id: mockViewFilter.id,
|
||||
fieldMetadataId: mockViewFilter.fieldMetadataId,
|
||||
value: mockViewFilter.value,
|
||||
displayValue: mockViewFilter.displayValue,
|
||||
operand: mockViewFilter.operand,
|
||||
viewFilterGroupId: mockViewFilter.viewFilterGroupId,
|
||||
positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup,
|
||||
definition: mockFilterDefinition,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not apply filters when current view is not found', () => {
|
||||
(usePrefetchedData as jest.Mock).mockReturnValue({
|
||||
records: [],
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
useApplyCurrentViewFiltersToCurrentRecordFilters();
|
||||
|
||||
const currentFilters = useRecoilComponentValueV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
applyCurrentViewFiltersToCurrentRecordFilters,
|
||||
currentFilters,
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
currentViewIdComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
}),
|
||||
mockView.id,
|
||||
);
|
||||
snapshot.set(
|
||||
availableFilterDefinitionsComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
}),
|
||||
[mockFilterDefinition],
|
||||
);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.applyCurrentViewFiltersToCurrentRecordFilters();
|
||||
});
|
||||
|
||||
expect(result.current.currentFilters).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle view with empty filters', () => {
|
||||
const viewWithNoFilters = {
|
||||
...mockView,
|
||||
viewFilters: [],
|
||||
};
|
||||
|
||||
(usePrefetchedData as jest.Mock).mockReturnValue({
|
||||
records: [viewWithNoFilters],
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
useApplyCurrentViewFiltersToCurrentRecordFilters();
|
||||
|
||||
const currentFilters = useRecoilComponentValueV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
applyCurrentViewFiltersToCurrentRecordFilters,
|
||||
currentFilters,
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
currentViewIdComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
}),
|
||||
mockView.id,
|
||||
);
|
||||
snapshot.set(
|
||||
availableFilterDefinitionsComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
}),
|
||||
[mockFilterDefinition],
|
||||
);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.applyCurrentViewFiltersToCurrentRecordFilters();
|
||||
});
|
||||
|
||||
expect(result.current.currentFilters).toEqual([]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,108 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { useApplyViewFiltersToCurrentRecordFilters } from '../useApplyViewFiltersToCurrentRecordFilters';
|
||||
|
||||
describe('useApplyViewFiltersToCurrentRecordFilters', () => {
|
||||
const mockAvailableFilterDefinition: RecordFilterDefinition = {
|
||||
fieldMetadataId: 'field-1',
|
||||
label: 'Test Field',
|
||||
type: 'TEXT',
|
||||
iconName: 'IconText',
|
||||
};
|
||||
|
||||
const mockViewFilter: ViewFilter = {
|
||||
__typename: 'ViewFilter',
|
||||
id: 'filter-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'test',
|
||||
displayValue: 'test',
|
||||
viewFilterGroupId: 'group-1',
|
||||
positionInViewFilterGroup: 0,
|
||||
definition: mockAvailableFilterDefinition,
|
||||
};
|
||||
|
||||
it('should apply view filters to current record filters', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyViewFiltersToCurrentRecordFilters } =
|
||||
useApplyViewFiltersToCurrentRecordFilters();
|
||||
|
||||
const currentFilters = useRecoilComponentValueV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
return { applyViewFiltersToCurrentRecordFilters, currentFilters };
|
||||
},
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
availableFilterDefinitionsComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
}),
|
||||
[mockAvailableFilterDefinition],
|
||||
);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.applyViewFiltersToCurrentRecordFilters([mockViewFilter]);
|
||||
});
|
||||
|
||||
expect(result.current.currentFilters).toEqual([
|
||||
{
|
||||
id: mockViewFilter.id,
|
||||
fieldMetadataId: mockViewFilter.fieldMetadataId,
|
||||
value: mockViewFilter.value,
|
||||
displayValue: mockViewFilter.displayValue,
|
||||
operand: mockViewFilter.operand,
|
||||
viewFilterGroupId: mockViewFilter.viewFilterGroupId,
|
||||
positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup,
|
||||
definition: mockAvailableFilterDefinition,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle empty view filters array', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { applyViewFiltersToCurrentRecordFilters } =
|
||||
useApplyViewFiltersToCurrentRecordFilters();
|
||||
|
||||
const currentFilters = useRecoilComponentValueV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
return { applyViewFiltersToCurrentRecordFilters, currentFilters };
|
||||
},
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
availableFilterDefinitionsComponentState.atomFamily({
|
||||
instanceId: 'instanceId',
|
||||
}),
|
||||
[mockAvailableFilterDefinition],
|
||||
);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.applyViewFiltersToCurrentRecordFilters([]);
|
||||
});
|
||||
|
||||
expect(result.current.currentFilters).toEqual([]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,42 @@
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||
import { View } from '@/views/types/View';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => {
|
||||
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
|
||||
|
||||
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState);
|
||||
|
||||
const setCurrentRecordFilters = useSetRecoilComponentStateV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
);
|
||||
|
||||
const applyCurrentViewFiltersToCurrentRecordFilters = () => {
|
||||
const currentView = views.find((view) => view.id === currentViewId);
|
||||
|
||||
if (isDefined(currentView)) {
|
||||
setCurrentRecordFilters(
|
||||
mapViewFiltersToFilters(
|
||||
currentView.viewFilters,
|
||||
availableFilterDefinitions,
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
applyCurrentViewFiltersToCurrentRecordFilters,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,31 @@
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
|
||||
export const useApplyViewFiltersToCurrentRecordFilters = () => {
|
||||
const setCurrentRecordFilters = useSetRecoilComponentStateV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
);
|
||||
|
||||
const applyViewFiltersToCurrentRecordFilters = (
|
||||
viewFilters: ViewFilter[],
|
||||
) => {
|
||||
const recordFiltersToApply = mapViewFiltersToFilters(
|
||||
viewFilters,
|
||||
availableFilterDefinitions,
|
||||
);
|
||||
|
||||
setCurrentRecordFilters(recordFiltersToApply);
|
||||
};
|
||||
|
||||
return {
|
||||
applyViewFiltersToCurrentRecordFilters,
|
||||
};
|
||||
};
|
||||
@ -9,6 +9,7 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
|
||||
import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect';
|
||||
import { RecordIndexContainerContextStoreObjectMetadataEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreObjectMetadataEffect';
|
||||
@ -81,28 +82,32 @@ export const RecordIndexPage = () => {
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
|
||||
}}
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
|
||||
}}
|
||||
>
|
||||
<PageTitle title={`${capitalize(objectNamePlural)}`} />
|
||||
<RecordIndexPageHeader />
|
||||
<PageBody>
|
||||
<StyledIndexContainer>
|
||||
<RecordIndexContainerContextStoreObjectMetadataEffect />
|
||||
<RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect />
|
||||
<MainContextStoreComponentInstanceIdSetterEffect />
|
||||
<RecordIndexContainer />
|
||||
</StyledIndexContainer>
|
||||
</PageBody>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
|
||||
}}
|
||||
>
|
||||
<PageTitle title={`${capitalize(objectNamePlural)}`} />
|
||||
<RecordIndexPageHeader />
|
||||
<PageBody>
|
||||
<StyledIndexContainer>
|
||||
<RecordIndexContainerContextStoreObjectMetadataEffect />
|
||||
<RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect />
|
||||
<MainContextStoreComponentInstanceIdSetterEffect />
|
||||
<RecordIndexContainer />
|
||||
</StyledIndexContainer>
|
||||
</PageBody>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</RecordIndexContextProvider>
|
||||
</PageContainer>
|
||||
|
||||
@ -5,6 +5,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
|
||||
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||
@ -46,62 +47,68 @@ export const RecordShowPage = () => {
|
||||
|
||||
return (
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
||||
>
|
||||
<RecordValueSetterEffect recordId={objectRecordId} />
|
||||
<PageContainer>
|
||||
<PageTitle title={pageTitle} />
|
||||
<RecordShowPageHeader
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
headerIcon={headerIcon}
|
||||
>
|
||||
<>
|
||||
{!isCommandMenuV2Enabled &&
|
||||
objectNameSingular === CoreObjectNameSingular.Workflow && (
|
||||
<RecordShowPageWorkflowHeader workflowId={objectRecordId} />
|
||||
)}
|
||||
{!isCommandMenuV2Enabled &&
|
||||
objectNameSingular ===
|
||||
CoreObjectNameSingular.WorkflowVersion && (
|
||||
<RecordShowPageWorkflowVersionHeader
|
||||
workflowVersionId={objectRecordId}
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
||||
>
|
||||
<RecordValueSetterEffect recordId={objectRecordId} />
|
||||
<PageContainer>
|
||||
<PageTitle title={pageTitle} />
|
||||
<RecordShowPageHeader
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
headerIcon={headerIcon}
|
||||
>
|
||||
<>
|
||||
{!isCommandMenuV2Enabled &&
|
||||
objectNameSingular === CoreObjectNameSingular.Workflow && (
|
||||
<RecordShowPageWorkflowHeader
|
||||
workflowId={objectRecordId}
|
||||
/>
|
||||
)}
|
||||
{!isCommandMenuV2Enabled &&
|
||||
objectNameSingular ===
|
||||
CoreObjectNameSingular.WorkflowVersion && (
|
||||
<RecordShowPageWorkflowVersionHeader
|
||||
workflowVersionId={objectRecordId}
|
||||
/>
|
||||
)}
|
||||
{(isCommandMenuV2Enabled ||
|
||||
(objectNameSingular !== CoreObjectNameSingular.Workflow &&
|
||||
objectNameSingular !==
|
||||
CoreObjectNameSingular.WorkflowVersion)) && (
|
||||
<RecordShowActionMenu
|
||||
{...{
|
||||
isFavorite,
|
||||
record,
|
||||
handleFavoriteButtonClick,
|
||||
objectMetadataItem,
|
||||
objectNameSingular,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(isCommandMenuV2Enabled ||
|
||||
(objectNameSingular !== CoreObjectNameSingular.Workflow &&
|
||||
objectNameSingular !==
|
||||
CoreObjectNameSingular.WorkflowVersion)) && (
|
||||
<RecordShowActionMenu
|
||||
{...{
|
||||
isFavorite,
|
||||
record,
|
||||
handleFavoriteButtonClick,
|
||||
objectMetadataItem,
|
||||
objectNameSingular,
|
||||
}}
|
||||
</>
|
||||
</RecordShowPageHeader>
|
||||
<PageBody>
|
||||
<TimelineActivityContext.Provider
|
||||
value={{ labelIdentifierValue: pageName }}
|
||||
>
|
||||
<RecordShowContainer
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</RecordShowPageHeader>
|
||||
<PageBody>
|
||||
<TimelineActivityContext.Provider
|
||||
value={{ labelIdentifierValue: pageName }}
|
||||
>
|
||||
<RecordShowContainer
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
loading={loading}
|
||||
/>
|
||||
</TimelineActivityContext.Provider>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</TimelineActivityContext.Provider>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -22,6 +22,7 @@ import { mockedApolloClient } from '~/testing/mockedApolloClient';
|
||||
|
||||
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
|
||||
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider';
|
||||
import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect';
|
||||
import { i18n } from '@lingui/core';
|
||||
@ -88,7 +89,13 @@ const Providers = () => {
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<IconsProvider>
|
||||
<PrefetchDataProvider>
|
||||
<Outlet />
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: 'storybook-test-record-filters',
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</PrefetchDataProvider>
|
||||
</IconsProvider>
|
||||
</SnackBarProviderScope>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { MockedResponse } from '@apollo/client/testing';
|
||||
import { ReactNode } from 'react';
|
||||
import { MutableSnapshot } from 'recoil';
|
||||
@ -33,25 +34,33 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({
|
||||
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<Wrapper>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: componentInstanceId }}
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: componentInstanceId,
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: componentInstanceId }}
|
||||
>
|
||||
<JestContextStoreSetter
|
||||
contextStoreTargetedRecordsRule={contextStoreTargetedRecordsRule}
|
||||
contextStoreNumberOfSelectedRecords={
|
||||
contextStoreNumberOfSelectedRecords
|
||||
}
|
||||
contextStoreCurrentObjectMetadataNameSingular={
|
||||
contextStoreCurrentObjectMetadataNameSingular
|
||||
}
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: componentInstanceId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JestContextStoreSetter>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
<JestContextStoreSetter
|
||||
contextStoreTargetedRecordsRule={contextStoreTargetedRecordsRule}
|
||||
contextStoreNumberOfSelectedRecords={
|
||||
contextStoreNumberOfSelectedRecords
|
||||
}
|
||||
contextStoreCurrentObjectMetadataNameSingular={
|
||||
contextStoreCurrentObjectMetadataNameSingular
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</JestContextStoreSetter>
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,14 +2,16 @@ import { MockedProvider, MockedResponse } from '@apollo/client/testing';
|
||||
import { ReactNode } from 'react';
|
||||
import { MutableSnapshot, RecoilRoot } from 'recoil';
|
||||
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
|
||||
export const getJestMetadataAndApolloMocksWrapper = ({
|
||||
apolloMocks,
|
||||
onInitializeRecoilSnapshot,
|
||||
}: {
|
||||
apolloMocks:
|
||||
apolloMocks?:
|
||||
| readonly MockedResponse<Record<string, any>, Record<string, any>>[]
|
||||
| undefined;
|
||||
onInitializeRecoilSnapshot?: (snapshot: MutableSnapshot) => void;
|
||||
@ -18,9 +20,17 @@ export const getJestMetadataAndApolloMocksWrapper = ({
|
||||
<RecoilRoot initializeState={onInitializeRecoilSnapshot}>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
||||
<JestObjectMetadataItemSetter>
|
||||
{children}
|
||||
</JestObjectMetadataItemSetter>
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'instanceId' }}
|
||||
>
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'instanceId' }}
|
||||
>
|
||||
<JestObjectMetadataItemSetter>
|
||||
{children}
|
||||
</JestObjectMetadataItemSetter>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</MockedProvider>
|
||||
</SnackBarProviderScope>
|
||||
</RecoilRoot>
|
||||
|
||||
Reference in New Issue
Block a user