diff --git a/src/core/explorer.ts b/src/core/explorer.ts index 841533db..530f78c0 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -4,17 +4,27 @@ import { DateRange } from '@/core/datetime.ts'; export enum TransactionExplorerConditionRelation { First = 'first', And = 'and', - Or = 'or' + Or = 'or', + AndSub = 'and(', + OrSub = 'or(', + SubEnd = ')' } +export type TransactionExplorerSubConditionStartRelation = '('; +export const TransactionExplorerSubConditionStartRelationPlaceholder: TransactionExplorerSubConditionStartRelation = '('; + export const TransactionExplorerConditionRelationPriority: Record = { [TransactionExplorerConditionRelation.First]: 0, [TransactionExplorerConditionRelation.Or]: 1, - [TransactionExplorerConditionRelation.And]: 2 + [TransactionExplorerConditionRelation.And]: 2, + [TransactionExplorerConditionRelation.AndSub]: 0, + [TransactionExplorerConditionRelation.OrSub]: 0, + [TransactionExplorerConditionRelation.SubEnd]: 0 }; export enum TransactionExplorerConditionFieldType { + Undefined = 'undefined', TransactionType = 'transactionType', TransactionCategory = 'transactionCategory', SourceAccount = 'sourceAccount', diff --git a/src/locales/de.json b/src/locales/de.json index 37078812..418e7671 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Heute", "Yesterday": "Gestern", "Recent 7 days": "Letzte 7 Tage", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/en.json b/src/locales/en.json index 9790ae47..dbadca96 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Today", "Yesterday": "Yesterday", "Recent 7 days": "Recent 7 days", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/es.json b/src/locales/es.json index 3666c947..0a723063 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Hoy", "Yesterday": "Ayer", "Recent 7 days": "Últimos 7 días", @@ -1750,6 +1752,7 @@ "Remove Query": "Eliminar Consulta", "Modify Query Name": "Modificar el Nombre de la Consulta", "Add Condition": "Añadir Condición", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Eliminar Condición", "No conditions defined. All transactions will match.": "Sin condiciones definidas. Todas las transacciones coincidirán.", "Unable to retrieve explorer list": "No se puede recuperar la lista de exploraciones", diff --git a/src/locales/fr.json b/src/locales/fr.json index b678e7d4..62a4a77a 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Aujourd'hui", "Yesterday": "Hier", "Recent 7 days": "7 derniers jours", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/it.json b/src/locales/it.json index 42667dd5..8bbc574a 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Oggi", "Yesterday": "Ieri", "Recent 7 days": "Ultimi 7 giorni", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/ja.json b/src/locales/ja.json index 89063b17..439d75da 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "今日", "Yesterday": "昨日", "Recent 7 days": "直近7日間", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/kn.json b/src/locales/kn.json index 18003ca7..9950183b 100644 --- a/src/locales/kn.json +++ b/src/locales/kn.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "ಇಂದು", "Yesterday": "ನಿನ್ನೆ", "Recent 7 days": "ಇತ್ತೀಚಿನ 7 ದಿನಗಳು", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/ko.json b/src/locales/ko.json index 84ddb07b..c4c37fc4 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "오늘", "Yesterday": "어제", "Recent 7 days": "최근 7일", @@ -1750,6 +1752,7 @@ "Remove Query": "쿼리 제거", "Modify Query Name": "쿼리 이름 수정", "Add Condition": "조건 추가", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "조건 제거", "No conditions defined. All transactions will match.": "조건이 정의되지 않았습니다. 모든 거래가 일치합니다.", "Unable to retrieve explorer list": "탐색기 목록을 가져올 수 없습니다", diff --git a/src/locales/nl.json b/src/locales/nl.json index 7f8e4be6..b4804b6c 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Vandaag", "Yesterday": "Gisteren", "Recent 7 days": "Afgelopen 7 dagen", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index c7da70e2..3ecefad5 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Hoje", "Yesterday": "Ontem", "Recent 7 days": "Últimos 7 dias", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/ru.json b/src/locales/ru.json index fd09521a..e3d5eb08 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Сегодня", "Yesterday": "Вчера", "Recent 7 days": "Последние 7 дней", @@ -1750,6 +1752,7 @@ "Remove Query": "Удалить запрос", "Modify Query Name": "Изменить название запроса", "Add Condition": "Добавить условие", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Удалить условие", "No conditions defined. All transactions will match.": "Нет условий. Подходят все транзакции.", "Unable to retrieve explorer list": "Не возможно получить список исследований", diff --git a/src/locales/sl.json b/src/locales/sl.json index cc75e9a4..fdc9de01 100644 --- a/src/locales/sl.json +++ b/src/locales/sl.json @@ -1517,6 +1517,8 @@ "WHERE": "KJER", "AND": "IN", "OR": "ALI", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Danes", "Yesterday": "Včeraj", "Recent 7 days": "Zadnjih 7 dni", @@ -1750,6 +1752,7 @@ "Remove Query": "Odstrani poizvedbo", "Modify Query Name": "Spremeni ime poizvedbe", "Add Condition": "Dodaj pogoj", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Odstrani pogoj", "No conditions defined. All transactions will match.": "Ni določenih pogojev. Prikazane bodo vse transakcije.", "Unable to retrieve explorer list": "Seznama raziskovanj ni mogoče pridobiti", diff --git a/src/locales/ta.json b/src/locales/ta.json index 090298b4..f75a5ed0 100644 --- a/src/locales/ta.json +++ b/src/locales/ta.json @@ -1517,6 +1517,8 @@ "WHERE": "எங்கே", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "இன்று", "Yesterday": "நேற்று", "Recent 7 days": "சமீபத்திய 7 நாட்கள்", @@ -1750,6 +1752,7 @@ "Remove Query": "வினவல் நீக்கு", "Modify Query Name": "வினவல் பெயரை மாற்று", "Add Condition": "நிபந்தனை சேர்", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "நிபந்தனை நீக்கு", "No conditions defined. All transactions will match.": "நிபந்தனைகள் வரையறுக்கப்படவில்லை. அனைத்து பரிவர்த்தனைகளும் பொருந்தும்.", "Unable to retrieve explorer list": "ஆய்வுக்கருவி பட்டியல் பெற முடியவில்லை", diff --git a/src/locales/th.json b/src/locales/th.json index 3b9558f0..78da67c2 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "วันนี้", "Yesterday": "เมื่อวาน", "Recent 7 days": "7 วันที่ผ่านมา", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/tr.json b/src/locales/tr.json index b11b13a0..0046d2f9 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Bugün", "Yesterday": "Dün", "Recent 7 days": "Son 7 gün", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/uk.json b/src/locales/uk.json index 1ba23497..357fc84f 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Сьогодні", "Yesterday": "Вчора", "Recent 7 days": "Останні 7 днів", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/vi.json b/src/locales/vi.json index 864fb567..b0b3cd85 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -1517,6 +1517,8 @@ "WHERE": "WHERE", "AND": "AND", "OR": "OR", + "AND SUB": "AND SUB", + "OR SUB": "OR SUB", "Today": "Hôm nay", "Yesterday": "Hôm qua", "Recent 7 days": "7 ngày gần đây", @@ -1750,6 +1752,7 @@ "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", "Add Condition": "Add Condition", + "Add Sub Condition": "Add Sub Condition", "Remove Condition": "Remove Condition", "No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.", "Unable to retrieve explorer list": "Unable to retrieve explorer list", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index c2a1dce2..588ce4c4 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1517,6 +1517,8 @@ "WHERE": "条件", "AND": "与", "OR": "或", + "AND SUB": "与子条件", + "OR SUB": "或子条件", "Today": "今天", "Yesterday": "昨天", "Recent 7 days": "最近7天", @@ -1750,6 +1752,7 @@ "Remove Query": "移除查询", "Modify Query Name": "修改查询名称", "Add Condition": "添加条件", + "Add Sub Condition": "添加子条件", "Remove Condition": "移除条件", "No conditions defined. All transactions will match.": "没有定义条件。所有交易都会匹配。", "Unable to retrieve explorer list": "无法获取探索列表", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index ed89c765..9632f097 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -1517,6 +1517,8 @@ "WHERE": "條件", "AND": "且", "OR": "或", + "AND SUB": "且子條件", + "OR SUB": "或子條件", "Today": "今天", "Yesterday": "昨天", "Recent 7 days": "最近7天", @@ -1750,6 +1752,7 @@ "Remove Query": "移除查詢", "Modify Query Name": "修改查詢名稱", "Add Condition": "新增條件", + "Add Sub Condition": "新增子條件", "Remove Condition": "移除條件", "No conditions defined. All transactions will match.": "沒有定義條件。所有交易都符合。", "Unable to retrieve explorer list": "無法取得探索清單", diff --git a/src/models/explorer.ts b/src/models/explorer.ts index 2e3af52c..78c30c5a 100644 --- a/src/models/explorer.ts +++ b/src/models/explorer.ts @@ -4,7 +4,9 @@ import { AccountType } from '@/core/account.ts'; import { TransactionType } from '@/core/transaction.ts'; import { ChartSortingType } from '@/core/statistics.ts'; import { + type TransactionExplorerSubConditionStartRelation, TransactionExplorerConditionRelation, + TransactionExplorerSubConditionStartRelationPlaceholder, TransactionExplorerConditionRelationPriority, TransactionExplorerConditionFieldType, TransactionExplorerConditionField, @@ -339,6 +341,10 @@ export class TransactionExplorerQuery { ); } + public addSubConditionEnd(): TransactionExplorerConditionWithRelation { + return new TransactionExplorerConditionWithRelation(new TransactionExplorerUndefinedCondition(), TransactionExplorerConditionRelation.SubEnd); + } + public match(transaction: TransactionInsightDataItem): boolean { if (!this.conditions || this.conditions.length < 1) { return true; @@ -434,7 +440,7 @@ export class TransactionExplorerQuery { return finalTokens; } - const operatorStack: TransactionExplorerConditionRelation[] = []; + const operatorStack: (TransactionExplorerConditionRelation | TransactionExplorerSubConditionStartRelation)[] = []; const firstCondition = this.conditions[0] as TransactionExplorerConditionWithRelation; if (firstCondition.relation !== TransactionExplorerConditionRelation.First) { @@ -452,22 +458,70 @@ export class TransactionExplorerQuery { throw new Error('only the first condition can have relation "first"'); } - const currentOperator = item.relation; + if (item.relation === TransactionExplorerConditionRelation.SubEnd) { + while (operatorStack.length > 0) { + const topOperator = operatorStack.pop(); - while (operatorStack.length > 0) { - const topOperator = operatorStack[operatorStack.length - 1]; - const isAndOrOperator = topOperator === TransactionExplorerConditionRelation.And || topOperator === TransactionExplorerConditionRelation.Or; + if (topOperator === TransactionExplorerSubConditionStartRelationPlaceholder) { + break; + } - if (isAndOrOperator && TransactionExplorerConditionRelationPriority[topOperator] >= TransactionExplorerConditionRelationPriority[currentOperator]) { - finalTokens.push(topOperator); - operatorStack.pop(); - } else { - break; + const isAndOrOperator = topOperator === TransactionExplorerConditionRelation.And || topOperator === TransactionExplorerConditionRelation.Or; + + if (isAndOrOperator) { + finalTokens.push(topOperator); + } else { + throw new Error('invalid operator in stack'); + } } - } + } else { // And, Or, AndSub, OrSub + let currentOperator: TransactionExplorerConditionRelation.And | TransactionExplorerConditionRelation.Or; + let startNewSubCondition = false; - operatorStack.push(currentOperator); - finalTokens.push(item.condition); + switch (item.relation) { + case TransactionExplorerConditionRelation.AndSub: + currentOperator = TransactionExplorerConditionRelation.And; + startNewSubCondition = true; + break; + case TransactionExplorerConditionRelation.OrSub: + currentOperator = TransactionExplorerConditionRelation.Or; + startNewSubCondition = true; + break; + case TransactionExplorerConditionRelation.And: + currentOperator = item.relation; + break; + case TransactionExplorerConditionRelation.Or: + currentOperator = item.relation; + break; + default: + throw new Error('invalid operator in stack'); + } + + while (operatorStack.length > 0) { + const topOperator = operatorStack[operatorStack.length - 1]; + + if (topOperator === TransactionExplorerSubConditionStartRelationPlaceholder) { + break; + } + + const isAndOrOperator = topOperator === TransactionExplorerConditionRelation.And || topOperator === TransactionExplorerConditionRelation.Or; + + if (isAndOrOperator && TransactionExplorerConditionRelationPriority[topOperator] >= TransactionExplorerConditionRelationPriority[currentOperator]) { + finalTokens.push(topOperator); + operatorStack.pop(); + } else { + break; + } + } + + operatorStack.push(currentOperator); + + if (startNewSubCondition) { + operatorStack.push(TransactionExplorerSubConditionStartRelationPlaceholder); + } + + finalTokens.push(item.condition); + } } while (operatorStack.length > 0) { @@ -483,6 +537,25 @@ export class TransactionExplorerQuery { return finalTokens; } + public getConditionNestingDepths(): number[] { + const depths: number[] = []; + let depth = 0; + + for (const item of this.conditions) { + if (item.relation === TransactionExplorerConditionRelation.SubEnd) { + depth--; + depths.push(depth); + } else if (item.relation === TransactionExplorerConditionRelation.AndSub || item.relation === TransactionExplorerConditionRelation.OrSub) { + depth++; + depths.push(depth); + } else { + depths.push(depth); + } + } + + return depths; + } + public clone(newId: string): TransactionExplorerQuery { const clonedConditions: TransactionExplorerConditionWithRelation[] = []; @@ -527,6 +600,7 @@ export class TransactionExplorerQuery { const id: string = idFieldValue; const name: string = nameFieldValue; const conditions: TransactionExplorerConditionWithRelation[] = []; + let conditionDepth = 0; for (const [item, index] of itemAndIndex(conditionsFieldValue)) { const condition = TransactionExplorerConditionWithRelation.parse(item); @@ -541,9 +615,20 @@ export class TransactionExplorerQuery { return null; } + if (condition.relation === TransactionExplorerConditionRelation.AndSub || + condition.relation === TransactionExplorerConditionRelation.OrSub) { + conditionDepth++; + } else if (condition.relation === TransactionExplorerConditionRelation.SubEnd) { + conditionDepth--; + } + conditions.push(condition); } + if (conditionDepth !== 0) { + return null; // unbalanced parentheses + } + return new TransactionExplorerQuery(id, name, conditions); } } @@ -609,6 +694,12 @@ export class TransactionExplorerConditionWithRelation { } public toJsonObject(): unknown { + if (this.relation === TransactionExplorerConditionRelation.SubEnd) { + return { + relation: this.relation + }; + } + return { condition: { field: this.condition.field, @@ -620,86 +711,94 @@ export class TransactionExplorerConditionWithRelation { } public static parse(data: unknown): TransactionExplorerConditionWithRelation | null { - if (typeof data !== 'object' || !data || !('condition' in data) || !('relation' in data)) { + if (typeof data !== 'object' || !data || !('relation' in data)) { return null; } - const conditionObject = data['condition']; const relation = data['relation']; - - if (typeof conditionObject !== 'object' || !conditionObject || !('field' in conditionObject) || !('operator' in conditionObject) || !('value' in conditionObject) || typeof relation !== 'string') { - return null; - } - - const conditionField = conditionObject['field']; - const conditionOperator = conditionObject['operator'] as TransactionExplorerConditionOperatorType; - const conditionValue = conditionObject['value']; - let condition: TransactionExplorerCondition | null = null; - switch (conditionField) { - case TransactionExplorerConditionField.TransactionType.value: - if (conditionOperator === TransactionExplorerConditionOperatorType.In && Array.isArray(conditionValue)) { - condition = new TransactionExplorerTransactionTypeCondition(conditionValue as number[]); - } - break; - case TransactionExplorerConditionField.TransactionCategory.value: - if (conditionOperator === TransactionExplorerConditionOperatorType.In && Array.isArray(conditionValue)) { - condition = new TransactionExplorerTransactionCategoryCondition(conditionValue as string[]); - } - break; - case TransactionExplorerConditionField.SourceAccount.value: - if (conditionOperator === TransactionExplorerConditionOperatorType.In && Array.isArray(conditionValue)) { - condition = new TransactionExplorerSourceAccountCondition(conditionValue as string[]); - } - break; - case TransactionExplorerConditionField.DestinationAccount.value: - if (conditionOperator === TransactionExplorerConditionOperatorType.In && Array.isArray(conditionValue)) { - condition = new TransactionExplorerDestinationAccountCondition(conditionValue as string[]); - } - break; - case TransactionExplorerConditionField.SourceAmount.value: - if (TransactionExplorerSourceAmountCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue) && conditionValue.length === 2) { - condition = new TransactionExplorerSourceAmountCondition(conditionOperator as AmountConditionOperator, conditionValue as [number, number]); - } - break; - case TransactionExplorerConditionField.DestinationAmount.value: - if (TransactionExplorerDestinationAmountCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue) && conditionValue.length === 2) { - condition = new TransactionExplorerDestinationAmountCondition(conditionOperator as AmountConditionOperator, conditionValue as [number, number]); - } - break; - case TransactionExplorerConditionField.GeoLocation.value: - if (TransactionExplorerGeoLocationCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) { - condition = new TransactionExplorerGeoLocationCondition(conditionOperator as GeoLocationConditionOperator, conditionValue as [number, number, number, number]); - } - break; - case TransactionExplorerConditionField.TransactionTag.value: - if (TransactionExplorerTransactionTagCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) { - condition = new TransactionExplorerTransactionTagCondition(conditionOperator as TransactionTagConditionOperator, conditionValue as string[]); - } - break; - case TransactionExplorerConditionField.Pictures.value: - if (TransactionExplorerPicturesCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) { - condition = new TransactionExplorerPicturesCondition(conditionOperator as PicturesConditionOperator, conditionValue as string[]); - } - break; - case TransactionExplorerConditionField.Description.value: - if (TransactionExplorerDescriptionCondition.supportedOperators[conditionOperator] && typeof conditionValue === 'string') { - condition = new TransactionExplorerDescriptionCondition(conditionOperator as DescriptionConditionOperator, conditionValue); - } - break; - default: - break; + if (relation === TransactionExplorerConditionRelation.First || + relation === TransactionExplorerConditionRelation.And || relation === TransactionExplorerConditionRelation.Or || + relation === TransactionExplorerConditionRelation.AndSub || relation === TransactionExplorerConditionRelation.OrSub) { + if (!('condition' in data)) { + return null; + } + + const conditionObject = data['condition']; + + if (typeof conditionObject !== 'object' || !conditionObject || !('field' in conditionObject) || !('operator' in conditionObject) || !('value' in conditionObject) || typeof relation !== 'string') { + return null; + } + + const conditionField = conditionObject['field']; + const conditionOperator = conditionObject['operator'] as TransactionExplorerConditionOperatorType; + const conditionValue = conditionObject['value']; + + switch (conditionField) { + case TransactionExplorerConditionField.TransactionType.value: + if (conditionOperator === TransactionExplorerConditionOperatorType.In && Array.isArray(conditionValue)) { + condition = new TransactionExplorerTransactionTypeCondition(conditionValue as number[]); + } + break; + case TransactionExplorerConditionField.TransactionCategory.value: + if (conditionOperator === TransactionExplorerConditionOperatorType.In && Array.isArray(conditionValue)) { + condition = new TransactionExplorerTransactionCategoryCondition(conditionValue as string[]); + } + break; + case TransactionExplorerConditionField.SourceAccount.value: + if (conditionOperator === TransactionExplorerConditionOperatorType.In && Array.isArray(conditionValue)) { + condition = new TransactionExplorerSourceAccountCondition(conditionValue as string[]); + } + break; + case TransactionExplorerConditionField.DestinationAccount.value: + if (conditionOperator === TransactionExplorerConditionOperatorType.In && Array.isArray(conditionValue)) { + condition = new TransactionExplorerDestinationAccountCondition(conditionValue as string[]); + } + break; + case TransactionExplorerConditionField.SourceAmount.value: + if (TransactionExplorerSourceAmountCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue) && conditionValue.length === 2) { + condition = new TransactionExplorerSourceAmountCondition(conditionOperator as AmountConditionOperator, conditionValue as [number, number]); + } + break; + case TransactionExplorerConditionField.DestinationAmount.value: + if (TransactionExplorerDestinationAmountCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue) && conditionValue.length === 2) { + condition = new TransactionExplorerDestinationAmountCondition(conditionOperator as AmountConditionOperator, conditionValue as [number, number]); + } + break; + case TransactionExplorerConditionField.GeoLocation.value: + if (TransactionExplorerGeoLocationCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) { + condition = new TransactionExplorerGeoLocationCondition(conditionOperator as GeoLocationConditionOperator, conditionValue as [number, number, number, number]); + } + break; + case TransactionExplorerConditionField.TransactionTag.value: + if (TransactionExplorerTransactionTagCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) { + condition = new TransactionExplorerTransactionTagCondition(conditionOperator as TransactionTagConditionOperator, conditionValue as string[]); + } + break; + case TransactionExplorerConditionField.Pictures.value: + if (TransactionExplorerPicturesCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) { + condition = new TransactionExplorerPicturesCondition(conditionOperator as PicturesConditionOperator, conditionValue as string[]); + } + break; + case TransactionExplorerConditionField.Description.value: + if (TransactionExplorerDescriptionCondition.supportedOperators[conditionOperator] && typeof conditionValue === 'string') { + condition = new TransactionExplorerDescriptionCondition(conditionOperator as DescriptionConditionOperator, conditionValue); + } + break; + default: + break; + } + } else if (relation === TransactionExplorerConditionRelation.SubEnd) { + condition = new TransactionExplorerUndefinedCondition(); + } else { + return null; } if (condition === null) { return null; } - if (relation !== TransactionExplorerConditionRelation.First && relation !== TransactionExplorerConditionRelation.And && relation !== TransactionExplorerConditionRelation.Or) { - return null; - } - return new TransactionExplorerConditionWithRelation(condition, relation); } } @@ -714,6 +813,24 @@ export interface TransactionExplorerCondition, allAccountsMap: Record, allTagsMap: Record): string; } +export class TransactionExplorerUndefinedCondition implements TransactionExplorerCondition { + public readonly field = TransactionExplorerConditionFieldType.Undefined; + public readonly operator = TransactionExplorerConditionOperatorType.Equals; + public value = ''; + + public getValueForStore(): string { + return this.value; + } + + public match(transaction: TransactionInsightDataItem): boolean { + return !!transaction; + } + + public toExpression(): string { + return ''; + } +} + export class TransactionExplorerTransactionTypeCondition implements TransactionExplorerCondition { public static readonly supportedOperators: PartialRecord = { [TransactionExplorerConditionOperatorType.In]: true diff --git a/src/views/desktop/insights/tabs/ExplorerQueryTab.vue b/src/views/desktop/insights/tabs/ExplorerQueryTab.vue index c18f2b4c..1b81ded7 100644 --- a/src/views/desktop/insights/tabs/ExplorerQueryTab.vue +++ b/src/views/desktop/insights/tabs/ExplorerQueryTab.vue @@ -98,11 +98,13 @@
-
+
@@ -335,6 +340,16 @@ {{ tt('Remove Condition') }}
+
+ + {{ tt('Add Sub Condition') }} + +
@@ -541,6 +556,25 @@ function getFilteredTransactionCategoriesDisplayContent(filterTransactionCategor return joinMultiText(selectedCategoryNames); } +function getConditionStyle(query: TransactionExplorerQuery, conditionIndex: number): Record { + const style: Record = {}; + const depths = query.getConditionNestingDepths(); + const item = query.conditions[conditionIndex]; + let depth = 0; + + if (item && item.relation === TransactionExplorerConditionRelation.SubEnd) { + depth = depths[conditionIndex - 1] ?? 0; + } else { + depth = depths[conditionIndex] ?? 0; + } + + if (depth > 0) { + style['margin-inline-start'] = (depth * 1.5) + 'rem'; + } + + return style; +} + function addQuery(): void { queries.value.push(TransactionExplorerQuery.create(generateRandomUUID())); } @@ -584,16 +618,88 @@ function addCondition(query: TransactionExplorerQuery): void { query.conditions.push(newCondition); } +function addSubCondition(query: TransactionExplorerQuery, subEndIndex: number): void { + const newCondition = query.addNewCondition(TransactionExplorerConditionField.TransactionType, false); + query.conditions.splice(subEndIndex, 0, newCondition); +} + function removeCondition(query: TransactionExplorerQuery, conditionIndex: number): void { + const item = query.conditions[conditionIndex]; + + if (!item || item.relation === TransactionExplorerConditionRelation.SubEnd) { + return; + } + query.conditions.splice(conditionIndex, 1); if (conditionIndex === 0 && query.conditions.length > 0) { const newFirstCondition = query.conditions[0]; if (newFirstCondition) { + if (newFirstCondition.relation === TransactionExplorerConditionRelation.AndSub || newFirstCondition.relation === TransactionExplorerConditionRelation.OrSub) { + removeSubCondition(query, conditionIndex + 1); + } + newFirstCondition.relation = TransactionExplorerConditionRelation.First; } } + + const oldStartSubCondition = item.relation === TransactionExplorerConditionRelation.AndSub || item.relation === TransactionExplorerConditionRelation.OrSub; + + if (oldStartSubCondition && conditionIndex < query.conditions.length && query.conditions[conditionIndex]) { + const nextItem = query.conditions[conditionIndex]; + + if (nextItem.relation === TransactionExplorerConditionRelation.SubEnd) { + query.conditions.splice(conditionIndex, 1); + } else if (nextItem.relation === TransactionExplorerConditionRelation.AndSub || nextItem.relation === TransactionExplorerConditionRelation.OrSub) { + nextItem.relation = item.relation; + removeSubCondition(query, conditionIndex); + } else { + nextItem.relation = item.relation; + } + } +} + +function removeSubCondition(query: TransactionExplorerQuery, conditionIndex: number): void { + let depth = 1; + + for (let i = conditionIndex + 1; i < query.conditions.length; i++) { + const currentCondition = query.conditions[i]; + + if (!currentCondition) { + continue; + } + + if (currentCondition.relation === TransactionExplorerConditionRelation.AndSub || currentCondition.relation === TransactionExplorerConditionRelation.OrSub) { + depth++; + } else if (currentCondition.relation === TransactionExplorerConditionRelation.SubEnd) { + depth--; + + if (depth === 0) { + query.conditions.splice(i, 1); + break; + } + } + } +} + +function updateItemRelation(query: TransactionExplorerQuery, conditionIndex: number, value: TransactionExplorerConditionRelation): void { + const item = query.conditions[conditionIndex]; + + if (!item || item.relation === TransactionExplorerConditionRelation.SubEnd) { + return; + } + + const oldStartSubCondition = item.relation === TransactionExplorerConditionRelation.AndSub || item.relation === TransactionExplorerConditionRelation.OrSub; + const newStartSubCondition = value === TransactionExplorerConditionRelation.AndSub || value === TransactionExplorerConditionRelation.OrSub; + + item.relation = value; + + if (!oldStartSubCondition && newStartSubCondition) { + query.conditions.splice(conditionIndex + 1, 0, query.addSubConditionEnd()); + } else if (oldStartSubCondition && !newStartSubCondition) { + removeSubCondition(query, conditionIndex); + } } function updateConditionField(query: TransactionExplorerQuery, conditionIndex: number, newField: TransactionExplorerConditionField | undefined): void {