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 { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
@ -43,6 +44,10 @@ export const DeleteMultipleRecordsAction = () => {
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const graphqlFilter = computeContextStoreFilters(
|
||||
@ -50,6 +55,7 @@ export const DeleteMultipleRecordsAction = () => {
|
||||
contextStoreFilters,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
);
|
||||
|
||||
const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
@ -43,6 +44,10 @@ export const DestroyMultipleRecordsAction = () => {
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const deletedAtFilter: RecordGqlOperationFilter = {
|
||||
@ -54,6 +59,7 @@ export const DestroyMultipleRecordsAction = () => {
|
||||
contextStoreFilters,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
),
|
||||
...deletedAtFilter,
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
@ -43,6 +44,10 @@ export const RestoreMultipleRecordsAction = () => {
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const deletedAtFilter: RecordGqlOperationFilter = {
|
||||
@ -55,6 +60,7 @@ export const RestoreMultipleRecordsAction = () => {
|
||||
contextStoreFilters,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
),
|
||||
...deletedAtFilter,
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/cons
|
||||
import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
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(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||
@ -100,6 +107,7 @@ describe('useSetGlobalCommandMenuContext', () => {
|
||||
currentViewType,
|
||||
commandMenuPageInfo,
|
||||
hasUserSelectedCommand,
|
||||
anyFieldFilterValue,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -113,6 +121,7 @@ describe('useSetGlobalCommandMenuContext', () => {
|
||||
});
|
||||
expect(result.current.numberOfSelectedRecords).toBe(2);
|
||||
expect(result.current.filters).toEqual([]);
|
||||
expect(result.current.anyFieldFilterValue).toEqual('');
|
||||
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
|
||||
expect(result.current.commandMenuPageInfo).toEqual({
|
||||
title: undefined,
|
||||
@ -131,6 +140,7 @@ describe('useSetGlobalCommandMenuContext', () => {
|
||||
});
|
||||
expect(result.current.numberOfSelectedRecords).toBe(0);
|
||||
expect(result.current.filters).toEqual([]);
|
||||
expect(result.current.anyFieldFilterValue).toEqual('');
|
||||
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
|
||||
expect(result.current.commandMenuPageInfo).toEqual({
|
||||
title: undefined,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
@ -76,6 +77,21 @@ export const useCopyContextStoreStates = () => {
|
||||
contextStoreFilters,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = snapshot
|
||||
.getLoadable(
|
||||
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyFrom,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyTo,
|
||||
}),
|
||||
contextStoreAnyFieldFilterValue,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewId = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
@ -39,6 +40,13 @@ export const useResetContextStoreStates = () => {
|
||||
[],
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
'',
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId,
|
||||
|
||||
@ -3,6 +3,7 @@ import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/cons
|
||||
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
@ -45,6 +46,13 @@ export const useSetGlobalCommandMenuContext = () => {
|
||||
[],
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreAnyFieldFilterValueComponentState.atomFamily({
|
||||
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||
}),
|
||||
'',
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
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 { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
@ -36,6 +37,11 @@ export const useFindManyRecordsSelectedInContextStore = ({
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
@ -58,9 +64,9 @@ export const useFindManyRecordsSelectedInContextStore = ({
|
||||
const queryFilter = computeContextStoreFilters(
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
objectMetadataItem!,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
);
|
||||
|
||||
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,
|
||||
mockFilterValueDependencies,
|
||||
'',
|
||||
);
|
||||
|
||||
expect(filters).toEqual({
|
||||
and: [
|
||||
{},
|
||||
{
|
||||
id: {
|
||||
in: ['1', '2', '3'],
|
||||
@ -67,10 +69,12 @@ describe('computeContextStoreFilters', () => {
|
||||
contextStoreFilters,
|
||||
personObjectMetadataItem,
|
||||
mockFilterValueDependencies,
|
||||
'',
|
||||
);
|
||||
|
||||
expect(filters).toEqual({
|
||||
and: [
|
||||
{},
|
||||
{
|
||||
or: [
|
||||
{
|
||||
|
||||
@ -4,6 +4,7 @@ import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGq
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||
import { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||
|
||||
export const computeContextStoreFilters = (
|
||||
@ -11,11 +12,19 @@ export const computeContextStoreFilters = (
|
||||
contextStoreFilters: RecordFilter[],
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
filterValueDependencies: RecordFilterValueDependencies,
|
||||
anyFieldFilterValue: string,
|
||||
) => {
|
||||
let queryFilter: RecordGqlOperationFilter | undefined;
|
||||
|
||||
const { recordGqlOperationFilter: recordGqlFilterForAnyFieldFilter } =
|
||||
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||
filterValue: anyFieldFilterValue,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
|
||||
queryFilter = makeAndFilterVariables([
|
||||
recordGqlFilterForAnyFieldFilter,
|
||||
computeRecordGqlOperationFilter({
|
||||
filterValueDependencies,
|
||||
fields: objectMetadataItem?.fields ?? [],
|
||||
@ -35,6 +44,7 @@ export const computeContextStoreFilters = (
|
||||
}
|
||||
if (contextStoreTargetedRecordsRule.mode === 'selection') {
|
||||
queryFilter = makeAndFilterVariables([
|
||||
recordGqlFilterForAnyFieldFilter,
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length > 0
|
||||
? {
|
||||
id: {
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
export const ObjectFilterDropdownAnyFieldSearchInput = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const [viewAnyFieldSearchValue, setViewAnyFieldSearchValue] =
|
||||
useRecoilComponentStateV2(viewAnyFieldSearchValueComponentState);
|
||||
const [anyFieldFilterSearchValue, setAnyFieldFilterSearchValue] =
|
||||
useRecoilComponentStateV2(anyFieldFilterValueComponentState);
|
||||
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = event.target.value;
|
||||
|
||||
setViewAnyFieldSearchValue(inputValue);
|
||||
setAnyFieldFilterSearchValue(inputValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
value={viewAnyFieldSearchValue}
|
||||
value={anyFieldFilterSearchValue}
|
||||
placeholder={t`Search any field`}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
|
||||
@ -30,8 +30,8 @@ export const useActorFieldDisplay = (): ActorFieldDisplayValue | undefined => {
|
||||
}
|
||||
|
||||
const relatedWorkspaceMember = [
|
||||
...currentWorkspaceDeletedMembers,
|
||||
...currentWorkspaceMembers,
|
||||
...(currentWorkspaceDeletedMembers ?? []),
|
||||
...(currentWorkspaceMembers ?? []),
|
||||
].find(
|
||||
(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),
|
||||
} as FloatFilter,
|
||||
};
|
||||
case RecordFilterOperand.Is:
|
||||
return {
|
||||
[correspondingFieldMetadataItem.name]: {
|
||||
eq: parseFloat(recordFilter.value),
|
||||
} as FloatFilter,
|
||||
};
|
||||
default:
|
||||
throw new Error(
|
||||
`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 { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
@ -40,6 +41,10 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const { totalCount } = useFindManyRecords({
|
||||
@ -52,6 +57,7 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
||||
contextStoreFilters,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
),
|
||||
limit: 1,
|
||||
skip: contextStoreTargetedRecordsRule.mode === 'selection',
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
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 { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
|
||||
@ -74,5 +76,22 @@ export const RecordIndexFiltersToContextStoreEffect = () => {
|
||||
};
|
||||
}, [recordIndexFilters, setContextStoreFilters]);
|
||||
|
||||
const setContextStoreAnyFieldFilterValue = useSetRecoilComponentStateV2(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setContextStoreAnyFieldFilterValue(anyFieldFilterValue);
|
||||
|
||||
return () => {
|
||||
setContextStoreAnyFieldFilterValue('');
|
||||
};
|
||||
}, [anyFieldFilterValue, setContextStoreAnyFieldFilterValue]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||
@ -72,6 +73,10 @@ export const useRecordIndexLazyFetchRecords = ({
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const contextStoreAnyFieldFilterValue = useRecoilComponentValueV2(
|
||||
contextStoreAnyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const findManyRecordsParams = useFindManyRecordIndexTableParams(
|
||||
@ -83,6 +88,7 @@ export const useRecordIndexLazyFetchRecords = ({
|
||||
contextStoreFilters,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
contextStoreAnyFieldFilterValue,
|
||||
);
|
||||
|
||||
const finalColumns = [
|
||||
|
||||
@ -2,9 +2,11 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
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 { combineFilters } from '@/object-record/record-filter/utils/combineFilters';
|
||||
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 { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
@ -37,18 +39,28 @@ export const useFindManyRecordIndexTableParams = (
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const stateFilter = computeRecordGqlOperationFilter({
|
||||
const currentFilters = computeRecordGqlOperationFilter({
|
||||
fields: objectMetadataItem?.fields ?? [],
|
||||
filterValueDependencies,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
recordFilters: currentRecordFilters,
|
||||
});
|
||||
|
||||
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||
objectMetadataItem,
|
||||
filterValue: anyFieldFilterValue,
|
||||
});
|
||||
|
||||
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
|
||||
|
||||
return {
|
||||
objectNameSingular,
|
||||
filter: combineFilters([stateFilter, recordGroupFilter]),
|
||||
filter: combineFilters([currentFilters, recordGroupFilter, anyFieldFilter]),
|
||||
orderBy,
|
||||
// If we have a current record group definition, we only want to fetch 8 records by page
|
||||
...(currentRecordGroupDefinition ? { limit: 8 } : {}),
|
||||
|
||||
@ -15,7 +15,9 @@ import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hook
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
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 { turnAnyFieldFilterIntoRecordGqlFilter } from '@/object-record/record-filter/utils/turnAnyFieldFilterIntoRecordGqlFilter';
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@ -64,6 +66,16 @@ export const useLoadRecordIndexBoardColumn = ({
|
||||
fields: objectMetadataItem.fields,
|
||||
});
|
||||
|
||||
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||
objectMetadataItem,
|
||||
filterValue: anyFieldFilterValue,
|
||||
});
|
||||
|
||||
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts);
|
||||
|
||||
const recordGqlFields = useRecordBoardRecordGqlFields({
|
||||
@ -78,6 +90,7 @@ export const useLoadRecordIndexBoardColumn = ({
|
||||
: { is: 'NULL' };
|
||||
|
||||
const combinedFilters = combineFilters([
|
||||
anyFieldFilter,
|
||||
requestFilters,
|
||||
{
|
||||
[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 { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
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 { 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { UserContext } from '@/users/contexts/UserContext';
|
||||
@ -54,10 +56,20 @@ export const useAggregateRecordsForHeader = ({
|
||||
recordIndexKanbanAggregateOperation,
|
||||
});
|
||||
|
||||
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||
objectMetadataItem,
|
||||
filterValue: anyFieldFilterValue,
|
||||
});
|
||||
|
||||
const { data } = useAggregateRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordGqlFieldsAggregate,
|
||||
filter: { ...requestFilters, ...additionalFilters },
|
||||
filter: { ...requestFilters, ...additionalFilters, ...anyFieldFilter },
|
||||
});
|
||||
|
||||
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 { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
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 { 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 { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
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({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordGqlFieldsAggregate,
|
||||
filter: { ...requestFilters, ...recordGroupFilter },
|
||||
filter: { ...requestFilters, ...recordGroupFilter, ...anyFieldFilter },
|
||||
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 { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
|
||||
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';
|
||||
|
||||
export const AnyFieldSearchChip = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const [viewAnyFieldSearchValue, setViewAnyFieldSearchValue] =
|
||||
useRecoilComponentStateV2(viewAnyFieldSearchValueComponentState);
|
||||
const [anyFieldFilterValue, setAnyFieldFilterValue] =
|
||||
useRecoilComponentStateV2(anyFieldFilterValueComponentState);
|
||||
|
||||
const handleRemoveClick = () => {
|
||||
closeDropdown();
|
||||
setViewAnyFieldSearchValue('');
|
||||
setAnyFieldFilterValue('');
|
||||
};
|
||||
|
||||
return (
|
||||
<SortOrFilterChip
|
||||
testId={ADVANCED_FILTER_DROPDOWN_ID}
|
||||
labelKey={'Any field :'}
|
||||
labelValue={viewAnyFieldSearchValue}
|
||||
labelKey={t`Any field :`}
|
||||
labelValue={anyFieldFilterValue}
|
||||
Icon={IconFilter}
|
||||
onRemove={handleRemoveClick}
|
||||
type="filter"
|
||||
|
||||
@ -22,6 +22,7 @@ import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAr
|
||||
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
||||
|
||||
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 { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
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 { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
|
||||
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
|
||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@ -123,8 +123,8 @@ export const ViewBarDetails = ({
|
||||
currentRecordSortsComponentState,
|
||||
);
|
||||
|
||||
const viewAnyFieldSearchValue = useRecoilComponentValueV2(
|
||||
viewAnyFieldSearchValueComponentState,
|
||||
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||
@ -189,7 +189,7 @@ export const ViewBarDetails = ({
|
||||
);
|
||||
|
||||
const shouldShowAnyFieldSearchChip =
|
||||
isNonEmptyString(viewAnyFieldSearchValue) || isAnyFieldSearchDropdownOpen;
|
||||
isNonEmptyString(anyFieldFilterValue) || isAnyFieldSearchDropdownOpen;
|
||||
|
||||
const shouldExpandViewBar =
|
||||
shouldShowAnyFieldSearchChip ||
|
||||
|
||||
@ -2,8 +2,10 @@ import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
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 { 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetViewGroupsFilters } from '@/views/hooks/useGetViewGroupsFilters';
|
||||
@ -30,11 +32,21 @@ export const useGetRecordIndexTotalCount = () => {
|
||||
fields: objectMetadataItem.fields,
|
||||
});
|
||||
|
||||
const anyFieldFilterValue = useRecoilComponentValueV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||
turnAnyFieldFilterIntoRecordGqlFilter({
|
||||
objectMetadataItem,
|
||||
filterValue: anyFieldFilterValue,
|
||||
});
|
||||
|
||||
const { data, loading } = useAggregateRecords<{
|
||||
id: { COUNT: number };
|
||||
}>({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
filter,
|
||||
filter: { ...filter, ...anyFieldFilter },
|
||||
recordGqlFieldsAggregate: {
|
||||
id: [AggregateOperations.COUNT],
|
||||
},
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
import { objectFilterDropdownAnyFieldSearchIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownAnyFieldSearchIsSelectedComponentState';
|
||||
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 { 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';
|
||||
|
||||
export const useOpenAnyFieldSearchFilterFromViewBar = () => {
|
||||
const setViewAnyFieldSearchValueComponentState = useSetRecoilComponentStateV2(
|
||||
viewAnyFieldSearchValueComponentState,
|
||||
const setAnyFieldFilterValue = useSetRecoilComponentStateV2(
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const setObjectFilterDropdownAnyFieldSearchIsSelectedComponentState =
|
||||
useSetRecoilComponentStateV2(
|
||||
objectFilterDropdownAnyFieldSearchIsSelectedComponentState,
|
||||
@ -19,14 +23,29 @@ export const useOpenAnyFieldSearchFilterFromViewBar = () => {
|
||||
objectFilterDropdownSearchInputComponentState,
|
||||
);
|
||||
|
||||
const translatedLabel = t`Search any field`;
|
||||
|
||||
const openAnyFieldSearchFilterFromViewBar = () => {
|
||||
const userHasAlreadyEnteredSearchInputForObjectDropdownSearch =
|
||||
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;
|
||||
|
||||
setViewAnyFieldSearchValueComponentState(filterValue);
|
||||
setAnyFieldFilterValue(filterValue);
|
||||
}
|
||||
|
||||
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';
|
||||
import { mockedWorkspaceMemberData } from '~/testing/mock-data/users';
|
||||
|
||||
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||
import { RecordShowPage } from '../RecordShowPage';
|
||||
|
||||
const personRecord = allMockPersonRecords[0];
|
||||
@ -62,7 +63,7 @@ export type Story = StoryObj<typeof RecordShowPage>;
|
||||
export const Default: Story = {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
decorators: [PageDecorator],
|
||||
decorators: [PageDecorator, ContextStoreDecorator],
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user