update name to insights explorer

This commit is contained in:
MaysWind
2026-01-03 16:40:53 +08:00
parent 8be5e8aa1d
commit cc0996e0d2
37 changed files with 1714 additions and 1714 deletions
+3 -3
View File
@@ -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
+21 -21
View File
@@ -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<number, DateRange> = {};
// 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;
-291
View File
@@ -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, number> = {
[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<string, TransactionExploreConditionField> = {};
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<string, TransactionExploreConditionOperator> = {};
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<string, TransactionExploreChartType> = {};
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<string, TransactionExploreDataDimension> = {};
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<string, TransactionExploreValueMetric> = {};
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;
+291
View File
@@ -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, number> = {
[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<string, TransactionExplorerConditionField> = {};
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<string, TransactionExplorerConditionOperator> = {};
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<string, TransactionExplorerChartType> = {};
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<string, TransactionExplorerDataDimension> = {};
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<string, TransactionExplorerValueMetric> = {};
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;
+10 -10
View File
@@ -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<string, boolean>;
// Exchange Rates Data Page
@@ -115,9 +115,9 @@ export const ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES: Record<string, UserAp
'autoSaveTransactionDraft': UserApplicationCloudSettingType.String,
'autoGetCurrentGeoLocation': UserApplicationCloudSettingType.Boolean,
'alwaysShowTransactionPicturesInMobileTransactionEditPage': UserApplicationCloudSettingType.Boolean,
// Insights & Explore Page
'insightsExploreDefaultDateRangeType': UserApplicationCloudSettingType.Number,
'timezoneUsedForInsightsExplorePage': UserApplicationCloudSettingType.Number,
// Insights Explorer Page
'insightsExplorerDefaultDateRangeType': UserApplicationCloudSettingType.Number,
'timezoneUsedForInsightsExplorerPage': UserApplicationCloudSettingType.Number,
// Account List Page
'totalAmountExcludeAccountIds': UserApplicationCloudSettingType.StringBooleanMap,
// Exchange Rates Data Page
@@ -165,9 +165,9 @@ export const DEFAULT_APPLICATION_SETTINGS: ApplicationSettings = {
autoSaveTransactionDraft: 'disabled',
autoGetCurrentGeoLocation: false,
alwaysShowTransactionPicturesInMobileTransactionEditPage: false,
// Insights & Explore Page
insightsExploreDefaultDateRangeType: DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE.type,
timezoneUsedForInsightsExplorePage: TimezoneTypeForStatistics.Default.type,
// Insights Explorer Page
insightsExplorerDefaultDateRangeType: DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE.type,
timezoneUsedForInsightsExplorerPage: TimezoneTypeForStatistics.Default.type,
// Account List Page
totalAmountExcludeAccountIds: {},
// Exchange Rates Data Page
+3 -3
View File
@@ -1704,11 +1704,11 @@
"Transaction Calendar": "Transaction Calendar",
"Transaction Details": "Transaktionsdetails",
"Statistics & Analysis": "Statistiken & Analysen",
"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": "Bestätigung jedes mal anzeigen",
"Automatically Add Geolocation": "Geolocation automatisch hinzufügen",
"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": "Wechselkursdatenseite",
+3 -3
View File
@@ -1704,11 +1704,11 @@
"Transaction Calendar": "Transaction Calendar",
"Transaction Details": "Transaction Details",
"Statistics & Analysis": "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": "Show Confirmation Every Time",
"Automatically Add Geolocation": "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": "Exchange Rates Data Page",
+3 -3
View File
@@ -1704,11 +1704,11 @@
"Transaction Calendar": "Calendario de Transacciones",
"Transaction Details": "Detalles",
"Statistics & Analysis": "Estadísticas y Análisis",
"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 Confirmación Cada Vez",
"Automatically Add Geolocation": "Agregar Geolocalización Automáticamente",
"Always Show Transaction Pictures": "Always Show Transaction Pictures",
"Insights & Explore Page": "Insights & Explore Page",
"Insights Explorer Page": "Insights Explorer Page",
"Account List Page": "Página de Cuentas",
"Accounts Included in Total": "Cuentas Incluidas en el Total",
"Exchange Rates Data Page": "Página de Tipos de Cambio",
+3 -3
View File
@@ -1704,11 +1704,11 @@
"Transaction Calendar": "Calendrier des transactions",
"Transaction Details": "Détails de la transaction",
"Statistics & Analysis": "Statistiques et 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": "Afficher la confirmation à chaque fois",
"Automatically Add Geolocation": "Ajouter automatiquement la géolocalisation",
"Always Show Transaction Pictures": "Toujours afficher les images de transaction",
"Insights & Explore Page": "Insights & Explore Page",
"Insights Explorer Page": "Insights Explorer Page",
"Account List Page": "Page de liste des comptes",
"Accounts Included in Total": "Comptes inclus dans le total",
"Exchange Rates Data Page": "Page des données de taux de change",
+11 -11
View File
@@ -143,12 +143,12 @@ import {
} from '@/core/statistics.ts';
import {
TransactionExploreConditionField,
TransactionExploreConditionOperator,
TransactionExploreDataDimension,
TransactionExploreValueMetric,
TransactionExploreChartType
} from '@/core/explore.ts';
TransactionExplorerConditionField,
TransactionExplorerConditionOperator,
TransactionExplorerDataDimension,
TransactionExplorerValueMetric,
TransactionExplorerChartType
} from '@/core/explorer.ts';
import {
type LocalizedImportFileCategoryAndTypes,
@@ -2389,11 +2389,11 @@ export function useI18n() {
getAllTransactionDefaultCategories,
getAllDisplayExchangeRates,
getAllSupportedImportFileCagtegoryAndTypes,
getAllTransactionExploreConditionFields: () => 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,
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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": "為替レートデータページ",
+3 -3
View File
@@ -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": "ವಿನಿಮಯ ದರಗಳ ಪುಟ",
+3 -3
View File
@@ -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": "환율 데이터 페이지",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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": "Страница данных о курсах валют",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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": "หน้าข้อมูลอัตราแลกเปลี่ยน",
+3 -3
View File
@@ -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ı",
+3 -3
View File
@@ -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": "Сторінка курсів валют",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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": "汇率数据页面",
+3 -3
View File
@@ -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": "匯率資料頁面",
-922
View File
@@ -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<string, TransactionCategory>, allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>): 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<TransactionExploreConditionOperatorType, true> = {};
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<T = TransactionExploreConditionFieldType, V = string | string[] | number[]> {
readonly field: T;
readonly operator: TransactionExploreConditionOperatorType;
value: V;
getValueForStore(): V;
match(transaction: TransactionInsightDataItem): boolean;
toExpression(allCategoriesMap: Record<string, TransactionCategory>, allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>): string;
}
export class TransactionExploreTransactionTypeCondition implements TransactionExploreCondition<TransactionExploreConditionFieldType.TransactionType, number[]> {
public static readonly supportedOperators: PartialRecord<TransactionExploreConditionOperatorType, true> = {
[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<TransactionExploreConditionFieldType.TransactionCategory, string[]> {
public static readonly supportedOperators: PartialRecord<TransactionExploreConditionOperatorType, true> = {
[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, TransactionCategory>): 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<TransactionExploreConditionFieldType.SourceAccount, string[]> {
public static readonly supportedOperators: PartialRecord<TransactionExploreConditionOperatorType, true> = {
[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<string, TransactionCategory>, allAccountsMap: Record<string, Account>): 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<TransactionExploreConditionFieldType.DestinationAccount, string[]> {
public static readonly supportedOperators: PartialRecord<TransactionExploreConditionOperatorType, true> = {
[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<string, TransactionCategory>, allAccountsMap: Record<string, Account>): 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<T = AmountConditionField> implements TransactionExploreCondition<T, [number, number]> {
public static readonly supportedOperators: PartialRecord<TransactionExploreConditionOperatorType, true> = {
[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<string, TransactionCategory>, allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>): 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<TransactionExploreConditionFieldType.SourceAmount> {
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<TransactionExploreConditionFieldType.DestinationAmount> {
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<TransactionExploreConditionFieldType.TransactionTag, string[]> {
public static readonly supportedOperators: PartialRecord<TransactionExploreConditionOperatorType, true> = {
[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<string, boolean> = {};
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<string, TransactionCategory>, allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>): 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<TransactionExploreConditionFieldType.Description, string> {
public static readonly supportedOperators: PartialRecord<TransactionExploreConditionOperatorType, true> = {
[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 '';
}
}
+922
View File
@@ -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<string, TransactionCategory>, allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>): 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<TransactionExplorerConditionOperatorType, true> = {};
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<T = TransactionExplorerConditionFieldType, V = string | string[] | number[]> {
readonly field: T;
readonly operator: TransactionExplorerConditionOperatorType;
value: V;
getValueForStore(): V;
match(transaction: TransactionInsightDataItem): boolean;
toExpression(allCategoriesMap: Record<string, TransactionCategory>, allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>): string;
}
export class TransactionExplorerTransactionTypeCondition implements TransactionExplorerCondition<TransactionExplorerConditionFieldType.TransactionType, number[]> {
public static readonly supportedOperators: PartialRecord<TransactionExplorerConditionOperatorType, true> = {
[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<TransactionExplorerConditionFieldType.TransactionCategory, string[]> {
public static readonly supportedOperators: PartialRecord<TransactionExplorerConditionOperatorType, true> = {
[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, TransactionCategory>): 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<TransactionExplorerConditionFieldType.SourceAccount, string[]> {
public static readonly supportedOperators: PartialRecord<TransactionExplorerConditionOperatorType, true> = {
[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<string, TransactionCategory>, allAccountsMap: Record<string, Account>): 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<TransactionExplorerConditionFieldType.DestinationAccount, string[]> {
public static readonly supportedOperators: PartialRecord<TransactionExplorerConditionOperatorType, true> = {
[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<string, TransactionCategory>, allAccountsMap: Record<string, Account>): 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<T = AmountConditionField> implements TransactionExplorerCondition<T, [number, number]> {
public static readonly supportedOperators: PartialRecord<TransactionExplorerConditionOperatorType, true> = {
[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<string, TransactionCategory>, allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>): 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<TransactionExplorerConditionFieldType.SourceAmount> {
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<TransactionExplorerConditionFieldType.DestinationAmount> {
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<TransactionExplorerConditionFieldType.TransactionTag, string[]> {
public static readonly supportedOperators: PartialRecord<TransactionExplorerConditionOperatorType, true> = {
[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<string, boolean> = {};
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<string, TransactionCategory>, allAccountsMap: Record<string, Account>, allTagsMap: Record<string, TransactionTag>): 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<TransactionExplorerConditionFieldType.Description, string> {
public static readonly supportedOperators: PartialRecord<TransactionExplorerConditionOperatorType, true> = {
[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 '';
}
}
+3 -3
View File
@@ -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'],
+190 -190
View File
@@ -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<string, string>;
categoryId: string;
categoryIdType: TransactionExploreDimensionType;
categoryIdType: TransactionExplorerDimensionType;
}
export interface CategoriedTransactions extends CategoriedInfo {
trasactions: Record<string, SeriesedTransactions>;
}
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<string, string>;
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<string, CategoriedTransactions>, categoryDimension: TransactionExploreDataDimension, seriesDemension: TransactionExploreDataDimension, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): void {
function addTransactionToCategoriedDataMap(categoriedDataMap: Record<string, CategoriedTransactions>, 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<TransactionExploreFilter>({
dateRangeType: DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE.type,
const transactionExplorerFilter = ref<TransactionExplorerFilter>({
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<TransactionInfoResponse[]>([]);
const transactionExploreStateInvalid = ref<boolean>(true);
const transactionExplorerAllData = ref<TransactionInfoResponse[]>([]);
const transactionExplorerStateInvalid = ref<boolean>(true);
const allTransactions = computed<TransactionInsightDataItem[]>(() => {
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<string, CategoriedTransactions> = {};
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<CategoriedTransactionExploreData[]>(() => {
const categoriedTransactionExplorerData = computed<CategoriedTransactionExplorerData[]>(() => {
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<string, SeriesedTransactions> = 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<TransactionInfoResponse[]> {
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
};
+3 -3
View File
@@ -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<string | null>(null);
@@ -62,7 +62,7 @@ export const useRootStore = defineStore('root', () => {
setNotificationContent(null);
exploresStore.resetTransactionExplores();
explorersStore.resetTransactionExplorers();
statisticsStore.resetTransactionStatistics();
overviewStore.resetTransactionOverview();
transactionsStore.resetTransactions();
+12 -12
View File
@@ -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
@@ -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 },
]
},
{
+2 -2
View File
@@ -45,9 +45,9 @@
</router-link>
</li>
<li class="nav-link">
<router-link to="/insights/explore">
<router-link to="/insights/explorer">
<v-icon class="nav-item-icon" :icon="mdiCompassOutline"/>
<span class="nav-item-title">{{ tt('Insights & Explore') }}</span>
<span class="nav-item-title">{{ tt('Insights Explorer') }}</span>
</router-link>
</li>
<li class="nav-section-title">
@@ -225,7 +225,7 @@
</v-col>
<v-col cols="12">
<v-card :title="tt('Insights & Explore Page')">
<v-card :title="tt('Insights Explorer Page')">
<v-form>
<v-card-text>
<v-row>
@@ -236,8 +236,8 @@
persistent-placeholder
:label="tt('Default Date Range')"
:placeholder="tt('Default Date Range')"
:items="allInsightsExploreDefaultDateRanges"
v-model="insightsExploreDefaultDateRangeType"
:items="allInsightsExplorerDefaultDateRanges"
v-model="insightsExplorerDefaultDateRangeType"
/>
</v-col>
@@ -249,7 +249,7 @@
:label="tt('Timezone Used for Date Range')"
:placeholder="tt('Timezone Used for Date Range')"
:items="allTimezoneTypesUsedForStatistics"
v-model="timezoneUsedForInsightsExplorePage"
v-model="timezoneUsedForInsightsExplorerPage"
/>
</v-col>
</v-row>
@@ -339,7 +339,7 @@ import { useAppSettingPageBase } from '@/views/base/settings/AppSettingsPageBase
import { useSettingsStore } from '@/stores/setting.ts';
import { useAccountsStore } from '@/stores/account.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useExploresStore } from '@/stores/explore.ts';
import { useExplorersStore } from '@/stores/explorer.ts';
import type { LocalizedSwitchOption } from '@/core/base.ts';
import { ThemeType } from '@/core/theme.ts';
@@ -383,7 +383,7 @@ const {
const settingsStore = useSettingsStore();
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const exploresStore = useExploresStore();
const explorersStore = useExplorersStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
@@ -392,7 +392,7 @@ const showTransactionCategoriesIncludedInHomePageOverviewDialog = ref<boolean>(f
const showAccountsIncludedInTotalDialog = ref<boolean>(false);
const enableDisableOptions = computed<LocalizedSwitchOption[]>(() => getAllEnableDisableOptions());
const allInsightsExploreDefaultDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplore, false));
const allInsightsExplorerDefaultDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplorer, false));
const currentTheme = computed<string>({
get: () => settingsStore.appSettings.theme,
@@ -414,16 +414,16 @@ const showAddTransactionButtonInDesktopNavbar = computed<boolean>({
set: (value) => settingsStore.setShowAddTransactionButtonInDesktopNavbar(value)
});
const insightsExploreDefaultDateRangeType = computed<number>({
get: () => settingsStore.appSettings.insightsExploreDefaultDateRangeType,
set: (value) => settingsStore.setInsightsExploreDefaultDateRangeType(value)
const insightsExplorerDefaultDateRangeType = computed<number>({
get: () => settingsStore.appSettings.insightsExplorerDefaultDateRangeType,
set: (value) => settingsStore.setInsightsExplorerDefaultDateRangeType(value)
});
const timezoneUsedForInsightsExplorePage = computed<number>({
get: () => settingsStore.appSettings.timezoneUsedForInsightsExplorePage,
const timezoneUsedForInsightsExplorerPage = computed<number>({
get: () => settingsStore.appSettings.timezoneUsedForInsightsExplorerPage,
set: (value: number) => {
settingsStore.setTimezoneUsedForInsightsExplorePage(value);
exploresStore.updateTransactionExploreInvalidState(true);
settingsStore.setTimezoneUsedForInsightsExplorerPage(value);
explorersStore.updateTransactionExplorerInvalidState(true);
}
});
@@ -19,9 +19,9 @@
/>
</div>
<v-tabs show-arrows class="my-4" direction="vertical"
:disabled="loading" v-model="currentExploreId">
:disabled="loading" v-model="currentExplorationId">
<v-tab class="tab-text-truncate" key="new" value="">
<span class="text-truncate">{{ tt('New Explore') }}</span>
<span class="text-truncate">{{ tt('New Exploration') }}</span>
</v-tab>
</v-tabs>
</v-navigation-drawer>
@@ -33,7 +33,7 @@
:ripple="false" :icon="true" @click="showNav = !showNav">
<v-icon :icon="mdiMenu" size="24" />
</v-btn>
<span>{{ tt('Insights & Explore') }}</span>
<span>{{ tt('Insights Explorer') }}</span>
<v-btn-group class="ms-4" color="default" density="comfortable" variant="outlined" divided>
<v-btn class="button-icon-with-direction" :icon="mdiArrowLeft"
:disabled="loading || !canShiftDateRange"
@@ -94,15 +94,15 @@
<v-window class="d-flex flex-grow-1 disable-tab-transition w-100-window-container" v-model="activeTab">
<v-window-item value="query">
<explore-query-tab :loading="loading" />
<explorer-query-tab :loading="loading" />
</v-window-item>
<v-window-item value="table">
<explore-data-table-tab ref="exploreDataTableTab"
<explorer-data-table-tab ref="explorerDataTableTab"
:loading="loading"
v-model:count-per-page="countPerPage" />
</v-window-item>
<v-window-item value="chart">
<explore-chart-tab ref="exploreChartTab"
<explorer-chart-tab ref="explorerChartTab"
:loading="loading" />
</v-window-item>
</v-window>
@@ -126,9 +126,9 @@
</template>
<script setup lang="ts">
import ExploreQueryTab from '@/views/desktop/insights/tabs/ExploreQueryTab.vue';
import ExploreDataTableTab from '@/views/desktop/insights/tabs/ExploreDataTableTab.vue';
import ExploreChartTab from '@/views/desktop/insights/tabs/ExploreChartTab.vue';
import ExplorerQueryTab from '@/views/desktop/insights/tabs/ExplorerQueryTab.vue';
import ExplorerDataTableTab from '@/views/desktop/insights/tabs/ExplorerDataTableTab.vue';
import ExplorerChartTab from '@/views/desktop/insights/tabs/ExplorerChartTab.vue';
import ExportDialog from '@/views/desktop/statistics/transaction/dialogs/ExportDialog.vue';
import SnackBar from '@/components/desktop/SnackBar.vue';
@@ -142,7 +142,7 @@ import { useUserStore } from '@/stores/user.ts';
import { useAccountsStore } from '@/stores/account.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { type TransactionExplorePartialFilter, type TransactionExploreFilter, useExploresStore } from '@/stores/explore.ts';
import { type TransactionExplorerPartialFilter, type TransactionExplorerFilter, useExplorersStore } from '@/stores/explorer.ts';
import type { NameNumeralValue } from '@/core/base.ts';
import type { NumeralSystem } from '@/core/numeral.ts';
@@ -169,7 +169,7 @@ import {
mdiExport
} from '@mdi/js';
interface InsightsExploreProps {
interface InsightsExplorerProps {
initId?: string;
initActiveTab?: string,
initDateRangeType?: string,
@@ -177,12 +177,12 @@ interface InsightsExploreProps {
initEndTime?: string,
}
const props = defineProps<InsightsExploreProps>();
const props = defineProps<InsightsExplorerProps>();
type ExplorePageTabType = 'query' | 'table' | 'chart';
type ExplorerPageTabType = 'query' | 'table' | 'chart';
type SnackBarType = InstanceType<typeof SnackBar>;
type ExploreDataTableTabType = InstanceType<typeof ExploreDataTableTab>;
type ExploreChartTabType = InstanceType<typeof ExploreChartTab>;
type ExplorerDataTableTabType = InstanceType<typeof ExplorerDataTableTab>;
type ExplorerChartTabType = InstanceType<typeof ExplorerChartTab>;
type ExportDialogType = InstanceType<typeof ExportDialog>;
const router = useRouter();
@@ -200,19 +200,19 @@ const userStore = useUserStore();
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const transactionTagsStore = useTransactionTagsStore();
const exploresStore = useExploresStore();
const explorersStore = useExplorersStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const exploreDataTableTab = useTemplateRef<ExploreDataTableTabType>('exploreDataTableTab');
const exploreChartTab = useTemplateRef<ExploreChartTabType>('exploreChartTab');
const explorerDataTableTab = useTemplateRef<ExplorerDataTableTabType>('explorerDataTableTab');
const explorerChartTab = useTemplateRef<ExplorerChartTabType>('explorerChartTab');
const exportDialog = useTemplateRef<ExportDialogType>('exportDialog');
const loading = ref<boolean>(true);
const initing = ref<boolean>(true);
const alwaysShowNav = ref<boolean>(display.mdAndUp.value);
const showNav = ref<boolean>(display.mdAndUp.value);
const activeTab = ref<ExplorePageTabType>('query');
const currentExploreId = ref<string>('');
const activeTab = ref<ExplorerPageTabType>('query');
const currentExplorationId = ref<string>('');
const countPerPage = ref<number>(15);
const showCustomDateRangeDialog = ref<boolean>(false);
@@ -220,16 +220,16 @@ const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDa
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const query = computed<TransactionExploreFilter>(() => exploresStore.transactionExploreFilter);
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => exploresStore.filteredTransactions);
const query = computed<TransactionExplorerFilter>(() => explorersStore.transactionExplorerFilter);
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => explorersStore.filteredTransactions);
const allDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplore, true));
const allDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplorer, true));
const canShiftDateRange = computed<boolean>(() => query.value.dateRangeType !== DateRange.All.type);
const displayQueryDateRangeName = computed<string>(() => formatDateRange(query.value.dateRangeType, query.value.startTime, query.value.endTime));
const displayQueryStartTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.startTime)));
const displayQueryEndTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.endTime)));
const allTabs = computed<{ name: string, value: ExplorePageTabType }[]>(() => {
const allTabs = computed<{ name: string, value: ExplorerPageTabType }[]>(() => {
return [
{
name: tt('Query'),
@@ -260,11 +260,11 @@ const allPageCounts = computed<NameNumeralValue[]>(() => {
});
function getFilterLinkUrl(): string {
return `/insights/explore?${exploresStore.getTransactionExplorePageParams(currentExploreId.value, activeTab.value)}`;
return `/insights/explorer?${explorersStore.getTransactionExplorerPageParams(currentExplorationId.value, activeTab.value)}`;
}
function init(initProps: InsightsExploreProps): void {
const filter: TransactionExplorePartialFilter = {
function init(initProps: InsightsExplorerProps): void {
const filter: TransactionExplorerPartialFilter = {
dateRangeType: initProps.initDateRangeType ? parseInt(initProps.initDateRangeType) : undefined,
startTime: initProps.initStartTime ? parseInt(initProps.initStartTime) : undefined,
endTime: initProps.initEndTime ? parseInt(initProps.initEndTime) : undefined
@@ -290,9 +290,9 @@ function init(initProps: InsightsExploreProps): void {
activeTab.value = 'query';
}
exploresStore.initTransactionExploreFilter(filter);
explorersStore.initTransactionExplorerFilter(filter);
if (!needReload && !exploresStore.transactionExploreStateInvalid) {
if (!needReload && !explorersStore.transactionExplorerStateInvalid) {
loading.value = false;
initing.value = false;
return;
@@ -303,7 +303,7 @@ function init(initProps: InsightsExploreProps): void {
transactionCategoriesStore.loadAllCategories({ force: false }),
transactionTagsStore.loadAllTags({ force: false })
]).then(() => {
return exploresStore.loadAllTransactions({ force: false });
return explorersStore.loadAllTransactions({ force: false });
}).then(() => {
loading.value = false;
initing.value = false;
@@ -320,7 +320,7 @@ function init(initProps: InsightsExploreProps): void {
function reload(force: boolean): Promise<unknown> | null {
loading.value = true;
return exploresStore.loadAllTransactions({
return explorersStore.loadAllTransactions({
force: force
}).then(() => {
loading.value = false;
@@ -339,13 +339,13 @@ function reload(force: boolean): Promise<unknown> | null {
function exportResults(): void {
if (activeTab.value === 'table' && filteredTransactions.value) {
const results = exploreDataTableTab.value?.buildExportResults();
const results = explorerDataTableTab.value?.buildExportResults();
if (results) {
exportDialog.value?.open(results);
}
} else if (activeTab.value === 'chart') {
const results = exploreChartTab.value?.buildExportResults();
const results = explorerChartTab.value?.buildExportResults();
if (results) {
exportDialog.value?.open(results);
@@ -367,7 +367,7 @@ function setDateFilter(dateType: number): void {
return;
}
const changed = exploresStore.updateTransactionExploreFilter({
const changed = explorersStore.updateTransactionExplorerFilter({
dateRangeType: dateRange.dateType,
startTime: dateRange.minTime,
endTime: dateRange.maxTime
@@ -375,7 +375,7 @@ function setDateFilter(dateType: number): void {
if (changed) {
loading.value = true;
exploresStore.updateTransactionExploreInvalidState(true);
explorersStore.updateTransactionExplorerInvalidState(true);
router.push(getFilterLinkUrl());
}
}
@@ -385,9 +385,9 @@ function setCustomDateFilter(startTime: number, endTime: number): void {
return;
}
const chartDateType = getDateTypeByDateRange(startTime, endTime, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.InsightsExplore);
const chartDateType = getDateTypeByDateRange(startTime, endTime, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.InsightsExplorer);
const changed = exploresStore.updateTransactionExploreFilter({
const changed = explorersStore.updateTransactionExplorerFilter({
dateRangeType: chartDateType,
startTime: startTime,
endTime: endTime
@@ -397,7 +397,7 @@ function setCustomDateFilter(startTime: number, endTime: number): void {
if (changed) {
loading.value = true;
exploresStore.updateTransactionExploreInvalidState(true);
explorersStore.updateTransactionExplorerInvalidState(true);
router.push(getFilterLinkUrl());
}
}
@@ -409,7 +409,7 @@ function shiftDateRange(scale: number): void {
const newDateRange = getShiftedDateRangeAndDateType(query.value.startTime, query.value.endTime, scale, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.Normal);
const changed = exploresStore.updateTransactionExploreFilter({
const changed = explorersStore.updateTransactionExplorerFilter({
dateRangeType: newDateRange.dateType,
startTime: newDateRange.minTime,
endTime: newDateRange.maxTime
@@ -417,7 +417,7 @@ function shiftDateRange(scale: number): void {
if (changed) {
loading.value = true;
exploresStore.updateTransactionExploreInvalidState(true);
explorersStore.updateTransactionExplorerInvalidState(true);
router.push(getFilterLinkUrl());
}
}
@@ -11,9 +11,9 @@
density="compact"
:disabled="loading"
:label="tt('Chart Type')"
:items="allTransactionExploreChartTypes"
:items="allTransactionExplorerChartTypes"
:model-value="currentChartType"
@update:model-value="updateChartType($event as TransactionExploreChartTypeValue)"
@update:model-value="updateChartType($event as TransactionExplorerChartTypeValue)"
/>
<v-select
class="flex-0-0"
@@ -23,9 +23,9 @@
density="compact"
:disabled="loading"
:label="tt('Axis / Category')"
:items="allTransactionExploreDataDimensions"
:items="allTransactionExplorerDataDimensions"
:model-value="currentCategoryDimension"
@update:model-value="updateCategoryDimension($event as TransactionExploreDataDimensionType)"
@update:model-value="updateCategoryDimension($event as TransactionExplorerDataDimensionType)"
/>
<v-select
class="flex-0-0"
@@ -33,14 +33,14 @@
item-title="name"
item-value="value"
density="compact"
:disabled="loading || !TransactionExploreChartType.valueOf(currentChartType)?.seriesDimensionRequired"
:disabled="loading || !TransactionExplorerChartType.valueOf(currentChartType)?.seriesDimensionRequired"
:label="tt('Series')"
:items="allTransactionExploreDataDimensions"
:model-value="TransactionExploreChartType.valueOf(currentChartType)?.seriesDimensionRequired ? currentSeriesDimension : TransactionExploreDataDimension.None.value"
@update:model-value="updateSeriesDimension($event as TransactionExploreDataDimensionType)"
:items="allTransactionExplorerDataDimensions"
:model-value="TransactionExplorerChartType.valueOf(currentChartType)?.seriesDimensionRequired ? currentSeriesDimension : TransactionExplorerDataDimension.None.value"
@update:model-value="updateSeriesDimension($event as TransactionExplorerDataDimensionType)"
>
<template #item="{ props, item }">
<v-list-item :disabled="item.value === currentCategoryDimension && item.value !== TransactionExploreDataDimension.SeriesDimensionDefault.value" v-bind="props">
<v-list-item :disabled="item.value === currentCategoryDimension && item.value !== TransactionExplorerDataDimension.SeriesDimensionDefault.value" v-bind="props">
<template #title>
<div class="text-truncate">{{ item.raw.name }}</div>
</template>
@@ -55,16 +55,16 @@
density="compact"
:disabled="loading"
:label="tt('Value Metric')"
:items="allTransactionExploreValueMetrics"
:items="allTransactionExplorerValueMetrics"
:model-value="currentValueMetric"
@update:model-value="updateValueMetric($event as TransactionExploreValueMetricType)"
@update:model-value="updateValueMetric($event as TransactionExplorerValueMetricType)"
/>
<v-spacer class="flex-1-1"/>
</div>
</v-col>
</v-row>
</v-card-text>
<v-card-text :class="{ 'readonly': loading }" v-if="currentChartType === TransactionExploreChartType.Pie.value">
<v-card-text :class="{ 'readonly': loading }" v-if="currentChartType === TransactionExplorerChartType.Pie.value">
<pie-chart
:items="[
{id: '1', name: '---', value: 60, color: '7c7c7f'},
@@ -79,12 +79,12 @@
v-if="loading"
/>
<pie-chart
:items="categoryDimensionTransactionExploreData && categoryDimensionTransactionExploreData.length ? categoryDimensionTransactionExploreData : []"
:items="categoryDimensionTransactionExplorerData && categoryDimensionTransactionExplorerData.length ? categoryDimensionTransactionExplorerData : []"
:min-valid-percent="0.0001"
:show-value="true"
:show-percent="true"
:enable-click-item="true"
:amount-value="exploresStore.transactionExploreFilter.valueMetric !== TransactionExploreValueMetric.TransactionCount.value"
:amount-value="explorersStore.transactionExplorerFilter.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value"
:default-currency="defaultCurrency"
id-field="categoryId"
name-field="categoryDisplayName"
@@ -93,7 +93,7 @@
@click="onClickPieChartItem"
/>
</v-card-text>
<v-card-text :class="{ 'readonly': loading }" v-if="currentChartType === TransactionExploreChartType.Radar.value">
<v-card-text :class="{ 'readonly': loading }" v-if="currentChartType === TransactionExplorerChartType.Radar.value">
<radar-chart
:items="[
{name: '---', value: 10},
@@ -109,11 +109,11 @@
v-if="loading"
/>
<radar-chart
:items="categoryDimensionTransactionExploreData && categoryDimensionTransactionExploreData.length ? categoryDimensionTransactionExploreData : []"
:items="categoryDimensionTransactionExplorerData && categoryDimensionTransactionExplorerData.length ? categoryDimensionTransactionExplorerData : []"
:min-valid-percent="0.0001"
:show-value="true"
:show-percent="true"
:amount-value="exploresStore.transactionExploreFilter.valueMetric !== TransactionExploreValueMetric.TransactionCount.value"
:amount-value="explorersStore.transactionExplorerFilter.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value"
:default-currency="defaultCurrency"
name-field="categoryDisplayName"
value-field="value"
@@ -132,20 +132,20 @@ import { useUserStore } from '@/stores/user.ts';
import {
type CategoriedInfo,
type SeriesedInfo,
TransactionExploreDimensionType,
useExploresStore
} from '@/stores/explore.ts';
TransactionExplorerDimensionType,
useExplorersStore
} from '@/stores/explorer.ts';
import { type NameValue } from '@/core/base.ts';
import { Month, WeekDay } from '@/core/datetime.ts';
import {
TransactionExploreChartTypeValue,
TransactionExploreChartType,
TransactionExploreDataDimensionType,
TransactionExploreDataDimension,
TransactionExploreValueMetricType,
TransactionExploreValueMetric
} from '@/core/explore.ts';
TransactionExplorerChartTypeValue,
TransactionExplorerChartType,
TransactionExplorerDataDimensionType,
TransactionExplorerDataDimension,
TransactionExplorerValueMetricType,
TransactionExplorerValueMetric
} from '@/core/explorer.ts';
import {
isDefined
@@ -155,26 +155,26 @@ import {
parseDateTimeFromUnixTime
} from '@/lib/datetime.ts';
interface InsightsExploreDataTableTabProps {
interface InsightsExplorerDataTableTabProps {
loading?: boolean;
}
interface CategoryDimensionData {
categoryDisplayName: string;
categoryId: string;
categoryIdType: TransactionExploreDimensionType;
categoryIdType: TransactionExplorerDimensionType;
value: number;
}
defineProps<InsightsExploreDataTableTabProps>();
defineProps<InsightsExplorerDataTableTabProps>();
const router = useRouter();
const {
tt,
getAllTransactionExploreDataDimensions,
getAllTransactionExploreValueMetrics,
getAllTransactionExploreChartTypes,
getAllTransactionExplorerDataDimensions,
getAllTransactionExplorerValueMetrics,
getAllTransactionExplorerChartTypes,
getMonthLongName,
getMonthdayShortName,
getWeekdayLongName,
@@ -190,31 +190,31 @@ const {
} = useI18n();
const userStore = useUserStore();
const exploresStore = useExploresStore();
const explorersStore = useExplorersStore();
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const allTransactionExploreDataDimensions = computed<NameValue[]>(() => getAllTransactionExploreDataDimensions());
const allTransactionExploreValueMetrics = computed<NameValue[]>(() => getAllTransactionExploreValueMetrics());
const allTransactionExploreChartTypes = computed<NameValue[]>(() => getAllTransactionExploreChartTypes());
const allTransactionExplorerDataDimensions = computed<NameValue[]>(() => getAllTransactionExplorerDataDimensions());
const allTransactionExplorerValueMetrics = computed<NameValue[]>(() => getAllTransactionExplorerValueMetrics());
const allTransactionExplorerChartTypes = computed<NameValue[]>(() => getAllTransactionExplorerChartTypes());
const currentCategoryDimension = computed<TransactionExploreDataDimensionType>(() => exploresStore.transactionExploreFilter.categoryDimension);
const currentSeriesDimension = computed<TransactionExploreDataDimensionType>(() => exploresStore.transactionExploreFilter.seriesDimension);
const currentValueMetric = computed<TransactionExploreValueMetricType>(() => exploresStore.transactionExploreFilter.valueMetric);
const currentChartType = computed<TransactionExploreChartTypeValue>(() => exploresStore.transactionExploreFilter.chartType);
const currentCategoryDimension = computed<TransactionExplorerDataDimensionType>(() => explorersStore.transactionExplorerFilter.categoryDimension);
const currentSeriesDimension = computed<TransactionExplorerDataDimensionType>(() => explorersStore.transactionExplorerFilter.seriesDimension);
const currentValueMetric = computed<TransactionExplorerValueMetricType>(() => explorersStore.transactionExplorerFilter.valueMetric);
const currentChartType = computed<TransactionExplorerChartTypeValue>(() => explorersStore.transactionExplorerFilter.chartType);
const categoryDimensionTransactionExploreData = computed<CategoryDimensionData[]>(() => {
if (currentChartType.value !== TransactionExploreChartType.Pie.value && currentChartType.value !== TransactionExploreChartType.Radar.value) {
const categoryDimensionTransactionExplorerData = computed<CategoryDimensionData[]>(() => {
if (currentChartType.value !== TransactionExplorerChartType.Pie.value && currentChartType.value !== TransactionExplorerChartType.Radar.value) {
return [];
}
if (!exploresStore.categoriedTransactionExploreData || !exploresStore.categoriedTransactionExploreData.length) {
if (!explorersStore.categoriedTransactionExplorerData || !explorersStore.categoriedTransactionExplorerData.length) {
return [];
}
const result: CategoryDimensionData[] = [];
for (const categoriedData of exploresStore.categoriedTransactionExploreData) {
for (const categoriedData of explorersStore.categoriedTransactionExplorerData) {
const data = categoriedData.data[0];
if (!isDefined(data)) {
@@ -238,18 +238,18 @@ function getCategoriedDataDisplayName(info: CategoriedInfo | SeriesedInfo): stri
let name: string = '';
let needI18n: boolean | undefined = false;
let i18nParameters: Record<string, unknown> | undefined = undefined;
let dimessionType: TransactionExploreDataDimensionType = TransactionExploreDataDimension.None.value;
let dimessionType: TransactionExplorerDataDimensionType = TransactionExplorerDataDimension.None.value;
if ('categoryName' in info) {
name = info.categoryName;
needI18n = info.categoryNameNeedI18n;
i18nParameters = info.categoryNameI18nParameters;
dimessionType = exploresStore.transactionExploreFilter.categoryDimension;
dimessionType = explorersStore.transactionExplorerFilter.categoryDimension;
} else if ('seriesName' in info) {
name = info.seriesName;
needI18n = info.seriesNameNeedI18n;
i18nParameters = info.seriesNameI18nParameters;
dimessionType = exploresStore.transactionExploreFilter.seriesDimension;
dimessionType = explorersStore.transactionExplorerFilter.seriesDimension;
}
let displayName: string = name;
@@ -262,32 +262,32 @@ function getCategoriedDataDisplayName(info: CategoriedInfo | SeriesedInfo): stri
}
// convert the name to formatted date time if needed
if (dimessionType === TransactionExploreDataDimension.DateTime.value) {
if (dimessionType === TransactionExplorerDataDimension.DateTime.value) {
displayName = formatDateTimeToShortDateTime(parseDateTimeFromUnixTime(parseInt(name)));
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByYearMonthDay.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByYearMonthDay.value) {
displayName = formatDateTimeToShortDate(parseDateTimeFromUnixTime(parseInt(name)));
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByYearMonth.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByYearMonth.value) {
displayName = formatDateTimeToGregorianLikeShortYearMonth(parseDateTimeFromUnixTime(parseInt(name)));
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByYearQuarter.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByYearQuarter.value) {
displayName = formatDateTimeToGregorianLikeYearQuarter(parseDateTimeFromUnixTime(parseInt(name)));
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByYear.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByYear.value) {
displayName = formatDateTimeToGregorianLikeShortYear(parseDateTimeFromUnixTime(parseInt(name)));
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByFiscalYear.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByFiscalYear.value) {
displayName = formatDateTimeToGregorianLikeFiscalYear(parseDateTimeFromUnixTime(parseInt(name)));
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByDayOfWeek.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByDayOfWeek.value) {
const weekDay = WeekDay.parse(name);
displayName = weekDay ? getWeekdayLongName(weekDay) : tt('Unknown');
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByDayOfMonth.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByDayOfMonth.value) {
displayName = getMonthdayShortName(parseInt(name));
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByMonthOfYear.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByMonthOfYear.value) {
const month = Month.valueOf(parseInt(name));
displayName = month ? getMonthLongName(month.name) : tt('Unknown');
} else if (dimessionType === TransactionExploreDataDimension.DateTimeByQuarterOfYear.value) {
} else if (dimessionType === TransactionExplorerDataDimension.DateTimeByQuarterOfYear.value) {
displayName = getQuarterName(parseInt(name));
}
if (dimessionType === TransactionExploreDataDimension.SourceAmount.value
|| dimessionType === TransactionExploreDataDimension.DestinationAmount.value) {
if (dimessionType === TransactionExplorerDataDimension.SourceAmount.value
|| dimessionType === TransactionExplorerDataDimension.DestinationAmount.value) {
if (name !== '' && name !== 'none' && Number.isFinite(parseInt(name))) {
displayName = formatAmountToLocalizedNumerals(parseInt(name));
}
@@ -296,26 +296,26 @@ function getCategoriedDataDisplayName(info: CategoriedInfo | SeriesedInfo): stri
return displayName;
}
function updateCategoryDimension(categoryDimension: TransactionExploreDataDimensionType): void {
exploresStore.updateTransactionExploreFilter({
function updateCategoryDimension(categoryDimension: TransactionExplorerDataDimensionType): void {
explorersStore.updateTransactionExplorerFilter({
categoryDimension: categoryDimension,
});
}
function updateSeriesDimension(seriesDimension: TransactionExploreDataDimensionType): void {
exploresStore.updateTransactionExploreFilter({
function updateSeriesDimension(seriesDimension: TransactionExplorerDataDimensionType): void {
explorersStore.updateTransactionExplorerFilter({
seriesDimension: seriesDimension,
});
}
function updateValueMetric(valueMetric: TransactionExploreValueMetricType): void {
exploresStore.updateTransactionExploreFilter({
function updateValueMetric(valueMetric: TransactionExplorerValueMetricType): void {
explorersStore.updateTransactionExplorerFilter({
valueMetric: valueMetric,
});
}
function updateChartType(chartType: TransactionExploreChartTypeValue): void {
exploresStore.updateTransactionExploreFilter({
function updateChartType(chartType: TransactionExplorerChartTypeValue): void {
explorersStore.updateTransactionExplorerFilter({
chartType: chartType,
});
}
@@ -326,7 +326,7 @@ function onClickPieChartItem(item: Record<string, unknown>): void {
}
const data = (item as unknown) as CategoryDimensionData;
const params: string = exploresStore.getTransactionListPageParams(data.categoryIdType, data.categoryId);
const params: string = explorersStore.getTransactionListPageParams(data.categoryIdType, data.categoryId);
if (params) {
router.push(`/transaction/list?${params}`);
@@ -334,15 +334,15 @@ function onClickPieChartItem(item: Record<string, unknown>): void {
}
function buildExportResults(): { headers: string[], data: string[][] } | undefined {
if (currentChartType.value === TransactionExploreChartType.Pie.value || currentChartType.value === TransactionExploreChartType.Radar.value) {
const valueMetric = TransactionExploreValueMetric.valueOf(exploresStore.transactionExploreFilter.valueMetric);
if (currentChartType.value === TransactionExplorerChartType.Pie.value || currentChartType.value === TransactionExplorerChartType.Radar.value) {
const valueMetric = TransactionExplorerValueMetric.valueOf(explorersStore.transactionExplorerFilter.valueMetric);
return {
headers: [
tt('Name'),
tt(valueMetric?.name ?? 'Unknown')
],
data: categoryDimensionTransactionExploreData.value.map(data => [
data: categoryDimensionTransactionExplorerData.value.map(data => [
data.categoryDisplayName,
valueMetric?.isAmount ? formatAmountToWesternArabicNumeralsWithoutDigitGrouping(data.value) : data.value.toString(10)
])
@@ -4,7 +4,7 @@
fixed-footer
multi-sort
item-value="index"
:class="{ 'insights-explore-table': true, 'text-sm': true, 'disabled': loading, 'loading-skeleton': loading }"
:class="{ 'insights-explorer-table': true, 'text-sm': true, 'disabled': loading, 'loading-skeleton': loading }"
:headers="dataTableHeaders"
:items="filteredTransactions"
:hover="true"
@@ -77,7 +77,7 @@ import { ref, computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useUserStore } from '@/stores/user.ts';
import { useExploresStore } from '@/stores/explore.ts';
import { useExplorersStore } from '@/stores/explorer.ts';
import { TransactionType } from '@/core/transaction.ts';
@@ -96,12 +96,12 @@ import {
mdiPencilBoxOutline
} from '@mdi/js';
interface InsightsExploreDataTableTabProps {
interface InsightsExplorerDataTableTabProps {
loading?: boolean;
countPerPage: number;
}
const props = defineProps<InsightsExploreDataTableTabProps>();
const props = defineProps<InsightsExplorerDataTableTabProps>();
const emit = defineEmits<{
(e: 'update:countPerPage', value: number): void;
}>();
@@ -115,13 +115,13 @@ const {
} = useI18n();
const userStore = useUserStore();
const exploresStore = useExploresStore();
const explorersStore = useExplorersStore();
const currentPage = ref<number>(1);
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => exploresStore.filteredTransactions);
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => explorersStore.filteredTransactions);
const itemsPerPage = computed<number>({
get: () => props.countPerPage,
@@ -274,7 +274,7 @@ defineExpose({
</script>
<style>
.v-table.insights-explore-table > .v-table__wrapper > table {
.v-table.insights-explorer-table > .v-table__wrapper > table {
th:not(:last-child),
td:not(:last-child) {
width: auto !important;
@@ -287,7 +287,7 @@ defineExpose({
}
}
.v-table.insights-explore-table.loading-skeleton tr.v-data-table-rows-no-data > td {
.v-table.insights-explorer-table.loading-skeleton tr.v-data-table-rows-no-data > td {
padding: 0;
}
</style>
@@ -91,8 +91,8 @@
density="compact"
item-title="displayName"
item-value="value"
:items="[{ value: TransactionExploreConditionRelation.First, displayName: tt('WHERE') }]"
:model-value="TransactionExploreConditionRelation.First"
:items="[{ value: TransactionExplorerConditionRelation.First, displayName: tt('WHERE') }]"
:model-value="TransactionExplorerConditionRelation.First"
v-if="conditionIndex < 1"
/>
@@ -104,8 +104,8 @@
item-value="value"
:disabled="loading || !!editingQuery"
:items="[
{ value: TransactionExploreConditionRelation.And, displayName: tt('AND') },
{ value: TransactionExploreConditionRelation.Or, displayName: tt('OR') }
{ value: TransactionExplorerConditionRelation.And, displayName: tt('AND') },
{ value: TransactionExplorerConditionRelation.Or, displayName: tt('OR') }
]"
v-model="conditionWithRelation.relation"
v-else-if="conditionIndex >= 1"
@@ -117,8 +117,8 @@
item-title="name"
item-value="value"
:disabled="loading || !!editingQuery"
:items="allTransactionExploreConditionFields"
@update:model-value="updateConditionField(queryIndex, conditionIndex, TransactionExploreConditionField.valueOf($event))"
:items="allTransactionExplorerConditionFields"
@update:model-value="updateConditionField(queryIndex, conditionIndex, TransactionExplorerConditionField.valueOf($event))"
v-model="conditionWithRelation.condition.field"
/>
@@ -128,7 +128,7 @@
item-title="name"
item-value="value"
:disabled="loading || !!editingQuery"
:items="getAllTransactionExploreConditionOperators(conditionWithRelation.getSupportedOperators())"
:items="getAllTransactionExplorerConditionOperators(conditionWithRelation.getSupportedOperators())"
v-model="conditionWithRelation.condition.operator"
/>
@@ -146,7 +146,7 @@
{ type: TransactionType.Transfer, displayName: tt('Transfer') }
]"
v-model="conditionWithRelation.condition.value"
v-if="conditionWithRelation.condition.field === TransactionExploreConditionField.TransactionType.value"
v-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.TransactionType.value"
>
<template #item="{ props, item }">
<v-list-item :value="item.value" v-bind="props">
@@ -170,7 +170,7 @@
:placeholder="tt('None')"
:model-value="getFilteredTransactionCategoriesDisplayContent(arrayItemToObjectField(conditionWithRelation.condition.value as string[], true))"
@click="currentCondition = conditionWithRelation.condition; showFilterTransactionCategoriesDialog = true"
v-else-if="conditionWithRelation.condition.field === TransactionExploreConditionField.TransactionCategory.value"
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.TransactionCategory.value"
/>
<v-text-field
@@ -184,7 +184,7 @@
:placeholder="tt('None')"
:model-value="getFilteredAccountsDisplayContent(arrayItemToObjectField(conditionWithRelation.condition.value as string[], true))"
@click="currentCondition = conditionWithRelation.condition; showFilterSourceAccountsDialog = true"
v-else-if="conditionWithRelation.condition.field === TransactionExploreConditionField.SourceAccount.value"
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.SourceAccount.value"
/>
<v-text-field
@@ -198,37 +198,37 @@
:placeholder="tt('None')"
:model-value="getFilteredAccountsDisplayContent(arrayItemToObjectField(conditionWithRelation.condition.value as string[], true))"
@click="currentCondition = conditionWithRelation.condition; showFilterDestinationAccountsDialog = true"
v-else-if="conditionWithRelation.condition.field === TransactionExploreConditionField.DestinationAccount.value"
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.DestinationAccount.value"
/>
<div class="d-flex w-100 align-center gap-2"
v-else-if="conditionWithRelation.condition.field === TransactionExploreConditionField.SourceAmount.value ||
conditionWithRelation.condition.field === TransactionExploreConditionField.DestinationAmount.value">
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.SourceAmount.value ||
conditionWithRelation.condition.field === TransactionExplorerConditionField.DestinationAmount.value">
<amount-input density="compact"
:currency="defaultCurrency"
:disabled="loading || !!editingQuery"
v-model="conditionWithRelation.condition.value[0]"
/>
<span class="ms-2 me-2"
v-if="conditionWithRelation.condition.operator === TransactionExploreConditionOperator.Between.value ||
conditionWithRelation.condition.operator === TransactionExploreConditionOperator.NotBetween.value">~</span>
v-if="conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.Between.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.NotBetween.value">~</span>
<amount-input density="compact"
:currency="defaultCurrency"
:disabled="loading || !!editingQuery"
v-model="conditionWithRelation.condition.value[1]"
v-if="conditionWithRelation.condition.operator === TransactionExploreConditionOperator.Between.value ||
conditionWithRelation.condition.operator === TransactionExploreConditionOperator.NotBetween.value"
v-if="conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.Between.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.NotBetween.value"
/>
</div>
<div class="d-flex w-100" v-else-if="conditionWithRelation.condition.field === TransactionExploreConditionField.TransactionTag.value">
<div class="d-flex w-100" v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.TransactionTag.value">
<v-text-field
disabled
persistent-placeholder
density="compact"
:placeholder="tt('None')"
v-if="conditionWithRelation.condition.field === TransactionExploreConditionField.TransactionTag.value &&
(conditionWithRelation.condition.operator === TransactionExploreConditionOperator.IsEmpty.value || conditionWithRelation.condition.operator === TransactionExploreConditionOperator.IsNotEmpty.value)"
v-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.TransactionTag.value &&
(conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.IsEmpty.value || conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.IsNotEmpty.value)"
/>
<v-autocomplete
@@ -245,7 +245,7 @@
:items="allTags"
v-model="conditionWithRelation.condition.value"
v-model:search="tagSearchContent"
v-else-if="conditionWithRelation.condition.operator !== TransactionExploreConditionOperator.IsEmpty.value && conditionWithRelation.condition.operator !== TransactionExploreConditionOperator.IsNotEmpty.value"
v-else-if="conditionWithRelation.condition.operator !== TransactionExplorerConditionOperator.IsEmpty.value && conditionWithRelation.condition.operator !== TransactionExplorerConditionOperator.IsNotEmpty.value"
>
<template #chip="{ props, item }">
<v-chip :prepend-icon="mdiPound" :text="item.title" v-bind="props"/>
@@ -285,16 +285,16 @@
<v-text-field disabled density="compact"
:placeholder="tt('None')"
v-else-if="conditionWithRelation.condition.field === TransactionExploreConditionField.Description.value &&
conditionWithRelation.condition.operator === TransactionExploreConditionOperator.IsEmpty.value || conditionWithRelation.condition.operator === TransactionExploreConditionOperator.IsNotEmpty.value"
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.Description.value &&
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.IsEmpty.value || conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.IsNotEmpty.value"
/>
<v-text-field density="compact"
:disabled="loading || !!editingQuery"
:placeholder="tt('None')"
v-model="conditionWithRelation.condition.value"
v-else-if="conditionWithRelation.condition.field === TransactionExploreConditionField.Description.value &&
conditionWithRelation.condition.operator !== TransactionExploreConditionOperator.IsEmpty.value && conditionWithRelation.condition.operator !== TransactionExploreConditionOperator.IsNotEmpty.value"
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.Description.value &&
conditionWithRelation.condition.operator !== TransactionExplorerConditionOperator.IsEmpty.value && conditionWithRelation.condition.operator !== TransactionExplorerConditionOperator.IsNotEmpty.value"
/>
</div>
@@ -371,25 +371,25 @@ import { useUserStore } from '@/stores/user.ts';
import { useAccountsStore } from '@/stores/account.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { useExploresStore } from '@/stores/explore.ts';
import { useExplorersStore } from '@/stores/explorer.ts';
import { type NameValue, entries, values } from '@/core/base.ts';
import { AccountType } from '@/core/account.ts';
import { TransactionType } from '@/core/transaction.ts';
import {
TransactionExploreConditionRelation,
TransactionExploreConditionField,
TransactionExploreConditionOperator
} from '@/core/explore.ts';
TransactionExplorerConditionRelation,
TransactionExplorerConditionField,
TransactionExplorerConditionOperator
} from '@/core/explorer.ts';
import {
type TransactionTag
} from '@/models/transaction_tag.ts';
import {
type TransactionExploreCondition,
TransactionExploreQuery
} from '@/models/explore.ts';
type TransactionExplorerCondition,
TransactionExplorerQuery
} from '@/models/explorer.ts';
import {
isArray,
@@ -409,46 +409,46 @@ import {
mdiPound
} from '@mdi/js';
interface ExploreQueryTabProps {
interface ExplorerQueryTabProps {
loading?: boolean;
}
type SnackBarType = InstanceType<typeof SnackBar>;
const props = defineProps<ExploreQueryTabProps>();
const props = defineProps<ExplorerQueryTabProps>();
const {
tt,
joinMultiText,
getAllTransactionExploreConditionFields,
getAllTransactionExploreConditionOperators
getAllTransactionExplorerConditionFields,
getAllTransactionExplorerConditionOperators
} = useI18n();
const userStore = useUserStore();
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const transactionTagsStore = useTransactionTagsStore();
const exploresStore = useExploresStore();
const explorersStore = useExplorersStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const currentCondition = ref<TransactionExploreCondition | undefined>(undefined);
const currentCondition = ref<TransactionExplorerCondition | undefined>(undefined);
const showExpression = ref<Record<number, boolean>>({});
const showFilterSourceAccountsDialog = ref<boolean>(false);
const showFilterDestinationAccountsDialog = ref<boolean>(false);
const showFilterTransactionCategoriesDialog = ref<boolean>(false);
const tagSearchContent = ref<string>('');
const editingQuery = ref<TransactionExploreQuery | undefined>(undefined);
const editingQuery = ref<TransactionExplorerQuery | undefined>(undefined);
const editingQueryName = ref<string>('');
const queries = computed<TransactionExploreQuery[]>(() => exploresStore.transactionExploreFilter.query);
const queries = computed<TransactionExplorerQuery[]>(() => explorersStore.transactionExplorerFilter.query);
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const hasAnyAccount = computed<boolean>(() => accountsStore.allPlainAccounts.length > 0);
const hasAnyTransactionCategory = computed<boolean>(() => !isObjectEmpty(transactionCategoriesStore.allTransactionCategoriesMap));
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
const allTransactionExploreConditionFields = computed<NameValue[]>(() => getAllTransactionExploreConditionFields());
const allTransactionExplorerConditionFields = computed<NameValue[]>(() => getAllTransactionExplorerConditionFields());
const isAllFilteredTagHidden = computed<boolean>(() => {
const lowerCaseTagSearchContent = tagSearchContent.value.toLowerCase();
@@ -534,10 +534,10 @@ function getFilteredTransactionCategoriesDisplayContent(filterTransactionCategor
}
function addQuery(): void {
queries.value.push(TransactionExploreQuery.create());
queries.value.push(TransactionExplorerQuery.create());
}
function updateQueryName(query: TransactionExploreQuery): void {
function updateQueryName(query: TransactionExplorerQuery): void {
query.name = editingQueryName.value;
editingQuery.value = undefined;
editingQueryName.value = '';
@@ -548,7 +548,7 @@ function cancelUpdateQueryName(): void {
editingQueryName.value = '';
}
function duplicateQuery(query: TransactionExploreQuery): void {
function duplicateQuery(query: TransactionExplorerQuery): void {
queries.value.push(query.clone());
}
@@ -572,13 +572,13 @@ function removeQuery(queryIndex: number): void {
showExpression.value = newShowExpression;
if (queries.value.length < 1) {
queries.value.push(TransactionExploreQuery.create());
queries.value.push(TransactionExplorerQuery.create());
}
}
function clearAllQueries(): void {
queries.value.length = 0;
queries.value.push(TransactionExploreQuery.create());
queries.value.push(TransactionExplorerQuery.create());
}
function addCondition(queryIndex: number): void {
@@ -588,7 +588,7 @@ function addCondition(queryIndex: number): void {
return;
}
const newCondition = query.addNewCondition(TransactionExploreConditionField.TransactionType, query.conditions.length < 1);
const newCondition = query.addNewCondition(TransactionExplorerConditionField.TransactionType, query.conditions.length < 1);
query.conditions.push(newCondition);
}
@@ -605,12 +605,12 @@ function removeCondition(queryIndex: number, conditionIndex: number): void {
const newFirstCondition = query.conditions[0];
if (newFirstCondition) {
newFirstCondition.relation = TransactionExploreConditionRelation.First;
newFirstCondition.relation = TransactionExplorerConditionRelation.First;
}
}
}
function updateConditionField(queryIndex: number, conditionIndex: number, newField: TransactionExploreConditionField | undefined): void {
function updateConditionField(queryIndex: number, conditionIndex: number, newField: TransactionExplorerConditionField | undefined): void {
if (!newField) {
return;
}
@@ -632,7 +632,7 @@ function updateConditionField(queryIndex: number, conditionIndex: number, newFie
}
function updateSourceAccount(changed: boolean, selectedAccountIds?: string[]): void {
if (!changed || !currentCondition.value || currentCondition.value.field !== TransactionExploreConditionField.SourceAccount.value) {
if (!changed || !currentCondition.value || currentCondition.value.field !== TransactionExplorerConditionField.SourceAccount.value) {
showFilterSourceAccountsDialog.value = false;
return;
}
@@ -643,7 +643,7 @@ function updateSourceAccount(changed: boolean, selectedAccountIds?: string[]): v
}
function updateDestinationAccount(changed: boolean, selectedAccountIds?: string[]): void {
if (!changed || !currentCondition.value || currentCondition.value.field !== TransactionExploreConditionField.DestinationAccount.value) {
if (!changed || !currentCondition.value || currentCondition.value.field !== TransactionExplorerConditionField.DestinationAccount.value) {
showFilterDestinationAccountsDialog.value = false;
return;
}
@@ -654,7 +654,7 @@ function updateDestinationAccount(changed: boolean, selectedAccountIds?: string[
}
function updateTransactionCategories(changed: boolean, selectedCategoryIds?: string[]): void {
if (!changed || !currentCondition.value || currentCondition.value.field !== TransactionExploreConditionField.TransactionCategory.value) {
if (!changed || !currentCondition.value || currentCondition.value.field !== TransactionExplorerConditionField.TransactionCategory.value) {
showFilterTransactionCategoriesDialog.value = false;
return;
}
@@ -674,14 +674,14 @@ function getExpression(queryIndex: number): string {
try {
return query.toExpression(transactionCategoriesStore.allTransactionCategoriesMap, accountsStore.allAccountsMap, transactionTagsStore.allTransactionTagsMap);
} catch (ex) {
logger.error('failed to generate expression for explore query#' + queryIndex, ex);
logger.error('failed to generate expression for explorer query#' + queryIndex, ex);
snackbar.value?.showError(tt('Failed to generate expression'));
return tt('Failed to generate expression');
}
}
if (queries.value.length === 0) {
queries.value.push(TransactionExploreQuery.create());
queries.value.push(TransactionExplorerQuery.create());
}
</script>