Add any field filter requests (#13336)
This PR adds any field filter request generation utils with its unit test. It also calls this new util in the relevant requests for table and board. This PR also adds a new corresponding state in context store so that the filter is handled in command menu and actions. We also add this new filter to the aggregate queries. The RecordShowPage story was also fixed.
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
|
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
|
||||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
@ -43,6 +44,10 @@ export const DeleteMultipleRecordsAction = () => {
|
|||||||
contextStoreFiltersComponentState,
|
contextStoreFiltersComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const { filterValueDependencies } = useFilterValueDependencies();
|
const { filterValueDependencies } = useFilterValueDependencies();
|
||||||
|
|
||||||
const graphqlFilter = computeContextStoreFilters(
|
const graphqlFilter = computeContextStoreFilters(
|
||||||
@ -50,6 +55,7 @@ export const DeleteMultipleRecordsAction = () => {
|
|||||||
contextStoreFilters,
|
contextStoreFilters,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
filterValueDependencies,
|
filterValueDependencies,
|
||||||
|
contextStoreAnyFieldFilterValue,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
|
const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
|
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
|
||||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
@ -43,6 +44,10 @@ export const DestroyMultipleRecordsAction = () => {
|
|||||||
contextStoreFiltersComponentState,
|
contextStoreFiltersComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const { filterValueDependencies } = useFilterValueDependencies();
|
const { filterValueDependencies } = useFilterValueDependencies();
|
||||||
|
|
||||||
const deletedAtFilter: RecordGqlOperationFilter = {
|
const deletedAtFilter: RecordGqlOperationFilter = {
|
||||||
@ -54,6 +59,7 @@ export const DestroyMultipleRecordsAction = () => {
|
|||||||
contextStoreFilters,
|
contextStoreFilters,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
filterValueDependencies,
|
filterValueDependencies,
|
||||||
|
contextStoreAnyFieldFilterValue,
|
||||||
),
|
),
|
||||||
...deletedAtFilter,
|
...deletedAtFilter,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
|
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
|
||||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
@ -43,6 +44,10 @@ export const RestoreMultipleRecordsAction = () => {
|
|||||||
contextStoreFiltersComponentState,
|
contextStoreFiltersComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const { filterValueDependencies } = useFilterValueDependencies();
|
const { filterValueDependencies } = useFilterValueDependencies();
|
||||||
|
|
||||||
const deletedAtFilter: RecordGqlOperationFilter = {
|
const deletedAtFilter: RecordGqlOperationFilter = {
|
||||||
@ -55,6 +60,7 @@ export const RestoreMultipleRecordsAction = () => {
|
|||||||
contextStoreFilters,
|
contextStoreFilters,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
filterValueDependencies,
|
filterValueDependencies,
|
||||||
|
contextStoreAnyFieldFilterValue,
|
||||||
),
|
),
|
||||||
...deletedAtFilter,
|
...deletedAtFilter,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/cons
|
|||||||
import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
|
import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
|
||||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
@ -80,6 +81,12 @@ describe('useSetGlobalCommandMenuContext', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const anyFieldFilterValue = useRecoilValue(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||||
|
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const currentViewType = useRecoilValue(
|
const currentViewType = useRecoilValue(
|
||||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||||
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||||
@ -100,6 +107,7 @@ describe('useSetGlobalCommandMenuContext', () => {
|
|||||||
currentViewType,
|
currentViewType,
|
||||||
commandMenuPageInfo,
|
commandMenuPageInfo,
|
||||||
hasUserSelectedCommand,
|
hasUserSelectedCommand,
|
||||||
|
anyFieldFilterValue,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -113,6 +121,7 @@ describe('useSetGlobalCommandMenuContext', () => {
|
|||||||
});
|
});
|
||||||
expect(result.current.numberOfSelectedRecords).toBe(2);
|
expect(result.current.numberOfSelectedRecords).toBe(2);
|
||||||
expect(result.current.filters).toEqual([]);
|
expect(result.current.filters).toEqual([]);
|
||||||
|
expect(result.current.anyFieldFilterValue).toEqual('');
|
||||||
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
|
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
|
||||||
expect(result.current.commandMenuPageInfo).toEqual({
|
expect(result.current.commandMenuPageInfo).toEqual({
|
||||||
title: undefined,
|
title: undefined,
|
||||||
@ -131,6 +140,7 @@ describe('useSetGlobalCommandMenuContext', () => {
|
|||||||
});
|
});
|
||||||
expect(result.current.numberOfSelectedRecords).toBe(0);
|
expect(result.current.numberOfSelectedRecords).toBe(0);
|
||||||
expect(result.current.filters).toEqual([]);
|
expect(result.current.filters).toEqual([]);
|
||||||
|
expect(result.current.anyFieldFilterValue).toEqual('');
|
||||||
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
|
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
|
||||||
expect(result.current.commandMenuPageInfo).toEqual({
|
expect(result.current.commandMenuPageInfo).toEqual({
|
||||||
title: undefined,
|
title: undefined,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||||
@ -76,6 +77,21 @@ export const useCopyContextStoreStates = () => {
|
|||||||
contextStoreFilters,
|
contextStoreFilters,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contextStoreAnyFieldFilterValue = snapshot
|
||||||
|
.getLoadable(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||||
|
instanceId: instanceIdToCopyFrom,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
set(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||||
|
instanceId: instanceIdToCopyTo,
|
||||||
|
}),
|
||||||
|
contextStoreAnyFieldFilterValue,
|
||||||
|
);
|
||||||
|
|
||||||
const contextStoreCurrentViewId = snapshot
|
const contextStoreCurrentViewId = snapshot
|
||||||
.getLoadable(
|
.getLoadable(
|
||||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
@ -39,6 +40,13 @@ export const useResetContextStoreStates = () => {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
set(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||||
|
instanceId,
|
||||||
|
}),
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
set(
|
set(
|
||||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||||
instanceId,
|
instanceId,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/cons
|
|||||||
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
|
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
|
||||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
@ -45,6 +46,13 @@ export const useSetGlobalCommandMenuContext = () => {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
set(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||||
|
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||||
|
}),
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
set(
|
set(
|
||||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||||
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
@ -36,6 +37,11 @@ export const useFindManyRecordsSelectedInContextStore = ({
|
|||||||
instanceId,
|
instanceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState,
|
||||||
|
instanceId,
|
||||||
|
);
|
||||||
|
|
||||||
const { filterValueDependencies } = useFilterValueDependencies();
|
const { filterValueDependencies } = useFilterValueDependencies();
|
||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
@ -58,9 +64,9 @@ export const useFindManyRecordsSelectedInContextStore = ({
|
|||||||
const queryFilter = computeContextStoreFilters(
|
const queryFilter = computeContextStoreFilters(
|
||||||
contextStoreTargetedRecordsRule,
|
contextStoreTargetedRecordsRule,
|
||||||
contextStoreFilters,
|
contextStoreFilters,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
objectMetadataItem,
|
||||||
objectMetadataItem!,
|
|
||||||
filterValueDependencies,
|
filterValueDependencies,
|
||||||
|
contextStoreAnyFieldFilterValue,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { records, loading, totalCount } = useFindManyRecords({
|
const { records, loading, totalCount } = useFindManyRecords({
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const contextStoreAnyFieldFilterValueComponentState =
|
||||||
|
createComponentStateV2<string>({
|
||||||
|
key: 'contextStoreAnyFieldFilterValueComponentState',
|
||||||
|
defaultValue: '',
|
||||||
|
componentInstanceContext: ContextStoreComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -26,10 +26,12 @@ describe('computeContextStoreFilters', () => {
|
|||||||
[],
|
[],
|
||||||
personObjectMetadataItem,
|
personObjectMetadataItem,
|
||||||
mockFilterValueDependencies,
|
mockFilterValueDependencies,
|
||||||
|
'',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(filters).toEqual({
|
expect(filters).toEqual({
|
||||||
and: [
|
and: [
|
||||||
|
{},
|
||||||
{
|
{
|
||||||
id: {
|
id: {
|
||||||
in: ['1', '2', '3'],
|
in: ['1', '2', '3'],
|
||||||
@ -67,10 +69,12 @@ describe('computeContextStoreFilters', () => {
|
|||||||
contextStoreFilters,
|
contextStoreFilters,
|
||||||
personObjectMetadataItem,
|
personObjectMetadataItem,
|
||||||
mockFilterValueDependencies,
|
mockFilterValueDependencies,
|
||||||
|
'',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(filters).toEqual({
|
expect(filters).toEqual({
|
||||||
and: [
|
and: [
|
||||||
|
{},
|
||||||
{
|
{
|
||||||
or: [
|
or: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGq
|
|||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
|
import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||||
|
|
||||||
export const computeContextStoreFilters = (
|
export const computeContextStoreFilters = (
|
||||||
@ -11,11 +12,19 @@ export const computeContextStoreFilters = (
|
|||||||
contextStoreFilters: RecordFilter[],
|
contextStoreFilters: RecordFilter[],
|
||||||
objectMetadataItem: ObjectMetadataItem,
|
objectMetadataItem: ObjectMetadataItem,
|
||||||
filterValueDependencies: RecordFilterValueDependencies,
|
filterValueDependencies: RecordFilterValueDependencies,
|
||||||
|
anyFieldFilterValue: string,
|
||||||
) => {
|
) => {
|
||||||
let queryFilter: RecordGqlOperationFilter | undefined;
|
let queryFilter: RecordGqlOperationFilter | undefined;
|
||||||
|
|
||||||
|
const { recordGqlOperationFilter: recordGqlFilterForAnyFieldFilter } =
|
||||||
|
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue: anyFieldFilterValue,
|
||||||
|
objectMetadataItem,
|
||||||
|
});
|
||||||
|
|
||||||
if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
|
if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
|
||||||
queryFilter = makeAndFilterVariables([
|
queryFilter = makeAndFilterVariables([
|
||||||
|
recordGqlFilterForAnyFieldFilter,
|
||||||
computeRecordGqlOperationFilter({
|
computeRecordGqlOperationFilter({
|
||||||
filterValueDependencies,
|
filterValueDependencies,
|
||||||
fields: objectMetadataItem?.fields ?? [],
|
fields: objectMetadataItem?.fields ?? [],
|
||||||
@ -35,6 +44,7 @@ export const computeContextStoreFilters = (
|
|||||||
}
|
}
|
||||||
if (contextStoreTargetedRecordsRule.mode === 'selection') {
|
if (contextStoreTargetedRecordsRule.mode === 'selection') {
|
||||||
queryFilter = makeAndFilterVariables([
|
queryFilter = makeAndFilterVariables([
|
||||||
|
recordGqlFilterForAnyFieldFilter,
|
||||||
contextStoreTargetedRecordsRule.selectedRecordIds.length > 0
|
contextStoreTargetedRecordsRule.selectedRecordIds.length > 0
|
||||||
? {
|
? {
|
||||||
id: {
|
id: {
|
||||||
|
|||||||
@ -1,25 +1,25 @@
|
|||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
|
||||||
export const ObjectFilterDropdownAnyFieldSearchInput = () => {
|
export const ObjectFilterDropdownAnyFieldSearchInput = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const [viewAnyFieldSearchValue, setViewAnyFieldSearchValue] =
|
const [anyFieldFilterSearchValue, setAnyFieldFilterSearchValue] =
|
||||||
useRecoilComponentStateV2(viewAnyFieldSearchValueComponentState);
|
useRecoilComponentStateV2(anyFieldFilterValueComponentState);
|
||||||
|
|
||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const inputValue = event.target.value;
|
const inputValue = event.target.value;
|
||||||
|
|
||||||
setViewAnyFieldSearchValue(inputValue);
|
setAnyFieldFilterSearchValue(inputValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuSearchInput
|
<DropdownMenuSearchInput
|
||||||
autoFocus
|
autoFocus
|
||||||
type="text"
|
type="text"
|
||||||
value={viewAnyFieldSearchValue}
|
value={anyFieldFilterSearchValue}
|
||||||
placeholder={t`Search any field`}
|
placeholder={t`Search any field`}
|
||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -30,8 +30,8 @@ export const useActorFieldDisplay = (): ActorFieldDisplayValue | undefined => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const relatedWorkspaceMember = [
|
const relatedWorkspaceMember = [
|
||||||
...currentWorkspaceDeletedMembers,
|
...(currentWorkspaceDeletedMembers ?? []),
|
||||||
...currentWorkspaceMembers,
|
...(currentWorkspaceMembers ?? []),
|
||||||
].find(
|
].find(
|
||||||
(workspaceMember) => workspaceMember.id === fieldValue.workspaceMemberId,
|
(workspaceMember) => workspaceMember.id === fieldValue.workspaceMemberId,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const anyFieldFilterValueComponentState = createComponentStateV2<string>(
|
||||||
|
{
|
||||||
|
key: 'anyFieldFilterValueComponentState',
|
||||||
|
defaultValue: '',
|
||||||
|
componentInstanceContext: RecordFiltersComponentInstanceContext,
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -0,0 +1,603 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { filterSelectOptionsOfFieldMetadataItem } from '@/object-record/record-filter/utils/filterSelectOptionsOfFieldMetadataItem';
|
||||||
|
import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
const baseFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
id: 'base-field-metadata-item-id',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
label: 'Test',
|
||||||
|
name: 'test',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const textFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'text-field-id',
|
||||||
|
name: 'textField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const addressFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'address-field-id',
|
||||||
|
type: FieldMetadataType.ADDRESS,
|
||||||
|
name: 'addressField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const linksFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'links-field-id',
|
||||||
|
type: FieldMetadataType.LINKS,
|
||||||
|
name: 'linksField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fullNameFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'full-name-field-id',
|
||||||
|
type: FieldMetadataType.FULL_NAME,
|
||||||
|
name: 'fullNameField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrayFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'array-field-id',
|
||||||
|
type: FieldMetadataType.ARRAY,
|
||||||
|
name: 'arrayField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const emailsFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'emails-field-id',
|
||||||
|
type: FieldMetadataType.EMAILS,
|
||||||
|
name: 'emailsField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const phonesFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'phones-field-id',
|
||||||
|
type: FieldMetadataType.PHONES,
|
||||||
|
name: 'phonesField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const numberFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'number-field-id',
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
name: 'numberField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const currencyFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'currency-field-id',
|
||||||
|
type: FieldMetadataType.CURRENCY,
|
||||||
|
name: 'currencyField',
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'select-field-id',
|
||||||
|
type: FieldMetadataType.SELECT,
|
||||||
|
name: 'selectField',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
color: 'blue',
|
||||||
|
id: '1',
|
||||||
|
label: 'blue',
|
||||||
|
position: 1,
|
||||||
|
value: 'BLUE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
id: '2',
|
||||||
|
label: 'red',
|
||||||
|
position: 2,
|
||||||
|
value: 'RED',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const multiSelectFieldMetadataItem: FieldMetadataItem = {
|
||||||
|
...baseFieldMetadataItem,
|
||||||
|
id: 'multi-select-field-id',
|
||||||
|
type: FieldMetadataType.MULTI_SELECT,
|
||||||
|
name: 'multiSelect',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
color: 'blue',
|
||||||
|
id: '1',
|
||||||
|
label: 'blue',
|
||||||
|
position: 1,
|
||||||
|
value: 'BLUE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
id: '2',
|
||||||
|
label: 'red',
|
||||||
|
position: 2,
|
||||||
|
value: 'RED',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||||
|
id: 'mock-object-metadata-item',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
indexMetadatas: [],
|
||||||
|
isActive: true,
|
||||||
|
isCustom: true,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
isRemote: false,
|
||||||
|
isSearchable: true,
|
||||||
|
isSystem: false,
|
||||||
|
labelIdentifierFieldMetadataId: 'mock-id',
|
||||||
|
labelPlural: 'Tests',
|
||||||
|
labelSingular: 'Test',
|
||||||
|
nameSingular: 'test',
|
||||||
|
namePlural: 'tests',
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockObjectMetadataItemWithAllFields: ObjectMetadataItem = {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [
|
||||||
|
textFieldMetadataItem,
|
||||||
|
addressFieldMetadataItem,
|
||||||
|
linksFieldMetadataItem,
|
||||||
|
fullNameFieldMetadataItem,
|
||||||
|
arrayFieldMetadataItem,
|
||||||
|
emailsFieldMetadataItem,
|
||||||
|
phonesFieldMetadataItem,
|
||||||
|
numberFieldMetadataItem,
|
||||||
|
currencyFieldMetadataItem,
|
||||||
|
selectFieldMetadataItem,
|
||||||
|
multiSelectFieldMetadataItem,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('turnAnyFieldFilterIntoRecordGqlFilter', () => {
|
||||||
|
describe('TEXT field type', () => {
|
||||||
|
it('should generate correct filter for text field', () => {
|
||||||
|
const filterValue = 'test';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [textFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
[textFieldMetadataItem.name]: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ADDRESS field type', () => {
|
||||||
|
it('should generate correct filter for address field', () => {
|
||||||
|
const filterValue = 'New York';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [addressFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[addressFieldMetadataItem.name]: {
|
||||||
|
addressStreet1: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[addressFieldMetadataItem.name]: {
|
||||||
|
addressStreet2: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[addressFieldMetadataItem.name]: {
|
||||||
|
addressCity: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[addressFieldMetadataItem.name]: {
|
||||||
|
addressState: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[addressFieldMetadataItem.name]: {
|
||||||
|
addressCountry: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[addressFieldMetadataItem.name]: {
|
||||||
|
addressPostcode: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('LINKS field type', () => {
|
||||||
|
it('should generate correct filter for links field', () => {
|
||||||
|
const filterValue = 'test';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [linksFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[linksFieldMetadataItem.name]: {
|
||||||
|
primaryLinkUrl: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[linksFieldMetadataItem.name]: {
|
||||||
|
primaryLinkLabel: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[linksFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: {
|
||||||
|
like: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('FULL_NAME field type', () => {
|
||||||
|
it('should generate correct filter for full name field', () => {
|
||||||
|
const filterValue = 'test';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [fullNameFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[fullNameFieldMetadataItem.name]: {
|
||||||
|
firstName: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[fullNameFieldMetadataItem.name]: {
|
||||||
|
lastName: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ARRAY field type', () => {
|
||||||
|
it('should generate correct filter for array field', () => {
|
||||||
|
const filterValue = 'test';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [arrayFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
[arrayFieldMetadataItem.name]: {
|
||||||
|
containsIlike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EMAILS field type', () => {
|
||||||
|
it('should generate correct filter for emails field', () => {
|
||||||
|
const filterValue = 'test';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [emailsFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[emailsFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[emailsFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PHONES field type', () => {
|
||||||
|
it('should generate correct filter for phones field', () => {
|
||||||
|
const filterValue = '123';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [phonesFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[phonesFieldMetadataItem.name]: {
|
||||||
|
primaryPhoneNumber: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[phonesFieldMetadataItem.name]: {
|
||||||
|
primaryPhoneCallingCode: {
|
||||||
|
ilike: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[phonesFieldMetadataItem.name]: {
|
||||||
|
additionalPhones: {
|
||||||
|
like: `%${filterValue}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('NUMBER field type', () => {
|
||||||
|
it('should generate correct filter for number field with numeric value', () => {
|
||||||
|
const filterValue = '123.1';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [numberFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
[numberFieldMetadataItem.name]: {
|
||||||
|
eq: 123.1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not generate filter for number field with non-numeric value', () => {
|
||||||
|
const filterValue = 'not a number';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [numberFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CURRENCY field type', () => {
|
||||||
|
it('should generate correct filter for currency field with numeric value', () => {
|
||||||
|
const filterValue = '123';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [currencyFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
[currencyFieldMetadataItem.name]: {
|
||||||
|
amountMicros: {
|
||||||
|
eq: 123000000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate correct filter for currency field with currency code', () => {
|
||||||
|
const filterValue = 'USD';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [currencyFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(result.recordGqlOperationFilter.or as any)?.[0][
|
||||||
|
currencyFieldMetadataItem.name
|
||||||
|
].currencyCode.in.includes(filterValue),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SELECT field type', () => {
|
||||||
|
it('should generate correct filter for select field with matching option', () => {
|
||||||
|
const filterValue = 'r';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [selectFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { foundCorrespondingSelectOptions: expectedOptions } =
|
||||||
|
filterSelectOptionsOfFieldMetadataItem({
|
||||||
|
fieldMetadataItem: selectFieldMetadataItem,
|
||||||
|
filterValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
[selectFieldMetadataItem.name]: {
|
||||||
|
in: expectedOptions?.map((option) => option.value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not generate filter for select field with non-matching value', () => {
|
||||||
|
const filterValue = 'not-found';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [selectFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MULTI_SELECT field type', () => {
|
||||||
|
it('should generate correct filter for multi-select field with matching option', () => {
|
||||||
|
const filterValue = 'r';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [multiSelectFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { foundCorrespondingSelectOptions: expectedOptions } =
|
||||||
|
filterSelectOptionsOfFieldMetadataItem({
|
||||||
|
fieldMetadataItem: multiSelectFieldMetadataItem,
|
||||||
|
filterValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter.or).toContainEqual({
|
||||||
|
[multiSelectFieldMetadataItem.name]: {
|
||||||
|
containsAny: expectedOptions?.map((option) => option.value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not generate filter for multi-select field with non-matching value', () => {
|
||||||
|
const filterValue = 'not-found';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [multiSelectFieldMetadataItem],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('combined field filters', () => {
|
||||||
|
it('should generate OR filter combining all matching field types', () => {
|
||||||
|
const filterValue = 'a';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: mockObjectMetadataItemWithAllFields,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter).toHaveProperty('or');
|
||||||
|
expect(Array.isArray(result.recordGqlOperationFilter.or)).toBe(true);
|
||||||
|
expect(
|
||||||
|
(result.recordGqlOperationFilter.or as any[])?.length,
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty filter value', () => {
|
||||||
|
const filterValue = '';
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem: mockObjectMetadataItemWithAllFields,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle object with no fields', () => {
|
||||||
|
const emptyObjectMetadata = {
|
||||||
|
...mockObjectMetadataItem,
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
filterValue: 'test',
|
||||||
|
objectMetadataItem: emptyObjectMetadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.recordGqlOperationFilter).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -303,6 +303,12 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
|
|||||||
lte: parseFloat(recordFilter.value),
|
lte: parseFloat(recordFilter.value),
|
||||||
} as FloatFilter,
|
} as FloatFilter,
|
||||||
};
|
};
|
||||||
|
case RecordFilterOperand.Is:
|
||||||
|
return {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
eq: parseFloat(recordFilter.value),
|
||||||
|
} as FloatFilter,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown operand ${recordFilter.operand} for ${filterType} filter`,
|
`Unknown operand ${recordFilter.operand} for ${filterType} filter`,
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export const createAnyFieldRecordFilterBaseProperties = ({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}: {
|
||||||
|
filterValue: string;
|
||||||
|
fieldMetadataItem: FieldMetadataItem;
|
||||||
|
}): Pick<
|
||||||
|
RecordFilter,
|
||||||
|
'id' | 'value' | 'displayValue' | 'label' | 'fieldMetadataId'
|
||||||
|
> => {
|
||||||
|
return {
|
||||||
|
id: v4(),
|
||||||
|
value: filterValue,
|
||||||
|
displayValue: '',
|
||||||
|
label: '',
|
||||||
|
fieldMetadataId: fieldMetadataItem.id,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
|
||||||
|
export const filterSelectOptionsOfFieldMetadataItem = ({
|
||||||
|
fieldMetadataItem,
|
||||||
|
filterValue,
|
||||||
|
}: {
|
||||||
|
fieldMetadataItem: FieldMetadataItem;
|
||||||
|
filterValue: string;
|
||||||
|
}) => {
|
||||||
|
const selectOptions = fieldMetadataItem.options;
|
||||||
|
|
||||||
|
const foundCorrespondingSelectOptions = selectOptions?.filter(
|
||||||
|
(selectOption) =>
|
||||||
|
selectOption.value
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(filterValue.toLocaleLowerCase()) ||
|
||||||
|
selectOption.label
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(filterValue.toLocaleLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
foundCorrespondingSelectOptions,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,236 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||||
|
import { turnRecordFilterIntoRecordGqlOperationFilter } from '@/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter';
|
||||||
|
import { createAnyFieldRecordFilterBaseProperties } from '@/object-record/record-filter/utils/createAnyFieldRecordFilterBaseProperties';
|
||||||
|
import { filterSelectOptionsOfFieldMetadataItem } from '@/object-record/record-filter/utils/filterSelectOptionsOfFieldMetadataItem';
|
||||||
|
import { CURRENCIES } from '@/settings/data-model/constants/Currencies';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
||||||
|
|
||||||
|
export const turnAnyFieldFilterIntoRecordGqlFilter = ({
|
||||||
|
filterValue,
|
||||||
|
objectMetadataItem,
|
||||||
|
}: {
|
||||||
|
filterValue: string;
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
}) => {
|
||||||
|
const fieldMetadataItems = objectMetadataItem.fields;
|
||||||
|
|
||||||
|
const anyFieldRecordFilters: RecordFilter[] = [];
|
||||||
|
|
||||||
|
const isFilterValueANumber = z.coerce.number().safeParse(filterValue).success;
|
||||||
|
|
||||||
|
for (const fieldMetadataItem of fieldMetadataItems) {
|
||||||
|
switch (fieldMetadataItem.type) {
|
||||||
|
case FieldMetadataType.TEXT: {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Contains,
|
||||||
|
type: 'TEXT',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.ADDRESS: {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Contains,
|
||||||
|
type: 'ADDRESS',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.LINKS: {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Contains,
|
||||||
|
type: 'LINKS',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.FULL_NAME: {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Contains,
|
||||||
|
type: 'FULL_NAME',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.ARRAY: {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Contains,
|
||||||
|
type: 'ARRAY',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.EMAILS: {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Contains,
|
||||||
|
type: 'EMAILS',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.PHONES: {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Contains,
|
||||||
|
type: 'PHONES',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.NUMBER: {
|
||||||
|
if (isFilterValueANumber) {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Is,
|
||||||
|
type: 'NUMBER',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.CURRENCY: {
|
||||||
|
if (isFilterValueANumber) {
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Is,
|
||||||
|
type: 'CURRENCY',
|
||||||
|
subFieldName: 'amountMicros',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNonEmptyString(filterValue)) {
|
||||||
|
const foundCorrespondingCurrencies = CURRENCIES.filter(
|
||||||
|
(currency) =>
|
||||||
|
currency.label.includes(filterValue) ||
|
||||||
|
currency.value.includes(filterValue),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isNonEmptyArray(foundCorrespondingCurrencies)) {
|
||||||
|
const arrayOfCurrenciesStringified = JSON.stringify(
|
||||||
|
foundCorrespondingCurrencies.map((currency) => currency.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
filterValue: arrayOfCurrenciesStringified,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Is,
|
||||||
|
type: 'CURRENCY',
|
||||||
|
subFieldName: 'currencyCode',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.SELECT: {
|
||||||
|
if (isNonEmptyString(filterValue)) {
|
||||||
|
const { foundCorrespondingSelectOptions } =
|
||||||
|
filterSelectOptionsOfFieldMetadataItem({
|
||||||
|
fieldMetadataItem,
|
||||||
|
filterValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isNonEmptyArray(foundCorrespondingSelectOptions)) {
|
||||||
|
const arrayOfSelectValues = JSON.stringify(
|
||||||
|
foundCorrespondingSelectOptions.map(
|
||||||
|
(selectOption) => selectOption.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
fieldMetadataItem,
|
||||||
|
filterValue: arrayOfSelectValues,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Is,
|
||||||
|
type: 'SELECT',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.MULTI_SELECT: {
|
||||||
|
if (isNonEmptyString(filterValue)) {
|
||||||
|
const { foundCorrespondingSelectOptions } =
|
||||||
|
filterSelectOptionsOfFieldMetadataItem({
|
||||||
|
fieldMetadataItem,
|
||||||
|
filterValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isNonEmptyArray(foundCorrespondingSelectOptions)) {
|
||||||
|
const arrayOfSelectValues = JSON.stringify(
|
||||||
|
foundCorrespondingSelectOptions.map(
|
||||||
|
(selectOption) => selectOption.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
anyFieldRecordFilters.push({
|
||||||
|
...createAnyFieldRecordFilterBaseProperties({
|
||||||
|
fieldMetadataItem,
|
||||||
|
filterValue: arrayOfSelectValues,
|
||||||
|
}),
|
||||||
|
operand: RecordFilterOperand.Contains,
|
||||||
|
type: 'MULTI_SELECT',
|
||||||
|
} satisfies RecordFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseRecordGqlOperationFilters = anyFieldRecordFilters
|
||||||
|
.map((recordFilter) =>
|
||||||
|
turnRecordFilterIntoRecordGqlOperationFilter({
|
||||||
|
filterValueDependencies: {},
|
||||||
|
fieldMetadataItems: objectMetadataItem.fields,
|
||||||
|
recordFilter,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.filter(isDefined);
|
||||||
|
|
||||||
|
const recordGqlOperationFilter: RecordGqlOperationFilter = {
|
||||||
|
or: baseRecordGqlOperationFilters,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (baseRecordGqlOperationFilters.length === 0) {
|
||||||
|
return { recordGqlOperationFilter: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
recordGqlOperationFilter,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
@ -40,6 +41,10 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
|||||||
contextStoreFiltersComponentState,
|
contextStoreFiltersComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const { filterValueDependencies } = useFilterValueDependencies();
|
const { filterValueDependencies } = useFilterValueDependencies();
|
||||||
|
|
||||||
const { totalCount } = useFindManyRecords({
|
const { totalCount } = useFindManyRecords({
|
||||||
@ -52,6 +57,7 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
|||||||
contextStoreFilters,
|
contextStoreFilters,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
filterValueDependencies,
|
filterValueDependencies,
|
||||||
|
contextStoreAnyFieldFilterValue,
|
||||||
),
|
),
|
||||||
limit: 1,
|
limit: 1,
|
||||||
skip: contextStoreTargetedRecordsRule.mode === 'selection',
|
skip: contextStoreTargetedRecordsRule.mode === 'selection',
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
|
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
|
||||||
@ -74,5 +76,22 @@ export const RecordIndexFiltersToContextStoreEffect = () => {
|
|||||||
};
|
};
|
||||||
}, [recordIndexFilters, setContextStoreFilters]);
|
}, [recordIndexFilters, setContextStoreFilters]);
|
||||||
|
|
||||||
|
const setContextStoreAnyFieldFilterValue = useSetRecoilComponentStateV2(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
anyFieldFilterValueComponentState,
|
||||||
|
recordIndexId,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setContextStoreAnyFieldFilterValue(anyFieldFilterValue);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setContextStoreAnyFieldFilterValue('');
|
||||||
|
};
|
||||||
|
}, [anyFieldFilterValue, setContextStoreAnyFieldFilterValue]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
|||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
|
||||||
|
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||||
@ -72,6 +73,10 @@ export const useRecordIndexLazyFetchRecords = ({
|
|||||||
contextStoreFiltersComponentState,
|
contextStoreFiltersComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
contextStoreAnyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const { filterValueDependencies } = useFilterValueDependencies();
|
const { filterValueDependencies } = useFilterValueDependencies();
|
||||||
|
|
||||||
const findManyRecordsParams = useFindManyRecordIndexTableParams(
|
const findManyRecordsParams = useFindManyRecordIndexTableParams(
|
||||||
@ -83,6 +88,7 @@ export const useRecordIndexLazyFetchRecords = ({
|
|||||||
contextStoreFilters,
|
contextStoreFilters,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
filterValueDependencies,
|
filterValueDependencies,
|
||||||
|
contextStoreAnyFieldFilterValue,
|
||||||
);
|
);
|
||||||
|
|
||||||
const finalColumns = [
|
const finalColumns = [
|
||||||
|
|||||||
@ -2,9 +2,11 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { combineFilters } from '@/object-record/record-filter/utils/combineFilters';
|
import { combineFilters } from '@/object-record/record-filter/utils/combineFilters';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
|
import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||||
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
||||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||||
@ -37,18 +39,28 @@ export const useFindManyRecordIndexTableParams = (
|
|||||||
|
|
||||||
const { filterValueDependencies } = useFilterValueDependencies();
|
const { filterValueDependencies } = useFilterValueDependencies();
|
||||||
|
|
||||||
const stateFilter = computeRecordGqlOperationFilter({
|
const currentFilters = computeRecordGqlOperationFilter({
|
||||||
fields: objectMetadataItem?.fields ?? [],
|
fields: objectMetadataItem?.fields ?? [],
|
||||||
filterValueDependencies,
|
filterValueDependencies,
|
||||||
recordFilterGroups: currentRecordFilterGroups,
|
recordFilterGroups: currentRecordFilterGroups,
|
||||||
recordFilters: currentRecordFilters,
|
recordFilters: currentRecordFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
anyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||||
|
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
objectMetadataItem,
|
||||||
|
filterValue: anyFieldFilterValue,
|
||||||
|
});
|
||||||
|
|
||||||
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
|
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
filter: combineFilters([stateFilter, recordGroupFilter]),
|
filter: combineFilters([currentFilters, recordGroupFilter, anyFieldFilter]),
|
||||||
orderBy,
|
orderBy,
|
||||||
// If we have a current record group definition, we only want to fetch 8 records by page
|
// If we have a current record group definition, we only want to fetch 8 records by page
|
||||||
...(currentRecordGroupDefinition ? { limit: 8 } : {}),
|
...(currentRecordGroupDefinition ? { limit: 8 } : {}),
|
||||||
|
|||||||
@ -15,7 +15,9 @@ import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hook
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||||
|
|
||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { combineFilters } from '@/object-record/record-filter/utils/combineFilters';
|
import { combineFilters } from '@/object-record/record-filter/utils/combineFilters';
|
||||||
|
import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -64,6 +66,16 @@ export const useLoadRecordIndexBoardColumn = ({
|
|||||||
fields: objectMetadataItem.fields,
|
fields: objectMetadataItem.fields,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
anyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||||
|
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
objectMetadataItem,
|
||||||
|
filterValue: anyFieldFilterValue,
|
||||||
|
});
|
||||||
|
|
||||||
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
|
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
|
||||||
|
|
||||||
const recordGqlFields = useRecordBoardRecordGqlFields({
|
const recordGqlFields = useRecordBoardRecordGqlFields({
|
||||||
@ -78,6 +90,7 @@ export const useLoadRecordIndexBoardColumn = ({
|
|||||||
: { is: 'NULL' };
|
: { is: 'NULL' };
|
||||||
|
|
||||||
const combinedFilters = combineFilters([
|
const combinedFilters = combineFilters([
|
||||||
|
anyFieldFilter,
|
||||||
requestFilters,
|
requestFilters,
|
||||||
{
|
{
|
||||||
[kanbanFieldMetadataItem.name]: recordIndexKanbanFieldMetadataFilterValue,
|
[kanbanFieldMetadataItem.name]: recordIndexKanbanFieldMetadataFilterValue,
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import { buildRecordGqlFieldsAggregateForView } from '@/object-record/record-boa
|
|||||||
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
|
import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { UserContext } from '@/users/contexts/UserContext';
|
import { UserContext } from '@/users/contexts/UserContext';
|
||||||
@ -54,10 +56,20 @@ export const useAggregateRecordsForHeader = ({
|
|||||||
recordIndexKanbanAggregateOperation,
|
recordIndexKanbanAggregateOperation,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
anyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||||
|
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
objectMetadataItem,
|
||||||
|
filterValue: anyFieldFilterValue,
|
||||||
|
});
|
||||||
|
|
||||||
const { data } = useAggregateRecords({
|
const { data } = useAggregateRecords({
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
recordGqlFieldsAggregate,
|
recordGqlFieldsAggregate,
|
||||||
filter: { ...requestFilters, ...additionalFilters },
|
filter: { ...requestFilters, ...additionalFilters, ...anyFieldFilter },
|
||||||
});
|
});
|
||||||
|
|
||||||
const { value, labelWithFieldName } = computeAggregateValueAndLabel({
|
const { value, labelWithFieldName } = computeAggregateValueAndLabel({
|
||||||
|
|||||||
@ -2,8 +2,10 @@ import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
|||||||
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
|
import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
@ -85,10 +87,20 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
|||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
anyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||||
|
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
objectMetadataItem,
|
||||||
|
filterValue: anyFieldFilterValue,
|
||||||
|
});
|
||||||
|
|
||||||
const { data, loading } = useAggregateRecords({
|
const { data, loading } = useAggregateRecords({
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
recordGqlFieldsAggregate,
|
recordGqlFieldsAggregate,
|
||||||
filter: { ...requestFilters, ...recordGroupFilter },
|
filter: { ...requestFilters, ...recordGroupFilter, ...anyFieldFilter },
|
||||||
skip: !isDefined(aggregateOperationForViewField),
|
skip: !isDefined(aggregateOperationForViewField),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +1,29 @@
|
|||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
|
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
|
||||||
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { IconFilter } from 'twenty-ui/display';
|
import { IconFilter } from 'twenty-ui/display';
|
||||||
|
|
||||||
export const AnyFieldSearchChip = () => {
|
export const AnyFieldSearchChip = () => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { closeDropdown } = useCloseDropdown();
|
const { closeDropdown } = useCloseDropdown();
|
||||||
|
|
||||||
const [viewAnyFieldSearchValue, setViewAnyFieldSearchValue] =
|
const [anyFieldFilterValue, setAnyFieldFilterValue] =
|
||||||
useRecoilComponentStateV2(viewAnyFieldSearchValueComponentState);
|
useRecoilComponentStateV2(anyFieldFilterValueComponentState);
|
||||||
|
|
||||||
const handleRemoveClick = () => {
|
const handleRemoveClick = () => {
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
setViewAnyFieldSearchValue('');
|
setAnyFieldFilterValue('');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SortOrFilterChip
|
<SortOrFilterChip
|
||||||
testId={ADVANCED_FILTER_DROPDOWN_ID}
|
testId={ADVANCED_FILTER_DROPDOWN_ID}
|
||||||
labelKey={'Any field :'}
|
labelKey={t`Any field :`}
|
||||||
labelValue={viewAnyFieldSearchValue}
|
labelValue={anyFieldFilterValue}
|
||||||
Icon={IconFilter}
|
Icon={IconFilter}
|
||||||
onRemove={handleRemoveClick}
|
onRemove={handleRemoveClick}
|
||||||
type="filter"
|
type="filter"
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAr
|
|||||||
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
||||||
|
|
||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { AnyFieldSearchDropdownButton } from '@/views/components/AnyFieldSearchDropdownButton';
|
import { AnyFieldSearchDropdownButton } from '@/views/components/AnyFieldSearchDropdownButton';
|
||||||
@ -29,7 +30,6 @@ import { ANY_FIELD_SEARCH_DROPDOWN_ID } from '@/views/constants/AnyFieldSearchDr
|
|||||||
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
|
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
|
||||||
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
|
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
|
||||||
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
|
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
|
||||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -123,8 +123,8 @@ export const ViewBarDetails = ({
|
|||||||
currentRecordSortsComponentState,
|
currentRecordSortsComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const viewAnyFieldSearchValue = useRecoilComponentValueV2(
|
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
viewAnyFieldSearchValueComponentState,
|
anyFieldFilterValueComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||||
@ -189,7 +189,7 @@ export const ViewBarDetails = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const shouldShowAnyFieldSearchChip =
|
const shouldShowAnyFieldSearchChip =
|
||||||
isNonEmptyString(viewAnyFieldSearchValue) || isAnyFieldSearchDropdownOpen;
|
isNonEmptyString(anyFieldFilterValue) || isAnyFieldSearchDropdownOpen;
|
||||||
|
|
||||||
const shouldExpandViewBar =
|
const shouldExpandViewBar =
|
||||||
shouldShowAnyFieldSearchChip ||
|
shouldShowAnyFieldSearchChip ||
|
||||||
|
|||||||
@ -2,8 +2,10 @@ import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/
|
|||||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
|
import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useGetViewGroupsFilters } from '@/views/hooks/useGetViewGroupsFilters';
|
import { useGetViewGroupsFilters } from '@/views/hooks/useGetViewGroupsFilters';
|
||||||
@ -30,11 +32,21 @@ export const useGetRecordIndexTotalCount = () => {
|
|||||||
fields: objectMetadataItem.fields,
|
fields: objectMetadataItem.fields,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||||
|
anyFieldFilterValueComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||||
|
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||||
|
objectMetadataItem,
|
||||||
|
filterValue: anyFieldFilterValue,
|
||||||
|
});
|
||||||
|
|
||||||
const { data, loading } = useAggregateRecords<{
|
const { data, loading } = useAggregateRecords<{
|
||||||
id: { COUNT: number };
|
id: { COUNT: number };
|
||||||
}>({
|
}>({
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
filter,
|
filter: { ...filter, ...anyFieldFilter },
|
||||||
recordGqlFieldsAggregate: {
|
recordGqlFieldsAggregate: {
|
||||||
id: [AggregateOperations.COUNT],
|
id: [AggregateOperations.COUNT],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
import { objectFilterDropdownAnyFieldSearchIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownAnyFieldSearchIsSelectedComponentState';
|
import { objectFilterDropdownAnyFieldSearchIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownAnyFieldSearchIsSelectedComponentState';
|
||||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||||
|
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||||
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
import { t } from '@lingui/core/macro';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
|
||||||
export const useOpenAnyFieldSearchFilterFromViewBar = () => {
|
export const useOpenAnyFieldSearchFilterFromViewBar = () => {
|
||||||
const setViewAnyFieldSearchValueComponentState = useSetRecoilComponentStateV2(
|
const setAnyFieldFilterValue = useSetRecoilComponentStateV2(
|
||||||
viewAnyFieldSearchValueComponentState,
|
anyFieldFilterValueComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||||
|
|
||||||
const setObjectFilterDropdownAnyFieldSearchIsSelectedComponentState =
|
const setObjectFilterDropdownAnyFieldSearchIsSelectedComponentState =
|
||||||
useSetRecoilComponentStateV2(
|
useSetRecoilComponentStateV2(
|
||||||
objectFilterDropdownAnyFieldSearchIsSelectedComponentState,
|
objectFilterDropdownAnyFieldSearchIsSelectedComponentState,
|
||||||
@ -19,14 +23,29 @@ export const useOpenAnyFieldSearchFilterFromViewBar = () => {
|
|||||||
objectFilterDropdownSearchInputComponentState,
|
objectFilterDropdownSearchInputComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const translatedLabel = t`Search any field`;
|
||||||
|
|
||||||
const openAnyFieldSearchFilterFromViewBar = () => {
|
const openAnyFieldSearchFilterFromViewBar = () => {
|
||||||
const userHasAlreadyEnteredSearchInputForObjectDropdownSearch =
|
const userHasAlreadyEnteredSearchInputForObjectDropdownSearch =
|
||||||
isNonEmptyString(objectFilterDropdownSearchInput);
|
isNonEmptyString(objectFilterDropdownSearchInput);
|
||||||
|
|
||||||
if (userHasAlreadyEnteredSearchInputForObjectDropdownSearch) {
|
const userInputIsMatchingAListMenuItem =
|
||||||
|
objectMetadataItem.fields.some((fieldMetadataItem) =>
|
||||||
|
fieldMetadataItem.label
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(objectFilterDropdownSearchInput.toLocaleLowerCase()),
|
||||||
|
) ||
|
||||||
|
translatedLabel
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(objectFilterDropdownSearchInput.toLocaleLowerCase());
|
||||||
|
|
||||||
|
if (
|
||||||
|
userHasAlreadyEnteredSearchInputForObjectDropdownSearch &&
|
||||||
|
!userInputIsMatchingAListMenuItem
|
||||||
|
) {
|
||||||
const filterValue = objectFilterDropdownSearchInput;
|
const filterValue = objectFilterDropdownSearchInput;
|
||||||
|
|
||||||
setViewAnyFieldSearchValueComponentState(filterValue);
|
setAnyFieldFilterValue(filterValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
setObjectFilterDropdownAnyFieldSearchIsSelectedComponentState(true);
|
setObjectFilterDropdownAnyFieldSearchIsSelectedComponentState(true);
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
|
||||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
|
||||||
|
|
||||||
export const viewAnyFieldSearchValueComponentState =
|
|
||||||
createComponentStateV2<string>({
|
|
||||||
key: 'viewAnyFieldSearchValueComponentState',
|
|
||||||
defaultValue: '',
|
|
||||||
componentInstanceContext: ViewComponentInstanceContext,
|
|
||||||
});
|
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
} from '~/testing/mock-data/people';
|
} from '~/testing/mock-data/people';
|
||||||
import { mockedWorkspaceMemberData } from '~/testing/mock-data/users';
|
import { mockedWorkspaceMemberData } from '~/testing/mock-data/users';
|
||||||
|
|
||||||
|
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||||
import { RecordShowPage } from '../RecordShowPage';
|
import { RecordShowPage } from '../RecordShowPage';
|
||||||
|
|
||||||
const personRecord = allMockPersonRecords[0];
|
const personRecord = allMockPersonRecords[0];
|
||||||
@ -62,7 +63,7 @@ export type Story = StoryObj<typeof RecordShowPage>;
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
decorators: [PageDecorator],
|
decorators: [PageDecorator, ContextStoreDecorator],
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user