Feat/complete filter order by types (#2943)
* Fixed orderBy bug * Fixed gitch select multiple record filter * Fixed RelationPicker search * Fixed OrderBy type * WIP * Finished RequestFilter typing * Finished RequestFilter type * Fixed missing import * Changed naming
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import { Note } from '@/activities/types/Note';
|
import { Note } from '@/activities/types/Note';
|
||||||
|
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
|
||||||
import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity';
|
import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity';
|
||||||
@ -19,7 +20,7 @@ export const useNotes = (entity: ActivityTargetableEntity) => {
|
|||||||
};
|
};
|
||||||
const orderBy = {
|
const orderBy = {
|
||||||
createdAt: 'AscNullsFirst',
|
createdAt: 'AscNullsFirst',
|
||||||
} as any; // TODO: finish typing
|
} as OrderByField;
|
||||||
|
|
||||||
const { records: notes } = useFindManyRecords({
|
const { records: notes } = useFindManyRecords({
|
||||||
skip: !activityTargets?.length,
|
skip: !activityTargets?.length,
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
PaginatedRecordTypeResults,
|
PaginatedRecordTypeResults,
|
||||||
} from '../types/PaginatedRecordTypeResults';
|
} from '../types/PaginatedRecordTypeResults';
|
||||||
import { mapPaginatedRecordsToRecords } from '../utils/mapPaginatedRecordsToRecords';
|
import { mapPaginatedRecordsToRecords } from '../utils/mapPaginatedRecordsToRecords';
|
||||||
|
import { ObjectRecordFilter } from '@/object-record/types/ObjectRecordFilter';
|
||||||
|
|
||||||
export const useFindManyRecords = <
|
export const useFindManyRecords = <
|
||||||
RecordType extends { id: string } & Record<string, any>,
|
RecordType extends { id: string } & Record<string, any>,
|
||||||
@ -36,7 +37,7 @@ export const useFindManyRecords = <
|
|||||||
onCompleted,
|
onCompleted,
|
||||||
skip,
|
skip,
|
||||||
}: ObjectMetadataItemIdentifier & {
|
}: ObjectMetadataItemIdentifier & {
|
||||||
filter?: any;
|
filter?: ObjectRecordFilter;
|
||||||
orderBy?: OrderByField;
|
orderBy?: OrderByField;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
onCompleted?: (data: PaginatedRecordTypeResults<RecordType>) => void;
|
onCompleted?: (data: PaginatedRecordTypeResults<RecordType>) => void;
|
||||||
|
|||||||
@ -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<PipelineStep>) => {
|
|
||||||
setSavedPipelineSteps(data.edges.map((edge) => edge.node));
|
|
||||||
},
|
|
||||||
[setSavedPipelineSteps],
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
records: opportunities,
|
|
||||||
loading,
|
|
||||||
fetchMoreRecords: fetchMoreOpportunities,
|
|
||||||
} = useFindManyRecords<Opportunity>({
|
|
||||||
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<Company>) => {
|
|
||||||
setSavedCompanies(data.edges.map((edge) => edge.node));
|
|
||||||
},
|
|
||||||
[setSavedCompanies],
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
opportunities,
|
|
||||||
loading,
|
|
||||||
fetchMoreOpportunities,
|
|
||||||
fetchMoreCompanies,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -3,7 +3,7 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { Company } from '@/companies/types/Company';
|
import { Company } from '@/companies/types/Company';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
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 { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
|
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
|
||||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||||
@ -43,7 +43,7 @@ export const useObjectRecordBoard = () => {
|
|||||||
savedPipelineStepsState,
|
savedPipelineStepsState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const filter = turnFiltersIntoWhereClause(
|
const filter = turnFiltersIntoObjectRecordFilters(
|
||||||
boardFilters,
|
boardFilters,
|
||||||
foundObjectMetadataItem?.fields ?? [],
|
foundObjectMetadataItem?.fields ?? [],
|
||||||
);
|
);
|
||||||
@ -70,8 +70,8 @@ export const useObjectRecordBoard = () => {
|
|||||||
} = useFindManyRecords<Opportunity>({
|
} = useFindManyRecords<Opportunity>({
|
||||||
skip: !savedPipelineSteps.length,
|
skip: !savedPipelineSteps.length,
|
||||||
objectNameSingular: 'opportunity',
|
objectNameSingular: 'opportunity',
|
||||||
filter: filter,
|
filter,
|
||||||
orderBy: orderBy as any, // TODO: finish typing
|
orderBy,
|
||||||
onCompleted: useCallback(() => {
|
onCompleted: useCallback(() => {
|
||||||
setIsBoardLoaded(true);
|
setIsBoardLoaded(true);
|
||||||
}, [setIsBoardLoaded]),
|
}, [setIsBoardLoaded]),
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useRecoilValue, useSetRecoilState } from 'recoil';
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
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 { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
|
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
@ -31,21 +31,20 @@ export const useObjectRecordTable = () => {
|
|||||||
const tableSorts = useRecoilValue(tableSortsState);
|
const tableSorts = useRecoilValue(tableSortsState);
|
||||||
const setLastRowVisible = useSetRecoilState(tableLastRowVisibleState);
|
const setLastRowVisible = useSetRecoilState(tableLastRowVisibleState);
|
||||||
|
|
||||||
const filter = turnFiltersIntoWhereClause(
|
const requestFilters = turnFiltersIntoObjectRecordFilters(
|
||||||
tableFilters,
|
tableFilters,
|
||||||
foundObjectMetadataItem?.fields ?? [],
|
foundObjectMetadataItem?.fields ?? [],
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: finish typing
|
|
||||||
const orderBy = turnSortsIntoOrderBy(
|
const orderBy = turnSortsIntoOrderBy(
|
||||||
tableSorts,
|
tableSorts,
|
||||||
foundObjectMetadataItem?.fields ?? [],
|
foundObjectMetadataItem?.fields ?? [],
|
||||||
) as any;
|
);
|
||||||
|
|
||||||
const { records, loading, fetchMoreRecords, queryStateIdentifier } =
|
const { records, loading, fetchMoreRecords, queryStateIdentifier } =
|
||||||
useFindManyRecords({
|
useFindManyRecords({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
filter,
|
filter: requestFilters,
|
||||||
orderBy,
|
orderBy,
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
setLastRowVisible(false);
|
setLastRowVisible(false);
|
||||||
|
|||||||
@ -1,237 +0,0 @@
|
|||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { Field } from '~/generated/graphql';
|
|
||||||
|
|
||||||
import { Filter } from '../types/Filter';
|
|
||||||
|
|
||||||
type FilterToTurnIntoWhereClause = Omit<Filter, 'definition'> & {
|
|
||||||
definition: {
|
|
||||||
type: Filter['definition']['type'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const turnFiltersIntoWhereClause = (
|
|
||||||
filters: FilterToTurnIntoWhereClause[],
|
|
||||||
fields: Pick<Field, 'id' | 'name'>[],
|
|
||||||
) => {
|
|
||||||
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 };
|
|
||||||
};
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||||
import { Field } from '~/generated/graphql';
|
import { Field } from '~/generated/graphql';
|
||||||
|
|
||||||
import { Sort } from '../types/Sort';
|
import { Sort } from '../types/Sort';
|
||||||
@ -5,8 +6,9 @@ import { Sort } from '../types/Sort';
|
|||||||
export const turnSortsIntoOrderBy = (
|
export const turnSortsIntoOrderBy = (
|
||||||
sorts: Sort[],
|
sorts: Sort[],
|
||||||
fields: Pick<Field, 'id' | 'name'>[],
|
fields: Pick<Field, 'id' | 'name'>[],
|
||||||
) => {
|
): OrderByField => {
|
||||||
const sortsObject: Record<string, 'AscNullsFirst' | 'DescNullsLast'> = {};
|
const sortsObject: Record<string, 'AscNullsFirst' | 'DescNullsLast'> = {};
|
||||||
|
|
||||||
if (!sorts.length) {
|
if (!sorts.length) {
|
||||||
const createdAtField = fields.find((field) => field.name === 'createdAt');
|
const createdAtField = fields.find((field) => field.name === 'createdAt');
|
||||||
if (createdAtField) {
|
if (createdAtField) {
|
||||||
@ -23,6 +25,7 @@ export const turnSortsIntoOrderBy = (
|
|||||||
[fields[0].name]: 'DescNullsFirst',
|
[fields[0].name]: 'DescNullsFirst',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
sorts.forEach((sort) => {
|
sorts.forEach((sort) => {
|
||||||
const correspondingField = fields.find(
|
const correspondingField = fields.find(
|
||||||
(field) => field.id === sort.fieldMetadataId,
|
(field) => field.id === sort.fieldMetadataId,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
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 { useRecordBoardActionBarEntriesInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardActionBarEntriesInternal';
|
||||||
import { useRecordBoardContextMenuEntriesInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardContextMenuEntriesInternal';
|
import { useRecordBoardContextMenuEntriesInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardContextMenuEntriesInternal';
|
||||||
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
|
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
@ -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<Filter, 'definition'> & {
|
||||||
|
definition: {
|
||||||
|
type: Filter['definition']['type'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const turnFiltersIntoObjectRecordFilters = (
|
||||||
|
rawUIFilters: RawUIFilter[],
|
||||||
|
fields: Pick<Field, 'id' | 'name'>[],
|
||||||
|
): 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 };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user