diff --git a/packages/twenty-front/src/modules/activities/notes/hooks/useNotes.ts b/packages/twenty-front/src/modules/activities/notes/hooks/useNotes.ts index c3184b5576..3b8c436451 100644 --- a/packages/twenty-front/src/modules/activities/notes/hooks/useNotes.ts +++ b/packages/twenty-front/src/modules/activities/notes/hooks/useNotes.ts @@ -1,4 +1,5 @@ import { Note } from '@/activities/types/Note'; +import { OrderByField } from '@/object-metadata/types/OrderByField'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity'; @@ -19,7 +20,7 @@ export const useNotes = (entity: ActivityTargetableEntity) => { }; const orderBy = { createdAt: 'AscNullsFirst', - } as any; // TODO: finish typing + } as OrderByField; const { records: notes } = useFindManyRecords({ skip: !activityTargets?.length, diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts index 9314db9d8f..19ea22a95f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts @@ -25,6 +25,7 @@ import { PaginatedRecordTypeResults, } from '../types/PaginatedRecordTypeResults'; import { mapPaginatedRecordsToRecords } from '../utils/mapPaginatedRecordsToRecords'; +import { ObjectRecordFilter } from '@/object-record/types/ObjectRecordFilter'; export const useFindManyRecords = < RecordType extends { id: string } & Record, @@ -36,7 +37,7 @@ export const useFindManyRecords = < onCompleted, skip, }: ObjectMetadataItemIdentifier & { - filter?: any; + filter?: ObjectRecordFilter; orderBy?: OrderByField; limit?: number; onCompleted?: (data: PaginatedRecordTypeResults) => void; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordBoard.1.ts b/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordBoard.1.ts deleted file mode 100644 index bf33af409f..0000000000 --- a/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordBoard.1.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { useCallback } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; - -import { Company } from '@/companies/types/Company'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { turnFiltersIntoWhereClause } from '@/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause'; -import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; -import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; -import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults'; -import { Opportunity } from '@/pipeline/types/Opportunity'; -import { PipelineStep } from '@/pipeline/types/PipelineStep'; - -import { useFindManyRecords } from './useFindManyRecords'; - -export const useObjectRecordBoard = () => { - const objectNameSingular = 'opportunity'; - - const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem( - { - objectNameSingular, - }, - ); - - const { - isBoardLoadedState, - boardFiltersState, - boardSortsState, - savedCompaniesState, - savedOpportunitiesState, - savedPipelineStepsState, - } = useRecordBoardScopedStates(); - - const setIsBoardLoaded = useSetRecoilState(isBoardLoadedState); - - const boardFilters = useRecoilValue(boardFiltersState); - const boardSorts = useRecoilValue(boardSortsState); - - const setSavedCompanies = useSetRecoilState(savedCompaniesState); - - const [savedOpportunities] = useRecoilState(savedOpportunitiesState); - - const [savedPipelineSteps, setSavedPipelineSteps] = useRecoilState( - savedPipelineStepsState, - ); - - const filter = turnFiltersIntoWhereClause( - boardFilters, - foundObjectMetadataItem?.fields ?? [], - ); - const orderBy = turnSortsIntoOrderBy( - boardSorts, - foundObjectMetadataItem?.fields ?? [], - ); - - useFindManyRecords({ - objectNameSingular: 'pipelineStep', - filter: {}, - onCompleted: useCallback( - (data: PaginatedRecordTypeResults) => { - setSavedPipelineSteps(data.edges.map((edge) => edge.node)); - }, - [setSavedPipelineSteps], - ), - }); - - const { - records: opportunities, - loading, - fetchMoreRecords: fetchMoreOpportunities, - } = useFindManyRecords({ - skip: !savedPipelineSteps.length, - objectNameSingular: 'opportunity', - filter: filter, - orderBy: orderBy as any, // TODO: finish typing - onCompleted: useCallback(() => { - setIsBoardLoaded(true); - }, [setIsBoardLoaded]), - }); - - const { fetchMoreRecords: fetchMoreCompanies } = useFindManyRecords({ - skip: !savedOpportunities.length, - objectNameSingular: 'company', - filter: { - id: { - in: savedOpportunities.map( - (opportunity) => opportunity.companyId || '', - ), - }, - }, - onCompleted: useCallback( - (data: PaginatedRecordTypeResults) => { - setSavedCompanies(data.edges.map((edge) => edge.node)); - }, - [setSavedCompanies], - ), - }); - - return { - opportunities, - loading, - fetchMoreOpportunities, - fetchMoreCompanies, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordBoard.ts b/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordBoard.ts index bf33af409f..54ab3decb2 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordBoard.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordBoard.ts @@ -3,7 +3,7 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { Company } from '@/companies/types/Company'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { turnFiltersIntoWhereClause } from '@/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause'; +import { turnFiltersIntoObjectRecordFilters } from '@/object-record/utils/turnFiltersIntoWhereClause'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults'; @@ -43,7 +43,7 @@ export const useObjectRecordBoard = () => { savedPipelineStepsState, ); - const filter = turnFiltersIntoWhereClause( + const filter = turnFiltersIntoObjectRecordFilters( boardFilters, foundObjectMetadataItem?.fields ?? [], ); @@ -70,8 +70,8 @@ export const useObjectRecordBoard = () => { } = useFindManyRecords({ skip: !savedPipelineSteps.length, objectNameSingular: 'opportunity', - filter: filter, - orderBy: orderBy as any, // TODO: finish typing + filter, + orderBy, onCompleted: useCallback(() => { setIsBoardLoaded(true); }, [setIsBoardLoaded]), diff --git a/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordTable.ts b/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordTable.ts index a06dce1541..5f0e2fce6d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordTable.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordTable.ts @@ -3,7 +3,7 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; -import { turnFiltersIntoWhereClause } from '@/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause'; +import { turnFiltersIntoObjectRecordFilters } from '@/object-record/utils/turnFiltersIntoWhereClause'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; @@ -31,21 +31,20 @@ export const useObjectRecordTable = () => { const tableSorts = useRecoilValue(tableSortsState); const setLastRowVisible = useSetRecoilState(tableLastRowVisibleState); - const filter = turnFiltersIntoWhereClause( + const requestFilters = turnFiltersIntoObjectRecordFilters( tableFilters, foundObjectMetadataItem?.fields ?? [], ); - // TODO: finish typing const orderBy = turnSortsIntoOrderBy( tableSorts, foundObjectMetadataItem?.fields ?? [], - ) as any; + ); const { records, loading, fetchMoreRecords, queryStateIdentifier } = useFindManyRecords({ objectNameSingular, - filter, + filter: requestFilters, orderBy, onCompleted: () => { setLastRowVisible(false); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause.ts deleted file mode 100644 index c320afa772..0000000000 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { Field } from '~/generated/graphql'; - -import { Filter } from '../types/Filter'; - -type FilterToTurnIntoWhereClause = Omit & { - definition: { - type: Filter['definition']['type']; - }; -}; - -export const turnFiltersIntoWhereClause = ( - filters: FilterToTurnIntoWhereClause[], - fields: Pick[], -) => { - const whereClause: any[] = []; - - filters.forEach((filter) => { - const correspondingField = fields.find( - (field) => field.id === filter.fieldMetadataId, - ); - if (!correspondingField) { - throw new Error( - `Could not find field ${filter.fieldMetadataId} in metadata object`, - ); - } - - switch (filter.definition.type) { - case 'EMAIL': - case 'PHONE': - case 'TEXT': - switch (filter.operand) { - case ViewFilterOperand.Contains: - whereClause.push({ - [correspondingField.name]: { - ilike: `%${filter.value}%`, - }, - }); - return; - case ViewFilterOperand.DoesNotContain: - whereClause.push({ - not: { - [correspondingField.name]: { - ilike: `%${filter.value}%`, - }, - }, - }); - return; - default: - throw new Error( - `Unknown operand ${filter.operand} for ${filter.definition.type} filter`, - ); - } - case 'DATE_TIME': - switch (filter.operand) { - case ViewFilterOperand.GreaterThan: - whereClause.push({ - [correspondingField.name]: { - gte: filter.value, - }, - }); - return; - case ViewFilterOperand.LessThan: - whereClause.push({ - [correspondingField.name]: { - lte: filter.value, - }, - }); - return; - default: - throw new Error( - `Unknown operand ${filter.operand} for ${filter.definition.type} filter`, - ); - } - case 'NUMBER': - switch (filter.operand) { - case ViewFilterOperand.GreaterThan: - whereClause.push({ - [correspondingField.name]: { - gte: parseFloat(filter.value), - }, - }); - return; - case ViewFilterOperand.LessThan: - whereClause.push({ - [correspondingField.name]: { - lte: parseFloat(filter.value), - }, - }); - return; - default: - throw new Error( - `Unknown operand ${filter.operand} for ${filter.definition.type} filter`, - ); - } - case 'RELATION': - try { - JSON.parse(filter.value); - } catch (e) { - throw new Error( - `Cannot parse filter value for RELATION filter : "${filter.value}"`, - ); - } - - const parsedRecordIds = JSON.parse(filter.value) as string[]; - - if (parsedRecordIds.length > 0) { - switch (filter.operand) { - case ViewFilterOperand.Is: - whereClause.push({ - [correspondingField.name + 'Id']: { - in: parsedRecordIds, - }, - }); - return; - case ViewFilterOperand.IsNot: - whereClause.push({ - not: { - [correspondingField.name + 'Id']: { - in: parsedRecordIds, - }, - }, - }); - return; - default: - throw new Error( - `Unknown operand ${filter.operand} for ${filter.definition.type} filter`, - ); - } - } - break; - case 'CURRENCY': - switch (filter.operand) { - case ViewFilterOperand.GreaterThan: - whereClause.push({ - [correspondingField.name]: { - amountMicros: { gte: parseFloat(filter.value) * 1000000 }, - }, - }); - return; - case ViewFilterOperand.LessThan: - whereClause.push({ - [correspondingField.name]: { - amountMicros: { lte: parseFloat(filter.value) * 1000000 }, - }, - }); - return; - default: - throw new Error( - `Unknown operand ${filter.operand} for ${filter.definition.type} filter`, - ); - } - case 'LINK': - switch (filter.operand) { - case ViewFilterOperand.Contains: - whereClause.push({ - [correspondingField.name]: { - url: { - ilike: `%${filter.value}%`, - }, - }, - }); - return; - case ViewFilterOperand.DoesNotContain: - whereClause.push({ - not: { - [correspondingField.name]: { - url: { - ilike: `%${filter.value}%`, - }, - }, - }, - }); - return; - default: - throw new Error( - `Unknown operand ${filter.operand} for ${filter.definition.type} filter`, - ); - } - case 'FULL_NAME': - switch (filter.operand) { - case ViewFilterOperand.Contains: - whereClause.push({ - or: [ - { - [correspondingField.name]: { - firstName: { - ilike: `%${filter.value}%`, - }, - }, - }, - { - [correspondingField.name]: { - firstName: { - ilike: `%${filter.value}%`, - }, - }, - }, - ], - }); - return; - case ViewFilterOperand.DoesNotContain: - whereClause.push({ - and: [ - { - not: { - [correspondingField.name]: { - firstName: { - ilike: `%${filter.value}%`, - }, - }, - }, - }, - { - not: { - [correspondingField.name]: { - lastName: { - ilike: `%${filter.value}%`, - }, - }, - }, - }, - ], - }); - return; - default: - throw new Error( - `Unknown operand ${filter.operand} for ${filter.definition.type} filter`, - ); - } - default: - throw new Error('Unknown filter type'); - } - }); - - return { and: whereClause }; -}; diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts index 8d432b35a9..10cf2d68aa 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts @@ -1,3 +1,4 @@ +import { OrderByField } from '@/object-metadata/types/OrderByField'; import { Field } from '~/generated/graphql'; import { Sort } from '../types/Sort'; @@ -5,8 +6,9 @@ import { Sort } from '../types/Sort'; export const turnSortsIntoOrderBy = ( sorts: Sort[], fields: Pick[], -) => { +): OrderByField => { const sortsObject: Record = {}; + if (!sorts.length) { const createdAtField = fields.find((field) => field.name === 'createdAt'); if (createdAtField) { @@ -23,6 +25,7 @@ export const turnSortsIntoOrderBy = ( [fields[0].name]: 'DescNullsFirst', }; } + sorts.forEach((sort) => { const correspondingField = fields.find( (field) => field.id === sort.fieldMetadataId, diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardInternalEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardInternalEffect.tsx index 53b432f8c8..e1014a52a2 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardInternalEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardInternalEffect.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { useObjectRecordBoard } from '@/object-record/hooks/useObjectRecordBoard.1'; +import { useObjectRecordBoard } from '@/object-record/hooks/useObjectRecordBoard'; import { useRecordBoardActionBarEntriesInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardActionBarEntriesInternal'; import { useRecordBoardContextMenuEntriesInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardContextMenuEntriesInternal'; import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; diff --git a/packages/twenty-front/src/modules/object-record/types/ObjectRecordFilter.ts b/packages/twenty-front/src/modules/object-record/types/ObjectRecordFilter.ts new file mode 100644 index 0000000000..db6cea2a9b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/ObjectRecordFilter.ts @@ -0,0 +1,72 @@ +export type UUIDFilterValue = string; + +export type IsFilter = 'NULL' | 'NOT_NULL'; + +export type UUIDFilter = { + eq?: UUIDFilterValue; + in?: UUIDFilterValue[]; + neq?: UUIDFilterValue; + is?: IsFilter; +}; + +export type StringFilter = { + eq?: string; + gt?: string; + gte?: string; + in?: string[]; + lt?: string; + lte?: string; + neq?: string; + startsWith?: string; + like?: string; + ilike?: string; + regex?: string; + iregex?: string; + is?: IsFilter; +}; + +export type FloatFilter = { + eq?: number; + gt?: number; + gte?: number; + in?: number[]; + lt?: number; + lte?: number; + neq?: number; + is?: IsFilter; +}; + + +export type DateFilter = { + eq?: string; + gt?: string; + gte?: string; + in?: string[]; + lt?: string; + lte?: string; + neq?: string; + is?: IsFilter; +}; + +export type CurrencyFilter = { + amountMicros?: FloatFilter; +}; + +export type URLFilter = { + url?: StringFilter; +}; + +export type FullNameFilter = { + firstName?: StringFilter; + lastName?: StringFilter; +}; + +export type LeafFilter = UUIDFilter | StringFilter | FloatFilter | DateFilter | CurrencyFilter | URLFilter | FullNameFilter + +export type ObjectRecordFilter = { + and?: ObjectRecordFilter[]; + or?: ObjectRecordFilter[]; + not?: ObjectRecordFilter; +} | { + [fieldName: string]: LeafFilter +} \ No newline at end of file diff --git a/packages/twenty-front/src/modules/object-record/utils/turnFiltersIntoWhereClause.ts b/packages/twenty-front/src/modules/object-record/utils/turnFiltersIntoWhereClause.ts new file mode 100644 index 0000000000..110d494a44 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/utils/turnFiltersIntoWhereClause.ts @@ -0,0 +1,245 @@ +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { Field } from '~/generated/graphql'; + +import { Filter } from '../object-filter-dropdown/types/Filter'; +import { CurrencyFilter, DateFilter, FloatFilter, FullNameFilter, ObjectRecordFilter, StringFilter, URLFilter } from '@/object-record/types/ObjectRecordFilter'; + +export type RawUIFilter = Omit & { + definition: { + type: Filter['definition']['type']; + }; +}; + +export const turnFiltersIntoObjectRecordFilters = ( + rawUIFilters: RawUIFilter[], + fields: Pick[], +): ObjectRecordFilter => { + const objectRecordFilters: ObjectRecordFilter[] = []; + + for(const rawUIFilter of rawUIFilters) { + const correspondingField = fields.find( + (field) => field.id === rawUIFilter.fieldMetadataId, + ); + + if (!correspondingField) { + throw new Error( + `Could not find field ${rawUIFilter.fieldMetadataId} in metadata object`, + ); + } + + switch (rawUIFilter.definition.type) { + case 'EMAIL': + case 'PHONE': + case 'TEXT': + switch (rawUIFilter.operand) { + case ViewFilterOperand.Contains: + objectRecordFilters.push({ + [correspondingField.name]: { + ilike: `%${rawUIFilter.value}%`, + } as StringFilter, + }); + break + case ViewFilterOperand.DoesNotContain: + objectRecordFilters.push({ + not: { + [correspondingField.name]: { + ilike: `%${rawUIFilter.value}%`, + } as StringFilter, + }, + }); + break + default: + throw new Error( + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + ); + } + break; + case 'DATE_TIME': + switch (rawUIFilter.operand) { + case ViewFilterOperand.GreaterThan: + objectRecordFilters.push({ + [correspondingField.name]: { + gte: rawUIFilter.value, + } as DateFilter, + }); + break; + case ViewFilterOperand.LessThan: + objectRecordFilters.push({ + [correspondingField.name]: { + lte: rawUIFilter.value, + } as DateFilter, + }); + break; + default: + throw new Error( + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + ); + } + break; + case 'NUMBER': + switch (rawUIFilter.operand) { + case ViewFilterOperand.GreaterThan: + objectRecordFilters.push({ + [correspondingField.name]: { + gte: parseFloat(rawUIFilter.value), + } as FloatFilter, + }); + break; + case ViewFilterOperand.LessThan: + objectRecordFilters.push({ + [correspondingField.name]: { + lte: parseFloat(rawUIFilter.value), + } as FloatFilter, + }); + break; + default: + throw new Error( + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + ); + } + break; + case 'RELATION': + try { + JSON.parse(rawUIFilter.value); + } catch (e) { + throw new Error( + `Cannot parse filter value for RELATION filter : "${rawUIFilter.value}"`, + ); + } + + const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[]; + + if (parsedRecordIds.length > 0) { + switch (rawUIFilter.operand) { + case ViewFilterOperand.Is: + objectRecordFilters.push({ + [correspondingField.name + 'Id']: { + in: parsedRecordIds, + } as StringFilter, + }); + break; + case ViewFilterOperand.IsNot: + objectRecordFilters.push({ + not: { + [correspondingField.name + 'Id']: { + in: parsedRecordIds, + } as StringFilter, + }, + }); + break; + default: + throw new Error( + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + ); + } + } + break; + case 'CURRENCY': + switch (rawUIFilter.operand) { + case ViewFilterOperand.GreaterThan: + objectRecordFilters.push({ + [correspondingField.name]: { + amountMicros: { gte: parseFloat(rawUIFilter.value) * 1000000 }, + } as CurrencyFilter, + }); + break; + case ViewFilterOperand.LessThan: + objectRecordFilters.push({ + [correspondingField.name]: { + amountMicros: { lte: parseFloat(rawUIFilter.value) * 1000000 }, + } as CurrencyFilter, + }); + break; + default: + throw new Error( + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + ); + } + break; + case 'LINK': + switch (rawUIFilter.operand) { + case ViewFilterOperand.Contains: + objectRecordFilters.push({ + [correspondingField.name]: { + url: { + ilike: `%${rawUIFilter.value}%`, + }, + } as URLFilter, + }); + break; + case ViewFilterOperand.DoesNotContain: + objectRecordFilters.push({ + not: { + [correspondingField.name]: { + url: { + ilike: `%${rawUIFilter.value}%`, + }, + } as URLFilter, + }, + }); + break; + default: + throw new Error( + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + ); + } + break; + case 'FULL_NAME': + switch (rawUIFilter.operand) { + case ViewFilterOperand.Contains: + objectRecordFilters.push({ + or: [ + { + [correspondingField.name]: { + firstName: { + ilike: `%${rawUIFilter.value}%`, + }, + } as FullNameFilter, + }, + { + [correspondingField.name]: { + lastName: { + ilike: `%${rawUIFilter.value}%`, + }, + } as FullNameFilter, + }, + ], + }); + break; + case ViewFilterOperand.DoesNotContain: + objectRecordFilters.push({ + and: [ + { + not: { + [correspondingField.name]: { + firstName: { + ilike: `%${rawUIFilter.value}%`, + }, + } as FullNameFilter, + }, + }, + { + not: { + [correspondingField.name]: { + lastName: { + ilike: `%${rawUIFilter.value}%`, + }, + } as FullNameFilter, + }, + }, + ], + }); + break; + default: + throw new Error( + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + ); + } + break; + default: + throw new Error('Unknown filter type'); + } + } + + return { and: objectRecordFilters }; +};