diff --git a/pkg/models/user_app_cloud_setting.go b/pkg/models/user_app_cloud_setting.go index 239033c7..6e904b35 100644 --- a/pkg/models/user_app_cloud_setting.go +++ b/pkg/models/user_app_cloud_setting.go @@ -29,9 +29,9 @@ var ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES = map[string]UserApplicationClo "autoSaveTransactionDraft": USER_APPLICATION_CLOUD_SETTING_TYPE_STRING, "autoGetCurrentGeoLocation": USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN, "alwaysShowTransactionPicturesInMobileTransactionEditPage": USER_APPLICATION_CLOUD_SETTING_TYPE_BOOLEAN, - // Insights & Explore Page - "insightsExploreDefaultDateRangeType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER, - "timezoneUsedForInsightsExplorePage": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER, + // Insights Explorer Page + "insightsExplorerDefaultDateRangeType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER, + "timezoneUsedForInsightsExplorerPage": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER, // Account List Page "totalAmountExcludeAccountIds": USER_APPLICATION_CLOUD_SETTING_TYPE_STRING_BOOLEAN_MAP, // Exchange Rates Data Page diff --git a/src/core/datetime.ts b/src/core/datetime.ts index c98c10f9..c1e57cfa 100644 --- a/src/core/datetime.ts +++ b/src/core/datetime.ts @@ -593,7 +593,7 @@ export enum DateRangeScene { Normal = 0, TrendAnalysis = 1, AssetTrends = 2, - InsightsExplore = 3 + InsightsExplorer = 3 } export class DateRange implements TypeAndName { @@ -601,38 +601,38 @@ export class DateRange implements TypeAndName { private static readonly allInstancesByType: Record = {}; // All date range - public static readonly All = new DateRange(0, 'All', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); + public static readonly All = new DateRange(0, 'All', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); // Date ranges for normal scene only - public static readonly Today = new DateRange(1, 'Today', false, false, DateRangeScene.Normal, DateRangeScene.InsightsExplore); - public static readonly Yesterday = new DateRange(2, 'Yesterday', false, false, DateRangeScene.Normal, DateRangeScene.InsightsExplore); - public static readonly LastSevenDays = new DateRange(3, 'Recent 7 days', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly LastThirtyDays = new DateRange(4, 'Recent 30 days', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly ThisWeek = new DateRange(5, 'This week', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly LastWeek = new DateRange(6, 'Last week', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly ThisMonth = new DateRange(7, 'This month', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly LastMonth = new DateRange(8, 'Last month', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); + public static readonly Today = new DateRange(1, 'Today', false, false, DateRangeScene.Normal, DateRangeScene.InsightsExplorer); + public static readonly Yesterday = new DateRange(2, 'Yesterday', false, false, DateRangeScene.Normal, DateRangeScene.InsightsExplorer); + public static readonly LastSevenDays = new DateRange(3, 'Recent 7 days', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly LastThirtyDays = new DateRange(4, 'Recent 30 days', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly ThisWeek = new DateRange(5, 'This week', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly LastWeek = new DateRange(6, 'Last week', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly ThisMonth = new DateRange(7, 'This month', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly LastMonth = new DateRange(8, 'Last month', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); // Date ranges for normal and trend analysis scene - public static readonly ThisYear = new DateRange(9, 'This year', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly LastYear = new DateRange(10, 'Last year', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly ThisFiscalYear = new DateRange(11, 'This fiscal year', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly LastFiscalYear = new DateRange(12, 'Last fiscal year', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); + public static readonly ThisYear = new DateRange(9, 'This year', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly LastYear = new DateRange(10, 'Last year', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly ThisFiscalYear = new DateRange(11, 'This fiscal year', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly LastFiscalYear = new DateRange(12, 'Last fiscal year', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); // Billing cycle date ranges for normal scene only public static readonly CurrentBillingCycle = new DateRange(51, 'Current Billing Cycle', true, true, DateRangeScene.Normal); public static readonly PreviousBillingCycle = new DateRange(52, 'Previous Billing Cycle', true, true, DateRangeScene.Normal); // Date ranges for trend analysis scene only - public static readonly RecentTwelveMonths = new DateRange(101, 'Recent 12 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly RecentTwentyFourMonths = new DateRange(102, 'Recent 24 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly RecentThirtySixMonths = new DateRange(103, 'Recent 36 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly RecentTwoYears = new DateRange(104, 'Recent 2 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly RecentThreeYears = new DateRange(105, 'Recent 3 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); - public static readonly RecentFiveYears = new DateRange(106, 'Recent 5 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); + public static readonly RecentTwelveMonths = new DateRange(101, 'Recent 12 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly RecentTwentyFourMonths = new DateRange(102, 'Recent 24 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly RecentThirtySixMonths = new DateRange(103, 'Recent 36 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly RecentTwoYears = new DateRange(104, 'Recent 2 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly RecentThreeYears = new DateRange(105, 'Recent 3 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); + public static readonly RecentFiveYears = new DateRange(106, 'Recent 5 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); // Custom date range - public static readonly Custom = new DateRange(255, 'Custom Date', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplore); + public static readonly Custom = new DateRange(255, 'Custom Date', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends, DateRangeScene.InsightsExplorer); public readonly type: number; public readonly name: string; diff --git a/src/core/explore.ts b/src/core/explore.ts deleted file mode 100644 index db03b194..00000000 --- a/src/core/explore.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { type NameValue } from '@/core/base.ts'; -import { DateRange } from '@/core/datetime.ts'; - -export enum TransactionExploreConditionRelation { - First = 'first', - And = 'and', - Or = 'or' -} - -export const TransactionExploreConditionRelationPriority: Record = { - [TransactionExploreConditionRelation.First]: 0, - [TransactionExploreConditionRelation.Or]: 1, - [TransactionExploreConditionRelation.And]: 2 -}; - - -export enum TransactionExploreConditionFieldType { - TransactionType = 'transactionType', - TransactionCategory = 'transactionCategory', - SourceAccount = 'sourceAccount', - DestinationAccount = 'destinationAccount', - SourceAmount = 'sourceAmount', - DestinationAmount = 'destinationAmount', - TransactionTag = 'transactionTag', - Description = 'description' -} - -export class TransactionExploreConditionField implements NameValue { - private static readonly allInstances: TransactionExploreConditionField[] = []; - private static readonly allInstancesByValue: Record = {}; - - public static readonly TransactionType = new TransactionExploreConditionField('Transaction Type', TransactionExploreConditionFieldType.TransactionType); - public static readonly TransactionCategory = new TransactionExploreConditionField('Category', TransactionExploreConditionFieldType.TransactionCategory); - public static readonly SourceAccount = new TransactionExploreConditionField('Source Account', TransactionExploreConditionFieldType.SourceAccount); - public static readonly DestinationAccount = new TransactionExploreConditionField('Destination Account', TransactionExploreConditionFieldType.DestinationAccount); - public static readonly SourceAmount = new TransactionExploreConditionField('Amount', TransactionExploreConditionFieldType.SourceAmount); - public static readonly DestinationAmount = new TransactionExploreConditionField('Transfer In Amount', TransactionExploreConditionFieldType.DestinationAmount); - public static readonly TransactionTag = new TransactionExploreConditionField('Tags', TransactionExploreConditionFieldType.TransactionTag); - public static readonly Description = new TransactionExploreConditionField('Description', TransactionExploreConditionFieldType.Description); - - public readonly name: string; - public readonly value: TransactionExploreConditionFieldType; - - private constructor(name: string, value: TransactionExploreConditionFieldType) { - this.name = name; - this.value = value; - - TransactionExploreConditionField.allInstances.push(this); - TransactionExploreConditionField.allInstancesByValue[value] = this; - } - - public static values(): TransactionExploreConditionField[] { - return TransactionExploreConditionField.allInstances; - } - - public static valueOf(value: string): TransactionExploreConditionField | undefined { - return TransactionExploreConditionField.allInstancesByValue[value]; - } -} - -export enum TransactionExploreConditionOperatorType { - In = 'in', - GreaterThan = 'greaterThan', - LessThan = 'lessThan', - Equals = 'equals', - NotEquals = 'notEquals', - Between = 'between', - NotBetween = 'notBetween', - HasAny = 'hasAny', - HasAll = 'hasAll', - NotHasAny = 'notHasAny', - NotHasAll = 'notHasAll', - IsEmpty = 'isEmpty', - IsNotEmpty = 'isNotEmpty', - Contains = 'contains', - NotContains = 'notContains', - StartsWith = 'startsWith', - NotStartsWith = 'notStartsWith', - EndsWith = 'endsWith', - NotEndsWith = 'notEndsWith' -} - -export class TransactionExploreConditionOperator implements NameValue { - private static readonly allInstances: TransactionExploreConditionOperator[] = []; - private static readonly allInstancesByValue: Record = {}; - - public static readonly In = new TransactionExploreConditionOperator('In', TransactionExploreConditionOperatorType.In); - public static readonly GreaterThan = new TransactionExploreConditionOperator('Greater than', TransactionExploreConditionOperatorType.GreaterThan); - public static readonly LessThan = new TransactionExploreConditionOperator('Less than', TransactionExploreConditionOperatorType.LessThan); - public static readonly Equals = new TransactionExploreConditionOperator('Equal to', TransactionExploreConditionOperatorType.Equals); - public static readonly NotEquals = new TransactionExploreConditionOperator('Not equal to', TransactionExploreConditionOperatorType.NotEquals); - public static readonly Between = new TransactionExploreConditionOperator('Between', TransactionExploreConditionOperatorType.Between); - public static readonly NotBetween = new TransactionExploreConditionOperator('Not between', TransactionExploreConditionOperatorType.NotBetween); - public static readonly HasAny = new TransactionExploreConditionOperator('Has any', TransactionExploreConditionOperatorType.HasAny); - public static readonly HasAll = new TransactionExploreConditionOperator('Has all', TransactionExploreConditionOperatorType.HasAll); - public static readonly NotHasAny = new TransactionExploreConditionOperator('Not has any', TransactionExploreConditionOperatorType.NotHasAny); - public static readonly NotHasAll = new TransactionExploreConditionOperator('Not has all', TransactionExploreConditionOperatorType.NotHasAll); - public static readonly IsEmpty = new TransactionExploreConditionOperator('Is empty', TransactionExploreConditionOperatorType.IsEmpty); - public static readonly IsNotEmpty = new TransactionExploreConditionOperator('Is not empty', TransactionExploreConditionOperatorType.IsNotEmpty); - public static readonly Contains = new TransactionExploreConditionOperator('Contains', TransactionExploreConditionOperatorType.Contains); - public static readonly NotContains = new TransactionExploreConditionOperator('Not contains', TransactionExploreConditionOperatorType.NotContains); - public static readonly StartsWith = new TransactionExploreConditionOperator('Starts with', TransactionExploreConditionOperatorType.StartsWith); - public static readonly NotStartsWith = new TransactionExploreConditionOperator('Not starts with', TransactionExploreConditionOperatorType.NotStartsWith); - public static readonly EndsWith = new TransactionExploreConditionOperator('Ends with', TransactionExploreConditionOperatorType.EndsWith); - public static readonly NotEndsWith = new TransactionExploreConditionOperator('Not ends with', TransactionExploreConditionOperatorType.NotEndsWith); - - public readonly name: string; - public readonly value: TransactionExploreConditionOperatorType; - - private constructor(name: string, value: TransactionExploreConditionOperatorType) { - this.name = name; - this.value = value; - - TransactionExploreConditionOperator.allInstances.push(this); - TransactionExploreConditionOperator.allInstancesByValue[value] = this; - } - - public static values(): TransactionExploreConditionOperator[] { - return TransactionExploreConditionOperator.allInstances; - } - - public static valueOf(value: string): TransactionExploreConditionOperator | undefined { - return TransactionExploreConditionOperator.allInstancesByValue[value]; - } -} - -export enum TransactionExploreChartTypeValue { - Pie = 'pie', - ColumnStacked = 'columnStacked', - Column100PercentStacked = 'column100%Stacked', - ColumnGrouped = 'columnGrouped', - LineGrouped = 'lineGrouped', - AreaStacked = 'areaStacked', - Area100PercentStacked = 'area100%Stacked', - BubbleGrouped = 'bubbleGrouped', - Radar = 'radar' -} - -export class TransactionExploreChartType implements NameValue { - private static readonly allInstances: TransactionExploreChartType[] = []; - private static readonly allInstancesByValue: Record = {}; - - public static readonly Pie = new TransactionExploreChartType('Pie Chart', TransactionExploreChartTypeValue.Pie, false); - public static readonly Radar = new TransactionExploreChartType('Radar Chart', TransactionExploreChartTypeValue.Radar, false); - - public static readonly Default = TransactionExploreChartType.Pie; - - public readonly name: string; - public readonly value: TransactionExploreChartTypeValue; - public readonly seriesDimensionRequired: boolean; - - private constructor(name: string, value: TransactionExploreChartTypeValue, seriesDimensionRequired: boolean) { - this.name = name; - this.value = value; - this.seriesDimensionRequired = seriesDimensionRequired; - - TransactionExploreChartType.allInstances.push(this); - TransactionExploreChartType.allInstancesByValue[value] = this; - } - - public static values(): TransactionExploreChartType[] { - return TransactionExploreChartType.allInstances; - } - - public static valueOf(value: string): TransactionExploreChartType | undefined { - return TransactionExploreChartType.allInstancesByValue[value]; - } -} - -export enum TransactionExploreDataDimensionType { - None = 'none', - Query = 'query', - DateTime = 'dateTime', - DateTimeByYearMonthDay = 'dateTimeByYearMonthDay', - DateTimeByYearMonth = 'dateTimeByYearMonth', - DateTimeByYearQuarter = 'dateTimeByYearQuarter', - DateTimeByYear = 'dateTimeByYear', - DateTimeByFiscalYear = 'dateTimeByFiscalYear', - DateTimeByDayOfWeek = 'dateTimeByDayOfWeek', - DateTimeByDayOfMonth = 'dateTimeByDayOfMonth', - DateTimeByMonthOfYear = 'dateTimeByMonthOfYear', - DateTimeByQuarterOfYear = 'dateTimeByQuarterOfYear', - TransactionType = 'transactionType', - SourceAccount = 'sourceAccount', - SourceAccountCategory = 'sourceAccountCategory', - SourceAccountCurrency = 'sourceAccountCurrency', - DestinationAccount = 'destinationAccount', - DestinationAccountCategory = 'destinationAccountCategory', - DestinationAccountCurrency = 'destinationAccountCurrency', - SourceAmount = 'sourceAmount', - DestinationAmount = 'destinationAmount', - PrimaryCategory = 'primaryCategory', - SecondaryCategory = 'secondaryCategory' -} - -export class TransactionExploreDataDimension implements NameValue { - private static readonly allInstances: TransactionExploreDataDimension[] = []; - private static readonly allInstancesByValue: Record = {}; - - public static readonly None = new TransactionExploreDataDimension('None', TransactionExploreDataDimensionType.None); - public static readonly Query = new TransactionExploreDataDimension('Query', TransactionExploreDataDimensionType.Query); - public static readonly DateTime = new TransactionExploreDataDimension('Transaction Time', TransactionExploreDataDimensionType.DateTime); - public static readonly DateTimeByYearMonthDay = new TransactionExploreDataDimension('Transaction Date', TransactionExploreDataDimensionType.DateTimeByYearMonthDay); - public static readonly DateTimeByYearMonth = new TransactionExploreDataDimension('Transaction Year-Month', TransactionExploreDataDimensionType.DateTimeByYearMonth); - public static readonly DateTimeByYearQuarter = new TransactionExploreDataDimension('Transaction Year-Quarter', TransactionExploreDataDimensionType.DateTimeByYearQuarter); - public static readonly DateTimeByYear = new TransactionExploreDataDimension('Transaction Year', TransactionExploreDataDimensionType.DateTimeByYear); - public static readonly DateTimeByFiscalYear = new TransactionExploreDataDimension('Transaction Fiscal Year', TransactionExploreDataDimensionType.DateTimeByFiscalYear); - public static readonly DateTimeByDayOfWeek = new TransactionExploreDataDimension('Transaction Day of Week', TransactionExploreDataDimensionType.DateTimeByDayOfWeek); - public static readonly DateTimeByDayOfMonth = new TransactionExploreDataDimension('Transaction Day of Month', TransactionExploreDataDimensionType.DateTimeByDayOfMonth); - public static readonly DateTimeByMonthOfYear = new TransactionExploreDataDimension('Transaction Month of Year', TransactionExploreDataDimensionType.DateTimeByMonthOfYear); - public static readonly DateTimeByQuarterOfYear = new TransactionExploreDataDimension('Transaction Quarter of Year', TransactionExploreDataDimensionType.DateTimeByQuarterOfYear); - public static readonly TransactionType = new TransactionExploreDataDimension('Transaction Type', TransactionExploreDataDimensionType.TransactionType); - public static readonly SourceAccount = new TransactionExploreDataDimension('Source Account', TransactionExploreDataDimensionType.SourceAccount); - public static readonly SourceAccountCategory = new TransactionExploreDataDimension('Source Account Category', TransactionExploreDataDimensionType.SourceAccountCategory); - public static readonly SourceAccountCurrency = new TransactionExploreDataDimension('Source Account Currency', TransactionExploreDataDimensionType.SourceAccountCurrency); - public static readonly DestinationAccount = new TransactionExploreDataDimension('Destination Account', TransactionExploreDataDimensionType.DestinationAccount); - public static readonly DestinationAccountCategory = new TransactionExploreDataDimension('Destination Account Category', TransactionExploreDataDimensionType.DestinationAccountCategory); - public static readonly DestinationAccountCurrency = new TransactionExploreDataDimension('Destination Account Currency', TransactionExploreDataDimensionType.DestinationAccountCurrency); - public static readonly PrimaryCategory = new TransactionExploreDataDimension('Primary Category', TransactionExploreDataDimensionType.PrimaryCategory); - public static readonly SecondaryCategory = new TransactionExploreDataDimension('Secondary Category', TransactionExploreDataDimensionType.SecondaryCategory); - public static readonly SourceAmount = new TransactionExploreDataDimension('Amount', TransactionExploreDataDimensionType.SourceAmount); - public static readonly DestinationAmount = new TransactionExploreDataDimension('Transfer In Amount', TransactionExploreDataDimensionType.DestinationAmount); - - public static readonly CategoryDimensionDefault = TransactionExploreDataDimension.Query; - public static readonly SeriesDimensionDefault = TransactionExploreDataDimension.None; - - public readonly name: string; - public readonly value: TransactionExploreDataDimensionType; - - private constructor(name: string, value: TransactionExploreDataDimensionType) { - this.name = name; - this.value = value; - - TransactionExploreDataDimension.allInstances.push(this); - TransactionExploreDataDimension.allInstancesByValue[value] = this; - } - - public static values(): TransactionExploreDataDimension[] { - return TransactionExploreDataDimension.allInstances; - } - - public static valueOf(value: string): TransactionExploreDataDimension | undefined { - return TransactionExploreDataDimension.allInstancesByValue[value]; - } -} - -export enum TransactionExploreValueMetricType { - TransactionCount = 'transactionCount', - SourceAmountSum = 'sourceAmountSum', - SourceAmountAverage = 'sourceAmountAverage', - SourceAmountMedian = 'sourceAmountMedian', - SourceAmountMinimum = 'sourceAmountMinimum', - SourceAmountMaximum = 'sourceAmountMaximum' -} - -export class TransactionExploreValueMetric implements NameValue { - private static readonly allInstances: TransactionExploreValueMetric[] = []; - private static readonly allInstancesByValue: Record = {}; - - public static readonly TransactionCount = new TransactionExploreValueMetric('Transaction Count', TransactionExploreValueMetricType.TransactionCount, false); - public static readonly SourceAmountSum = new TransactionExploreValueMetric('Total Amount', TransactionExploreValueMetricType.SourceAmountSum, true); - public static readonly SourceAmountAverage = new TransactionExploreValueMetric('Average Amount', TransactionExploreValueMetricType.SourceAmountAverage, true); - public static readonly SourceAmountMedian = new TransactionExploreValueMetric('Median Amount', TransactionExploreValueMetricType.SourceAmountMedian, true); - public static readonly SourceAmountMinimum = new TransactionExploreValueMetric('Minimum Amount', TransactionExploreValueMetricType.SourceAmountMinimum, true); - public static readonly SourceAmountMaximum = new TransactionExploreValueMetric('Maximum Amount', TransactionExploreValueMetricType.SourceAmountMaximum, true); - - public static readonly Default = TransactionExploreValueMetric.SourceAmountSum; - - public readonly name: string; - public readonly value: TransactionExploreValueMetricType; - public readonly isAmount: boolean; - - private constructor(name: string, value: TransactionExploreValueMetricType, isAmount: boolean) { - this.name = name; - this.value = value; - this.isAmount = isAmount; - - TransactionExploreValueMetric.allInstances.push(this); - TransactionExploreValueMetric.allInstancesByValue[value] = this; - } - - public static values(): TransactionExploreValueMetric[] { - return TransactionExploreValueMetric.allInstances; - } - - public static valueOf(value: string): TransactionExploreValueMetric | undefined { - return TransactionExploreValueMetric.allInstancesByValue[value]; - } -} - -export const DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE: DateRange = DateRange.ThisMonth; diff --git a/src/core/explorer.ts b/src/core/explorer.ts new file mode 100644 index 00000000..f8e150b4 --- /dev/null +++ b/src/core/explorer.ts @@ -0,0 +1,291 @@ +import { type NameValue } from '@/core/base.ts'; +import { DateRange } from '@/core/datetime.ts'; + +export enum TransactionExplorerConditionRelation { + First = 'first', + And = 'and', + Or = 'or' +} + +export const TransactionExplorerConditionRelationPriority: Record = { + [TransactionExplorerConditionRelation.First]: 0, + [TransactionExplorerConditionRelation.Or]: 1, + [TransactionExplorerConditionRelation.And]: 2 +}; + + +export enum TransactionExplorerConditionFieldType { + TransactionType = 'transactionType', + TransactionCategory = 'transactionCategory', + SourceAccount = 'sourceAccount', + DestinationAccount = 'destinationAccount', + SourceAmount = 'sourceAmount', + DestinationAmount = 'destinationAmount', + TransactionTag = 'transactionTag', + Description = 'description' +} + +export class TransactionExplorerConditionField implements NameValue { + private static readonly allInstances: TransactionExplorerConditionField[] = []; + private static readonly allInstancesByValue: Record = {}; + + public static readonly TransactionType = new TransactionExplorerConditionField('Transaction Type', TransactionExplorerConditionFieldType.TransactionType); + public static readonly TransactionCategory = new TransactionExplorerConditionField('Category', TransactionExplorerConditionFieldType.TransactionCategory); + public static readonly SourceAccount = new TransactionExplorerConditionField('Source Account', TransactionExplorerConditionFieldType.SourceAccount); + public static readonly DestinationAccount = new TransactionExplorerConditionField('Destination Account', TransactionExplorerConditionFieldType.DestinationAccount); + public static readonly SourceAmount = new TransactionExplorerConditionField('Amount', TransactionExplorerConditionFieldType.SourceAmount); + public static readonly DestinationAmount = new TransactionExplorerConditionField('Transfer In Amount', TransactionExplorerConditionFieldType.DestinationAmount); + public static readonly TransactionTag = new TransactionExplorerConditionField('Tags', TransactionExplorerConditionFieldType.TransactionTag); + public static readonly Description = new TransactionExplorerConditionField('Description', TransactionExplorerConditionFieldType.Description); + + public readonly name: string; + public readonly value: TransactionExplorerConditionFieldType; + + private constructor(name: string, value: TransactionExplorerConditionFieldType) { + this.name = name; + this.value = value; + + TransactionExplorerConditionField.allInstances.push(this); + TransactionExplorerConditionField.allInstancesByValue[value] = this; + } + + public static values(): TransactionExplorerConditionField[] { + return TransactionExplorerConditionField.allInstances; + } + + public static valueOf(value: string): TransactionExplorerConditionField | undefined { + return TransactionExplorerConditionField.allInstancesByValue[value]; + } +} + +export enum TransactionExplorerConditionOperatorType { + In = 'in', + GreaterThan = 'greaterThan', + LessThan = 'lessThan', + Equals = 'equals', + NotEquals = 'notEquals', + Between = 'between', + NotBetween = 'notBetween', + HasAny = 'hasAny', + HasAll = 'hasAll', + NotHasAny = 'notHasAny', + NotHasAll = 'notHasAll', + IsEmpty = 'isEmpty', + IsNotEmpty = 'isNotEmpty', + Contains = 'contains', + NotContains = 'notContains', + StartsWith = 'startsWith', + NotStartsWith = 'notStartsWith', + EndsWith = 'endsWith', + NotEndsWith = 'notEndsWith' +} + +export class TransactionExplorerConditionOperator implements NameValue { + private static readonly allInstances: TransactionExplorerConditionOperator[] = []; + private static readonly allInstancesByValue: Record = {}; + + public static readonly In = new TransactionExplorerConditionOperator('In', TransactionExplorerConditionOperatorType.In); + public static readonly GreaterThan = new TransactionExplorerConditionOperator('Greater than', TransactionExplorerConditionOperatorType.GreaterThan); + public static readonly LessThan = new TransactionExplorerConditionOperator('Less than', TransactionExplorerConditionOperatorType.LessThan); + public static readonly Equals = new TransactionExplorerConditionOperator('Equal to', TransactionExplorerConditionOperatorType.Equals); + public static readonly NotEquals = new TransactionExplorerConditionOperator('Not equal to', TransactionExplorerConditionOperatorType.NotEquals); + public static readonly Between = new TransactionExplorerConditionOperator('Between', TransactionExplorerConditionOperatorType.Between); + public static readonly NotBetween = new TransactionExplorerConditionOperator('Not between', TransactionExplorerConditionOperatorType.NotBetween); + public static readonly HasAny = new TransactionExplorerConditionOperator('Has any', TransactionExplorerConditionOperatorType.HasAny); + public static readonly HasAll = new TransactionExplorerConditionOperator('Has all', TransactionExplorerConditionOperatorType.HasAll); + public static readonly NotHasAny = new TransactionExplorerConditionOperator('Not has any', TransactionExplorerConditionOperatorType.NotHasAny); + public static readonly NotHasAll = new TransactionExplorerConditionOperator('Not has all', TransactionExplorerConditionOperatorType.NotHasAll); + public static readonly IsEmpty = new TransactionExplorerConditionOperator('Is empty', TransactionExplorerConditionOperatorType.IsEmpty); + public static readonly IsNotEmpty = new TransactionExplorerConditionOperator('Is not empty', TransactionExplorerConditionOperatorType.IsNotEmpty); + public static readonly Contains = new TransactionExplorerConditionOperator('Contains', TransactionExplorerConditionOperatorType.Contains); + public static readonly NotContains = new TransactionExplorerConditionOperator('Not contains', TransactionExplorerConditionOperatorType.NotContains); + public static readonly StartsWith = new TransactionExplorerConditionOperator('Starts with', TransactionExplorerConditionOperatorType.StartsWith); + public static readonly NotStartsWith = new TransactionExplorerConditionOperator('Not starts with', TransactionExplorerConditionOperatorType.NotStartsWith); + public static readonly EndsWith = new TransactionExplorerConditionOperator('Ends with', TransactionExplorerConditionOperatorType.EndsWith); + public static readonly NotEndsWith = new TransactionExplorerConditionOperator('Not ends with', TransactionExplorerConditionOperatorType.NotEndsWith); + + public readonly name: string; + public readonly value: TransactionExplorerConditionOperatorType; + + private constructor(name: string, value: TransactionExplorerConditionOperatorType) { + this.name = name; + this.value = value; + + TransactionExplorerConditionOperator.allInstances.push(this); + TransactionExplorerConditionOperator.allInstancesByValue[value] = this; + } + + public static values(): TransactionExplorerConditionOperator[] { + return TransactionExplorerConditionOperator.allInstances; + } + + public static valueOf(value: string): TransactionExplorerConditionOperator | undefined { + return TransactionExplorerConditionOperator.allInstancesByValue[value]; + } +} + +export enum TransactionExplorerChartTypeValue { + Pie = 'pie', + ColumnStacked = 'columnStacked', + Column100PercentStacked = 'column100%Stacked', + ColumnGrouped = 'columnGrouped', + LineGrouped = 'lineGrouped', + AreaStacked = 'areaStacked', + Area100PercentStacked = 'area100%Stacked', + BubbleGrouped = 'bubbleGrouped', + Radar = 'radar' +} + +export class TransactionExplorerChartType implements NameValue { + private static readonly allInstances: TransactionExplorerChartType[] = []; + private static readonly allInstancesByValue: Record = {}; + + public static readonly Pie = new TransactionExplorerChartType('Pie Chart', TransactionExplorerChartTypeValue.Pie, false); + public static readonly Radar = new TransactionExplorerChartType('Radar Chart', TransactionExplorerChartTypeValue.Radar, false); + + public static readonly Default = TransactionExplorerChartType.Pie; + + public readonly name: string; + public readonly value: TransactionExplorerChartTypeValue; + public readonly seriesDimensionRequired: boolean; + + private constructor(name: string, value: TransactionExplorerChartTypeValue, seriesDimensionRequired: boolean) { + this.name = name; + this.value = value; + this.seriesDimensionRequired = seriesDimensionRequired; + + TransactionExplorerChartType.allInstances.push(this); + TransactionExplorerChartType.allInstancesByValue[value] = this; + } + + public static values(): TransactionExplorerChartType[] { + return TransactionExplorerChartType.allInstances; + } + + public static valueOf(value: string): TransactionExplorerChartType | undefined { + return TransactionExplorerChartType.allInstancesByValue[value]; + } +} + +export enum TransactionExplorerDataDimensionType { + None = 'none', + Query = 'query', + DateTime = 'dateTime', + DateTimeByYearMonthDay = 'dateTimeByYearMonthDay', + DateTimeByYearMonth = 'dateTimeByYearMonth', + DateTimeByYearQuarter = 'dateTimeByYearQuarter', + DateTimeByYear = 'dateTimeByYear', + DateTimeByFiscalYear = 'dateTimeByFiscalYear', + DateTimeByDayOfWeek = 'dateTimeByDayOfWeek', + DateTimeByDayOfMonth = 'dateTimeByDayOfMonth', + DateTimeByMonthOfYear = 'dateTimeByMonthOfYear', + DateTimeByQuarterOfYear = 'dateTimeByQuarterOfYear', + TransactionType = 'transactionType', + SourceAccount = 'sourceAccount', + SourceAccountCategory = 'sourceAccountCategory', + SourceAccountCurrency = 'sourceAccountCurrency', + DestinationAccount = 'destinationAccount', + DestinationAccountCategory = 'destinationAccountCategory', + DestinationAccountCurrency = 'destinationAccountCurrency', + SourceAmount = 'sourceAmount', + DestinationAmount = 'destinationAmount', + PrimaryCategory = 'primaryCategory', + SecondaryCategory = 'secondaryCategory' +} + +export class TransactionExplorerDataDimension implements NameValue { + private static readonly allInstances: TransactionExplorerDataDimension[] = []; + private static readonly allInstancesByValue: Record = {}; + + public static readonly None = new TransactionExplorerDataDimension('None', TransactionExplorerDataDimensionType.None); + public static readonly Query = new TransactionExplorerDataDimension('Query', TransactionExplorerDataDimensionType.Query); + public static readonly DateTime = new TransactionExplorerDataDimension('Transaction Time', TransactionExplorerDataDimensionType.DateTime); + public static readonly DateTimeByYearMonthDay = new TransactionExplorerDataDimension('Transaction Date', TransactionExplorerDataDimensionType.DateTimeByYearMonthDay); + public static readonly DateTimeByYearMonth = new TransactionExplorerDataDimension('Transaction Year-Month', TransactionExplorerDataDimensionType.DateTimeByYearMonth); + public static readonly DateTimeByYearQuarter = new TransactionExplorerDataDimension('Transaction Year-Quarter', TransactionExplorerDataDimensionType.DateTimeByYearQuarter); + public static readonly DateTimeByYear = new TransactionExplorerDataDimension('Transaction Year', TransactionExplorerDataDimensionType.DateTimeByYear); + public static readonly DateTimeByFiscalYear = new TransactionExplorerDataDimension('Transaction Fiscal Year', TransactionExplorerDataDimensionType.DateTimeByFiscalYear); + public static readonly DateTimeByDayOfWeek = new TransactionExplorerDataDimension('Transaction Day of Week', TransactionExplorerDataDimensionType.DateTimeByDayOfWeek); + public static readonly DateTimeByDayOfMonth = new TransactionExplorerDataDimension('Transaction Day of Month', TransactionExplorerDataDimensionType.DateTimeByDayOfMonth); + public static readonly DateTimeByMonthOfYear = new TransactionExplorerDataDimension('Transaction Month of Year', TransactionExplorerDataDimensionType.DateTimeByMonthOfYear); + public static readonly DateTimeByQuarterOfYear = new TransactionExplorerDataDimension('Transaction Quarter of Year', TransactionExplorerDataDimensionType.DateTimeByQuarterOfYear); + public static readonly TransactionType = new TransactionExplorerDataDimension('Transaction Type', TransactionExplorerDataDimensionType.TransactionType); + public static readonly SourceAccount = new TransactionExplorerDataDimension('Source Account', TransactionExplorerDataDimensionType.SourceAccount); + public static readonly SourceAccountCategory = new TransactionExplorerDataDimension('Source Account Category', TransactionExplorerDataDimensionType.SourceAccountCategory); + public static readonly SourceAccountCurrency = new TransactionExplorerDataDimension('Source Account Currency', TransactionExplorerDataDimensionType.SourceAccountCurrency); + public static readonly DestinationAccount = new TransactionExplorerDataDimension('Destination Account', TransactionExplorerDataDimensionType.DestinationAccount); + public static readonly DestinationAccountCategory = new TransactionExplorerDataDimension('Destination Account Category', TransactionExplorerDataDimensionType.DestinationAccountCategory); + public static readonly DestinationAccountCurrency = new TransactionExplorerDataDimension('Destination Account Currency', TransactionExplorerDataDimensionType.DestinationAccountCurrency); + public static readonly PrimaryCategory = new TransactionExplorerDataDimension('Primary Category', TransactionExplorerDataDimensionType.PrimaryCategory); + public static readonly SecondaryCategory = new TransactionExplorerDataDimension('Secondary Category', TransactionExplorerDataDimensionType.SecondaryCategory); + public static readonly SourceAmount = new TransactionExplorerDataDimension('Amount', TransactionExplorerDataDimensionType.SourceAmount); + public static readonly DestinationAmount = new TransactionExplorerDataDimension('Transfer In Amount', TransactionExplorerDataDimensionType.DestinationAmount); + + public static readonly CategoryDimensionDefault = TransactionExplorerDataDimension.Query; + public static readonly SeriesDimensionDefault = TransactionExplorerDataDimension.None; + + public readonly name: string; + public readonly value: TransactionExplorerDataDimensionType; + + private constructor(name: string, value: TransactionExplorerDataDimensionType) { + this.name = name; + this.value = value; + + TransactionExplorerDataDimension.allInstances.push(this); + TransactionExplorerDataDimension.allInstancesByValue[value] = this; + } + + public static values(): TransactionExplorerDataDimension[] { + return TransactionExplorerDataDimension.allInstances; + } + + public static valueOf(value: string): TransactionExplorerDataDimension | undefined { + return TransactionExplorerDataDimension.allInstancesByValue[value]; + } +} + +export enum TransactionExplorerValueMetricType { + TransactionCount = 'transactionCount', + SourceAmountSum = 'sourceAmountSum', + SourceAmountAverage = 'sourceAmountAverage', + SourceAmountMedian = 'sourceAmountMedian', + SourceAmountMinimum = 'sourceAmountMinimum', + SourceAmountMaximum = 'sourceAmountMaximum' +} + +export class TransactionExplorerValueMetric implements NameValue { + private static readonly allInstances: TransactionExplorerValueMetric[] = []; + private static readonly allInstancesByValue: Record = {}; + + public static readonly TransactionCount = new TransactionExplorerValueMetric('Transaction Count', TransactionExplorerValueMetricType.TransactionCount, false); + public static readonly SourceAmountSum = new TransactionExplorerValueMetric('Total Amount', TransactionExplorerValueMetricType.SourceAmountSum, true); + public static readonly SourceAmountAverage = new TransactionExplorerValueMetric('Average Amount', TransactionExplorerValueMetricType.SourceAmountAverage, true); + public static readonly SourceAmountMedian = new TransactionExplorerValueMetric('Median Amount', TransactionExplorerValueMetricType.SourceAmountMedian, true); + public static readonly SourceAmountMinimum = new TransactionExplorerValueMetric('Minimum Amount', TransactionExplorerValueMetricType.SourceAmountMinimum, true); + public static readonly SourceAmountMaximum = new TransactionExplorerValueMetric('Maximum Amount', TransactionExplorerValueMetricType.SourceAmountMaximum, true); + + public static readonly Default = TransactionExplorerValueMetric.SourceAmountSum; + + public readonly name: string; + public readonly value: TransactionExplorerValueMetricType; + public readonly isAmount: boolean; + + private constructor(name: string, value: TransactionExplorerValueMetricType, isAmount: boolean) { + this.name = name; + this.value = value; + this.isAmount = isAmount; + + TransactionExplorerValueMetric.allInstances.push(this); + TransactionExplorerValueMetric.allInstancesByValue[value] = this; + } + + public static values(): TransactionExplorerValueMetric[] { + return TransactionExplorerValueMetric.allInstances; + } + + public static valueOf(value: string): TransactionExplorerValueMetric | undefined { + return TransactionExplorerValueMetric.allInstancesByValue[value]; + } +} + +export const DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE: DateRange = DateRange.ThisMonth; diff --git a/src/core/setting.ts b/src/core/setting.ts index cf9dc28b..2d5f35c6 100644 --- a/src/core/setting.ts +++ b/src/core/setting.ts @@ -10,7 +10,7 @@ import { DEFAULT_TREND_CHART_DATA_RANGE, DEFAULT_ASSET_TRENDS_CHART_DATA_RANGE } from './statistics.ts'; -import { DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE } from './explore.ts'; +import { DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE } from './explorer.ts'; import { DEFAULT_CURRENCY_CODE } from '@/consts/currency.ts'; export type ApplicationSettingKey = string; @@ -50,9 +50,9 @@ export interface ApplicationSettings extends BaseApplicationSetting { autoSaveTransactionDraft: string; autoGetCurrentGeoLocation: boolean; alwaysShowTransactionPicturesInMobileTransactionEditPage: boolean; - // Insights & Explore Page - insightsExploreDefaultDateRangeType: number; - timezoneUsedForInsightsExplorePage: number; + // Insights Explorer Page + insightsExplorerDefaultDateRangeType: number; + timezoneUsedForInsightsExplorerPage: number; // Account List Page totalAmountExcludeAccountIds: Record; // Exchange Rates Data Page @@ -115,9 +115,9 @@ export const ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES: Record getLocalizedNameValue(TransactionExploreConditionField.values()), - getAllTransactionExploreConditionOperators: (operators?: TransactionExploreConditionOperator[]) => getLocalizedNameValue(operators ?? TransactionExploreConditionOperator.values()), - getAllTransactionExploreDataDimensions: (operators?: TransactionExploreDataDimension[]) => getLocalizedNameValue(operators ?? TransactionExploreDataDimension.values()), - getAllTransactionExploreValueMetrics: (operators?: TransactionExploreValueMetric[]) => getLocalizedNameValue(operators ?? TransactionExploreValueMetric.values()), - getAllTransactionExploreChartTypes: (operators?: TransactionExploreChartType[]) => getLocalizedNameValue(operators ?? TransactionExploreChartType.values()), + getAllTransactionExplorerConditionFields: () => getLocalizedNameValue(TransactionExplorerConditionField.values()), + getAllTransactionExplorerConditionOperators: (operators?: TransactionExplorerConditionOperator[]) => getLocalizedNameValue(operators ?? TransactionExplorerConditionOperator.values()), + getAllTransactionExplorerDataDimensions: (operators?: TransactionExplorerDataDimension[]) => getLocalizedNameValue(operators ?? TransactionExplorerDataDimension.values()), + getAllTransactionExplorerValueMetrics: (operators?: TransactionExplorerValueMetric[]) => getLocalizedNameValue(operators ?? TransactionExplorerValueMetric.values()), + getAllTransactionExplorerChartTypes: (operators?: TransactionExplorerChartType[]) => getLocalizedNameValue(operators ?? TransactionExplorerChartType.values()), // get localized info getLanguageInfo, getMonthShortName, diff --git a/src/locales/it.json b/src/locales/it.json index 9752414d..bc2f878f 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "Transaction Calendar", "Transaction Details": "Dettagli transazione", "Statistics & Analysis": "Statistiche e analisi", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "Mostra conferma ogni volta", "Automatically Add Geolocation": "Aggiungi automaticamente geolocalizzazione", "Always Show Transaction Pictures": "Always Show Transaction Pictures", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "Account List Page", "Accounts Included in Total": "Accounts Included in Total", "Exchange Rates Data Page": "Pagina dati tassi di cambio", diff --git a/src/locales/ja.json b/src/locales/ja.json index ba46772b..fab3ae9c 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "Transaction Calendar", "Transaction Details": "取引の詳細", "Statistics & Analysis": "統計と分析", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "確認を毎回表示", "Automatically Add Geolocation": "座標を自動的に追加", "Always Show Transaction Pictures": "Always Show Transaction Pictures", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "Account List Page", "Accounts Included in Total": "Accounts Included in Total", "Exchange Rates Data Page": "為替レートデータページ", diff --git a/src/locales/kn.json b/src/locales/kn.json index 340ff72f..99b9dcd7 100644 --- a/src/locales/kn.json +++ b/src/locales/kn.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "ವಹಿವಾಟು ಕ್ಯಾಲೆಂಡರ್", "Transaction Details": "ವಹಿವಾಟಿನ ವಿವರಗಳು", "Statistics & Analysis": "ಸಂಖ್ಯಾಶಾಸ್ತ್ರ ಮತ್ತು ವಿಶ್ಲೇಷಣೆ", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "ಪ್ರತಿ ಬಾರಿ ದೃಢೀಕರಣ ತೋರಿಸಿ", "Automatically Add Geolocation": "ಭೌಗೋಳಿಕ ಸ್ಥಾನವನ್ನು ಸ್ವಯಂ ಸೇರಿಸಿ", "Always Show Transaction Pictures": "ವಹಿವಾಟು ಚಿತ್ರಗಳನ್ನು ಯಾವಾಗಲೂ ತೋರಿಸಿ", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "ಖಾತೆ ಪಟ್ಟಿ ಪುಟ", "Accounts Included in Total": "ಒಟ್ಟು ಮೊತ್ತದಲ್ಲಿ ಒಳಗೊಂಡ ಖಾತೆಗಳು", "Exchange Rates Data Page": "ವಿನಿಮಯ ದರಗಳ ಪುಟ", diff --git a/src/locales/ko.json b/src/locales/ko.json index 39833f48..6394b04c 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "거래 달력", "Transaction Details": "거래 세부사항", "Statistics & Analysis": "통계 및 분석", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "매번 확인 표시", "Automatically Add Geolocation": "지리적 위치 자동 추가", "Always Show Transaction Pictures": "거래 사진 항상 표시", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "계좌 목록 페이지", "Accounts Included in Total": "총계에 포함된 계좌", "Exchange Rates Data Page": "환율 데이터 페이지", diff --git a/src/locales/nl.json b/src/locales/nl.json index ad9a72f3..b8ed8e16 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "Transactiekalender", "Transaction Details": "Transactiedetails", "Statistics & Analysis": "Statistieken & Analyse", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "Elke keer bevestiging tonen", "Automatically Add Geolocation": "Geolocatie automatisch toevoegen", "Always Show Transaction Pictures": "Transactie-afbeeldingen altijd tonen", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "Rekeningenpagina", "Accounts Included in Total": "Rekeningen opgenomen in totaal", "Exchange Rates Data Page": "Wisselkoersgegevenspagina", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 0035db7e..c3c40ee1 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "Calendário de Transações", "Transaction Details": "Detalhes da Transação", "Statistics & Analysis": "Estatísticas e Análise", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "Mostrar Confirmação Toda Vez", "Automatically Add Geolocation": "Adicionar Geolocalização Automaticamente", "Always Show Transaction Pictures": "Sempre Mostrar Imagens de Transações", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "Account List Page", "Accounts Included in Total": "Accounts Included in Total", "Exchange Rates Data Page": "Página de Dados de Taxas de Câmbio", diff --git a/src/locales/ru.json b/src/locales/ru.json index 79a200cf..a1fdae23 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "Transaction Calendar", "Transaction Details": "Детали транзакции", "Statistics & Analysis": "Статистика и анализ", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "Показывать подтверждение каждый раз", "Automatically Add Geolocation": "Автоматически добавлять геолокацию", "Always Show Transaction Pictures": "Always Show Transaction Pictures", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "Account List Page", "Accounts Included in Total": "Accounts Included in Total", "Exchange Rates Data Page": "Страница данных о курсах валют", diff --git a/src/locales/sl.json b/src/locales/sl.json index 1e557236..af629885 100644 --- a/src/locales/sl.json +++ b/src/locales/sl.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "Koledar transakcij", "Transaction Details": "Podrobnosti transakcije", "Statistics & Analysis": "Statistika in analiza", - "Insights & Explore": "Vpogledi in raziskovanje", + "Insights Explorer": "Vpogledi in raziskovanje", "Query": "Poizvedba", "Data Table": "Podatkovna tabela", "Chart": "Grafikon", - "New Explore": "Novo raziskovanje", + "New Exploration": "Novo raziskovanje", "Add Query": "Dodaj poizvedbo", "Remove Query": "Odstrani poizvedbo", "Modify Query Name": "Spremeni ime poizvedbe", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "Vsakič prikaži potrditev", "Automatically Add Geolocation": "Samodejno dodaj geolokacijo", "Always Show Transaction Pictures": "Vedno prikaži slike transakcij", - "Insights & Explore Page": "Stran za vpoglede in raziskovanje", + "Insights Explorer Page": "Stran za vpoglede in raziskovanje", "Account List Page": "Stran s seznamom računov", "Accounts Included in Total": "Računi vključeni v skupno vsoto", "Exchange Rates Data Page": "Stran z menjalnimi tečaji", diff --git a/src/locales/th.json b/src/locales/th.json index a2eeae6a..9b157e92 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "ปฏิทินธุรกรรม", "Transaction Details": "รายละเอียดธุรกรรม", "Statistics & Analysis": "สถิติ & การวิเคราะห์", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "แสดงการยืนยันทุกครั้ง", "Automatically Add Geolocation": "เพิ่มตำแหน่งทางภูมิศาสตร์อัตโนมัติ", "Always Show Transaction Pictures": "แสดงรูปภาพรายการเสมอ", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "หน้าบัญชี", "Accounts Included in Total": "บัญชีที่รวมในผลรวม", "Exchange Rates Data Page": "หน้าข้อมูลอัตราแลกเปลี่ยน", diff --git a/src/locales/tr.json b/src/locales/tr.json index 6f0db410..fa1d3d4b 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "İşlem Takvimi", "Transaction Details": "İşlem Detayları", "Statistics & Analysis": "İstatistik & Analiz", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "Her Seferinde Onay Göster", "Automatically Add Geolocation": "Otomatik Olarak Konum Ekle", "Always Show Transaction Pictures": "İşlem Resimlerini Her Zaman Göster", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "Hesap Listesi Sayfası", "Accounts Included in Total": "Toplama Dahil Edilen Hesaplar", "Exchange Rates Data Page": "Döviz Kuru Verileri Sayfası", diff --git a/src/locales/uk.json b/src/locales/uk.json index 4db7c5e8..64a809d9 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "Transaction Calendar", "Transaction Details": "Деталі по транзакціях", "Statistics & Analysis": "Статистика та аналіз", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "Показувати підтвердження щоразу", "Automatically Add Geolocation": "Автоматично додавати геолокацію", "Always Show Transaction Pictures": "Always Show Transaction Pictures", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "Account List Page", "Accounts Included in Total": "Accounts Included in Total", "Exchange Rates Data Page": "Сторінка курсів валют", diff --git a/src/locales/vi.json b/src/locales/vi.json index 6bc194ac..a3296382 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "Transaction Calendar", "Transaction Details": "Chi tiết giao dịch", "Statistics & Analysis": "Thống kê & Phân tích", - "Insights & Explore": "Insights & Explore", + "Insights Explorer": "Insights Explorer", "Query": "Query", "Data Table": "Data Table", "Chart": "Chart", - "New Explore": "New Explore", + "New Exploration": "New Exploration", "Add Query": "Add Query", "Remove Query": "Remove Query", "Modify Query Name": "Modify Query Name", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "Hiển thị xác nhận mỗi lần", "Automatically Add Geolocation": "Tự động thêm vị trí địa lý", "Always Show Transaction Pictures": "Always Show Transaction Pictures", - "Insights & Explore Page": "Insights & Explore Page", + "Insights Explorer Page": "Insights Explorer Page", "Account List Page": "Account List Page", "Accounts Included in Total": "Accounts Included in Total", "Exchange Rates Data Page": "Trang dữ liệu tỷ giá hối đoái", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index 930d750c..27284a1d 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "交易日历", "Transaction Details": "交易详情", "Statistics & Analysis": "统计分析", - "Insights & Explore": "洞察探索", + "Insights Explorer": "洞察探索", "Query": "查询", "Data Table": "数据表格", "Chart": "图表", - "New Explore": "新的探索", + "New Exploration": "新的探索", "Add Query": "添加查询", "Remove Query": "移除查询", "Modify Query Name": "修改查询名称", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "每次提示确认", "Automatically Add Geolocation": "自动添加地理位置", "Always Show Transaction Pictures": "总是显示交易图片", - "Insights & Explore Page": "洞察探索页面", + "Insights Explorer Page": "洞察探索页面", "Account List Page": "账户列表页面", "Accounts Included in Total": "计入总金额的账户", "Exchange Rates Data Page": "汇率数据页面", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index 4bbf2c10..f092d475 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -1704,11 +1704,11 @@ "Transaction Calendar": "交易日曆", "Transaction Details": "交易詳情", "Statistics & Analysis": "統計分析", - "Insights & Explore": "洞察探索", + "Insights Explorer": "洞察探索", "Query": "查詢", "Data Table": "資料表格", "Chart": "圖表", - "New Explore": "新的探索", + "New Exploration": "新的探索", "Add Query": "新增查詢", "Remove Query": "移除查詢", "Modify Query Name": "修改查詢名稱", @@ -2186,7 +2186,7 @@ "Show Confirmation Every Time": "每次提示確認", "Automatically Add Geolocation": "自動新增地理位置", "Always Show Transaction Pictures": "總是顯示交易圖片", - "Insights & Explore Page": "洞察探索頁面", + "Insights Explorer Page": "洞察探索頁面", "Account List Page": "帳戶清單頁面", "Accounts Included in Total": "計入總金額的帳戶", "Exchange Rates Data Page": "匯率資料頁面", diff --git a/src/models/explore.ts b/src/models/explore.ts deleted file mode 100644 index 83c34abe..00000000 --- a/src/models/explore.ts +++ /dev/null @@ -1,922 +0,0 @@ -import { type PartialRecord, itemAndIndex, keysIfValueEquals } from '@/core/base.ts'; -import { AccountType } from '@/core/account.ts'; -import { TransactionType } from '@/core/transaction.ts'; -import { - TransactionExploreConditionRelation, - TransactionExploreConditionRelationPriority, - TransactionExploreConditionFieldType, - TransactionExploreConditionField, - TransactionExploreConditionOperatorType, - TransactionExploreConditionOperator -} from '@/core/explore.ts'; - -import { Account } from '@/models/account.ts'; -import { TransactionCategory } from '@/models/transaction_category.ts'; -import { TransactionTag } from '@/models/transaction_tag.ts'; -import { type TransactionInsightDataItem } from '@/models/transaction.ts'; - -interface ExpressionNode { - textualExpression: string; - operator?: TransactionExploreConditionRelation; -} - -export class TransactionExploreQuery { - public name: string; - public conditions: TransactionExploreConditionWithRelation[]; - - private constructor(name: string, conditions: TransactionExploreConditionWithRelation[]) { - this.name = name; - this.conditions = conditions; - } - - public addNewCondition(field: TransactionExploreConditionField, isFirst: boolean): TransactionExploreConditionWithRelation { - let condition: TransactionExploreCondition; - - switch (field) { - case TransactionExploreConditionField.TransactionType: - condition = new TransactionExploreTransactionTypeCondition([ TransactionType.Expense, TransactionType.Income, TransactionType.Transfer ]); - break; - case TransactionExploreConditionField.TransactionCategory: - condition = new TransactionExploreTransactionCategoryCondition([]); - break; - case TransactionExploreConditionField.SourceAccount: - condition = new TransactionExploreSourceAccountCondition([]); - break; - case TransactionExploreConditionField.DestinationAccount: - condition = new TransactionExploreDestinationAccountCondition([]); - break; - case TransactionExploreConditionField.SourceAmount: - condition = new TransactionExploreSourceAmountCondition(TransactionExploreConditionOperatorType.Between, [0, 0]); - break; - case TransactionExploreConditionField.DestinationAmount: - condition = new TransactionExploreDestinationAmountCondition(TransactionExploreConditionOperatorType.Between, [0, 0]); - break; - case TransactionExploreConditionField.TransactionTag: - condition = new TransactionExploreTransactionTagCondition(TransactionExploreConditionOperatorType.HasAny, []); - break; - case TransactionExploreConditionField.Description: - condition = new TransactionExploreDescriptionCondition(TransactionExploreConditionOperatorType.Contains, ''); - break; - default: - condition = new TransactionExploreTransactionTypeCondition([ TransactionType.Expense, TransactionType.Income, TransactionType.Transfer ]); - break; - } - - return new TransactionExploreConditionWithRelation( - condition, - isFirst ? TransactionExploreConditionRelation.First : TransactionExploreConditionRelation.And - ); - } - - public match(transaction: TransactionInsightDataItem): boolean { - if (!this.conditions || this.conditions.length < 1) { - return true; - } - - const postfixExprTokens = this.getPostfixExprTokens(); - const stack: boolean[] = []; - - for (const token of postfixExprTokens) { - if (token === TransactionExploreConditionRelation.And || token === TransactionExploreConditionRelation.Or) { - const right = stack.pop(); - const left = stack.pop(); - - if (left === undefined || right === undefined) { - throw new Error('invalid postfix expression'); - } - - if (token === TransactionExploreConditionRelation.And) { - stack.push(left && right); - } else if (token === TransactionExploreConditionRelation.Or) { - stack.push(left || right); - } else { - throw new Error('invalid postfix expression'); - } - } else { - stack.push(token.match(transaction)); - } - } - - if (stack.length !== 1) { - throw new Error('invalid postfix evaluation result'); - } - - return stack[0] as boolean; - } - - public toExpression(allCategoriesMap: Record, allAccountsMap: Record, allTagsMap: Record): string { - if (!this.conditions || this.conditions.length < 1) { - return ''; - } - - const postfixExprTokens = this.getPostfixExprTokens(); - const stack: ExpressionNode[] = []; - - for (const token of postfixExprTokens) { - if (token === TransactionExploreConditionRelation.And || token === TransactionExploreConditionRelation.Or) { - const right = stack.pop(); - const left = stack.pop(); - - if (left === undefined || right === undefined) { - throw new Error('invalid postfix expression'); - } - - let leftExpression = left.textualExpression; - let rightExpression = right.textualExpression; - - if (left.operator && left.operator !== token) { - leftExpression = `(${leftExpression})`; - } - - if (right.operator && right.operator !== token) { - rightExpression = `(${rightExpression})`; - } - - stack.push({ - textualExpression: `${leftExpression} ${token.toUpperCase()} ${rightExpression}`, - operator: token - }); - } else { - stack.push({ - textualExpression: token.toExpression(allCategoriesMap, allAccountsMap, allTagsMap) - }); - } - } - - if (stack.length !== 1) { - throw new Error('invalid postfix evaluation result'); - } - - const finalNode = stack[0]; - - if (!finalNode) { - throw new Error('invalid postfix evaluation result'); - } - - return finalNode.textualExpression; - } - - public getPostfixExprTokens(): (TransactionExploreCondition | TransactionExploreConditionRelation.And | TransactionExploreConditionRelation.Or)[] { - const finalTokens: (TransactionExploreCondition | TransactionExploreConditionRelation.And | TransactionExploreConditionRelation.Or)[] = []; - - if (this.conditions.length < 1) { - return finalTokens; - } - - const operatorStack: TransactionExploreConditionRelation[] = []; - const firstCondition = this.conditions[0] as TransactionExploreConditionWithRelation; - - if (firstCondition.relation !== TransactionExploreConditionRelation.First) { - throw new Error('the first condition must have relation "first"'); - } - - finalTokens.push(firstCondition.condition); - - for (const [item, index] of itemAndIndex(this.conditions)) { - if (index < 1) { - continue; - } - - if (item.relation === TransactionExploreConditionRelation.First) { - throw new Error('only the first condition can have relation "first"'); - } - - const currentOperator = item.relation; - - while (operatorStack.length > 0) { - const topOperator = operatorStack[operatorStack.length - 1]; - const isAndOrOperator = topOperator === TransactionExploreConditionRelation.And || topOperator === TransactionExploreConditionRelation.Or; - - if (isAndOrOperator && TransactionExploreConditionRelationPriority[topOperator] >= TransactionExploreConditionRelationPriority[currentOperator]) { - finalTokens.push(topOperator); - operatorStack.pop(); - } else { - break; - } - } - - operatorStack.push(currentOperator); - finalTokens.push(item.condition); - } - - while (operatorStack.length > 0) { - const topOperator = operatorStack.pop(); - - if (topOperator !== TransactionExploreConditionRelation.And && topOperator !== TransactionExploreConditionRelation.Or) { - throw new Error('invalid operator in stack'); - } - - finalTokens.push(topOperator); - } - - return finalTokens; - } - - public clone(): TransactionExploreQuery { - const clonedConditions: TransactionExploreConditionWithRelation[] = []; - - for (const condition of this.conditions) { - const clonedCondition = TransactionExploreConditionWithRelation.parse(condition.toJsonObject()); - - if (!clonedCondition) { - continue; - } - - clonedConditions.push(clonedCondition); - } - - return new TransactionExploreQuery(this.name, clonedConditions); - } - - public toJson(): string { - return JSON.stringify({ - name: this.name, - conditions: this.conditions.map(condition => condition.toJsonObject()) - }); - } - - public static create(): TransactionExploreQuery { - return new TransactionExploreQuery("", []); - } - - public static parse(json: string): TransactionExploreQuery | 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)) { - return null; - } - - const name: string = nameFieldValue; - const conditions: TransactionExploreConditionWithRelation[] = []; - - for (const [item, index] of itemAndIndex(conditionsFieldValue)) { - const condition = TransactionExploreConditionWithRelation.parse(item); - - if (condition === null) { - return null; - } - - if (index === 0 && condition.relation !== TransactionExploreConditionRelation.First) { - return null; - } else if (index > 0 && condition.relation === TransactionExploreConditionRelation.First) { - return null; - } - - conditions.push(condition); - } - - return new TransactionExploreQuery(name, conditions); - } -} - -export class TransactionExploreConditionWithRelation { - public condition: TransactionExploreCondition; - public relation: TransactionExploreConditionRelation; - - constructor(condition: TransactionExploreCondition, relation: TransactionExploreConditionRelation) { - this.condition = condition; - this.relation = relation; - } - - public getSupportedOperators(): TransactionExploreConditionOperator[] { - let operatorTypes: PartialRecord = {}; - - switch (this.condition.field) { - case TransactionExploreConditionField.TransactionType.value: - operatorTypes = TransactionExploreTransactionTypeCondition.supportedOperators; - break; - case TransactionExploreConditionField.TransactionCategory.value: - operatorTypes = TransactionExploreTransactionCategoryCondition.supportedOperators; - break; - case TransactionExploreConditionField.SourceAccount.value: - operatorTypes = TransactionExploreSourceAccountCondition.supportedOperators; - break; - case TransactionExploreConditionField.DestinationAccount.value: - operatorTypes = TransactionExploreDestinationAccountCondition.supportedOperators; - break; - case TransactionExploreConditionField.SourceAmount.value: - operatorTypes = TransactionExploreSourceAmountCondition.supportedOperators; - break; - case TransactionExploreConditionField.DestinationAmount.value: - operatorTypes = TransactionExploreDestinationAmountCondition.supportedOperators; - break; - case TransactionExploreConditionField.TransactionTag.value: - operatorTypes = TransactionExploreTransactionTagCondition.supportedOperators; - break; - case TransactionExploreConditionField.Description.value: - operatorTypes = TransactionExploreDescriptionCondition.supportedOperators; - break; - default: - return []; - } - - const result: TransactionExploreConditionOperator[] = []; - - for (const key of keysIfValueEquals(operatorTypes, true)) { - const operator = TransactionExploreConditionOperator.valueOf(key); - - if (operator) { - result.push(operator); - } - } - - return result; - } - - public toJsonObject(): unknown { - return { - condition: { - field: this.condition.field, - operator: this.condition.operator, - value: this.condition.getValueForStore() - }, - relation: this.relation - }; - } - - public static parse(data: unknown): TransactionExploreConditionWithRelation | null { - if (typeof data !== 'object' || !data || !('condition' in 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 TransactionExploreConditionOperatorType; - const conditionValue = conditionObject['value']; - - let condition: TransactionExploreCondition | null = null; - - switch (conditionField) { - case TransactionExploreConditionField.TransactionType.value: - if (conditionOperator === TransactionExploreConditionOperatorType.In && Array.isArray(conditionValue)) { - condition = new TransactionExploreTransactionTypeCondition(conditionValue as number[]); - } - break; - case TransactionExploreConditionField.TransactionCategory.value: - if (conditionOperator === TransactionExploreConditionOperatorType.In && Array.isArray(conditionValue)) { - condition = new TransactionExploreTransactionCategoryCondition(conditionValue as string[]); - } - break; - case TransactionExploreConditionField.SourceAccount.value: - if (conditionOperator === TransactionExploreConditionOperatorType.In && Array.isArray(conditionValue)) { - condition = new TransactionExploreSourceAccountCondition(conditionValue as string[]); - } - break; - case TransactionExploreConditionField.DestinationAccount.value: - if (conditionOperator === TransactionExploreConditionOperatorType.In && Array.isArray(conditionValue)) { - condition = new TransactionExploreDestinationAccountCondition(conditionValue as string[]); - } - break; - case TransactionExploreConditionField.SourceAmount.value: - if (TransactionExploreSourceAmountCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue) && conditionValue.length === 2) { - condition = new TransactionExploreSourceAmountCondition(conditionOperator as AmountConditionOperator, conditionValue as [number, number]); - } - break; - case TransactionExploreConditionField.DestinationAmount.value: - if (TransactionExploreDestinationAmountCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue) && conditionValue.length === 2) { - condition = new TransactionExploreDestinationAmountCondition(conditionOperator as AmountConditionOperator, conditionValue as [number, number]); - } - break; - case TransactionExploreConditionField.TransactionTag.value: - if (TransactionExploreTransactionTagCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) { - condition = new TransactionExploreTransactionTagCondition(conditionOperator as TransactionTagConditionOperator, conditionValue as string[]); - } - break; - case TransactionExploreConditionField.Description.value: - if (TransactionExploreDescriptionCondition.supportedOperators[conditionOperator] && typeof conditionValue === 'string') { - condition = new TransactionExploreDescriptionCondition(conditionOperator as DescriptionConditionOperator, conditionValue); - } - break; - default: - break; - } - - if (condition === null) { - return null; - } - - if (relation !== TransactionExploreConditionRelation.First && relation !== TransactionExploreConditionRelation.And && relation !== TransactionExploreConditionRelation.Or) { - return null; - } - - return new TransactionExploreConditionWithRelation(condition, relation); - } -} - -export interface TransactionExploreCondition { - readonly field: T; - readonly operator: TransactionExploreConditionOperatorType; - value: V; - - getValueForStore(): V; - match(transaction: TransactionInsightDataItem): boolean; - toExpression(allCategoriesMap: Record, allAccountsMap: Record, allTagsMap: Record): string; -} - -export class TransactionExploreTransactionTypeCondition implements TransactionExploreCondition { - public static readonly supportedOperators: PartialRecord = { - [TransactionExploreConditionOperatorType.In]: true - }; - public readonly field = TransactionExploreConditionFieldType.TransactionType; - public readonly operator: TransactionExploreConditionOperatorType.In = TransactionExploreConditionOperatorType.In; - public value: number[]; - - constructor(value: number[]) { - this.value = value; - } - - public getValueForStore(): number[] { - return this.value; - } - - public match(transaction: TransactionInsightDataItem): boolean { - return this.value.includes(transaction.type); - } - - public toExpression(): string { - const textualTypes = this.value.map(type => { - if (type === TransactionType.Income) { - return 'Income'; - } else if (type === TransactionType.Expense) { - return 'Expense'; - } else if (type === TransactionType.Transfer) { - return 'Transfer'; - } else { - return type.toString(); - } - }).join(', '); - return `type IN (${textualTypes})`; - } -} - -export class TransactionExploreTransactionCategoryCondition implements TransactionExploreCondition { - public static readonly supportedOperators: PartialRecord = { - [TransactionExploreConditionOperatorType.In]: true - }; - public readonly field = TransactionExploreConditionFieldType.TransactionCategory; - public readonly operator: TransactionExploreConditionOperatorType.In = TransactionExploreConditionOperatorType.In; - public value: string[]; - - constructor(value: string[]) { - this.value = value; - } - - public getValueForStore(): string[] { - return this.value; - } - - public match(transaction: TransactionInsightDataItem): boolean { - return this.value.includes(transaction.primaryCategory?.id ?? '') || this.value.includes(transaction.secondaryCategory?.id ?? transaction.categoryId); - } - - public toExpression(allCategoriesMap: Record): string { - const textualCategories = this.value.map(id => { - const category = allCategoriesMap[id]; - - if (category) { - if (!category.parentId || category.parentId === '0') { - return ''; - } - - return `'${category.name}'`; - } else { - return `'${id}'`; - } - }).filter(item => !!item).join(', '); - return `category IN (${textualCategories})`; - } -} - -export class TransactionExploreSourceAccountCondition implements TransactionExploreCondition { - public static readonly supportedOperators: PartialRecord = { - [TransactionExploreConditionOperatorType.In]: true - }; - public readonly field = TransactionExploreConditionFieldType.SourceAccount; - public readonly operator: TransactionExploreConditionOperatorType.In = TransactionExploreConditionOperatorType.In; - public value: string[]; - - constructor(value: string[]) { - this.value = value; - } - - public getValueForStore(): string[] { - return this.value; - } - - public match(transaction: TransactionInsightDataItem): boolean { - return this.value.includes(transaction.sourceAccountId); - } - - public toExpression(allCategoriesMap: Record, allAccountsMap: Record): string { - const textualAccounts = this.value.map(id => { - const account = allAccountsMap[id]; - - if (account) { - if (account.type === AccountType.MultiSubAccounts.type) { - return ''; - } - - return `'${account.name}'`; - } else { - return `'${id}'`; - } - }).filter(item => !!item).join(', '); - return `source_account IN (${textualAccounts})`; - } -} - -export class TransactionExploreDestinationAccountCondition implements TransactionExploreCondition { - public static readonly supportedOperators: PartialRecord = { - [TransactionExploreConditionOperatorType.In]: true - }; - public readonly field = TransactionExploreConditionFieldType.DestinationAccount; - public readonly operator: TransactionExploreConditionOperatorType.In = TransactionExploreConditionOperatorType.In; - public value: string[]; - - constructor(value: string[]) { - this.value = value; - } - - public getValueForStore(): string[] { - return this.value; - } - - public match(transaction: TransactionInsightDataItem): boolean { - return this.value.includes(transaction.destinationAccountId); - } - - public toExpression(allCategoriesMap: Record, allAccountsMap: Record): string { - const textualAccounts = this.value.map(id => { - const account = allAccountsMap[id]; - - if (account) { - if (account.type === AccountType.MultiSubAccounts.type) { - return ''; - } - - return `'${account.name}'`; - } else { - return `'${id}'`; - } - }).filter(item => !!item).join(', '); - return `destination_account IN (${textualAccounts})`; - } -} - -type AmountConditionField = TransactionExploreConditionFieldType.SourceAmount | TransactionExploreConditionFieldType.DestinationAmount; -type AmountConditionOperator = TransactionExploreConditionOperatorType.Equals | - TransactionExploreConditionOperatorType.NotEquals | - TransactionExploreConditionOperatorType.GreaterThan | - TransactionExploreConditionOperatorType.LessThan | - TransactionExploreConditionOperatorType.Between | - TransactionExploreConditionOperatorType.NotBetween; - -export abstract class AbstractTransactionExploreAmountCondition implements TransactionExploreCondition { - public static readonly supportedOperators: PartialRecord = { - [TransactionExploreConditionOperatorType.Equals]: true, - [TransactionExploreConditionOperatorType.NotEquals]: true, - [TransactionExploreConditionOperatorType.GreaterThan]: true, - [TransactionExploreConditionOperatorType.LessThan]: true, - [TransactionExploreConditionOperatorType.Between]: true, - [TransactionExploreConditionOperatorType.NotBetween]: true - }; - public abstract readonly field: T; - public readonly operator: AmountConditionOperator = TransactionExploreConditionOperatorType.Between; - public value: [number, number]; - - protected constructor(operator: AmountConditionOperator, value: [number, number]) { - this.operator = operator; - this.value = value; - } - - public getValueForStore(): [number, number] { - if (this.operator === TransactionExploreConditionOperatorType.Between || this.operator === TransactionExploreConditionOperatorType.NotBetween) { - return [this.value[0], this.value[1]]; - } else { - return [this.value[0], this.value[0]]; - } - } - - public abstract match(transaction: TransactionInsightDataItem): boolean; - public abstract toExpression(allCategoriesMap: Record, allAccountsMap: Record, allTagsMap: Record): string; - - protected matchAmount(amount: number): boolean { - switch (this.operator) { - case TransactionExploreConditionOperatorType.GreaterThan: - return amount > this.value[0]; - case TransactionExploreConditionOperatorType.LessThan: - return amount < this.value[0]; - case TransactionExploreConditionOperatorType.Equals: - return amount === this.value[0]; - case TransactionExploreConditionOperatorType.NotEquals: - return amount !== this.value[0]; - case TransactionExploreConditionOperatorType.Between: - return amount >= this.value[0] && amount <= this.value[1]; - case TransactionExploreConditionOperatorType.NotBetween: - return amount < this.value[0] || amount > this.value[1]; - default: - return false; - } - } - - protected getExpression(amountFieldName: string): string { - let expressionAmount1 = this.value[0].toString(10); - let expressionAmount2 = this.value[1].toString(10); - - if (expressionAmount1.length > 2) { - expressionAmount1 = `${expressionAmount1.substring(0, expressionAmount1.length - 2)}.${expressionAmount1.substring(expressionAmount1.length - 2)}`; - } - - if (expressionAmount2.length > 2) { - expressionAmount2 = `${expressionAmount2.substring(0, expressionAmount2.length - 2)}.${expressionAmount2.substring(expressionAmount2.length - 2)}`; - } - - switch (this.operator) { - case TransactionExploreConditionOperatorType.GreaterThan: - return `${amountFieldName} > ${expressionAmount1}`; - case TransactionExploreConditionOperatorType.LessThan: - return `${amountFieldName} < ${expressionAmount1}`; - case TransactionExploreConditionOperatorType.Equals: - return `${amountFieldName} = ${expressionAmount1}`; - case TransactionExploreConditionOperatorType.NotEquals: - return `${amountFieldName} <> ${expressionAmount1}`; - case TransactionExploreConditionOperatorType.Between: - return `(${amountFieldName} >= ${expressionAmount1} AND ${amountFieldName} <= ${expressionAmount2})`; - case TransactionExploreConditionOperatorType.NotBetween: - return `(${amountFieldName} < ${expressionAmount1} OR ${amountFieldName} > ${expressionAmount2})`; - default: - return ''; - } - } -} - -export class TransactionExploreSourceAmountCondition extends AbstractTransactionExploreAmountCondition { - public readonly field = TransactionExploreConditionFieldType.SourceAmount; - - constructor(operator: AmountConditionOperator, value: [number, number]) { - super(operator, value); - } - - public match(transaction: TransactionInsightDataItem): boolean { - return super.matchAmount(transaction.sourceAmount); - } - - public toExpression(): string { - return this.getExpression('source_amount'); - } -} - -export class TransactionExploreDestinationAmountCondition extends AbstractTransactionExploreAmountCondition { - public readonly field = TransactionExploreConditionFieldType.DestinationAmount; - - constructor(operator: AmountConditionOperator, value: [number, number]) { - super(operator, value); - } - - public match(transaction: TransactionInsightDataItem): boolean { - return super.matchAmount(transaction.destinationAmount); - } - - public toExpression(): string { - return this.getExpression('destination_amount'); - } -} - -type TransactionTagConditionOperator = TransactionExploreConditionOperatorType.IsEmpty | - TransactionExploreConditionOperatorType.IsNotEmpty | - TransactionExploreConditionOperatorType.Equals | - TransactionExploreConditionOperatorType.NotEquals | - TransactionExploreConditionOperatorType.HasAny | - TransactionExploreConditionOperatorType.HasAll | - TransactionExploreConditionOperatorType.NotHasAny | - TransactionExploreConditionOperatorType.NotHasAll; - -export class TransactionExploreTransactionTagCondition implements TransactionExploreCondition { - public static readonly supportedOperators: PartialRecord = { - [TransactionExploreConditionOperatorType.IsEmpty]: true, - [TransactionExploreConditionOperatorType.IsNotEmpty]: true, - [TransactionExploreConditionOperatorType.Equals]: true, - [TransactionExploreConditionOperatorType.NotEquals]: true, - [TransactionExploreConditionOperatorType.HasAny]: true, - [TransactionExploreConditionOperatorType.HasAll]: true, - [TransactionExploreConditionOperatorType.NotHasAny]: true, - [TransactionExploreConditionOperatorType.NotHasAll]: true - }; - public readonly field = TransactionExploreConditionFieldType.TransactionTag; - public readonly operator: TransactionTagConditionOperator = TransactionExploreConditionOperatorType.HasAny; - public value: string[]; - - constructor(operator: TransactionTagConditionOperator, value: string[]) { - this.operator = operator; - this.value = value; - } - - public getValueForStore(): string[] { - if (this.operator === TransactionExploreConditionOperatorType.IsEmpty || this.operator === TransactionExploreConditionOperatorType.IsNotEmpty) { - return []; - } - - return this.value; - } - - public match(transaction: TransactionInsightDataItem): boolean { - const transactionTags: Record = {}; - - for (const tagId of transaction.tagIds) { - transactionTags[tagId] = true; - } - - if (this.operator === TransactionExploreConditionOperatorType.IsEmpty || this.value.length < 1) { - return transaction.tagIds.length < 1; - } else if (this.operator === TransactionExploreConditionOperatorType.IsNotEmpty) { - return transaction.tagIds.length > 0; - } else if (this.operator === TransactionExploreConditionOperatorType.Equals || this.operator === TransactionExploreConditionOperatorType.NotEquals) { - let hasAll = true; - - for (const tagId of this.value) { - if (!transactionTags[tagId]) { - hasAll = false; - break; - } - } - - const hasSameCount = transaction.tagIds.length === this.value.length; - - if (this.operator === TransactionExploreConditionOperatorType.Equals && hasAll && hasSameCount) { - return true; - } else if (this.operator === TransactionExploreConditionOperatorType.NotEquals && (!hasAll || !hasSameCount)) { - return true; - } - } else if (this.operator === TransactionExploreConditionOperatorType.HasAny || this.operator === TransactionExploreConditionOperatorType.NotHasAny) { - let hasAny = false; - - for (const tagId of this.value) { - if (transactionTags[tagId]) { - hasAny = true; - break; - } - } - - if (this.operator === TransactionExploreConditionOperatorType.HasAny && hasAny) { - return true; - } else if (this.operator === TransactionExploreConditionOperatorType.NotHasAny && !hasAny) { - return true; - } - } else if (this.operator === TransactionExploreConditionOperatorType.HasAll || this.operator === TransactionExploreConditionOperatorType.NotHasAll) { - let hasAll = true; - - for (const tagId of this.value) { - if (!transactionTags[tagId]) { - hasAll = false; - break; - } - } - - if (this.operator === TransactionExploreConditionOperatorType.HasAll && hasAll) { - return true; - } else if (this.operator === TransactionExploreConditionOperatorType.NotHasAll && !hasAll) { - return true; - } - } - - return false; - } - - public toExpression(allCategoriesMap: Record, allAccountsMap: Record, allTagsMap: Record): string { - if (this.operator === TransactionExploreConditionOperatorType.IsEmpty) { - return `tags IS EMPTY`; - } else if (this.operator === TransactionExploreConditionOperatorType.IsNotEmpty) { - return `tags IS NOT EMPTY`; - } - - const textualTags = this.value.map(id => { - const tag = allTagsMap[id]; - - if (tag) { - return `'${tag.name}'`; - } else { - return `'${id}'`; - } - }).join(', '); - - if (this.operator === TransactionExploreConditionOperatorType.Equals) { - return `tags FULL MATCHES (${textualTags})`; - } else if (this.operator === TransactionExploreConditionOperatorType.NotEquals) { - return `tags NOT FULL MATCHES (${textualTags})`; - } else if (this.operator === TransactionExploreConditionOperatorType.HasAny) { - return `tags HAS ANY (${textualTags})`; - } else if (this.operator === TransactionExploreConditionOperatorType.HasAll) { - return `tags HAS ALL (${textualTags})`; - } else if (this.operator === TransactionExploreConditionOperatorType.NotHasAny) { - return `tags NOT HAS ANY (${textualTags})`; - } else if (this.operator === TransactionExploreConditionOperatorType.NotHasAll) { - return `tags NOT HAS ALL (${textualTags})`; - } else { - return ''; - } - } -} - -type DescriptionConditionOperator = TransactionExploreConditionOperatorType.IsEmpty | - TransactionExploreConditionOperatorType.IsNotEmpty | - TransactionExploreConditionOperatorType.Equals | - TransactionExploreConditionOperatorType.NotEquals | - TransactionExploreConditionOperatorType.Contains | - TransactionExploreConditionOperatorType.NotContains | - TransactionExploreConditionOperatorType.StartsWith | - TransactionExploreConditionOperatorType.NotStartsWith | - TransactionExploreConditionOperatorType.EndsWith | - TransactionExploreConditionOperatorType.NotEndsWith; - -export class TransactionExploreDescriptionCondition implements TransactionExploreCondition { - public static readonly supportedOperators: PartialRecord = { - [TransactionExploreConditionOperatorType.IsEmpty]: true, - [TransactionExploreConditionOperatorType.IsNotEmpty]: true, - [TransactionExploreConditionOperatorType.Equals]: true, - [TransactionExploreConditionOperatorType.NotEquals]: true, - [TransactionExploreConditionOperatorType.Contains]: true, - [TransactionExploreConditionOperatorType.NotContains]: true, - [TransactionExploreConditionOperatorType.StartsWith]: true, - [TransactionExploreConditionOperatorType.NotStartsWith]: true, - [TransactionExploreConditionOperatorType.EndsWith]: true, - [TransactionExploreConditionOperatorType.NotEndsWith]: true - }; - public readonly field = TransactionExploreConditionFieldType.Description; - public readonly operator: DescriptionConditionOperator = TransactionExploreConditionOperatorType.Contains; - public value: string; - - constructor(operator: DescriptionConditionOperator, value: string) { - this.operator = operator; - this.value = value; - } - - public getValueForStore(): string { - if (this.operator === TransactionExploreConditionOperatorType.IsEmpty || this.operator === TransactionExploreConditionOperatorType.IsNotEmpty) { - return ''; - } - - return this.value; - } - - public match(transaction: TransactionInsightDataItem): boolean { - const description = transaction.comment || ''; - - if (this.operator === TransactionExploreConditionOperatorType.IsEmpty) { - return description.length === 0; - } else if (this.operator === TransactionExploreConditionOperatorType.IsNotEmpty) { - return description.length > 0; - } else if (this.operator === TransactionExploreConditionOperatorType.Equals) { - return description === this.value; - } else if (this.operator === TransactionExploreConditionOperatorType.NotEquals) { - return description !== this.value; - } else if (this.operator === TransactionExploreConditionOperatorType.Contains) { - return description.includes(this.value); - } else if (this.operator === TransactionExploreConditionOperatorType.NotContains) { - return !description.includes(this.value); - } else if (this.operator === TransactionExploreConditionOperatorType.StartsWith) { - return description.startsWith(this.value); - } else if (this.operator === TransactionExploreConditionOperatorType.NotStartsWith) { - return !description.startsWith(this.value); - } else if (this.operator === TransactionExploreConditionOperatorType.EndsWith) { - return description.endsWith(this.value); - } else if (this.operator === TransactionExploreConditionOperatorType.NotEndsWith) { - return !description.endsWith(this.value); - } - - return false; - } - - public toExpression(): string { - if (this.operator === TransactionExploreConditionOperatorType.IsEmpty) { - return `description IS EMPTY`; - } else if (this.operator === TransactionExploreConditionOperatorType.IsNotEmpty) { - return `description IS NOT EMPTY`; - } else if (this.operator === TransactionExploreConditionOperatorType.Equals) { - return `description = '${this.value.replace(/'/g, "''")}'`; - } else if (this.operator === TransactionExploreConditionOperatorType.NotEquals) { - return `description <> '${this.value.replace(/'/g, "''")}'`; - } else if (this.operator === TransactionExploreConditionOperatorType.Contains) { - return `description CONTAINS '${this.value.replace(/'/g, "''")}'`; - } else if (this.operator === TransactionExploreConditionOperatorType.NotContains) { - return `description NOT CONTAINS '${this.value.replace(/'/g, "''")}'`; - } else if (this.operator === TransactionExploreConditionOperatorType.StartsWith) { - return `description STARTS WITH '${this.value.replace(/'/g, "''")}'`; - } else if (this.operator === TransactionExploreConditionOperatorType.NotStartsWith) { - return `description NOT STARTS WITH '${this.value.replace(/'/g, "''")}'`; - } else if (this.operator === TransactionExploreConditionOperatorType.EndsWith) { - return `description ENDS WITH '${this.value.replace(/'/g, "''")}'`; - } else if (this.operator === TransactionExploreConditionOperatorType.NotEndsWith) { - return `description NOT ENDS WITH '${this.value.replace(/'/g, "''")}'`; - } - - return ''; - } -} diff --git a/src/models/explorer.ts b/src/models/explorer.ts new file mode 100644 index 00000000..3d04f1bc --- /dev/null +++ b/src/models/explorer.ts @@ -0,0 +1,922 @@ +import { type PartialRecord, itemAndIndex, keysIfValueEquals } from '@/core/base.ts'; +import { AccountType } from '@/core/account.ts'; +import { TransactionType } from '@/core/transaction.ts'; +import { + TransactionExplorerConditionRelation, + TransactionExplorerConditionRelationPriority, + TransactionExplorerConditionFieldType, + TransactionExplorerConditionField, + TransactionExplorerConditionOperatorType, + TransactionExplorerConditionOperator +} from '@/core/explorer.ts'; + +import { Account } from '@/models/account.ts'; +import { TransactionCategory } from '@/models/transaction_category.ts'; +import { TransactionTag } from '@/models/transaction_tag.ts'; +import { type TransactionInsightDataItem } from '@/models/transaction.ts'; + +interface ExpressionNode { + textualExpression: string; + operator?: TransactionExplorerConditionRelation; +} + +export class TransactionExplorerQuery { + public name: string; + public conditions: TransactionExplorerConditionWithRelation[]; + + private constructor(name: string, conditions: TransactionExplorerConditionWithRelation[]) { + this.name = name; + this.conditions = conditions; + } + + public addNewCondition(field: TransactionExplorerConditionField, isFirst: boolean): TransactionExplorerConditionWithRelation { + let condition: TransactionExplorerCondition; + + switch (field) { + case TransactionExplorerConditionField.TransactionType: + condition = new TransactionExplorerTransactionTypeCondition([ TransactionType.Expense, TransactionType.Income, TransactionType.Transfer ]); + break; + case TransactionExplorerConditionField.TransactionCategory: + condition = new TransactionExplorerTransactionCategoryCondition([]); + break; + case TransactionExplorerConditionField.SourceAccount: + condition = new TransactionExplorerSourceAccountCondition([]); + break; + case TransactionExplorerConditionField.DestinationAccount: + condition = new TransactionExplorerDestinationAccountCondition([]); + break; + case TransactionExplorerConditionField.SourceAmount: + condition = new TransactionExplorerSourceAmountCondition(TransactionExplorerConditionOperatorType.Between, [0, 0]); + break; + case TransactionExplorerConditionField.DestinationAmount: + condition = new TransactionExplorerDestinationAmountCondition(TransactionExplorerConditionOperatorType.Between, [0, 0]); + break; + case TransactionExplorerConditionField.TransactionTag: + condition = new TransactionExplorerTransactionTagCondition(TransactionExplorerConditionOperatorType.HasAny, []); + break; + case TransactionExplorerConditionField.Description: + condition = new TransactionExplorerDescriptionCondition(TransactionExplorerConditionOperatorType.Contains, ''); + break; + default: + condition = new TransactionExplorerTransactionTypeCondition([ TransactionType.Expense, TransactionType.Income, TransactionType.Transfer ]); + break; + } + + return new TransactionExplorerConditionWithRelation( + condition, + isFirst ? TransactionExplorerConditionRelation.First : TransactionExplorerConditionRelation.And + ); + } + + public match(transaction: TransactionInsightDataItem): boolean { + if (!this.conditions || this.conditions.length < 1) { + return true; + } + + const postfixExprTokens = this.getPostfixExprTokens(); + const stack: boolean[] = []; + + for (const token of postfixExprTokens) { + if (token === TransactionExplorerConditionRelation.And || token === TransactionExplorerConditionRelation.Or) { + const right = stack.pop(); + const left = stack.pop(); + + if (left === undefined || right === undefined) { + throw new Error('invalid postfix expression'); + } + + if (token === TransactionExplorerConditionRelation.And) { + stack.push(left && right); + } else if (token === TransactionExplorerConditionRelation.Or) { + stack.push(left || right); + } else { + throw new Error('invalid postfix expression'); + } + } else { + stack.push(token.match(transaction)); + } + } + + if (stack.length !== 1) { + throw new Error('invalid postfix evaluation result'); + } + + return stack[0] as boolean; + } + + public toExpression(allCategoriesMap: Record, allAccountsMap: Record, allTagsMap: Record): string { + if (!this.conditions || this.conditions.length < 1) { + return ''; + } + + const postfixExprTokens = this.getPostfixExprTokens(); + const stack: ExpressionNode[] = []; + + for (const token of postfixExprTokens) { + if (token === TransactionExplorerConditionRelation.And || token === TransactionExplorerConditionRelation.Or) { + const right = stack.pop(); + const left = stack.pop(); + + if (left === undefined || right === undefined) { + throw new Error('invalid postfix expression'); + } + + let leftExpression = left.textualExpression; + let rightExpression = right.textualExpression; + + if (left.operator && left.operator !== token) { + leftExpression = `(${leftExpression})`; + } + + if (right.operator && right.operator !== token) { + rightExpression = `(${rightExpression})`; + } + + stack.push({ + textualExpression: `${leftExpression} ${token.toUpperCase()} ${rightExpression}`, + operator: token + }); + } else { + stack.push({ + textualExpression: token.toExpression(allCategoriesMap, allAccountsMap, allTagsMap) + }); + } + } + + if (stack.length !== 1) { + throw new Error('invalid postfix evaluation result'); + } + + const finalNode = stack[0]; + + if (!finalNode) { + throw new Error('invalid postfix evaluation result'); + } + + return finalNode.textualExpression; + } + + public getPostfixExprTokens(): (TransactionExplorerCondition | TransactionExplorerConditionRelation.And | TransactionExplorerConditionRelation.Or)[] { + const finalTokens: (TransactionExplorerCondition | TransactionExplorerConditionRelation.And | TransactionExplorerConditionRelation.Or)[] = []; + + if (this.conditions.length < 1) { + return finalTokens; + } + + const operatorStack: TransactionExplorerConditionRelation[] = []; + const firstCondition = this.conditions[0] as TransactionExplorerConditionWithRelation; + + if (firstCondition.relation !== TransactionExplorerConditionRelation.First) { + throw new Error('the first condition must have relation "first"'); + } + + finalTokens.push(firstCondition.condition); + + for (const [item, index] of itemAndIndex(this.conditions)) { + if (index < 1) { + continue; + } + + if (item.relation === TransactionExplorerConditionRelation.First) { + throw new Error('only the first condition can have relation "first"'); + } + + const currentOperator = item.relation; + + while (operatorStack.length > 0) { + const topOperator = operatorStack[operatorStack.length - 1]; + const isAndOrOperator = topOperator === TransactionExplorerConditionRelation.And || topOperator === TransactionExplorerConditionRelation.Or; + + if (isAndOrOperator && TransactionExplorerConditionRelationPriority[topOperator] >= TransactionExplorerConditionRelationPriority[currentOperator]) { + finalTokens.push(topOperator); + operatorStack.pop(); + } else { + break; + } + } + + operatorStack.push(currentOperator); + finalTokens.push(item.condition); + } + + while (operatorStack.length > 0) { + const topOperator = operatorStack.pop(); + + if (topOperator !== TransactionExplorerConditionRelation.And && topOperator !== TransactionExplorerConditionRelation.Or) { + throw new Error('invalid operator in stack'); + } + + finalTokens.push(topOperator); + } + + return finalTokens; + } + + public clone(): TransactionExplorerQuery { + const clonedConditions: TransactionExplorerConditionWithRelation[] = []; + + for (const condition of this.conditions) { + const clonedCondition = TransactionExplorerConditionWithRelation.parse(condition.toJsonObject()); + + if (!clonedCondition) { + continue; + } + + clonedConditions.push(clonedCondition); + } + + return new TransactionExplorerQuery(this.name, clonedConditions); + } + + public toJson(): string { + return JSON.stringify({ + name: this.name, + conditions: this.conditions.map(condition => condition.toJsonObject()) + }); + } + + public static create(): TransactionExplorerQuery { + return new TransactionExplorerQuery("", []); + } + + 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)) { + return null; + } + + const name: string = nameFieldValue; + const conditions: TransactionExplorerConditionWithRelation[] = []; + + for (const [item, index] of itemAndIndex(conditionsFieldValue)) { + const condition = TransactionExplorerConditionWithRelation.parse(item); + + if (condition === null) { + return null; + } + + if (index === 0 && condition.relation !== TransactionExplorerConditionRelation.First) { + return null; + } else if (index > 0 && condition.relation === TransactionExplorerConditionRelation.First) { + return null; + } + + conditions.push(condition); + } + + return new TransactionExplorerQuery(name, conditions); + } +} + +export class TransactionExplorerConditionWithRelation { + public condition: TransactionExplorerCondition; + public relation: TransactionExplorerConditionRelation; + + constructor(condition: TransactionExplorerCondition, relation: TransactionExplorerConditionRelation) { + this.condition = condition; + this.relation = relation; + } + + public getSupportedOperators(): TransactionExplorerConditionOperator[] { + let operatorTypes: PartialRecord = {}; + + switch (this.condition.field) { + case TransactionExplorerConditionField.TransactionType.value: + operatorTypes = TransactionExplorerTransactionTypeCondition.supportedOperators; + break; + case TransactionExplorerConditionField.TransactionCategory.value: + operatorTypes = TransactionExplorerTransactionCategoryCondition.supportedOperators; + break; + case TransactionExplorerConditionField.SourceAccount.value: + operatorTypes = TransactionExplorerSourceAccountCondition.supportedOperators; + break; + case TransactionExplorerConditionField.DestinationAccount.value: + operatorTypes = TransactionExplorerDestinationAccountCondition.supportedOperators; + break; + case TransactionExplorerConditionField.SourceAmount.value: + operatorTypes = TransactionExplorerSourceAmountCondition.supportedOperators; + break; + case TransactionExplorerConditionField.DestinationAmount.value: + operatorTypes = TransactionExplorerDestinationAmountCondition.supportedOperators; + break; + case TransactionExplorerConditionField.TransactionTag.value: + operatorTypes = TransactionExplorerTransactionTagCondition.supportedOperators; + break; + case TransactionExplorerConditionField.Description.value: + operatorTypes = TransactionExplorerDescriptionCondition.supportedOperators; + break; + default: + return []; + } + + const result: TransactionExplorerConditionOperator[] = []; + + for (const key of keysIfValueEquals(operatorTypes, true)) { + const operator = TransactionExplorerConditionOperator.valueOf(key); + + if (operator) { + result.push(operator); + } + } + + return result; + } + + public toJsonObject(): unknown { + return { + condition: { + field: this.condition.field, + operator: this.condition.operator, + value: this.condition.getValueForStore() + }, + relation: this.relation + }; + } + + public static parse(data: unknown): TransactionExplorerConditionWithRelation | null { + if (typeof data !== 'object' || !data || !('condition' in 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.TransactionTag.value: + if (TransactionExplorerTransactionTagCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) { + condition = new TransactionExplorerTransactionTagCondition(conditionOperator as TransactionTagConditionOperator, 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 (condition === null) { + return null; + } + + if (relation !== TransactionExplorerConditionRelation.First && relation !== TransactionExplorerConditionRelation.And && relation !== TransactionExplorerConditionRelation.Or) { + return null; + } + + return new TransactionExplorerConditionWithRelation(condition, relation); + } +} + +export interface TransactionExplorerCondition { + readonly field: T; + readonly operator: TransactionExplorerConditionOperatorType; + value: V; + + getValueForStore(): V; + match(transaction: TransactionInsightDataItem): boolean; + toExpression(allCategoriesMap: Record, allAccountsMap: Record, allTagsMap: Record): string; +} + +export class TransactionExplorerTransactionTypeCondition implements TransactionExplorerCondition { + public static readonly supportedOperators: PartialRecord = { + [TransactionExplorerConditionOperatorType.In]: true + }; + public readonly field = TransactionExplorerConditionFieldType.TransactionType; + public readonly operator: TransactionExplorerConditionOperatorType.In = TransactionExplorerConditionOperatorType.In; + public value: number[]; + + constructor(value: number[]) { + this.value = value; + } + + public getValueForStore(): number[] { + return this.value; + } + + public match(transaction: TransactionInsightDataItem): boolean { + return this.value.includes(transaction.type); + } + + public toExpression(): string { + const textualTypes = this.value.map(type => { + if (type === TransactionType.Income) { + return 'Income'; + } else if (type === TransactionType.Expense) { + return 'Expense'; + } else if (type === TransactionType.Transfer) { + return 'Transfer'; + } else { + return type.toString(); + } + }).join(', '); + return `type IN (${textualTypes})`; + } +} + +export class TransactionExplorerTransactionCategoryCondition implements TransactionExplorerCondition { + public static readonly supportedOperators: PartialRecord = { + [TransactionExplorerConditionOperatorType.In]: true + }; + public readonly field = TransactionExplorerConditionFieldType.TransactionCategory; + public readonly operator: TransactionExplorerConditionOperatorType.In = TransactionExplorerConditionOperatorType.In; + public value: string[]; + + constructor(value: string[]) { + this.value = value; + } + + public getValueForStore(): string[] { + return this.value; + } + + public match(transaction: TransactionInsightDataItem): boolean { + return this.value.includes(transaction.primaryCategory?.id ?? '') || this.value.includes(transaction.secondaryCategory?.id ?? transaction.categoryId); + } + + public toExpression(allCategoriesMap: Record): string { + const textualCategories = this.value.map(id => { + const category = allCategoriesMap[id]; + + if (category) { + if (!category.parentId || category.parentId === '0') { + return ''; + } + + return `'${category.name}'`; + } else { + return `'${id}'`; + } + }).filter(item => !!item).join(', '); + return `category IN (${textualCategories})`; + } +} + +export class TransactionExplorerSourceAccountCondition implements TransactionExplorerCondition { + public static readonly supportedOperators: PartialRecord = { + [TransactionExplorerConditionOperatorType.In]: true + }; + public readonly field = TransactionExplorerConditionFieldType.SourceAccount; + public readonly operator: TransactionExplorerConditionOperatorType.In = TransactionExplorerConditionOperatorType.In; + public value: string[]; + + constructor(value: string[]) { + this.value = value; + } + + public getValueForStore(): string[] { + return this.value; + } + + public match(transaction: TransactionInsightDataItem): boolean { + return this.value.includes(transaction.sourceAccountId); + } + + public toExpression(allCategoriesMap: Record, allAccountsMap: Record): string { + const textualAccounts = this.value.map(id => { + const account = allAccountsMap[id]; + + if (account) { + if (account.type === AccountType.MultiSubAccounts.type) { + return ''; + } + + return `'${account.name}'`; + } else { + return `'${id}'`; + } + }).filter(item => !!item).join(', '); + return `source_account IN (${textualAccounts})`; + } +} + +export class TransactionExplorerDestinationAccountCondition implements TransactionExplorerCondition { + public static readonly supportedOperators: PartialRecord = { + [TransactionExplorerConditionOperatorType.In]: true + }; + public readonly field = TransactionExplorerConditionFieldType.DestinationAccount; + public readonly operator: TransactionExplorerConditionOperatorType.In = TransactionExplorerConditionOperatorType.In; + public value: string[]; + + constructor(value: string[]) { + this.value = value; + } + + public getValueForStore(): string[] { + return this.value; + } + + public match(transaction: TransactionInsightDataItem): boolean { + return this.value.includes(transaction.destinationAccountId); + } + + public toExpression(allCategoriesMap: Record, allAccountsMap: Record): string { + const textualAccounts = this.value.map(id => { + const account = allAccountsMap[id]; + + if (account) { + if (account.type === AccountType.MultiSubAccounts.type) { + return ''; + } + + return `'${account.name}'`; + } else { + return `'${id}'`; + } + }).filter(item => !!item).join(', '); + return `destination_account IN (${textualAccounts})`; + } +} + +type AmountConditionField = TransactionExplorerConditionFieldType.SourceAmount | TransactionExplorerConditionFieldType.DestinationAmount; +type AmountConditionOperator = TransactionExplorerConditionOperatorType.Equals | + TransactionExplorerConditionOperatorType.NotEquals | + TransactionExplorerConditionOperatorType.GreaterThan | + TransactionExplorerConditionOperatorType.LessThan | + TransactionExplorerConditionOperatorType.Between | + TransactionExplorerConditionOperatorType.NotBetween; + +export abstract class AbstractTransactionExplorerAmountCondition implements TransactionExplorerCondition { + public static readonly supportedOperators: PartialRecord = { + [TransactionExplorerConditionOperatorType.Equals]: true, + [TransactionExplorerConditionOperatorType.NotEquals]: true, + [TransactionExplorerConditionOperatorType.GreaterThan]: true, + [TransactionExplorerConditionOperatorType.LessThan]: true, + [TransactionExplorerConditionOperatorType.Between]: true, + [TransactionExplorerConditionOperatorType.NotBetween]: true + }; + public abstract readonly field: T; + public readonly operator: AmountConditionOperator = TransactionExplorerConditionOperatorType.Between; + public value: [number, number]; + + protected constructor(operator: AmountConditionOperator, value: [number, number]) { + this.operator = operator; + this.value = value; + } + + public getValueForStore(): [number, number] { + if (this.operator === TransactionExplorerConditionOperatorType.Between || this.operator === TransactionExplorerConditionOperatorType.NotBetween) { + return [this.value[0], this.value[1]]; + } else { + return [this.value[0], this.value[0]]; + } + } + + public abstract match(transaction: TransactionInsightDataItem): boolean; + public abstract toExpression(allCategoriesMap: Record, allAccountsMap: Record, allTagsMap: Record): string; + + protected matchAmount(amount: number): boolean { + switch (this.operator) { + case TransactionExplorerConditionOperatorType.GreaterThan: + return amount > this.value[0]; + case TransactionExplorerConditionOperatorType.LessThan: + return amount < this.value[0]; + case TransactionExplorerConditionOperatorType.Equals: + return amount === this.value[0]; + case TransactionExplorerConditionOperatorType.NotEquals: + return amount !== this.value[0]; + case TransactionExplorerConditionOperatorType.Between: + return amount >= this.value[0] && amount <= this.value[1]; + case TransactionExplorerConditionOperatorType.NotBetween: + return amount < this.value[0] || amount > this.value[1]; + default: + return false; + } + } + + protected getExpression(amountFieldName: string): string { + let expressionAmount1 = this.value[0].toString(10); + let expressionAmount2 = this.value[1].toString(10); + + if (expressionAmount1.length > 2) { + expressionAmount1 = `${expressionAmount1.substring(0, expressionAmount1.length - 2)}.${expressionAmount1.substring(expressionAmount1.length - 2)}`; + } + + if (expressionAmount2.length > 2) { + expressionAmount2 = `${expressionAmount2.substring(0, expressionAmount2.length - 2)}.${expressionAmount2.substring(expressionAmount2.length - 2)}`; + } + + switch (this.operator) { + case TransactionExplorerConditionOperatorType.GreaterThan: + return `${amountFieldName} > ${expressionAmount1}`; + case TransactionExplorerConditionOperatorType.LessThan: + return `${amountFieldName} < ${expressionAmount1}`; + case TransactionExplorerConditionOperatorType.Equals: + return `${amountFieldName} = ${expressionAmount1}`; + case TransactionExplorerConditionOperatorType.NotEquals: + return `${amountFieldName} <> ${expressionAmount1}`; + case TransactionExplorerConditionOperatorType.Between: + return `(${amountFieldName} >= ${expressionAmount1} AND ${amountFieldName} <= ${expressionAmount2})`; + case TransactionExplorerConditionOperatorType.NotBetween: + return `(${amountFieldName} < ${expressionAmount1} OR ${amountFieldName} > ${expressionAmount2})`; + default: + return ''; + } + } +} + +export class TransactionExplorerSourceAmountCondition extends AbstractTransactionExplorerAmountCondition { + public readonly field = TransactionExplorerConditionFieldType.SourceAmount; + + constructor(operator: AmountConditionOperator, value: [number, number]) { + super(operator, value); + } + + public match(transaction: TransactionInsightDataItem): boolean { + return super.matchAmount(transaction.sourceAmount); + } + + public toExpression(): string { + return this.getExpression('source_amount'); + } +} + +export class TransactionExplorerDestinationAmountCondition extends AbstractTransactionExplorerAmountCondition { + public readonly field = TransactionExplorerConditionFieldType.DestinationAmount; + + constructor(operator: AmountConditionOperator, value: [number, number]) { + super(operator, value); + } + + public match(transaction: TransactionInsightDataItem): boolean { + return super.matchAmount(transaction.destinationAmount); + } + + public toExpression(): string { + return this.getExpression('destination_amount'); + } +} + +type TransactionTagConditionOperator = TransactionExplorerConditionOperatorType.IsEmpty | + TransactionExplorerConditionOperatorType.IsNotEmpty | + TransactionExplorerConditionOperatorType.Equals | + TransactionExplorerConditionOperatorType.NotEquals | + TransactionExplorerConditionOperatorType.HasAny | + TransactionExplorerConditionOperatorType.HasAll | + TransactionExplorerConditionOperatorType.NotHasAny | + TransactionExplorerConditionOperatorType.NotHasAll; + +export class TransactionExplorerTransactionTagCondition implements TransactionExplorerCondition { + public static readonly supportedOperators: PartialRecord = { + [TransactionExplorerConditionOperatorType.IsEmpty]: true, + [TransactionExplorerConditionOperatorType.IsNotEmpty]: true, + [TransactionExplorerConditionOperatorType.Equals]: true, + [TransactionExplorerConditionOperatorType.NotEquals]: true, + [TransactionExplorerConditionOperatorType.HasAny]: true, + [TransactionExplorerConditionOperatorType.HasAll]: true, + [TransactionExplorerConditionOperatorType.NotHasAny]: true, + [TransactionExplorerConditionOperatorType.NotHasAll]: true + }; + public readonly field = TransactionExplorerConditionFieldType.TransactionTag; + public readonly operator: TransactionTagConditionOperator = TransactionExplorerConditionOperatorType.HasAny; + public value: string[]; + + constructor(operator: TransactionTagConditionOperator, value: string[]) { + this.operator = operator; + this.value = value; + } + + public getValueForStore(): string[] { + if (this.operator === TransactionExplorerConditionOperatorType.IsEmpty || this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) { + return []; + } + + return this.value; + } + + public match(transaction: TransactionInsightDataItem): boolean { + const transactionTags: Record = {}; + + for (const tagId of transaction.tagIds) { + transactionTags[tagId] = true; + } + + if (this.operator === TransactionExplorerConditionOperatorType.IsEmpty || this.value.length < 1) { + return transaction.tagIds.length < 1; + } else if (this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) { + return transaction.tagIds.length > 0; + } else if (this.operator === TransactionExplorerConditionOperatorType.Equals || this.operator === TransactionExplorerConditionOperatorType.NotEquals) { + let hasAll = true; + + for (const tagId of this.value) { + if (!transactionTags[tagId]) { + hasAll = false; + break; + } + } + + const hasSameCount = transaction.tagIds.length === this.value.length; + + if (this.operator === TransactionExplorerConditionOperatorType.Equals && hasAll && hasSameCount) { + return true; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotEquals && (!hasAll || !hasSameCount)) { + return true; + } + } else if (this.operator === TransactionExplorerConditionOperatorType.HasAny || this.operator === TransactionExplorerConditionOperatorType.NotHasAny) { + let hasAny = false; + + for (const tagId of this.value) { + if (transactionTags[tagId]) { + hasAny = true; + break; + } + } + + if (this.operator === TransactionExplorerConditionOperatorType.HasAny && hasAny) { + return true; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotHasAny && !hasAny) { + return true; + } + } else if (this.operator === TransactionExplorerConditionOperatorType.HasAll || this.operator === TransactionExplorerConditionOperatorType.NotHasAll) { + let hasAll = true; + + for (const tagId of this.value) { + if (!transactionTags[tagId]) { + hasAll = false; + break; + } + } + + if (this.operator === TransactionExplorerConditionOperatorType.HasAll && hasAll) { + return true; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotHasAll && !hasAll) { + return true; + } + } + + return false; + } + + public toExpression(allCategoriesMap: Record, allAccountsMap: Record, allTagsMap: Record): string { + if (this.operator === TransactionExplorerConditionOperatorType.IsEmpty) { + return `tags IS EMPTY`; + } else if (this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) { + return `tags IS NOT EMPTY`; + } + + const textualTags = this.value.map(id => { + const tag = allTagsMap[id]; + + if (tag) { + return `'${tag.name}'`; + } else { + return `'${id}'`; + } + }).join(', '); + + if (this.operator === TransactionExplorerConditionOperatorType.Equals) { + return `tags FULL MATCHES (${textualTags})`; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotEquals) { + return `tags NOT FULL MATCHES (${textualTags})`; + } else if (this.operator === TransactionExplorerConditionOperatorType.HasAny) { + return `tags HAS ANY (${textualTags})`; + } else if (this.operator === TransactionExplorerConditionOperatorType.HasAll) { + return `tags HAS ALL (${textualTags})`; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotHasAny) { + return `tags NOT HAS ANY (${textualTags})`; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotHasAll) { + return `tags NOT HAS ALL (${textualTags})`; + } else { + return ''; + } + } +} + +type DescriptionConditionOperator = TransactionExplorerConditionOperatorType.IsEmpty | + TransactionExplorerConditionOperatorType.IsNotEmpty | + TransactionExplorerConditionOperatorType.Equals | + TransactionExplorerConditionOperatorType.NotEquals | + TransactionExplorerConditionOperatorType.Contains | + TransactionExplorerConditionOperatorType.NotContains | + TransactionExplorerConditionOperatorType.StartsWith | + TransactionExplorerConditionOperatorType.NotStartsWith | + TransactionExplorerConditionOperatorType.EndsWith | + TransactionExplorerConditionOperatorType.NotEndsWith; + +export class TransactionExplorerDescriptionCondition implements TransactionExplorerCondition { + public static readonly supportedOperators: PartialRecord = { + [TransactionExplorerConditionOperatorType.IsEmpty]: true, + [TransactionExplorerConditionOperatorType.IsNotEmpty]: true, + [TransactionExplorerConditionOperatorType.Equals]: true, + [TransactionExplorerConditionOperatorType.NotEquals]: true, + [TransactionExplorerConditionOperatorType.Contains]: true, + [TransactionExplorerConditionOperatorType.NotContains]: true, + [TransactionExplorerConditionOperatorType.StartsWith]: true, + [TransactionExplorerConditionOperatorType.NotStartsWith]: true, + [TransactionExplorerConditionOperatorType.EndsWith]: true, + [TransactionExplorerConditionOperatorType.NotEndsWith]: true + }; + public readonly field = TransactionExplorerConditionFieldType.Description; + public readonly operator: DescriptionConditionOperator = TransactionExplorerConditionOperatorType.Contains; + public value: string; + + constructor(operator: DescriptionConditionOperator, value: string) { + this.operator = operator; + this.value = value; + } + + public getValueForStore(): string { + if (this.operator === TransactionExplorerConditionOperatorType.IsEmpty || this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) { + return ''; + } + + return this.value; + } + + public match(transaction: TransactionInsightDataItem): boolean { + const description = transaction.comment || ''; + + if (this.operator === TransactionExplorerConditionOperatorType.IsEmpty) { + return description.length === 0; + } else if (this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) { + return description.length > 0; + } else if (this.operator === TransactionExplorerConditionOperatorType.Equals) { + return description === this.value; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotEquals) { + return description !== this.value; + } else if (this.operator === TransactionExplorerConditionOperatorType.Contains) { + return description.includes(this.value); + } else if (this.operator === TransactionExplorerConditionOperatorType.NotContains) { + return !description.includes(this.value); + } else if (this.operator === TransactionExplorerConditionOperatorType.StartsWith) { + return description.startsWith(this.value); + } else if (this.operator === TransactionExplorerConditionOperatorType.NotStartsWith) { + return !description.startsWith(this.value); + } else if (this.operator === TransactionExplorerConditionOperatorType.EndsWith) { + return description.endsWith(this.value); + } else if (this.operator === TransactionExplorerConditionOperatorType.NotEndsWith) { + return !description.endsWith(this.value); + } + + return false; + } + + public toExpression(): string { + if (this.operator === TransactionExplorerConditionOperatorType.IsEmpty) { + return `description IS EMPTY`; + } else if (this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) { + return `description IS NOT EMPTY`; + } else if (this.operator === TransactionExplorerConditionOperatorType.Equals) { + return `description = '${this.value.replace(/'/g, "''")}'`; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotEquals) { + return `description <> '${this.value.replace(/'/g, "''")}'`; + } else if (this.operator === TransactionExplorerConditionOperatorType.Contains) { + return `description CONTAINS '${this.value.replace(/'/g, "''")}'`; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotContains) { + return `description NOT CONTAINS '${this.value.replace(/'/g, "''")}'`; + } else if (this.operator === TransactionExplorerConditionOperatorType.StartsWith) { + return `description STARTS WITH '${this.value.replace(/'/g, "''")}'`; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotStartsWith) { + return `description NOT STARTS WITH '${this.value.replace(/'/g, "''")}'`; + } else if (this.operator === TransactionExplorerConditionOperatorType.EndsWith) { + return `description ENDS WITH '${this.value.replace(/'/g, "''")}'`; + } else if (this.operator === TransactionExplorerConditionOperatorType.NotEndsWith) { + return `description NOT ENDS WITH '${this.value.replace(/'/g, "''")}'`; + } + + return ''; + } +} diff --git a/src/router/desktop.ts b/src/router/desktop.ts index f653b34b..2c2e4810 100644 --- a/src/router/desktop.ts +++ b/src/router/desktop.ts @@ -18,7 +18,7 @@ import TransactionListPage from '@/views/desktop/transactions/ListPage.vue'; import StatisticsTransactionPage from '@/views/desktop/statistics/TransactionPage.vue'; -import InsightsExplorePage from '@/views/desktop/insights/ExplorePage.vue'; +import InsightsExplorerPage from '@/views/desktop/insights/ExplorerPage.vue'; import AccountListPage from '@/views/desktop/accounts/ListPage.vue'; @@ -139,8 +139,8 @@ const router = createRouter({ }) }, { - path: '/insights/explore', - component: InsightsExplorePage, + path: '/insights/explorer', + component: InsightsExplorerPage, beforeEnter: checkLogin, props: route => ({ initId: route.query['id'], diff --git a/src/stores/explore.ts b/src/stores/explorer.ts similarity index 63% rename from src/stores/explore.ts rename to src/stores/explorer.ts index 52a770b5..a4c2b537 100644 --- a/src/stores/explore.ts +++ b/src/stores/explorer.ts @@ -15,14 +15,14 @@ import { TimezoneTypeForStatistics } from '@/core/timezone.ts'; import { AccountCategory } from '@/core/account.ts'; import { TransactionType } from '@/core/transaction.ts'; import { - TransactionExploreChartTypeValue, - TransactionExploreChartType, - TransactionExploreDataDimensionType, - TransactionExploreDataDimension, - TransactionExploreValueMetricType, - TransactionExploreValueMetric, - DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE -} from '@/core/explore.ts'; + TransactionExplorerChartTypeValue, + TransactionExplorerChartType, + TransactionExplorerDataDimensionType, + TransactionExplorerDataDimension, + TransactionExplorerValueMetricType, + TransactionExplorerValueMetric, + DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE +} from '@/core/explorer.ts'; import { type Account } from '@/models/account.ts'; import { type TransactionCategory } from '@/models/transaction_category.ts'; @@ -32,8 +32,8 @@ import { type TransactionInsightDataItem } from '@/models/transaction.ts'; import { - TransactionExploreQuery -} from '@/models/explore.ts'; + TransactionExplorerQuery +} from '@/models/explorer.ts'; import { isDefined, @@ -54,7 +54,7 @@ import { import services from '@/lib/services.ts'; import logger from '@/lib/logger.ts'; -export enum TransactionExploreDimensionType { +export enum TransactionExplorerDimensionType { TransactionType = 'transactionType', Category = 'category', Account = 'account', @@ -62,26 +62,26 @@ export enum TransactionExploreDimensionType { Other = 'other' } -export interface TransactionExplorePartialFilter { +export interface TransactionExplorerPartialFilter { dateRangeType?: number; startTime?: number; endTime?: number; queryId?: string; - chartType?: TransactionExploreChartTypeValue; - categoryDimension?: TransactionExploreDataDimensionType; - seriesDimension?: TransactionExploreDataDimensionType; - valueMetric?: TransactionExploreValueMetricType; + chartType?: TransactionExplorerChartTypeValue; + categoryDimension?: TransactionExplorerDataDimensionType; + seriesDimension?: TransactionExplorerDataDimensionType; + valueMetric?: TransactionExplorerValueMetricType; } -export interface TransactionExploreFilter extends TransactionExplorePartialFilter { +export interface TransactionExplorerFilter extends TransactionExplorerPartialFilter { dateRangeType: number; startTime: number; endTime: number; - query: TransactionExploreQuery[]; - chartType: TransactionExploreChartTypeValue; - categoryDimension: TransactionExploreDataDimensionType; - seriesDimension: TransactionExploreDataDimensionType; - valueMetric: TransactionExploreValueMetricType; + query: TransactionExplorerQuery[]; + chartType: TransactionExplorerChartTypeValue; + categoryDimension: TransactionExplorerDataDimensionType; + seriesDimension: TransactionExplorerDataDimensionType; + valueMetric: TransactionExplorerValueMetricType; } export interface CategoriedInfo { @@ -89,15 +89,15 @@ export interface CategoriedInfo { categoryNameNeedI18n?: boolean; categoryNameI18nParameters?: Record; categoryId: string; - categoryIdType: TransactionExploreDimensionType; + categoryIdType: TransactionExplorerDimensionType; } export interface CategoriedTransactions extends CategoriedInfo { trasactions: Record; } -export interface CategoriedTransactionExploreData extends CategoriedInfo { - data: CategoriedTransactionExploreDataItem[]; +export interface CategoriedTransactionExplorerData extends CategoriedInfo { + data: CategoriedTransactionExplorerDataItem[]; } export interface SeriesedInfo { @@ -105,18 +105,18 @@ export interface SeriesedInfo { seriesNameNeedI18n?: boolean; seriesNameI18nParameters?: Record; seriesId: string; - seriesIdType: TransactionExploreDimensionType; + seriesIdType: TransactionExplorerDimensionType; } export interface SeriesedTransactions extends SeriesedInfo { trasactions: TransactionInsightDataItem[]; } -export interface CategoriedTransactionExploreDataItem extends SeriesedInfo { +export interface CategoriedTransactionExplorerDataItem extends SeriesedInfo { value: number; } -export const useExploresStore = defineStore('explores', () => { +export const useExplorersStore = defineStore('explorers', () => { const settingsStore = useSettingsStore(); const userStore = useUserStore(); const accountsStore = useAccountsStore(); @@ -124,27 +124,27 @@ export const useExploresStore = defineStore('explores', () => { const transactionTagsStore = useTransactionTagsStore(); const exchangeRatesStore = useExchangeRatesStore(); - function getDataCategoryInfo(dimension: TransactionExploreDataDimension, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): CategoriedInfo { + function getDataCategoryInfo(dimension: TransactionExplorerDataDimension, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): CategoriedInfo { let transactionTimeUtfOffset: number | undefined = undefined; - if (settingsStore.appSettings.timezoneUsedForInsightsExplorePage === TimezoneTypeForStatistics.TransactionTimezone.type) { + if (settingsStore.appSettings.timezoneUsedForInsightsExplorerPage === TimezoneTypeForStatistics.TransactionTimezone.type) { transactionTimeUtfOffset = transaction.utcOffset; } - if (dimension === TransactionExploreDataDimension.None) { - const valueMetric = TransactionExploreValueMetric.valueOf(transactionExploreFilter.value.valueMetric); + if (dimension === TransactionExplorerDataDimension.None) { + const valueMetric = TransactionExplorerValueMetric.valueOf(transactionExplorerFilter.value.valueMetric); return { categoryName: valueMetric?.name ?? 'Unknown', categoryNameNeedI18n: true, categoryId: 'none', - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.Query) { + } else if (dimension === TransactionExplorerDataDimension.Query) { if (queryName) { return { categoryName: queryName, categoryId: (queryIndex + 1).toString(10), - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; } else { return { @@ -154,90 +154,90 @@ export const useExploresStore = defineStore('explores', () => { index: (queryIndex + 1).toString(10) }, categoryId: (queryIndex + 1).toString(10), - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; } - } else if (dimension === TransactionExploreDataDimension.DateTime) { + } else if (dimension === TransactionExplorerDataDimension.DateTime) { const unixTime = transaction.time.toString(10); return { categoryName: unixTime, categoryId: unixTime, - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByYearMonthDay) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByYearMonthDay) { const unixTime = getDayFirstUnixTimeBySpecifiedUnixTime(transaction.time, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, categoryId: unixTime, - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByYearMonth) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByYearMonth) { const unixTime = getMonthFirstUnixTimeBySpecifiedUnixTime(transaction.time, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, categoryId: unixTime, - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByYearQuarter) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByYearQuarter) { const unixTime = getQuarterFirstUnixTimeBySpecifiedUnixTime(transaction.time, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, categoryId: unixTime, - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByYear) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByYear) { const unixTime = getYearFirstUnixTimeBySpecifiedUnixTime(transaction.time, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, categoryId: unixTime, - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByFiscalYear) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByFiscalYear) { const unixTime = getFiscalYearStartUnixTime(transaction.time, userStore.currentUserFiscalYearStart, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, categoryId: unixTime, - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByDayOfWeek) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByDayOfWeek) { const dateTime = isDefined(transactionTimeUtfOffset) ? parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transactionTimeUtfOffset) : parseDateTimeFromUnixTime(transaction.time); return { categoryName: dateTime.getWeekDay().name, categoryId: dateTime.getWeekDay().type.toString(10), - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByDayOfMonth) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByDayOfMonth) { const dateTime = isDefined(transactionTimeUtfOffset) ? parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transactionTimeUtfOffset) : parseDateTimeFromUnixTime(transaction.time); return { categoryName: dateTime.getGregorianCalendarDay().toString(10), categoryId: dateTime.getGregorianCalendarDay().toString(10), - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByMonthOfYear) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByMonthOfYear) { const dateTime = isDefined(transactionTimeUtfOffset) ? parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transactionTimeUtfOffset) : parseDateTimeFromUnixTime(transaction.time); return { categoryName: dateTime.getGregorianCalendarMonth().toString(10), categoryId: dateTime.getGregorianCalendarMonth().toString(10), - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DateTimeByQuarterOfYear) { + } else if (dimension === TransactionExplorerDataDimension.DateTimeByQuarterOfYear) { const dateTime = isDefined(transactionTimeUtfOffset) ? parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transactionTimeUtfOffset) : parseDateTimeFromUnixTime(transaction.time); return { categoryName: dateTime.getGregorianCalendarQuarter().toString(10), categoryId: dateTime.getGregorianCalendarQuarter().toString(10), - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.TransactionType) { + } else if (dimension === TransactionExplorerDataDimension.TransactionType) { let transactionTypeName = 'Unknown'; if (transaction.type === TransactionType.ModifyBalance) { @@ -254,89 +254,89 @@ export const useExploresStore = defineStore('explores', () => { categoryName: transactionTypeName, categoryNameNeedI18n: true, categoryId: transaction.type.toString(10), - categoryIdType: TransactionExploreDimensionType.TransactionType + categoryIdType: TransactionExplorerDimensionType.TransactionType }; - } else if (dimension === TransactionExploreDataDimension.SourceAccount) { + } else if (dimension === TransactionExplorerDataDimension.SourceAccount) { return { categoryName: transaction.sourceAccountName || 'Unknown', categoryNameNeedI18n: !transaction.sourceAccountName, categoryId: transaction.sourceAccountId || 'unknown', - categoryIdType: TransactionExploreDimensionType.Account + categoryIdType: TransactionExplorerDimensionType.Account }; - } else if (dimension === TransactionExploreDataDimension.SourceAccountCategory) { + } else if (dimension === TransactionExplorerDataDimension.SourceAccountCategory) { const accountCategory = AccountCategory.valueOf(transaction.sourceAccount.category); return { categoryName: accountCategory?.name || 'Unknown', categoryNameNeedI18n: true, categoryId: accountCategory?.type.toString(10) || 'unknown', - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.SourceAccountCurrency) { + } else if (dimension === TransactionExplorerDataDimension.SourceAccountCurrency) { return { categoryName: transaction.sourceAccount.currency || 'Unknown', categoryNameNeedI18n: !transaction.sourceAccount.currency, categoryId: transaction.sourceAccount.currency || 'unknown', - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DestinationAccount) { + } else if (dimension === TransactionExplorerDataDimension.DestinationAccount) { return { categoryName: transaction.type === TransactionType.Transfer ? (transaction.destinationAccountName || 'Unknown') : 'None', categoryNameNeedI18n: transaction.type !== TransactionType.Transfer || !transaction.destinationAccountName, categoryId: transaction.type === TransactionType.Transfer ? (transaction.destinationAccountId || 'unknown') : 'none', - categoryIdType: TransactionExploreDimensionType.Account + categoryIdType: TransactionExplorerDimensionType.Account }; - } else if (dimension === TransactionExploreDataDimension.DestinationAccountCategory) { + } else if (dimension === TransactionExplorerDataDimension.DestinationAccountCategory) { const accountCategory = transaction.type === TransactionType.Transfer && transaction.destinationAccount ? AccountCategory.valueOf(transaction.destinationAccount.category) : undefined; return { categoryName: transaction.type === TransactionType.Transfer ? (accountCategory?.name || 'Unknown') : 'None', categoryNameNeedI18n: true, categoryId: transaction.type === TransactionType.Transfer ? (accountCategory?.name || 'unknown') : 'none', - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.DestinationAccountCurrency) { + } else if (dimension === TransactionExplorerDataDimension.DestinationAccountCurrency) { return { categoryName: transaction.type === TransactionType.Transfer ? (transaction.destinationAccount?.currency || 'Unknown') : 'None', categoryNameNeedI18n: transaction.type !== TransactionType.Transfer || !transaction.destinationAccount?.currency, categoryId: transaction.type === TransactionType.Transfer ? (transaction.destinationAccount?.currency || 'unknown') : 'none', - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; - } else if (dimension === TransactionExploreDataDimension.PrimaryCategory) { + } else if (dimension === TransactionExplorerDataDimension.PrimaryCategory) { return { categoryName: transaction.primaryCategory.name, categoryId: transaction.primaryCategory.id, - categoryIdType: TransactionExploreDimensionType.Category + categoryIdType: TransactionExplorerDimensionType.Category }; - } else if (dimension === TransactionExploreDataDimension.SecondaryCategory) { + } else if (dimension === TransactionExplorerDataDimension.SecondaryCategory) { return { categoryName: transaction.secondaryCategory.name, categoryId: transaction.categoryId, - categoryIdType: TransactionExploreDimensionType.Category + categoryIdType: TransactionExplorerDimensionType.Category }; - } else if (dimension === TransactionExploreDataDimension.SourceAmount) { + } else if (dimension === TransactionExplorerDataDimension.SourceAmount) { return { categoryName: transaction.sourceAmount.toString(10), categoryId: transaction.sourceAmount.toString(10), - categoryIdType: TransactionExploreDimensionType.Amount + categoryIdType: TransactionExplorerDimensionType.Amount }; - } else if (dimension === TransactionExploreDataDimension.DestinationAmount) { + } else if (dimension === TransactionExplorerDataDimension.DestinationAmount) { return { categoryName: transaction.type === TransactionType.Transfer ? transaction.destinationAmount.toString(10) : 'None', categoryNameNeedI18n: transaction.type !== TransactionType.Transfer, categoryId: transaction.type === TransactionType.Transfer ? transaction.destinationAmount.toString(10) : 'none', - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; } else { return { categoryName: '', categoryId: '', - categoryIdType: TransactionExploreDimensionType.Other + categoryIdType: TransactionExplorerDimensionType.Other }; } } - function addTransactionToCategoriedDataMap(categoriedDataMap: Record, categoryDimension: TransactionExploreDataDimension, seriesDemension: TransactionExploreDataDimension, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): void { + function addTransactionToCategoriedDataMap(categoriedDataMap: Record, categoryDimension: TransactionExplorerDataDimension, seriesDemension: TransactionExplorerDataDimension, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): void { const categoriedInfo = getDataCategoryInfo(categoryDimension, queryName, queryIndex, transaction); let categoriedData = categoriedDataMap[categoriedInfo.categoryId]; @@ -370,28 +370,28 @@ export const useExploresStore = defineStore('explores', () => { seriesedData.trasactions.push(transaction); } - const transactionExploreFilter = ref({ - dateRangeType: DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE.type, + const transactionExplorerFilter = ref({ + dateRangeType: DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE.type, startTime: 0, endTime: 0, query: [], - categoryDimension: TransactionExploreDataDimension.CategoryDimensionDefault.value, - seriesDimension: TransactionExploreDataDimension.SeriesDimensionDefault.value, - valueMetric: TransactionExploreValueMetric.Default.value, - chartType: TransactionExploreChartType.Default.value + categoryDimension: TransactionExplorerDataDimension.CategoryDimensionDefault.value, + seriesDimension: TransactionExplorerDataDimension.SeriesDimensionDefault.value, + valueMetric: TransactionExplorerValueMetric.Default.value, + chartType: TransactionExplorerChartType.Default.value }); - const transactionExploreAllData = ref([]); - const transactionExploreStateInvalid = ref(true); + const transactionExplorerAllData = ref([]); + const transactionExplorerStateInvalid = ref(true); const allTransactions = computed(() => { - if (!transactionExploreAllData.value || transactionExploreAllData.value.length < 1) { + if (!transactionExplorerAllData.value || transactionExplorerAllData.value.length < 1) { return []; } const result: TransactionInsightDataItem[] = []; - for (const transaction of transactionExploreAllData.value) { + for (const transaction of transactionExplorerAllData.value) { const sourceAccount: Account | undefined = accountsStore.allAccountsMap[transaction.sourceAccountId]; if (!sourceAccount) { @@ -463,14 +463,14 @@ export const useExploresStore = defineStore('explores', () => { return []; } - if (!transactionExploreFilter.value.query || transactionExploreFilter.value.query.length < 1) { + if (!transactionExplorerFilter.value.query || transactionExplorerFilter.value.query.length < 1) { return allTransactions.value; } const result: TransactionInsightDataItem[] = []; for (const transaction of allTransactions.value) { - for (const query of transactionExploreFilter.value.query) { + for (const query of transactionExplorerFilter.value.query) { if (query.match(transaction)) { result.push(transaction); break; @@ -486,9 +486,9 @@ export const useExploresStore = defineStore('explores', () => { return {}; } - const chartType = TransactionExploreChartType.valueOf(transactionExploreFilter.value.chartType); - const categoryDimension = TransactionExploreDataDimension.valueOf(transactionExploreFilter.value.categoryDimension); - const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExploreDataDimension.valueOf(transactionExploreFilter.value.seriesDimension) : TransactionExploreDataDimension.SeriesDimensionDefault; + const chartType = TransactionExplorerChartType.valueOf(transactionExplorerFilter.value.chartType); + const categoryDimension = TransactionExplorerDataDimension.valueOf(transactionExplorerFilter.value.categoryDimension); + const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExplorerDataDimension.valueOf(transactionExplorerFilter.value.seriesDimension) : TransactionExplorerDataDimension.SeriesDimensionDefault; if (!chartType || !categoryDimension || !seriesDimension) { return {}; @@ -497,16 +497,16 @@ export const useExploresStore = defineStore('explores', () => { const categoriedDataMap: Record = {}; for (const transaction of allTransactions.value) { - if (!transactionExploreFilter.value.query || transactionExploreFilter.value.query.length < 1) { + if (!transactionExplorerFilter.value.query || transactionExplorerFilter.value.query.length < 1) { addTransactionToCategoriedDataMap(categoriedDataMap, categoryDimension, seriesDimension, '', 0, transaction); continue; } - for (const [query, index] of itemAndIndex(transactionExploreFilter.value.query)) { + for (const [query, index] of itemAndIndex(transactionExplorerFilter.value.query)) { if (query.match(transaction)) { addTransactionToCategoriedDataMap(categoriedDataMap, categoryDimension, seriesDimension, query.name, index, transaction); - if (categoryDimension !== TransactionExploreDataDimension.Query) { + if (categoryDimension !== TransactionExplorerDataDimension.Query) { break; } } @@ -516,30 +516,30 @@ export const useExploresStore = defineStore('explores', () => { return categoriedDataMap; }); - const categoriedTransactionExploreData = computed(() => { + const categoriedTransactionExplorerData = computed(() => { if (!allTransactions.value || allTransactions.value.length < 1) { return []; } - const chartType = TransactionExploreChartType.valueOf(transactionExploreFilter.value.chartType); - const categoryDimension = TransactionExploreDataDimension.valueOf(transactionExploreFilter.value.categoryDimension); - const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExploreDataDimension.valueOf(transactionExploreFilter.value.seriesDimension) : TransactionExploreDataDimension.SeriesDimensionDefault; - const valueMetric = TransactionExploreValueMetric.valueOf(transactionExploreFilter.value.valueMetric); + const chartType = TransactionExplorerChartType.valueOf(transactionExplorerFilter.value.chartType); + const categoryDimension = TransactionExplorerDataDimension.valueOf(transactionExplorerFilter.value.categoryDimension); + const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExplorerDataDimension.valueOf(transactionExplorerFilter.value.seriesDimension) : TransactionExplorerDataDimension.SeriesDimensionDefault; + const valueMetric = TransactionExplorerValueMetric.valueOf(transactionExplorerFilter.value.valueMetric); if (!chartType || !categoryDimension || !seriesDimension || !valueMetric) { return []; } const defaultCurrency = userStore.currentUserDefaultCurrency; - const result: CategoriedTransactionExploreData[] = []; + const result: CategoriedTransactionExplorerData[] = []; const categoriedDataMap = categoriedTransactions.value; for (const categoriedTransactions of values(categoriedDataMap)) { - const dataItems: CategoriedTransactionExploreDataItem[] = []; + const dataItems: CategoriedTransactionExplorerDataItem[] = []; let allSeriesedTransactions: Record = categoriedTransactions.trasactions; // merge all series into one for pie/radar chart - if (chartType === TransactionExploreChartType.Pie || chartType === TransactionExploreChartType.Radar) { + if (chartType === TransactionExplorerChartType.Pie || chartType === TransactionExplorerChartType.Radar) { const transactions: TransactionInsightDataItem[] = []; for (const seriesedTransactions of values(categoriedTransactions.trasactions)) { @@ -551,7 +551,7 @@ export const useExploresStore = defineStore('explores', () => { seriesName: valueMetric?.name ?? 'Unknown', seriesNameNeedI18n: true, seriesId: 'none', - seriesIdType: TransactionExploreDimensionType.Other, + seriesIdType: TransactionExplorerDimensionType.Other, trasactions: transactions }; } @@ -589,22 +589,22 @@ export const useExploresStore = defineStore('explores', () => { let value: number = 0; - if (valueMetric === TransactionExploreValueMetric.TransactionCount) { + if (valueMetric === TransactionExplorerValueMetric.TransactionCount) { value = allSourceAmountsInDefaultCurrency.length; - } else if (valueMetric === TransactionExploreValueMetric.SourceAmountSum) { + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountSum) { value = totalSourceAmountSumInDefaultCurrency; - } else if (valueMetric === TransactionExploreValueMetric.SourceAmountAverage) { + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountAverage) { value = allSourceAmountsInDefaultCurrency.length > 0 ? Math.trunc(totalSourceAmountSumInDefaultCurrency / allSourceAmountsInDefaultCurrency.length) : 0; - } else if (valueMetric === TransactionExploreValueMetric.SourceAmountMedian) { + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountMedian) { if (allSourceAmountsInDefaultCurrency.length > 0) { allSourceAmountsInDefaultCurrency.sort((a, b) => a - b); value = allSourceAmountsInDefaultCurrency[Math.floor(allSourceAmountsInDefaultCurrency.length / 2)] as number; } else { value = 0; } - } else if (valueMetric === TransactionExploreValueMetric.SourceAmountMinimum) { + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountMinimum) { value = minimumSourceAmountInDefaultCurrency === Number.MAX_SAFE_INTEGER ? 0 : minimumSourceAmountInDefaultCurrency; - } else if (valueMetric === TransactionExploreValueMetric.SourceAmountMaximum) { + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountMaximum) { value = maximumSourceAmountInDefaultCurrency === Number.MIN_SAFE_INTEGER ? 0 : maximumSourceAmountInDefaultCurrency; } @@ -631,150 +631,150 @@ export const useExploresStore = defineStore('explores', () => { return result; }); - function updateTransactionExploreInvalidState(invalidState: boolean): void { - transactionExploreStateInvalid.value = invalidState; + function updateTransactionExplorerInvalidState(invalidState: boolean): void { + transactionExplorerStateInvalid.value = invalidState; } - function resetTransactionExplores(): void { - transactionExploreFilter.value.dateRangeType = DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE.type; - transactionExploreFilter.value.startTime = 0; - transactionExploreFilter.value.endTime = 0; - transactionExploreFilter.value.query = []; - transactionExploreFilter.value.chartType = TransactionExploreChartType.Default.value; - transactionExploreFilter.value.categoryDimension = TransactionExploreDataDimension.CategoryDimensionDefault.value; - transactionExploreFilter.value.seriesDimension = TransactionExploreDataDimension.SeriesDimensionDefault.value; - transactionExploreFilter.value.valueMetric = TransactionExploreValueMetric.Default.value; - transactionExploreAllData.value = []; - transactionExploreStateInvalid.value = true; + function resetTransactionExplorers(): void { + transactionExplorerFilter.value.dateRangeType = DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE.type; + transactionExplorerFilter.value.startTime = 0; + transactionExplorerFilter.value.endTime = 0; + transactionExplorerFilter.value.query = []; + transactionExplorerFilter.value.chartType = TransactionExplorerChartType.Default.value; + transactionExplorerFilter.value.categoryDimension = TransactionExplorerDataDimension.CategoryDimensionDefault.value; + transactionExplorerFilter.value.seriesDimension = TransactionExplorerDataDimension.SeriesDimensionDefault.value; + transactionExplorerFilter.value.valueMetric = TransactionExplorerValueMetric.Default.value; + transactionExplorerAllData.value = []; + transactionExplorerStateInvalid.value = true; } - function initTransactionExploreFilter(filter?: TransactionExplorePartialFilter, resetQuery?: boolean): void { + function initTransactionExplorerFilter(filter?: TransactionExplorerPartialFilter, resetQuery?: boolean): void { if (filter && isInteger(filter.dateRangeType)) { - transactionExploreFilter.value.dateRangeType = filter.dateRangeType; + transactionExplorerFilter.value.dateRangeType = filter.dateRangeType; } else { - transactionExploreFilter.value.dateRangeType = settingsStore.appSettings.insightsExploreDefaultDateRangeType; + transactionExplorerFilter.value.dateRangeType = settingsStore.appSettings.insightsExplorerDefaultDateRangeType; } let dateRangeTypeValid = true; - if (!DateRange.isAvailableForScene(transactionExploreFilter.value.dateRangeType, DateRangeScene.InsightsExplore)) { - transactionExploreFilter.value.dateRangeType = DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE.type; + if (!DateRange.isAvailableForScene(transactionExplorerFilter.value.dateRangeType, DateRangeScene.InsightsExplorer)) { + transactionExplorerFilter.value.dateRangeType = DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE.type; dateRangeTypeValid = false; } - if (dateRangeTypeValid && transactionExploreFilter.value.dateRangeType === DateRange.Custom.type) { + if (dateRangeTypeValid && transactionExplorerFilter.value.dateRangeType === DateRange.Custom.type) { if (filter && isInteger(filter.startTime)) { - transactionExploreFilter.value.startTime = filter.startTime; + transactionExplorerFilter.value.startTime = filter.startTime; } else { - transactionExploreFilter.value.startTime = 0; + transactionExplorerFilter.value.startTime = 0; } if (filter && isInteger(filter.endTime)) { - transactionExploreFilter.value.endTime = filter.endTime; + transactionExplorerFilter.value.endTime = filter.endTime; } else { - transactionExploreFilter.value.endTime = 0; + transactionExplorerFilter.value.endTime = 0; } } else { - const dateRange = getDateRangeByDateType(transactionExploreFilter.value.dateRangeType, userStore.currentUserFirstDayOfWeek, userStore.currentUserFiscalYearStart); + const dateRange = getDateRangeByDateType(transactionExplorerFilter.value.dateRangeType, userStore.currentUserFirstDayOfWeek, userStore.currentUserFiscalYearStart); if (dateRange) { - transactionExploreFilter.value.dateRangeType = dateRange.dateType; - transactionExploreFilter.value.startTime = dateRange.minTime; - transactionExploreFilter.value.endTime = dateRange.maxTime; + transactionExplorerFilter.value.dateRangeType = dateRange.dateType; + transactionExplorerFilter.value.startTime = dateRange.minTime; + transactionExplorerFilter.value.endTime = dateRange.maxTime; } } if (resetQuery) { - transactionExploreFilter.value.query = []; - transactionExploreFilter.value.chartType = TransactionExploreChartType.Default.value; - transactionExploreFilter.value.categoryDimension = TransactionExploreDataDimension.CategoryDimensionDefault.value; - transactionExploreFilter.value.seriesDimension = TransactionExploreDataDimension.SeriesDimensionDefault.value; - transactionExploreFilter.value.valueMetric = TransactionExploreValueMetric.Default.value; + transactionExplorerFilter.value.query = []; + transactionExplorerFilter.value.chartType = TransactionExplorerChartType.Default.value; + transactionExplorerFilter.value.categoryDimension = TransactionExplorerDataDimension.CategoryDimensionDefault.value; + transactionExplorerFilter.value.seriesDimension = TransactionExplorerDataDimension.SeriesDimensionDefault.value; + transactionExplorerFilter.value.valueMetric = TransactionExplorerValueMetric.Default.value; } } - function updateTransactionExploreFilter(filter: TransactionExplorePartialFilter): boolean { + function updateTransactionExplorerFilter(filter: TransactionExplorerPartialFilter): boolean { let changed = false; - if (filter && isInteger(filter.dateRangeType) && transactionExploreFilter.value.dateRangeType !== filter.dateRangeType) { - transactionExploreFilter.value.dateRangeType = filter.dateRangeType; + if (filter && isInteger(filter.dateRangeType) && transactionExplorerFilter.value.dateRangeType !== filter.dateRangeType) { + transactionExplorerFilter.value.dateRangeType = filter.dateRangeType; changed = true; } - if (filter && isInteger(filter.startTime) && transactionExploreFilter.value.startTime !== filter.startTime) { - transactionExploreFilter.value.startTime = filter.startTime; + if (filter && isInteger(filter.startTime) && transactionExplorerFilter.value.startTime !== filter.startTime) { + transactionExplorerFilter.value.startTime = filter.startTime; changed = true; } - if (filter && isInteger(filter.endTime) && transactionExploreFilter.value.endTime !== filter.endTime) { - transactionExploreFilter.value.endTime = filter.endTime; + if (filter && isInteger(filter.endTime) && transactionExplorerFilter.value.endTime !== filter.endTime) { + transactionExplorerFilter.value.endTime = filter.endTime; changed = true; } - if (filter && isDefined(filter.chartType) && transactionExploreFilter.value.chartType !== filter.chartType) { - transactionExploreFilter.value.chartType = filter.chartType; + if (filter && isDefined(filter.chartType) && transactionExplorerFilter.value.chartType !== filter.chartType) { + transactionExplorerFilter.value.chartType = filter.chartType; changed = true; } - if (filter && isDefined(filter.categoryDimension) && transactionExploreFilter.value.categoryDimension !== filter.categoryDimension) { - transactionExploreFilter.value.categoryDimension = filter.categoryDimension; + if (filter && isDefined(filter.categoryDimension) && transactionExplorerFilter.value.categoryDimension !== filter.categoryDimension) { + transactionExplorerFilter.value.categoryDimension = filter.categoryDimension; changed = true; } - if (filter && isDefined(filter.seriesDimension) && transactionExploreFilter.value.seriesDimension !== filter.seriesDimension) { - transactionExploreFilter.value.seriesDimension = filter.seriesDimension; + if (filter && isDefined(filter.seriesDimension) && transactionExplorerFilter.value.seriesDimension !== filter.seriesDimension) { + transactionExplorerFilter.value.seriesDimension = filter.seriesDimension; changed = true; } - if (filter && isDefined(filter.valueMetric) && transactionExploreFilter.value.valueMetric !== filter.valueMetric) { - transactionExploreFilter.value.valueMetric = filter.valueMetric; + if (filter && isDefined(filter.valueMetric) && transactionExplorerFilter.value.valueMetric !== filter.valueMetric) { + transactionExplorerFilter.value.valueMetric = filter.valueMetric; changed = true; } - if (transactionExploreFilter.value.seriesDimension === transactionExploreFilter.value.categoryDimension && transactionExploreFilter.value.seriesDimension !== TransactionExploreDataDimension.SeriesDimensionDefault.value) { - transactionExploreFilter.value.seriesDimension = TransactionExploreDataDimension.SeriesDimensionDefault.value; + if (transactionExplorerFilter.value.seriesDimension === transactionExplorerFilter.value.categoryDimension && transactionExplorerFilter.value.seriesDimension !== TransactionExplorerDataDimension.SeriesDimensionDefault.value) { + transactionExplorerFilter.value.seriesDimension = TransactionExplorerDataDimension.SeriesDimensionDefault.value; changed = true; } return changed; } - function getTransactionExplorePageParams(currentExploreId: string, activeTab: string): string { + function getTransactionExplorerPageParams(currentExplorationId: string, activeTab: string): string { const querys: string[] = []; - if (currentExploreId) { - querys.push('id=' + currentExploreId); + if (currentExplorationId) { + querys.push('id=' + currentExplorationId); } if (activeTab) { querys.push('activeTab=' + activeTab); } - querys.push('dateRangeType=' + transactionExploreFilter.value.dateRangeType); - querys.push('startTime=' + transactionExploreFilter.value.startTime); - querys.push('endTime=' + transactionExploreFilter.value.endTime); + querys.push('dateRangeType=' + transactionExplorerFilter.value.dateRangeType); + querys.push('startTime=' + transactionExplorerFilter.value.startTime); + querys.push('endTime=' + transactionExplorerFilter.value.endTime); return querys.join('&'); } - function getTransactionListPageParams(dimensionType: TransactionExploreDimensionType, itemId: string): string { + function getTransactionListPageParams(dimensionType: TransactionExplorerDimensionType, itemId: string): string { const querys: string[] = []; - if (dimensionType === TransactionExploreDimensionType.TransactionType) { + if (dimensionType === TransactionExplorerDimensionType.TransactionType) { querys.push(`type=${itemId}`); - } else if (dimensionType === TransactionExploreDimensionType.Account) { + } else if (dimensionType === TransactionExplorerDimensionType.Account) { querys.push(`accountIds=${itemId}`); - } else if (dimensionType === TransactionExploreDimensionType.Category) { + } else if (dimensionType === TransactionExplorerDimensionType.Category) { querys.push(`categoryIds=${itemId}`); - } else if (dimensionType === TransactionExploreDimensionType.Amount) { + } else if (dimensionType === TransactionExplorerDimensionType.Amount) { querys.push(`amountFilter=${encodeURIComponent(AmountFilterType.EqualTo.toTextualFilter(parseInt(itemId)))}`); } else { return ''; } - querys.push('dateType=' + transactionExploreFilter.value.dateRangeType); - querys.push('minTime=' + transactionExploreFilter.value.startTime); - querys.push('maxTime=' + transactionExploreFilter.value.endTime); + querys.push('dateType=' + transactionExplorerFilter.value.dateRangeType); + querys.push('minTime=' + transactionExplorerFilter.value.startTime); + querys.push('maxTime=' + transactionExplorerFilter.value.endTime); return querys.join('&'); } @@ -782,8 +782,8 @@ export const useExploresStore = defineStore('explores', () => { function loadAllTransactions({ force }: { force: boolean }): Promise { return new Promise((resolve, reject) => { services.getAllTransactions({ - startTime: transactionExploreFilter.value.startTime, - endTime: transactionExploreFilter.value.endTime + startTime: transactionExplorerFilter.value.startTime, + endTime: transactionExplorerFilter.value.endTime }).then(response => { const data = response.data; @@ -792,16 +792,16 @@ export const useExploresStore = defineStore('explores', () => { return; } - if (transactionExploreStateInvalid.value) { - updateTransactionExploreInvalidState(false); + if (transactionExplorerStateInvalid.value) { + updateTransactionExplorerInvalidState(false); } - if (force && data.result && isEquals(transactionExploreAllData.value, data.result)) { + if (force && data.result && isEquals(transactionExplorerAllData.value, data.result)) { reject({ message: 'Data is up to date', isUpToDate: true }); return; } - transactionExploreAllData.value = data.result; + transactionExplorerAllData.value = data.result; resolve(data.result); }).catch(error => { @@ -820,17 +820,17 @@ export const useExploresStore = defineStore('explores', () => { return { // states - transactionExploreFilter, - transactionExploreStateInvalid, + transactionExplorerFilter: transactionExplorerFilter, + transactionExplorerStateInvalid, // computed filteredTransactions, - categoriedTransactionExploreData, + categoriedTransactionExplorerData, // functions - updateTransactionExploreInvalidState, - resetTransactionExplores, - initTransactionExploreFilter, - updateTransactionExploreFilter, - getTransactionExplorePageParams, + updateTransactionExplorerInvalidState, + resetTransactionExplorers, + initTransactionExplorerFilter, + updateTransactionExplorerFilter, + getTransactionExplorerPageParams, getTransactionListPageParams, loadAllTransactions }; diff --git a/src/stores/index.ts b/src/stores/index.ts index 7a822f16..0942a685 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -10,7 +10,7 @@ import { useTransactionTemplatesStore } from './transactionTemplate.ts'; import { useTransactionsStore } from './transaction.ts'; import { useOverviewStore } from './overview.ts'; import { useStatisticsStore } from './statistics.ts'; -import { useExploresStore } from './explore.ts'; +import { useExplorersStore } from './explorer.ts'; import { useExchangeRatesStore } from './exchangeRates.ts'; import type { AuthResponse, RegisterResponse } from '@/models/auth_response.ts'; @@ -50,7 +50,7 @@ export const useRootStore = defineStore('root', () => { const transactionsStore = useTransactionsStore(); const overviewStore = useOverviewStore(); const statisticsStore = useStatisticsStore(); - const exploresStore = useExploresStore(); + const explorersStore = useExplorersStore(); const exchangeRatesStore = useExchangeRatesStore(); const currentNotification = ref(null); @@ -62,7 +62,7 @@ export const useRootStore = defineStore('root', () => { setNotificationContent(null); - exploresStore.resetTransactionExplores(); + explorersStore.resetTransactionExplorers(); statisticsStore.resetTransactionStatistics(); overviewStore.resetTransactionOverview(); transactionsStore.resetTransactions(); diff --git a/src/stores/setting.ts b/src/stores/setting.ts index 8844c888..f81bc2b7 100644 --- a/src/stores/setting.ts +++ b/src/stores/setting.ts @@ -245,17 +245,17 @@ export const useSettingsStore = defineStore('settings', () => { updateUserApplicationCloudSettingValue('alwaysShowTransactionPicturesInMobileTransactionEditPage', value); } - // Insights & Explore Page - function setInsightsExploreDefaultDateRangeType(value: number): void { - updateApplicationSettingsValue('insightsExploreDefaultDateRangeType', value); - appSettings.value.insightsExploreDefaultDateRangeType = value; - updateUserApplicationCloudSettingValue('insightsExploreDefaultDateRangeType', value); + // Insights Explorer Page + function setInsightsExplorerDefaultDateRangeType(value: number): void { + updateApplicationSettingsValue('insightsExplorerDefaultDateRangeType', value); + appSettings.value.insightsExplorerDefaultDateRangeType = value; + updateUserApplicationCloudSettingValue('insightsExplorerDefaultDateRangeType', value); } - function setTimezoneUsedForInsightsExplorePage(value: number): void { - updateApplicationSettingsValue('timezoneUsedForInsightsExplorePage', value); - appSettings.value.timezoneUsedForInsightsExplorePage = value; - updateUserApplicationCloudSettingValue('timezoneUsedForInsightsExplorePage', value); + function setTimezoneUsedForInsightsExplorerPage(value: number): void { + updateApplicationSettingsValue('timezoneUsedForInsightsExplorerPage', value); + appSettings.value.timezoneUsedForInsightsExplorerPage = value; + updateUserApplicationCloudSettingValue('timezoneUsedForInsightsExplorerPage', value); } // Account List Page @@ -480,9 +480,9 @@ export const useSettingsStore = defineStore('settings', () => { setAutoSaveTransactionDraft, setAutoGetCurrentGeoLocation, setAlwaysShowTransactionPicturesInMobileTransactionEditPage, - // -- Insights & Explore Page - setInsightsExploreDefaultDateRangeType, - setTimezoneUsedForInsightsExplorePage, + // -- Insights Explorer Page + setInsightsExplorerDefaultDateRangeType, + setTimezoneUsedForInsightsExplorerPage, // -- Account List Page setTotalAmountExcludeAccountIds, // -- Exchange Rates Data Page diff --git a/src/views/base/settings/AppCloudSyncPageBase.ts b/src/views/base/settings/AppCloudSyncPageBase.ts index 1d304082..83f43583 100644 --- a/src/views/base/settings/AppCloudSyncPageBase.ts +++ b/src/views/base/settings/AppCloudSyncPageBase.ts @@ -51,10 +51,10 @@ export const ALL_APPLICATION_CLOUD_SETTINGS: CategorizedApplicationCloudSettingI ] }, { - categoryName: 'Insights & Explore Page', + categoryName: 'Insights Explorer Page', items: [ - { settingKey: 'insightsExploreDefaultDateRangeType', settingName: 'Default Date Range', mobile: false, desktop: true }, - { settingKey: 'timezoneUsedForInsightsExplorePage', settingName: 'Timezone Used for Date Range', mobile: false, desktop: true }, + { settingKey: 'insightsExplorerDefaultDateRangeType', settingName: 'Default Date Range', mobile: false, desktop: true }, + { settingKey: 'timezoneUsedForInsightsExplorerPage', settingName: 'Timezone Used for Date Range', mobile: false, desktop: true }, ] }, { diff --git a/src/views/desktop/MainLayout.vue b/src/views/desktop/MainLayout.vue index 50ccb98c..2ff4aca8 100644 --- a/src/views/desktop/MainLayout.vue +++ b/src/views/desktop/MainLayout.vue @@ -45,9 +45,9 @@