diff --git a/src/models/explorer.ts b/src/models/explorer.ts index 2efeb280..45405b5c 100644 --- a/src/models/explorer.ts +++ b/src/models/explorer.ts @@ -28,7 +28,7 @@ export class InsightsExplorerBasicInfo implements InsightsExplorerInfoResponse { public name: string; public displayOrder: number; public hidden: boolean; - public data: Record = {}; + public data: Record = {}; private constructor(id: string, name: string, displayOrder: number, hidden: boolean) { this.id = id; @@ -98,9 +98,9 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { this.chartSortingType = chartSortingType; } - public get data(): Record { + public get data(): Record { return { - queries: this.queries.map(q => q.toJson()), + queries: this.queries.map(q => q.toJsonObject()), timezoneUsedForDateRange: this.timezoneUsedForDateRange, chartType: this.chartType, categoryDimension: this.categoryDimension, @@ -139,10 +139,10 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { if (data) { if (Array.isArray(data['queries'])) { - const textualQueries = data['queries'] as string[]; + const queryItems = data['queries'] as object[]; - for (const textualQuery of textualQueries) { - const query = TransactionExplorerQuery.parse(textualQuery); + for (const queryItem of queryItems) { + const query = TransactionExplorerQuery.parse(queryItem); if (query) { queries.push(query); @@ -190,13 +190,13 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { ); } - public static createNewExplorer(name?: string): InsightsExplorer { + public static createNewExplorer(newQueryId: string): InsightsExplorer { return new InsightsExplorer( '', - name || '', + '', 0, false, - [TransactionExplorerQuery.create()], + [TransactionExplorerQuery.create(newQueryId)], InsightsExplorer.Default.timezoneUsedForDateRange, InsightsExplorer.Default.chartType, InsightsExplorer.Default.categoryDimension, @@ -209,14 +209,14 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { export interface InsightsExplorerCreateRequest { readonly name: string; - readonly data: Record; + readonly data: Record; readonly clientSessionId?: string; } export interface InsightsExplorerModifyRequest { readonly id: string; readonly name: string; - readonly data: Record; + readonly data: Record; readonly hidden: boolean; readonly clientSessionId?: string; } @@ -244,7 +244,7 @@ export interface InsightsExplorerInfoResponse { readonly name: string; readonly displayOrder: number; readonly hidden: boolean; - readonly data: Record; + readonly data: Record; } interface ExpressionNode { @@ -253,10 +253,12 @@ interface ExpressionNode { } export class TransactionExplorerQuery { + public id: string; public name: string; public conditions: TransactionExplorerConditionWithRelation[]; - private constructor(name: string, conditions: TransactionExplorerConditionWithRelation[]) { + private constructor(id: string, name: string, conditions: TransactionExplorerConditionWithRelation[]) { + this.id = id; this.name = name; this.conditions = conditions; } @@ -450,7 +452,7 @@ export class TransactionExplorerQuery { return finalTokens; } - public clone(): TransactionExplorerQuery { + public clone(newId: string): TransactionExplorerQuery { const clonedConditions: TransactionExplorerConditionWithRelation[] = []; for (const condition of this.conditions) { @@ -463,29 +465,35 @@ export class TransactionExplorerQuery { clonedConditions.push(clonedCondition); } - return new TransactionExplorerQuery(this.name, clonedConditions); + return new TransactionExplorerQuery(newId, this.name, clonedConditions); } - public toJson(): string { - return JSON.stringify({ + public toJsonObject(): object { + return { + id: this.id, name: this.name, conditions: this.conditions.map(condition => condition.toJsonObject()) - }); + }; } - public static create(): TransactionExplorerQuery { - return new TransactionExplorerQuery("", []); + public static create(id: string): TransactionExplorerQuery { + return new TransactionExplorerQuery(id, "", []); } - public static parse(json: string): TransactionExplorerQuery | null { - const parsed = JSON.parse(json); - const nameFieldValue = parsed['name'] as unknown; - const conditionsFieldValue = parsed['conditions'] as unknown; - - if (typeof nameFieldValue !== 'string' || !Array.isArray(conditionsFieldValue)) { + public static parse(obj: object): TransactionExplorerQuery | null { + if (!('id' in obj) || !('name' in obj) || !('conditions' in obj)) { return null; } + const idFieldValue = obj['id'] as unknown; + const nameFieldValue = obj['name'] as unknown; + const conditionsFieldValue = obj['conditions'] as unknown; + + if (typeof idFieldValue !== 'string' || typeof nameFieldValue !== 'string' || !Array.isArray(conditionsFieldValue)) { + return null; + } + + const id: string = idFieldValue; const name: string = nameFieldValue; const conditions: TransactionExplorerConditionWithRelation[] = []; @@ -505,7 +513,7 @@ export class TransactionExplorerQuery { conditions.push(condition); } - return new TransactionExplorerQuery(name, conditions); + return new TransactionExplorerQuery(id, name, conditions); } } diff --git a/src/stores/explorer.ts b/src/stores/explorer.ts index 89ac54d7..9f54d1a1 100644 --- a/src/stores/explorer.ts +++ b/src/stores/explorer.ts @@ -48,6 +48,7 @@ import { getDateRangeByDateType, getFiscalYearFromUnixTime } from '@/lib/datetime.ts'; +import { generateRandomUUID } from '@/lib/misc.ts'; import services, { type ApiResponsePromise } from '@/lib/services.ts'; import logger from '@/lib/logger.ts'; @@ -473,7 +474,7 @@ export const useExplorersStore = defineStore('explorers', () => { const allInsightsExplorerBasicInfos = ref([]); const allInsightsExplorerBasicInfosMap = ref>({}); - const currentInsightsExplorer = ref(InsightsExplorer.createNewExplorer()); + const currentInsightsExplorer = ref(InsightsExplorer.createNewExplorer(generateRandomUUID())); const insightsExplorerListStateInvalid = ref(true); const allTransactions = computed(() => { @@ -743,7 +744,7 @@ export const useExplorersStore = defineStore('explorers', () => { transactionExplorerFilter.value.startTime = 0; transactionExplorerFilter.value.endTime = 0; transactionExplorerAllData.value = []; - currentInsightsExplorer.value = InsightsExplorer.createNewExplorer(); + currentInsightsExplorer.value = InsightsExplorer.createNewExplorer(generateRandomUUID()); transactionExplorerStateInvalid.value = true; } @@ -784,7 +785,7 @@ export const useExplorersStore = defineStore('explorers', () => { } if (resetQuery) { - currentInsightsExplorer.value = InsightsExplorer.createNewExplorer(); + currentInsightsExplorer.value = InsightsExplorer.createNewExplorer(generateRandomUUID()); } } diff --git a/src/views/desktop/insights/ExplorerPage.vue b/src/views/desktop/insights/ExplorerPage.vue index 6f4c0c0e..dace0ffa 100644 --- a/src/views/desktop/insights/ExplorerPage.vue +++ b/src/views/desktop/insights/ExplorerPage.vue @@ -340,7 +340,7 @@ function init(initProps: InsightsExplorerProps): void { loadExplorer(initProps.initId); } } else { - explorersStore.updateCurrentInsightsExplorer(InsightsExplorer.createNewExplorer()); + explorersStore.updateCurrentInsightsExplorer(InsightsExplorer.createNewExplorer(generateRandomUUID())); } if (!needReload && !explorersStore.transactionExplorerStateInvalid && !explorersStore.insightsExplorerListStateInvalid) { @@ -394,7 +394,7 @@ function createNewExplorer(): void { return; } - explorersStore.updateCurrentInsightsExplorer(InsightsExplorer.createNewExplorer()); + explorersStore.updateCurrentInsightsExplorer(InsightsExplorer.createNewExplorer(generateRandomUUID())); router.push(getFilterLinkUrl()); } diff --git a/src/views/desktop/insights/tabs/ExplorerQueryTab.vue b/src/views/desktop/insights/tabs/ExplorerQueryTab.vue index 3f27c29f..0e9f6616 100644 --- a/src/views/desktop/insights/tabs/ExplorerQueryTab.vue +++ b/src/views/desktop/insights/tabs/ExplorerQueryTab.vue @@ -10,340 +10,345 @@ @click="clearAllQueries">{{ tt('Clear All') }} - -
- - - - {{ query.name || tt('format.misc.queryIndex', { index: queryIndex + 1 }) }} -
- - -
- - - {{ tt('Update') }} - - - - {{ tt('Cancel') }} - - - - {{ tt('Modify Query Name') }} - - - - {{ tt('Duplicate') }} - - - - - - - - {{ tt('Remove Query') }} - -
+ + + + @@ -407,6 +412,8 @@ import { arrayItemToObjectField } from '@/lib/common.ts'; +import { generateRandomUUID } from '@/lib/misc.ts'; + import logger from '@/lib/logger.ts'; import { @@ -416,6 +423,7 @@ import { mdiContentCopy, mdiCheck, mdiClose, + mdiDrag, mdiPound } from '@mdi/js'; @@ -444,7 +452,7 @@ const explorersStore = useExplorersStore(); const snackbar = useTemplateRef('snackbar'); const currentCondition = ref(undefined); -const showExpression = ref>({}); +const showExpression = ref>({}); const showFilterSourceAccountsDialog = ref(false); const showFilterDestinationAccountsDialog = ref(false); const showFilterTransactionCategoriesDialog = ref(false); @@ -452,7 +460,12 @@ const tagSearchContent = ref(''); const editingQuery = ref(undefined); const editingQueryName = ref(''); -const queries = computed(() => explorersStore.currentInsightsExplorer.queries); +const queries = computed({ + get: () => explorersStore.currentInsightsExplorer.queries, + set: (value: TransactionExplorerQuery[]) => { + explorersStore.currentInsightsExplorer.queries = value; + } +}); const defaultCurrency = computed(() => userStore.currentUserDefaultCurrency); const hasAnyAccount = computed(() => accountsStore.allPlainAccounts.length > 0); @@ -545,7 +558,7 @@ function getFilteredTransactionCategoriesDisplayContent(filterTransactionCategor } function addQuery(): void { - queries.value.push(TransactionExplorerQuery.create()); + queries.value.push(TransactionExplorerQuery.create(generateRandomUUID())); } function updateQueryName(query: TransactionExplorerQuery): void { @@ -560,7 +573,7 @@ function cancelUpdateQueryName(): void { } function duplicateQuery(query: TransactionExplorerQuery): void { - queries.value.push(query.clone()); + queries.value.push(query.clone(generateRandomUUID())); } function removeQuery(queryIndex: number): void { @@ -583,33 +596,21 @@ function removeQuery(queryIndex: number): void { showExpression.value = newShowExpression; if (queries.value.length < 1) { - queries.value.push(TransactionExplorerQuery.create()); + queries.value.push(TransactionExplorerQuery.create(generateRandomUUID())); } } function clearAllQueries(): void { queries.value.length = 0; - queries.value.push(TransactionExplorerQuery.create()); + queries.value.push(TransactionExplorerQuery.create(generateRandomUUID())); } -function addCondition(queryIndex: number): void { - const query = queries.value[queryIndex]; - - if (!query) { - return; - } - +function addCondition(query: TransactionExplorerQuery): void { const newCondition = query.addNewCondition(TransactionExplorerConditionField.TransactionType, query.conditions.length < 1); query.conditions.push(newCondition); } -function removeCondition(queryIndex: number, conditionIndex: number): void { - const query = queries.value[queryIndex]; - - if (!query) { - return; - } - +function removeCondition(query: TransactionExplorerQuery, conditionIndex: number): void { query.conditions.splice(conditionIndex, 1); if (conditionIndex === 0 && query.conditions.length > 0) { @@ -621,17 +622,11 @@ function removeCondition(queryIndex: number, conditionIndex: number): void { } } -function updateConditionField(queryIndex: number, conditionIndex: number, newField: TransactionExplorerConditionField | undefined): void { +function updateConditionField(query: TransactionExplorerQuery, conditionIndex: number, newField: TransactionExplorerConditionField | undefined): void { if (!newField) { return; } - const query = queries.value[queryIndex]; - - if (!query) { - return; - } - const oldConditionWithRelation = query.conditions[conditionIndex]; if (!oldConditionWithRelation) { @@ -675,10 +670,8 @@ function updateTransactionCategories(changed: boolean, selectedCategoryIds?: str showFilterTransactionCategoriesDialog.value = false; } -function getExpression(queryIndex: number): string { - const query = queries.value[queryIndex]; - - if (!query || !query.conditions || query.conditions.length < 1) { +function getExpression(query: TransactionExplorerQuery, queryIndex: number): string { + if (!query.conditions || query.conditions.length < 1) { return ''; } @@ -692,7 +685,7 @@ function getExpression(queryIndex: number): string { } if (queries.value.length === 0) { - queries.value.push(TransactionExplorerQuery.create()); + queries.value.push(TransactionExplorerQuery.create(generateRandomUUID())); }