diff --git a/src/CONST.ts b/src/CONST.ts index bcf900bc4578..3b879e10c345 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5208,6 +5208,39 @@ const CONST = { APPROVE: 'approve', PAY: 'pay', }, + SYNTAX_OPERATORS: { + AND: 'and', + OR: 'or', + EQUAL_TO: 'eq', + NOT_EQUAL_TO: 'neq', + GREATER_THAN: 'gt', + GREATER_THAN_OR_EQUAL_TO: 'gte', + LOWER_THAN: 'lt', + LOWER_THAN_OR_EQUAL_TO: 'lte', + }, + SYNTAX_ROOT_KEYS: { + TYPE: 'type', + STATUS: 'status', + SORT_BY: 'sortBy', + SORT_ORDER: 'sortOrder', + OFFSET: 'offset', + }, + SYNTAX_FILTER_KEYS: { + DATE: 'date', + AMOUNT: 'amount', + EXPENSE_TYPE: 'expenseType', + CURRENCY: 'currency', + MERCHANT: 'merchant', + DESCRIPTION: 'description', + FROM: 'from', + TO: 'to', + CATEGORY: 'category', + TAG: 'tag', + TAX_RATE: 'taxRate', + CARD_ID: 'cardID', + REPORT_ID: 'reportID', + KEYWORD: 'keyword', + }, }, REFERRER: { diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index cff74fe08a0a..2238b0f49855 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -26,4 +26,21 @@ type SearchContext = { setSelectedTransactionIds: (selectedTransactionIds: string[]) => void; }; -export type {SelectedTransactionInfo, SelectedTransactions, SearchColumnType, SortOrder, SearchContext}; +type ASTNode = { + operator: ValueOf; + left: ValueOf | ASTNode; + right: string | ASTNode; +}; + +type QueryFilter = { + operator: ValueOf; + value: string | number; +}; + +type AllFieldKeys = ValueOf | ValueOf; + +type QueryFilters = { + [K in AllFieldKeys]: QueryFilter | QueryFilter[]; +}; + +export type {SelectedTransactionInfo, SelectedTransactions, SearchColumnType, SortOrder, SearchContext, ASTNode, QueryFilter, QueryFilters, AllFieldKeys}; diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 3225a21c2661..7085171a3ce8 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -1,4 +1,5 @@ -import type {SearchColumnType, SortOrder} from '@components/Search/types'; +import type {ValueOf} from 'type-fest'; +import type {AllFieldKeys, ASTNode, QueryFilter, QueryFilters, SearchColumnType, SortOrder} from '@components/Search/types'; import ReportListItem from '@components/SelectionList/Search/ReportListItem'; import TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; import type {ListItem, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; @@ -309,6 +310,12 @@ function getQueryHashFromString(query: string): number { type JSONQuery = { input: string; hash: number; + type: string; + status: string; + sortBy: string; + sortOrder: string; + offset: number; + filters: ASTNode; }; function buildJSONQuery(query: string) { @@ -323,6 +330,65 @@ function buildJSONQuery(query: string) { } } +function getFilters(query: string, fields: Array>) { + let jsonQuery; + try { + jsonQuery = searchParser.parse(query) as JSONQuery; + } catch (e) { + console.error(e); + return; + } + + const filters = {} as QueryFilters; + + fields.forEach((field) => { + const rootFieldKey = field as ValueOf; + if (jsonQuery[rootFieldKey] === undefined) { + return; + } + + filters[field] = { + operator: 'eq', + value: jsonQuery[rootFieldKey], + }; + }); + + function traverse(node: ASTNode) { + if (!node.operator) { + return; + } + + if (typeof node?.left === 'object') { + traverse(node.left); + } + + if (typeof node?.right === 'object') { + traverse(node.right); + } + + const nodeKey = node.left as ValueOf; + if (!fields.includes(nodeKey)) { + return; + } + + if (!filters[nodeKey]) { + filters[nodeKey] = []; + } + + const filterArray = filters[nodeKey] as QueryFilter[]; + filterArray.push({ + operator: node.operator, + value: node.right as string | number, + }); + } + + if (jsonQuery.filters) { + traverse(jsonQuery.filters); + } + + return filters; +} + export { buildJSONQuery, getListItem, @@ -336,4 +402,5 @@ export { isReportListItemType, isTransactionListItemType, isSearchResultsEmpty, + getFilters, };