mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-17 16:24:25 +08:00
show total income and total expense in categorical overview chart
This commit is contained in:
@@ -14,33 +14,31 @@ import { useI18n } from '@/locales/helpers.ts';
|
|||||||
|
|
||||||
import { useUserStore } from '@/stores/user.ts';
|
import { useUserStore } from '@/stores/user.ts';
|
||||||
|
|
||||||
import type {
|
import {
|
||||||
SortableTransactionStatisticDataItem,
|
type TransactionCategoricalOverviewAnalysisDataItem,
|
||||||
TransactionStatisticResponseItemWithInfo
|
type TransactionCategoricalOverviewAnalysisDataItemOutflowItem,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType
|
||||||
} from '@/models/transaction.ts';
|
} from '@/models/transaction.ts';
|
||||||
import type { Account } from '@/models/account.ts';
|
|
||||||
|
|
||||||
import { values } from '@/core/base.ts';
|
import { values } from '@/core/base.ts';
|
||||||
import { ThemeType } from '@/core/theme.ts';
|
import { ThemeType } from '@/core/theme.ts';
|
||||||
import { CategoryType } from '@/core/category.ts';
|
|
||||||
import { TransactionRelatedAccountType } from '@/core/transaction.ts';
|
|
||||||
|
|
||||||
import { isNumber } from '@/lib/common.ts';
|
import { isNumber } from '@/lib/common.ts';
|
||||||
import { sortStatisticsItems } from '@/lib/statistics.ts';
|
|
||||||
import { getExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
import { getExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
||||||
|
|
||||||
enum SankeyChartDepth {
|
enum SankeyChartDepth {
|
||||||
PrimaryIncomeCategory = 0,
|
PrimaryIncomeCategory = 0,
|
||||||
SecondaryIncomeCategory = 1,
|
SecondaryIncomeCategory = 1,
|
||||||
Account = 2,
|
AccountForIncome = 2,
|
||||||
AccountWithTransfer = 3,
|
AccountForExpense = 3,
|
||||||
SecondaryExpenseCategory = 4,
|
SecondaryExpenseCategory = 4,
|
||||||
PrimaryExpenseCategory = 5
|
PrimaryExpenseCategory = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SankeyChartNodeItemType {
|
enum SankeyChartNodeItemType {
|
||||||
Account = 'account',
|
Account = 'account',
|
||||||
Category = 'category'
|
Category = 'category',
|
||||||
|
NetCashFlow = 'netCashFlow'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SankeyChartData {
|
interface SankeyChartData {
|
||||||
@@ -48,16 +46,20 @@ interface SankeyChartData {
|
|||||||
links: SankeyChartLinkItem[];
|
links: SankeyChartLinkItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SankeyChartNodeItem extends SortableTransactionStatisticDataItem {
|
interface SankeyChartNodeItem {
|
||||||
|
dateItemType: TransactionCategoricalOverviewAnalysisDataItemType;
|
||||||
itemType: SankeyChartNodeItemType;
|
itemType: SankeyChartNodeItemType;
|
||||||
itemId: string;
|
itemId: string;
|
||||||
name: string;
|
name: string;
|
||||||
nameId: string;
|
|
||||||
displayName: string;
|
displayName: string;
|
||||||
displayOrders: number[];
|
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
|
accountNetCashFlow?: number;
|
||||||
percent?: number;
|
percent?: number;
|
||||||
depth: number;
|
depth: number;
|
||||||
|
itemStyle?: {
|
||||||
|
color?: string;
|
||||||
|
opacity?: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SankeyChartLinkItem {
|
interface SankeyChartLinkItem {
|
||||||
@@ -74,8 +76,7 @@ interface SankeyChartLinkItem {
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
skeleton?: boolean;
|
skeleton?: boolean;
|
||||||
items: TransactionStatisticResponseItemWithInfo[];
|
items: TransactionCategoricalOverviewAnalysisDataItem[];
|
||||||
sortingType: number;
|
|
||||||
defaultCurrency?: string;
|
defaultCurrency?: string;
|
||||||
enableClickItem?: boolean;
|
enableClickItem?: boolean;
|
||||||
}>();
|
}>();
|
||||||
@@ -87,230 +88,134 @@ const emit = defineEmits<{
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
tt,
|
||||||
formatAmountToLocalizedNumeralsWithCurrency,
|
formatAmountToLocalizedNumeralsWithCurrency,
|
||||||
formatPercentToLocalizedNumerals
|
formatPercentToLocalizedNumerals
|
||||||
} = useI18n();
|
} = useI18n();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const overviewDataItemTypeSankeyChartNodeItemTypeMap: Record<TransactionCategoricalOverviewAnalysisDataItemType, SankeyChartNodeItemType> = {
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.IncomeByPrimaryCategory]: SankeyChartNodeItemType.Category,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.IncomeBySecondaryCategory]: SankeyChartNodeItemType.Category,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount]: SankeyChartNodeItemType.Account,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount]: SankeyChartNodeItemType.Account,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow]: SankeyChartNodeItemType.NetCashFlow,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.ExpenseBySecondaryCategory]: SankeyChartNodeItemType.Category,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByPrimaryCategory]: SankeyChartNodeItemType.Category
|
||||||
|
};
|
||||||
|
|
||||||
|
const overviewDataItemTypeSankeyChartNodeItemDepthMap: Record<TransactionCategoricalOverviewAnalysisDataItemType, number> = {
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.IncomeByPrimaryCategory]: SankeyChartDepth.PrimaryIncomeCategory,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.IncomeBySecondaryCategory]: SankeyChartDepth.SecondaryIncomeCategory,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount]: SankeyChartDepth.AccountForIncome,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount]: SankeyChartDepth.AccountForExpense,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow]: SankeyChartDepth.SecondaryExpenseCategory,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.ExpenseBySecondaryCategory]: SankeyChartDepth.SecondaryExpenseCategory,
|
||||||
|
[TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByPrimaryCategory]: SankeyChartDepth.PrimaryExpenseCategory
|
||||||
|
};
|
||||||
|
|
||||||
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
|
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
|
||||||
|
|
||||||
const sankeyData = computed<SankeyChartData>(() => {
|
const sankeyData = computed<SankeyChartData>(() => {
|
||||||
const primaryIncomeCategoryNodesMap: Record<string, SankeyChartNodeItem> = {};
|
const nodes: SankeyChartNodeItem[] = [];
|
||||||
const secondaryIncomeCategoryNodesMap: Record<string, SankeyChartNodeItem> = {};
|
const links: SankeyChartLinkItem[] = [];
|
||||||
const incomeAccountNodesMap: Record<string, SankeyChartNodeItem> = {};
|
|
||||||
const expenseAccountNodesMap: Record<string, SankeyChartNodeItem> = {};
|
|
||||||
const secondaryExpenseCategoryNodesMap: Record<string, SankeyChartNodeItem> = {};
|
|
||||||
const primaryExpenseCategoryNodesMap: Record<string, SankeyChartNodeItem> = {};
|
|
||||||
const linksMap: Record<string, SankeyChartLinkItem> = {};
|
|
||||||
const accountsMap: Record<string, Account> = {};
|
|
||||||
|
|
||||||
for (const item of props.items) {
|
for (const item of props.items) {
|
||||||
if (!item.primaryAccount || !item.account || !item.primaryCategory || !item.category || !item.amountInDefaultCurrency) {
|
if (item.hidden) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.account.hidden || item.primaryAccount.hidden || item.category.hidden || item.primaryCategory.hidden) {
|
const itemType = overviewDataItemTypeSankeyChartNodeItemTypeMap[item.type];
|
||||||
|
const depth = overviewDataItemTypeSankeyChartNodeItemDepthMap[item.type];
|
||||||
|
|
||||||
|
if (!itemType || itemType === SankeyChartNodeItemType.NetCashFlow || depth === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.relatedAccount && (item.relatedAccountType === TransactionRelatedAccountType.TransferFrom || item.relatedAccount.hidden || !item.relatedPrimaryAccount || item.relatedPrimaryAccount.hidden)) {
|
if (item.totalAmount === 0 && item.outflows.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const incomeAccountNameId = `income_account:${item.account.id}`;
|
const nodeItem: SankeyChartNodeItem = {
|
||||||
const expenseAccountNameId = `expense_account:${item.account.id}`;
|
dateItemType: item.type,
|
||||||
accountsMap[item.account.id] = item.account;
|
itemType: itemType,
|
||||||
|
itemId: item.id,
|
||||||
|
name: `${item.type}:${item.id}`,
|
||||||
|
displayName: item.name,
|
||||||
|
totalAmount: item.totalAmount,
|
||||||
|
percent: item.percent,
|
||||||
|
depth: depth
|
||||||
|
};
|
||||||
|
|
||||||
updateNodeItem(incomeAccountNodesMap, {
|
if (!isNumber(nodeItem.percent) && nodeItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount) {
|
||||||
itemType: SankeyChartNodeItemType.Account,
|
nodeItem.itemStyle = {
|
||||||
id: item.account.id,
|
color: '#aaa',
|
||||||
name: item.account.name,
|
opacity: 0.5
|
||||||
nameId: incomeAccountNameId,
|
};
|
||||||
displayOrders: [item.account.displayOrder],
|
|
||||||
amount: item.primaryCategory.type == CategoryType.Income ? item.amountInDefaultCurrency : 0,
|
|
||||||
depth: SankeyChartDepth.Account
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeItem(expenseAccountNodesMap, {
|
|
||||||
itemType: SankeyChartNodeItemType.Account,
|
|
||||||
id: item.account.id,
|
|
||||||
name: item.account.name,
|
|
||||||
nameId: expenseAccountNameId,
|
|
||||||
displayOrders: [item.account.displayOrder],
|
|
||||||
amount: item.primaryCategory.type == CategoryType.Expense ? item.amountInDefaultCurrency : 0,
|
|
||||||
depth: SankeyChartDepth.AccountWithTransfer
|
|
||||||
});
|
|
||||||
|
|
||||||
if (item.primaryCategory.type == CategoryType.Income) {
|
|
||||||
updateNodeItem(primaryIncomeCategoryNodesMap, {
|
|
||||||
itemType: SankeyChartNodeItemType.Category,
|
|
||||||
id: item.primaryCategory.id,
|
|
||||||
name: item.primaryCategory.name,
|
|
||||||
nameId: item.primaryCategory.id,
|
|
||||||
displayOrders: [item.primaryCategory.displayOrder],
|
|
||||||
amount: item.amountInDefaultCurrency,
|
|
||||||
depth: SankeyChartDepth.PrimaryIncomeCategory
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeItem(secondaryIncomeCategoryNodesMap, {
|
|
||||||
itemType: SankeyChartNodeItemType.Category,
|
|
||||||
id: item.category.id,
|
|
||||||
name: item.category.name,
|
|
||||||
nameId: item.category.id,
|
|
||||||
displayOrders: [item.primaryCategory.displayOrder, item.category.displayOrder],
|
|
||||||
amount: item.amountInDefaultCurrency,
|
|
||||||
depth: SankeyChartDepth.SecondaryIncomeCategory
|
|
||||||
});
|
|
||||||
|
|
||||||
updateLinkItem(linksMap, {
|
|
||||||
sourceItemType: SankeyChartNodeItemType.Category,
|
|
||||||
sourceItemId: item.primaryCategory.id,
|
|
||||||
source: item.primaryCategory.id,
|
|
||||||
sourceName: item.primaryCategory.name,
|
|
||||||
targetItemType: SankeyChartNodeItemType.Category,
|
|
||||||
targetItemId: item.category.id,
|
|
||||||
target: item.category.id,
|
|
||||||
targetName: item.category.name,
|
|
||||||
value: item.amountInDefaultCurrency
|
|
||||||
});
|
|
||||||
|
|
||||||
updateLinkItem(linksMap, {
|
|
||||||
sourceItemType: SankeyChartNodeItemType.Category,
|
|
||||||
sourceItemId: item.category.id,
|
|
||||||
source: item.category.id,
|
|
||||||
sourceName: item.category.name,
|
|
||||||
targetItemType: SankeyChartNodeItemType.Account,
|
|
||||||
targetItemId: item.account.id,
|
|
||||||
target: incomeAccountNameId,
|
|
||||||
targetName: item.account.name,
|
|
||||||
value: item.amountInDefaultCurrency
|
|
||||||
});
|
|
||||||
} else if (item.primaryCategory.type == CategoryType.Expense) {
|
|
||||||
updateNodeItem(secondaryExpenseCategoryNodesMap, {
|
|
||||||
itemType: SankeyChartNodeItemType.Category,
|
|
||||||
id: item.category.id,
|
|
||||||
name: item.category.name,
|
|
||||||
nameId: item.category.id,
|
|
||||||
displayOrders: [item.primaryCategory.displayOrder, item.category.displayOrder],
|
|
||||||
amount: item.amountInDefaultCurrency,
|
|
||||||
depth: SankeyChartDepth.SecondaryExpenseCategory
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeItem(primaryExpenseCategoryNodesMap, {
|
|
||||||
itemType: SankeyChartNodeItemType.Category,
|
|
||||||
id: item.primaryCategory.id,
|
|
||||||
name: item.primaryCategory.name,
|
|
||||||
nameId: item.primaryCategory.id,
|
|
||||||
displayOrders: [item.primaryCategory.displayOrder],
|
|
||||||
amount: item.amountInDefaultCurrency,
|
|
||||||
depth: SankeyChartDepth.PrimaryExpenseCategory
|
|
||||||
});
|
|
||||||
|
|
||||||
updateLinkItem(linksMap, {
|
|
||||||
sourceItemType: SankeyChartNodeItemType.Account,
|
|
||||||
sourceItemId: item.account.id,
|
|
||||||
source: expenseAccountNameId,
|
|
||||||
sourceName: item.account.name,
|
|
||||||
targetItemType: SankeyChartNodeItemType.Category,
|
|
||||||
targetItemId: item.category.id,
|
|
||||||
target: item.category.id,
|
|
||||||
targetName: item.category.name,
|
|
||||||
value: item.amountInDefaultCurrency
|
|
||||||
});
|
|
||||||
|
|
||||||
updateLinkItem(linksMap, {
|
|
||||||
sourceItemType: SankeyChartNodeItemType.Category,
|
|
||||||
sourceItemId: item.category.id,
|
|
||||||
source: item.category.id,
|
|
||||||
sourceName: item.category.name,
|
|
||||||
targetItemType: SankeyChartNodeItemType.Category,
|
|
||||||
targetItemId: item.primaryCategory.id,
|
|
||||||
target: item.primaryCategory.id,
|
|
||||||
targetName: item.primaryCategory.name,
|
|
||||||
value: item.amountInDefaultCurrency
|
|
||||||
});
|
|
||||||
} else if (item.primaryCategory.type == CategoryType.Transfer && item.relatedAccount) {
|
|
||||||
const relatedAccountNameId = `expense_account:${item.relatedAccount.id}`;
|
|
||||||
accountsMap[item.relatedAccount.id] = item.relatedAccount;
|
|
||||||
updateLinkItem(linksMap, {
|
|
||||||
sourceItemType: SankeyChartNodeItemType.Account,
|
|
||||||
sourceItemId: item.account.id,
|
|
||||||
source: incomeAccountNameId,
|
|
||||||
sourceName: item.account.name,
|
|
||||||
targetItemType: SankeyChartNodeItemType.Account,
|
|
||||||
targetItemId: item.relatedAccount.id,
|
|
||||||
target: relatedAccountNameId,
|
|
||||||
targetName: item.relatedAccount.name,
|
|
||||||
value: item.amountInDefaultCurrency
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const account of values(accountsMap)) {
|
if (nodeItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount) {
|
||||||
const incomeAccountNameId = `income_account:${account.id}`;
|
for (const outflowItem of item.outflows) {
|
||||||
const expenseAccountNameId = `expense_account:${account.id}`;
|
if (outflowItem.relatedItem.type !== TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let totalOutflowAmount = 0;
|
nodeItem.accountNetCashFlow = (nodeItem.accountNetCashFlow ?? 0) + outflowItem.amount;
|
||||||
let totalInflowAmount = 0;
|
|
||||||
|
|
||||||
for (const link of values(linksMap)) {
|
|
||||||
if (link.sourceItemType === SankeyChartNodeItemType.Account && link.sourceItemId === account.id) {
|
|
||||||
totalOutflowAmount += link.value;
|
|
||||||
} else if (link.targetItemType === SankeyChartNodeItemType.Account && link.targetItemId === account.id) {
|
|
||||||
totalInflowAmount += link.value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const amountDifference = totalOutflowAmount - totalInflowAmount;
|
nodes.push(nodeItem);
|
||||||
|
|
||||||
if (amountDifference > 0) {
|
const combinedOutflows: Record<string, TransactionCategoricalOverviewAnalysisDataItemOutflowItem> = {};
|
||||||
updateNodeItem(incomeAccountNodesMap, {
|
|
||||||
itemType: SankeyChartNodeItemType.Account,
|
for (const outflowItem of item.outflows) {
|
||||||
id: account.id,
|
const relatedItem = outflowItem.relatedItem;
|
||||||
name: account.name,
|
|
||||||
nameId: incomeAccountNameId,
|
if (!relatedItem) {
|
||||||
displayOrders: [account.displayOrder],
|
continue;
|
||||||
amount: amountDifference,
|
}
|
||||||
depth: SankeyChartDepth.AccountWithTransfer
|
|
||||||
});
|
if (outflowItem.relatedItem) {
|
||||||
} else if (amountDifference < 0) {
|
const key = `${item.type}:${item.id}-${outflowItem.relatedItem.type}:${outflowItem.relatedItem.id}`;
|
||||||
updateNodeItem(expenseAccountNodesMap, {
|
let combinedOutflow: TransactionCategoricalOverviewAnalysisDataItemOutflowItem | undefined = combinedOutflows[key];
|
||||||
itemType: SankeyChartNodeItemType.Account,
|
|
||||||
id: account.id,
|
if (!combinedOutflow) {
|
||||||
name: account.name,
|
combinedOutflow = {
|
||||||
nameId: expenseAccountNameId,
|
relatedItem: outflowItem.relatedItem,
|
||||||
displayOrders: [account.displayOrder],
|
amount: 0
|
||||||
amount: -amountDifference,
|
};
|
||||||
depth: SankeyChartDepth.AccountWithTransfer
|
combinedOutflows[key] = combinedOutflow;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
combinedOutflow.amount += outflowItem.amount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.abs(amountDifference) > 0) {
|
for (const outflowItem of values(combinedOutflows)) {
|
||||||
updateLinkItem(linksMap, {
|
const relatedItem = outflowItem.relatedItem;
|
||||||
sourceItemType: SankeyChartNodeItemType.Account,
|
const transferItemType = overviewDataItemTypeSankeyChartNodeItemTypeMap[relatedItem.type];
|
||||||
sourceItemId: account.id,
|
|
||||||
source: incomeAccountNameId,
|
if (!transferItemType) {
|
||||||
sourceName: account.name,
|
continue;
|
||||||
targetItemType: SankeyChartNodeItemType.Account,
|
}
|
||||||
targetItemId: account.id,
|
|
||||||
target: expenseAccountNameId,
|
const linkItem: SankeyChartLinkItem = {
|
||||||
targetName: account.name,
|
sourceItemType: itemType,
|
||||||
value: Math.abs(amountDifference)
|
sourceItemId: item.id,
|
||||||
});
|
source: `${item.type}:${item.id}`,
|
||||||
|
sourceDisplayName: item.name,
|
||||||
|
targetItemType: transferItemType,
|
||||||
|
targetItemId: relatedItem.id,
|
||||||
|
target: `${relatedItem.type}:${relatedItem.id}`,
|
||||||
|
targetDisplayName: relatedItem.name,
|
||||||
|
value: outflowItem.amount
|
||||||
|
};
|
||||||
|
|
||||||
|
links.push(linkItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodes: SankeyChartNodeItem[] = [];
|
|
||||||
const links: SankeyChartLinkItem[] = [];
|
|
||||||
addFinalSortedNodeItems(primaryIncomeCategoryNodesMap, nodes);
|
|
||||||
addFinalSortedNodeItems(secondaryIncomeCategoryNodesMap, nodes);
|
|
||||||
addFinalSortedNodeItems(incomeAccountNodesMap, nodes);
|
|
||||||
addFinalSortedNodeItems(expenseAccountNodesMap, nodes);
|
|
||||||
addFinalSortedNodeItems(secondaryExpenseCategoryNodesMap, nodes);
|
|
||||||
addFinalSortedNodeItems(primaryExpenseCategoryNodesMap, nodes);
|
|
||||||
addFinalLinkItems(linksMap, links);
|
|
||||||
|
|
||||||
const ret: SankeyChartData = {
|
const ret: SankeyChartData = {
|
||||||
nodes: nodes,
|
nodes: nodes,
|
||||||
links: links
|
links: links
|
||||||
@@ -335,15 +240,44 @@ const chartOptions = computed<object>(() => {
|
|||||||
const dataItem = params.data as SankeyChartNodeItem;
|
const dataItem = params.data as SankeyChartNodeItem;
|
||||||
const value = dataItem.totalAmount;
|
const value = dataItem.totalAmount;
|
||||||
const displayValue = formatAmountToLocalizedNumeralsWithCurrency(value, props.defaultCurrency);
|
const displayValue = formatAmountToLocalizedNumeralsWithCurrency(value, props.defaultCurrency);
|
||||||
|
let displayTypeName = '';
|
||||||
|
|
||||||
|
if (dataItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.IncomeByPrimaryCategory) {
|
||||||
|
displayTypeName = tt('Income By Primary Category');
|
||||||
|
} else if (dataItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.IncomeBySecondaryCategory) {
|
||||||
|
displayTypeName = tt('Income By Secondary Category');
|
||||||
|
} else if (dataItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount) {
|
||||||
|
displayTypeName = tt('Income By Account');
|
||||||
|
} else if (dataItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount) {
|
||||||
|
displayTypeName = tt('Expense By Account');
|
||||||
|
} else if (dataItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow) {
|
||||||
|
displayTypeName = tt('Net Cash Flow');
|
||||||
|
} else if (dataItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.ExpenseBySecondaryCategory) {
|
||||||
|
displayTypeName = tt('Expense By Secondary Category');
|
||||||
|
} else if (dataItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByPrimaryCategory) {
|
||||||
|
displayTypeName = tt('Expense By Primary Category');
|
||||||
|
}
|
||||||
|
|
||||||
let tooltip = `<div><span>${dataItem.displayName}</span>`;
|
let tooltip = `<div><span>${dataItem.displayName}</span>`;
|
||||||
|
|
||||||
if (isNumber(dataItem.percent)) {
|
if (displayTypeName && (dataItem.dateItemType !== TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount || isNumber(dataItem.percent))) {
|
||||||
|
tooltip = `<div class="mb-1">${displayTypeName}</div>` + tooltip;
|
||||||
|
} else if (dataItem.dateItemType === TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount) {
|
||||||
|
tooltip = `<div class="mb-1">${tt('Account Balance')}</div>` + tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNumber(dataItem.percent) && dataItem.percent > 0) {
|
||||||
const displayPercent = formatPercentToLocalizedNumerals(dataItem.percent, 2, '<0.01');
|
const displayPercent = formatPercentToLocalizedNumerals(dataItem.percent, 2, '<0.01');
|
||||||
tooltip += `<span class="ms-1" style="float: inline-end">(${displayPercent})</span>`;
|
tooltip += `<span class="ms-1" style="float: inline-end">(${displayPercent})</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltip += `<span class="ms-5" style="float: inline-end">${displayValue}</span></div>`;
|
tooltip += `<span class="ms-5" style="float: inline-end">${displayValue}</span></div>`;
|
||||||
|
|
||||||
|
if (isNumber(dataItem.accountNetCashFlow) && dataItem.accountNetCashFlow !== 0) {
|
||||||
|
const displayAccountNetCashFlow = formatAmountToLocalizedNumeralsWithCurrency(dataItem.accountNetCashFlow, props.defaultCurrency);
|
||||||
|
tooltip += `<div class="mt-1"><span>${tt('Net Cash Flow')}</span><span class="ms-5" style="float: inline-end">${displayAccountNetCashFlow}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
return tooltip;
|
return tooltip;
|
||||||
} else if (params.dataType === 'edge') {
|
} else if (params.dataType === 'edge') {
|
||||||
const dataItem = params.data as SankeyChartLinkItem;
|
const dataItem = params.data as SankeyChartLinkItem;
|
||||||
@@ -359,7 +293,7 @@ const chartOptions = computed<object>(() => {
|
|||||||
{
|
{
|
||||||
type: 'sankey',
|
type: 'sankey',
|
||||||
left: 10,
|
left: 10,
|
||||||
top: 10,
|
top: 0,
|
||||||
bottom: 10,
|
bottom: 10,
|
||||||
roam: true,
|
roam: true,
|
||||||
layoutIterations: 0,
|
layoutIterations: 0,
|
||||||
@@ -393,7 +327,7 @@ const chartOptions = computed<object>(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
depth: SankeyChartDepth.Account,
|
depth: SankeyChartDepth.AccountForIncome,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: '#c07d43',
|
color: '#c07d43',
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
@@ -404,7 +338,7 @@ const chartOptions = computed<object>(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
depth: SankeyChartDepth.AccountWithTransfer,
|
depth: SankeyChartDepth.AccountForExpense,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: '#c07d43',
|
color: '#c07d43',
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
@@ -448,78 +382,6 @@ const chartOptions = computed<object>(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateNodeItem(nodesMap: Record<string, SankeyChartNodeItem>, { itemType, id, name, nameId, displayOrders, amount, depth }: { itemType: SankeyChartNodeItemType, id: string, name: string, nameId: string, displayOrders: number[], amount: number, depth: number }) {
|
|
||||||
const node: SankeyChartNodeItem | undefined = nodesMap[nameId];
|
|
||||||
|
|
||||||
if (!node) {
|
|
||||||
nodesMap[nameId] = {
|
|
||||||
itemType: itemType,
|
|
||||||
itemId: id,
|
|
||||||
name: name,
|
|
||||||
nameId: nameId,
|
|
||||||
displayName: name,
|
|
||||||
displayOrders: displayOrders,
|
|
||||||
totalAmount: amount,
|
|
||||||
depth: depth
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
node.totalAmount += amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLinkItem(linksMap: Record<string, SankeyChartLinkItem>, { sourceItemType, sourceItemId, source, sourceName, targetItemType, targetItemId, target, targetName, value }: { sourceItemType: SankeyChartNodeItemType, sourceItemId: string, source: string, sourceName: string, targetItemType: SankeyChartNodeItemType, targetItemId: string, target: string, targetName: string, value: number }) {
|
|
||||||
const key = `${source}:${target}`;
|
|
||||||
const link: SankeyChartLinkItem | undefined = linksMap[key];
|
|
||||||
|
|
||||||
if (!link) {
|
|
||||||
linksMap[key] = {
|
|
||||||
sourceItemType: sourceItemType,
|
|
||||||
sourceItemId: sourceItemId,
|
|
||||||
source: source,
|
|
||||||
sourceDisplayName: sourceName,
|
|
||||||
targetItemType: targetItemType,
|
|
||||||
targetItemId: targetItemId,
|
|
||||||
target: target,
|
|
||||||
targetDisplayName: targetName,
|
|
||||||
value: value
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
link.value += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFinalSortedNodeItems(nodesMap: Record<string, SankeyChartNodeItem>, allNodesArray: SankeyChartNodeItem[]): void {
|
|
||||||
const nodesArray: SankeyChartNodeItem[] = [];
|
|
||||||
let totalAmount = 0;
|
|
||||||
|
|
||||||
for (const node of values(nodesMap)) {
|
|
||||||
if (node.totalAmount > 0) {
|
|
||||||
totalAmount += node.totalAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodesArray.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
sortStatisticsItems(nodesArray, props.sortingType);
|
|
||||||
|
|
||||||
for (const node of nodesArray) {
|
|
||||||
node.name = node.nameId;
|
|
||||||
node.percent = node.totalAmount > 0 && totalAmount > 0 ? (node.totalAmount / totalAmount) * 100 : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
allNodesArray.push(...nodesArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFinalLinkItems(linksMap: Record<string, SankeyChartLinkItem>, allLinksArray: SankeyChartLinkItem[]): void {
|
|
||||||
const linksArray: SankeyChartLinkItem[] = [];
|
|
||||||
|
|
||||||
for (const link of values(linksMap)) {
|
|
||||||
linksArray.push(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
allLinksArray.push(...linksArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickItem(e: ECElementEvent): void {
|
function clickItem(e: ECElementEvent): void {
|
||||||
if (!props.enableClickItem || e.componentType !== 'series' || e.seriesType !=='sankey') {
|
if (!props.enableClickItem || e.componentType !== 'series' || e.seriesType !=='sankey') {
|
||||||
return;
|
return;
|
||||||
@@ -531,13 +393,22 @@ function clickItem(e: ECElementEvent): void {
|
|||||||
|
|
||||||
if (e.dataType === 'node') {
|
if (e.dataType === 'node') {
|
||||||
const dataItem = e.data as SankeyChartNodeItem;
|
const dataItem = e.data as SankeyChartNodeItem;
|
||||||
|
|
||||||
|
if (dataItem.itemType === SankeyChartNodeItemType.NetCashFlow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
emit('click', dataItem.itemType, dataItem.itemId);
|
emit('click', dataItem.itemType, dataItem.itemId);
|
||||||
} else if (e.dataType === 'edge') {
|
} else if (e.dataType === 'edge') {
|
||||||
const dataItem = e.data as SankeyChartLinkItem;
|
const dataItem = e.data as SankeyChartLinkItem;
|
||||||
|
|
||||||
|
if (dataItem.sourceItemType === SankeyChartNodeItemType.NetCashFlow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (dataItem.sourceItemType === dataItem.targetItemType && dataItem.sourceItemId === dataItem.targetItemId) {
|
if (dataItem.sourceItemType === dataItem.targetItemType && dataItem.sourceItemId === dataItem.targetItemId) {
|
||||||
emit('click', dataItem.sourceItemType, dataItem.sourceItemId);
|
emit('click', dataItem.sourceItemType, dataItem.sourceItemId);
|
||||||
} else {
|
} else if (dataItem.targetItemType !== SankeyChartNodeItemType.NetCashFlow) {
|
||||||
emit('click', dataItem.sourceItemType, dataItem.sourceItemId, dataItem.targetItemType, dataItem.targetItemId);
|
emit('click', dataItem.sourceItemType, dataItem.sourceItemId, dataItem.targetItemType, dataItem.targetItemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -762,6 +762,41 @@ export interface TransactionStatisticDataItemBase extends SortableTransactionSta
|
|||||||
readonly totalAmount: number;
|
readonly totalAmount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TransactionCategoricalOverviewAnalysisData {
|
||||||
|
readonly totalIncome: number;
|
||||||
|
readonly totalExpense: number;
|
||||||
|
readonly items: TransactionCategoricalOverviewAnalysisDataItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TransactionCategoricalOverviewAnalysisDataItemType {
|
||||||
|
IncomeByPrimaryCategory = 'incomeByPrimaryCategory',
|
||||||
|
IncomeBySecondaryCategory = 'incomeBySecondaryCategory',
|
||||||
|
IncomeByAccount = 'incomeByAccount',
|
||||||
|
ExpenseByAccount = 'expenseByAccount',
|
||||||
|
NetCashFlow = 'netCashFlow',
|
||||||
|
ExpenseBySecondaryCategory = 'expenseBySecondaryCategory',
|
||||||
|
ExpenseByPrimaryCategory = 'expenseByPrimaryCategory'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionCategoricalOverviewAnalysisDataItem extends SortableTransactionStatisticDataItem {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly type: TransactionCategoricalOverviewAnalysisDataItemType;
|
||||||
|
readonly displayOrders: number[];
|
||||||
|
readonly hidden: boolean;
|
||||||
|
readonly inflows: TransactionCategoricalOverviewAnalysisDataItemOutflowItem[];
|
||||||
|
readonly outflows: TransactionCategoricalOverviewAnalysisDataItemOutflowItem[];
|
||||||
|
totalAmount: number;
|
||||||
|
totalNonNegativeAmount: number;
|
||||||
|
includeInPercent?: boolean;
|
||||||
|
percent?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionCategoricalOverviewAnalysisDataItemOutflowItem {
|
||||||
|
readonly relatedItem: TransactionCategoricalOverviewAnalysisDataItem;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TransactionCategoricalAnalysisData {
|
export interface TransactionCategoricalAnalysisData {
|
||||||
readonly totalAmount: number;
|
readonly totalAmount: number;
|
||||||
readonly items: TransactionCategoricalAnalysisDataItem[];
|
readonly items: TransactionCategoricalAnalysisDataItem[];
|
||||||
|
|||||||
+303
-24
@@ -28,20 +28,23 @@ import {
|
|||||||
import { DEFAULT_ACCOUNT_ICON, DEFAULT_CATEGORY_ICON } from '@/consts/icon.ts';
|
import { DEFAULT_ACCOUNT_ICON, DEFAULT_CATEGORY_ICON } from '@/consts/icon.ts';
|
||||||
import { DEFAULT_ACCOUNT_COLOR, DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
|
import { DEFAULT_ACCOUNT_COLOR, DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
|
||||||
|
|
||||||
import type {
|
import {
|
||||||
TransactionStatisticResponse,
|
type TransactionStatisticResponse,
|
||||||
TransactionStatisticResponseItem,
|
type TransactionStatisticResponseItem,
|
||||||
TransactionStatisticTrendsResponseItem,
|
type TransactionStatisticTrendsResponseItem,
|
||||||
TransactionStatisticResponseItemWithInfo,
|
type TransactionStatisticResponseItemWithInfo,
|
||||||
TransactionStatisticResponseWithInfo,
|
type TransactionStatisticResponseWithInfo,
|
||||||
TransactionStatisticTrendsResponseItemWithInfo,
|
type TransactionStatisticTrendsResponseItemWithInfo,
|
||||||
TransactionStatisticDataItemType,
|
type TransactionStatisticDataItemType,
|
||||||
TransactionStatisticDataItemBase,
|
type TransactionStatisticDataItemBase,
|
||||||
TransactionCategoricalAnalysisData,
|
type TransactionCategoricalOverviewAnalysisData,
|
||||||
TransactionCategoricalAnalysisDataItem,
|
type TransactionCategoricalOverviewAnalysisDataItem,
|
||||||
TransactionTrendsAnalysisData,
|
type TransactionCategoricalAnalysisData,
|
||||||
TransactionTrendsAnalysisDataItem,
|
type TransactionCategoricalAnalysisDataItem,
|
||||||
TransactionTrendsAnalysisDataAmount
|
type TransactionTrendsAnalysisData,
|
||||||
|
type TransactionTrendsAnalysisDataItem,
|
||||||
|
type TransactionTrendsAnalysisDataAmount,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType
|
||||||
} from '@/models/transaction.ts';
|
} from '@/models/transaction.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -203,36 +206,276 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
|||||||
return getCategoryTotalAmountItems(transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items, transactionStatisticsFilter.value);
|
return getCategoryTotalAmountItems(transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items, transactionStatisticsFilter.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const categoricalAllAnalysisData = computed<TransactionStatisticResponseWithInfo | null>(() => {
|
const categoricalOverviewAnalysisData = computed<TransactionCategoricalOverviewAnalysisData | null>(() => {
|
||||||
if (!transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value || !transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items) {
|
if (!transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value || !transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allDataItems: TransactionStatisticResponseItemWithInfo[] = [];
|
const allDataItemsMap: Record<string, TransactionCategoricalOverviewAnalysisDataItem> = {};
|
||||||
|
const allIncomeByPrimaryCategoryDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
||||||
|
const allIncomeBySecondaryCategoryDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
||||||
|
const allIncomeByAccountDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
||||||
|
const allExpenseByAccountDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
||||||
|
const allExpenseBySecondaryCategoryDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
||||||
|
const allExpenseByPrimaryCategoryDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
||||||
|
const allOpeningBalanceDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
||||||
|
const allNetCashFlowDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
||||||
|
|
||||||
|
let totalIncome: number = 0;
|
||||||
|
let totalExpense: number = 0;
|
||||||
|
|
||||||
for (const item of transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items) {
|
for (const item of transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items) {
|
||||||
if (!item.primaryAccount || !item.account || !item.primaryCategory || !item.category) {
|
if (!item.primaryAccount || !item.account || !item.primaryCategory || !item.category) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.relatedAccount && item.relatedAccountType === TransactionRelatedAccountType.TransferFrom) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNumber(item.amountInDefaultCurrency)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (transactionStatisticsFilter.value.filterAccountIds && transactionStatisticsFilter.value.filterAccountIds[item.account.id]) {
|
if (transactionStatisticsFilter.value.filterAccountIds && transactionStatisticsFilter.value.filterAccountIds[item.account.id]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionStatisticsFilter.value.filterAccountIds && item.relatedAccount && transactionStatisticsFilter.value.filterAccountIds[item.relatedAccount.id]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transactionStatisticsFilter.value.filterCategoryIds && transactionStatisticsFilter.value.filterCategoryIds[item.category.id]) {
|
if (transactionStatisticsFilter.value.filterCategoryIds && transactionStatisticsFilter.value.filterCategoryIds[item.category.id]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
allDataItems.push(item);
|
if (item.category.type === CategoryType.Income) {
|
||||||
|
totalIncome += item.amountInDefaultCurrency;
|
||||||
|
} else if (item.category.type === CategoryType.Expense) {
|
||||||
|
totalExpense += item.amountInDefaultCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
const incomeByAccountKey = `${TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount}:${item.account.id}`;
|
||||||
|
const expenseByAccountKey = `${TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount}:${item.account.id}`;
|
||||||
|
let incomeByAccountItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[incomeByAccountKey];
|
||||||
|
let expenseByAccountItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[expenseByAccountKey];
|
||||||
|
|
||||||
|
if (!incomeByAccountItem) {
|
||||||
|
incomeByAccountItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
||||||
|
item.account.id,
|
||||||
|
item.account.name,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount,
|
||||||
|
[item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
|
||||||
|
item.primaryAccount.hidden || item.account.hidden);
|
||||||
|
allDataItemsMap[incomeByAccountKey] = incomeByAccountItem;
|
||||||
|
allIncomeByAccountDataItems.push(incomeByAccountItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expenseByAccountItem) {
|
||||||
|
expenseByAccountItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
||||||
|
item.account.id,
|
||||||
|
item.account.name,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount,
|
||||||
|
[item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
|
||||||
|
item.primaryAccount.hidden || item.account.hidden);
|
||||||
|
allDataItemsMap[expenseByAccountKey] = expenseByAccountItem;
|
||||||
|
allExpenseByAccountDataItems.push(expenseByAccountItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.category.type === CategoryType.Income) {
|
||||||
|
const primaryCategoryItemKey = `${TransactionCategoricalOverviewAnalysisDataItemType.IncomeByPrimaryCategory}:${item.primaryCategory.id}`;
|
||||||
|
const secondaryCategoryItemKey = `${TransactionCategoricalOverviewAnalysisDataItemType.IncomeBySecondaryCategory}:${item.category.id}`;
|
||||||
|
|
||||||
|
let primaryCategoryDataItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[primaryCategoryItemKey];
|
||||||
|
let secondaryCategoryDataItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[secondaryCategoryItemKey];
|
||||||
|
|
||||||
|
if (!primaryCategoryDataItem) {
|
||||||
|
primaryCategoryDataItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
||||||
|
item.primaryCategory.id,
|
||||||
|
item.primaryCategory.name,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.IncomeByPrimaryCategory,
|
||||||
|
[item.primaryCategory.displayOrder],
|
||||||
|
item.primaryCategory.hidden);
|
||||||
|
allDataItemsMap[primaryCategoryItemKey] = primaryCategoryDataItem;
|
||||||
|
allIncomeByPrimaryCategoryDataItems.push(primaryCategoryDataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secondaryCategoryDataItem) {
|
||||||
|
secondaryCategoryDataItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
||||||
|
item.category.id,
|
||||||
|
item.category.name,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.IncomeBySecondaryCategory,
|
||||||
|
[item.primaryCategory.displayOrder, item.category.displayOrder],
|
||||||
|
item.primaryCategory.hidden || item.category.hidden);
|
||||||
|
allDataItemsMap[secondaryCategoryItemKey] = secondaryCategoryDataItem;
|
||||||
|
allIncomeBySecondaryCategoryDataItems.push(secondaryCategoryDataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryCategoryDataItem.totalAmount += item.amountInDefaultCurrency;
|
||||||
|
primaryCategoryDataItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
||||||
|
primaryCategoryDataItem.includeInPercent = true;
|
||||||
|
primaryCategoryDataItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: secondaryCategoryDataItem });
|
||||||
|
|
||||||
|
secondaryCategoryDataItem.totalAmount += item.amountInDefaultCurrency;
|
||||||
|
secondaryCategoryDataItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
||||||
|
secondaryCategoryDataItem.includeInPercent = true;
|
||||||
|
secondaryCategoryDataItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: primaryCategoryDataItem });
|
||||||
|
secondaryCategoryDataItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: incomeByAccountItem });
|
||||||
|
|
||||||
|
incomeByAccountItem.totalAmount += item.amountInDefaultCurrency;
|
||||||
|
incomeByAccountItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
||||||
|
incomeByAccountItem.includeInPercent = true;
|
||||||
|
incomeByAccountItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: secondaryCategoryDataItem });
|
||||||
|
} else if (item.category.type === CategoryType.Expense) {
|
||||||
|
const primaryCategoryItemKey = `${TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByPrimaryCategory}:${item.primaryCategory.id}`;
|
||||||
|
const secondaryCategoryItemKey = `${TransactionCategoricalOverviewAnalysisDataItemType.ExpenseBySecondaryCategory}:${item.category.id}`;
|
||||||
|
|
||||||
|
let primaryCategoryDataItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[primaryCategoryItemKey];
|
||||||
|
let secondaryCategoryDataItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[secondaryCategoryItemKey];
|
||||||
|
|
||||||
|
if (!primaryCategoryDataItem) {
|
||||||
|
primaryCategoryDataItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
||||||
|
item.primaryCategory.id,
|
||||||
|
item.primaryCategory.name,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByPrimaryCategory,
|
||||||
|
[item.primaryCategory.displayOrder],
|
||||||
|
item.primaryCategory.hidden);
|
||||||
|
allDataItemsMap[primaryCategoryItemKey] = primaryCategoryDataItem;
|
||||||
|
allExpenseByPrimaryCategoryDataItems.push(primaryCategoryDataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secondaryCategoryDataItem) {
|
||||||
|
secondaryCategoryDataItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
||||||
|
item.category.id,
|
||||||
|
item.category.name,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.ExpenseBySecondaryCategory,
|
||||||
|
[item.primaryCategory.displayOrder, item.category.displayOrder],
|
||||||
|
item.primaryCategory.hidden || item.category.hidden);
|
||||||
|
allDataItemsMap[secondaryCategoryItemKey] = secondaryCategoryDataItem;
|
||||||
|
allExpenseBySecondaryCategoryDataItems.push(secondaryCategoryDataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
expenseByAccountItem.totalAmount += item.amountInDefaultCurrency;
|
||||||
|
expenseByAccountItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
||||||
|
expenseByAccountItem.includeInPercent = true;
|
||||||
|
expenseByAccountItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: secondaryCategoryDataItem });
|
||||||
|
|
||||||
|
secondaryCategoryDataItem.totalAmount += item.amountInDefaultCurrency;
|
||||||
|
secondaryCategoryDataItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0
|
||||||
|
secondaryCategoryDataItem.includeInPercent = true;
|
||||||
|
secondaryCategoryDataItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: expenseByAccountItem });
|
||||||
|
secondaryCategoryDataItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: primaryCategoryDataItem });
|
||||||
|
|
||||||
|
primaryCategoryDataItem.totalAmount += item.amountInDefaultCurrency;
|
||||||
|
primaryCategoryDataItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
||||||
|
primaryCategoryDataItem.includeInPercent = true;
|
||||||
|
primaryCategoryDataItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: secondaryCategoryDataItem });
|
||||||
|
} else if (item.category.type === CategoryType.Transfer && item.relatedPrimaryAccount && item.relatedAccount) {
|
||||||
|
const transferToAccountKey = `${TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount}:${item.relatedAccount.id}`;
|
||||||
|
let transferToAccountItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[transferToAccountKey];
|
||||||
|
|
||||||
|
if (!transferToAccountItem) {
|
||||||
|
transferToAccountItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
||||||
|
item.relatedAccount.id,
|
||||||
|
item.relatedAccount.name,
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount,
|
||||||
|
[item.relatedPrimaryAccount.category, item.relatedPrimaryAccount.displayOrder, item.relatedAccount.displayOrder],
|
||||||
|
item.relatedPrimaryAccount.hidden || item.relatedAccount.hidden);
|
||||||
|
allDataItemsMap[transferToAccountKey] = transferToAccountItem;
|
||||||
|
allExpenseByAccountDataItems.push(transferToAccountItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
incomeByAccountItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: transferToAccountItem });
|
||||||
|
transferToAccountItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: incomeByAccountItem });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortCategoricalOverviewAnalysisDataItems(allIncomeByPrimaryCategoryDataItems, transactionStatisticsFilter.value);
|
||||||
|
sortCategoricalOverviewAnalysisDataItems(allIncomeBySecondaryCategoryDataItems, transactionStatisticsFilter.value);
|
||||||
|
sortCategoricalOverviewAnalysisDataItems(allIncomeByAccountDataItems, transactionStatisticsFilter.value);
|
||||||
|
sortCategoricalOverviewAnalysisDataItems(allExpenseByAccountDataItems, transactionStatisticsFilter.value);
|
||||||
|
sortCategoricalOverviewAnalysisDataItems(allExpenseBySecondaryCategoryDataItems, transactionStatisticsFilter.value);
|
||||||
|
sortCategoricalOverviewAnalysisDataItems(allExpenseByPrimaryCategoryDataItems, transactionStatisticsFilter.value);
|
||||||
|
|
||||||
|
for (const item of allExpenseByAccountDataItems) {
|
||||||
|
const incomeByAccountKey = `${TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount}:${item.id}`;
|
||||||
|
const incomeByAccountItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[incomeByAccountKey];
|
||||||
|
|
||||||
|
let accountTotalInflowsAmount: number = 0;
|
||||||
|
let accountTotalIncomeAmount: number = 0;
|
||||||
|
let accountTotalTransferAmount: number = 0;
|
||||||
|
let accountTotalOutflowsAmount: number = 0;
|
||||||
|
|
||||||
|
if (incomeByAccountItem) {
|
||||||
|
for (const inflow of incomeByAccountItem.inflows) {
|
||||||
|
accountTotalInflowsAmount += inflow.amount;
|
||||||
|
accountTotalIncomeAmount += inflow.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const outflow of incomeByAccountItem.outflows) {
|
||||||
|
accountTotalTransferAmount += outflow.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const inflow of item.inflows) {
|
||||||
|
if (inflow.relatedItem.type === item.type && inflow.relatedItem.id === item.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
accountTotalInflowsAmount += inflow.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const outflow of item.outflows) {
|
||||||
|
accountTotalOutflowsAmount += outflow.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountBalance: number = accountTotalIncomeAmount - accountTotalTransferAmount - accountTotalOutflowsAmount;
|
||||||
|
const accountNetCashFlow: number = accountTotalInflowsAmount - accountTotalTransferAmount - accountTotalOutflowsAmount;
|
||||||
|
|
||||||
|
if (incomeByAccountItem && accountsStore.allAccountsMap[item.id]?.isAsset) {
|
||||||
|
if (accountBalance > 0) { // has positive balance, transfer the amount from income account to expense account
|
||||||
|
incomeByAccountItem.outflows.push({ amount: accountBalance + accountTotalOutflowsAmount, relatedItem: item });
|
||||||
|
item.inflows.push({ amount: accountBalance + accountTotalOutflowsAmount, relatedItem: incomeByAccountItem });
|
||||||
|
} else if (accountNetCashFlow < 0) { // has negative net cash flow, add the difference to income account
|
||||||
|
incomeByAccountItem.totalAmount += -accountNetCashFlow;
|
||||||
|
incomeByAccountItem.totalNonNegativeAmount += -accountNetCashFlow > 0 ? -accountNetCashFlow : 0;
|
||||||
|
incomeByAccountItem.outflows.push({ amount: -accountNetCashFlow, relatedItem: item });
|
||||||
|
item.inflows.push({ amount: -accountNetCashFlow, relatedItem: incomeByAccountItem });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountNetCashFlow > 0) {
|
||||||
|
let netCashFlowItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow];
|
||||||
|
|
||||||
|
if (!netCashFlowItem) {
|
||||||
|
netCashFlowItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow,
|
||||||
|
'Net Cash Flow',
|
||||||
|
TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow,
|
||||||
|
[Number.MAX_SAFE_INTEGER],
|
||||||
|
false);
|
||||||
|
allDataItemsMap[TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow] = netCashFlowItem;
|
||||||
|
allNetCashFlowDataItems.push(netCashFlowItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.outflows.push({ amount: accountNetCashFlow, relatedItem: netCashFlowItem });
|
||||||
|
|
||||||
|
netCashFlowItem.totalAmount += accountNetCashFlow;
|
||||||
|
netCashFlowItem.totalNonNegativeAmount += accountNetCashFlow > 0 ? accountNetCashFlow : 0;
|
||||||
|
netCashFlowItem.inflows.push({ amount: accountNetCashFlow, relatedItem: item });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [
|
||||||
|
...allIncomeByPrimaryCategoryDataItems,
|
||||||
|
...allIncomeBySecondaryCategoryDataItems,
|
||||||
|
...allIncomeByAccountDataItems,
|
||||||
|
...allOpeningBalanceDataItems,
|
||||||
|
...allExpenseByAccountDataItems,
|
||||||
|
...allExpenseBySecondaryCategoryDataItems,
|
||||||
|
...allNetCashFlowDataItems,
|
||||||
|
...allExpenseByPrimaryCategoryDataItems
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startTime: transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.startTime,
|
totalIncome: totalIncome,
|
||||||
endTime: transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.endTime,
|
totalExpense: totalExpense,
|
||||||
items: allDataItems
|
items: allDataItems
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -466,6 +709,42 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
|||||||
return trendsData;
|
return trendsData;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createNewTransactionCategoricalOverviewAnalysisDataItem(id: string, name: string, type: TransactionCategoricalOverviewAnalysisDataItemType, displayOrders: number[], hidden: boolean): TransactionCategoricalOverviewAnalysisDataItem {
|
||||||
|
const dataItem: TransactionCategoricalOverviewAnalysisDataItem = {
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
type: type,
|
||||||
|
displayOrders: displayOrders,
|
||||||
|
hidden: hidden,
|
||||||
|
inflows: [],
|
||||||
|
outflows: [],
|
||||||
|
totalAmount: 0,
|
||||||
|
totalNonNegativeAmount: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
return dataItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortCategoricalOverviewAnalysisDataItems(items: TransactionCategoricalOverviewAnalysisDataItem[], transactionStatisticsFilter: TransactionStatisticsFilter): void {
|
||||||
|
let totalNonNegativeAmount: number = 0;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
totalNonNegativeAmount += item.totalNonNegativeAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalNonNegativeAmount > 0) {
|
||||||
|
for (const item of items) {
|
||||||
|
if (!item.includeInPercent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.percent = item.totalAmount * 100 / totalNonNegativeAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortStatisticsItems(items, transactionStatisticsFilter.sortingType);
|
||||||
|
}
|
||||||
|
|
||||||
function assembleAccountAndCategoryInfo(items: TransactionStatisticResponseItem[]): TransactionStatisticResponseItemWithInfo[] {
|
function assembleAccountAndCategoryInfo(items: TransactionStatisticResponseItem[]): TransactionStatisticResponseItemWithInfo[] {
|
||||||
const finalItems: TransactionStatisticResponseItemWithInfo[] = [];
|
const finalItems: TransactionStatisticResponseItemWithInfo[] = [];
|
||||||
const defaultCurrency = userStore.currentUserDefaultCurrency;
|
const defaultCurrency = userStore.currentUserDefaultCurrency;
|
||||||
@@ -1289,7 +1568,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
|||||||
transactionStatisticsStateInvalid,
|
transactionStatisticsStateInvalid,
|
||||||
// computed states
|
// computed states
|
||||||
categoricalAnalysisChartDataCategory,
|
categoricalAnalysisChartDataCategory,
|
||||||
categoricalAllAnalysisData,
|
categoricalOverviewAnalysisData,
|
||||||
categoricalAnalysisData,
|
categoricalAnalysisData,
|
||||||
trendsAnalysisData,
|
trendsAnalysisData,
|
||||||
// functions
|
// functions
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import { DISPLAY_HIDDEN_AMOUNT } from '@/consts/numeral.ts';
|
import { DISPLAY_HIDDEN_AMOUNT } from '@/consts/numeral.ts';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
TransactionStatisticResponseWithInfo,
|
TransactionCategoricalOverviewAnalysisData,
|
||||||
TransactionCategoricalAnalysisData,
|
TransactionCategoricalAnalysisData,
|
||||||
TransactionCategoricalAnalysisDataItem,
|
TransactionCategoricalAnalysisDataItem,
|
||||||
TransactionTrendsAnalysisData
|
TransactionTrendsAnalysisData
|
||||||
@@ -249,8 +249,8 @@ export function useStatisticsTransactionPageBase() {
|
|||||||
query.value.chartDataType === ChartDataType.NetIncome.type;
|
query.value.chartDataType === ChartDataType.NetIncome.type;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const categoricalOverviewAnalysisData = computed<TransactionCategoricalOverviewAnalysisData | null>(() => statisticsStore.categoricalOverviewAnalysisData);
|
||||||
const categoricalAnalysisData = computed<TransactionCategoricalAnalysisData>(() => statisticsStore.categoricalAnalysisData);
|
const categoricalAnalysisData = computed<TransactionCategoricalAnalysisData>(() => statisticsStore.categoricalAnalysisData);
|
||||||
const categoricalAllAnalysisData = computed<TransactionStatisticResponseWithInfo | null>(() => statisticsStore.categoricalAllAnalysisData);
|
|
||||||
const trendsAnalysisData = computed<TransactionTrendsAnalysisData | null>(() => statisticsStore.trendsAnalysisData);
|
const trendsAnalysisData = computed<TransactionTrendsAnalysisData | null>(() => statisticsStore.trendsAnalysisData);
|
||||||
|
|
||||||
function canShowCustomDateRange(dateRangeType: number): boolean {
|
function canShowCustomDateRange(dateRangeType: number): boolean {
|
||||||
@@ -323,8 +323,8 @@ export function useStatisticsTransactionPageBase() {
|
|||||||
showTotalAmountInTrendsChart,
|
showTotalAmountInTrendsChart,
|
||||||
showStackedInTrendsChart,
|
showStackedInTrendsChart,
|
||||||
translateNameInTrendsChart,
|
translateNameInTrendsChart,
|
||||||
|
categoricalOverviewAnalysisData,
|
||||||
categoricalAnalysisData,
|
categoricalAnalysisData,
|
||||||
categoricalAllAnalysisData,
|
|
||||||
trendsAnalysisData,
|
trendsAnalysisData,
|
||||||
// functions
|
// functions
|
||||||
canShowCustomDateRange,
|
canShowCustomDateRange,
|
||||||
|
|||||||
@@ -172,7 +172,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-card-text class="statistics-overview-title pt-0" :class="{ 'disabled': loading }"
|
<v-card-text class="statistics-overview-title pt-0" :class="{ 'disabled': loading }"
|
||||||
v-if="queryAnalysisType === StatisticsAnalysisType.CategoricalAnalysis && !isQuerySpecialChartType && (initing || (categoricalAnalysisData && categoricalAnalysisData.items && categoricalAnalysisData.items.length))">
|
v-if="queryAnalysisType === StatisticsAnalysisType.CategoricalAnalysis && isQuerySpecialChartType && queryChartDataType === ChartDataType.Overview.type && (initing || categoricalOverviewAnalysisData && categoricalOverviewAnalysisData.items && categoricalOverviewAnalysisData.items.length)">
|
||||||
|
<span class="statistics-subtitle">{{ tt('Total Income') }}</span>
|
||||||
|
<span class="statistics-overview-amount ms-3 text-income"
|
||||||
|
v-if="!initing && categoricalOverviewAnalysisData && categoricalOverviewAnalysisData.items && categoricalOverviewAnalysisData.items.length">
|
||||||
|
{{ getDisplayAmount(categoricalOverviewAnalysisData.totalIncome, defaultCurrency) }}
|
||||||
|
</span>
|
||||||
|
<v-skeleton-loader class="skeleton-no-margin ms-3 mb-2"
|
||||||
|
width="120px" type="text" :loading="true"
|
||||||
|
v-else-if="initing"></v-skeleton-loader>
|
||||||
|
<span class="statistics-subtitle ms-3">{{ tt('Total Expense') }}</span>
|
||||||
|
<span class="statistics-overview-amount ms-3 text-expense"
|
||||||
|
v-if="!initing && categoricalOverviewAnalysisData && categoricalOverviewAnalysisData.items && categoricalOverviewAnalysisData.items.length">
|
||||||
|
{{ getDisplayAmount(categoricalOverviewAnalysisData.totalExpense, defaultCurrency) }}
|
||||||
|
</span>
|
||||||
|
<v-skeleton-loader class="skeleton-no-margin ms-3 mb-2"
|
||||||
|
width="120px" type="text" :loading="true"
|
||||||
|
v-else-if="initing"></v-skeleton-loader>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-text class="statistics-overview-title pt-0" :class="{ 'disabled': loading }"
|
||||||
|
v-else-if="queryAnalysisType === StatisticsAnalysisType.CategoricalAnalysis && !isQuerySpecialChartType && (initing || (categoricalAnalysisData && categoricalAnalysisData.items && categoricalAnalysisData.items.length))">
|
||||||
<span class="statistics-subtitle">{{ totalAmountName }}</span>
|
<span class="statistics-subtitle">{{ totalAmountName }}</span>
|
||||||
<span class="statistics-overview-amount ms-3"
|
<span class="statistics-overview-amount ms-3"
|
||||||
:class="statisticsTextColor"
|
:class="statisticsTextColor"
|
||||||
@@ -185,8 +205,11 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-text class="statistics-overview-title pt-0"
|
<v-card-text class="statistics-overview-title pt-0"
|
||||||
v-else-if="!initing && ((queryAnalysisType === StatisticsAnalysisType.CategoricalAnalysis && !isQuerySpecialChartType && (!categoricalAnalysisData || !categoricalAnalysisData.items || !categoricalAnalysisData.items.length))
|
v-else-if="!initing && (
|
||||||
|| (queryAnalysisType === StatisticsAnalysisType.TrendAnalysis && (!trendsAnalysisData || !trendsAnalysisData.items || !trendsAnalysisData.items.length)))">
|
(queryAnalysisType === StatisticsAnalysisType.CategoricalAnalysis && isQuerySpecialChartType && queryChartDataType === ChartDataType.Overview.type && (!categoricalOverviewAnalysisData || !categoricalOverviewAnalysisData.items || !categoricalOverviewAnalysisData.items.length))
|
||||||
|
|| (queryAnalysisType === StatisticsAnalysisType.CategoricalAnalysis && !isQuerySpecialChartType && (!categoricalAnalysisData || !categoricalAnalysisData.items || !categoricalAnalysisData.items.length))
|
||||||
|
|| (queryAnalysisType === StatisticsAnalysisType.TrendAnalysis && (!trendsAnalysisData || !trendsAnalysisData.items || !trendsAnalysisData.items.length))
|
||||||
|
)">
|
||||||
<span class="statistics-subtitle statistics-overview-empty-tip">{{ tt('No transaction data') }}</span>
|
<span class="statistics-subtitle statistics-overview-empty-tip">{{ tt('No transaction data') }}</span>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
@@ -198,8 +221,7 @@
|
|||||||
v-if="initing"
|
v-if="initing"
|
||||||
/>
|
/>
|
||||||
<account-and-category-sankey-chart
|
<account-and-category-sankey-chart
|
||||||
:items="categoricalAllAnalysisData && categoricalAllAnalysisData.items && categoricalAllAnalysisData.items.length ? categoricalAllAnalysisData.items : []"
|
:items="categoricalOverviewAnalysisData && categoricalOverviewAnalysisData.items && categoricalOverviewAnalysisData.items.length ? categoricalOverviewAnalysisData.items : []"
|
||||||
:sorting-type="querySortingType"
|
|
||||||
:enable-click-item="true"
|
:enable-click-item="true"
|
||||||
:default-currency="defaultCurrency"
|
:default-currency="defaultCurrency"
|
||||||
v-else-if="!initing"
|
v-else-if="!initing"
|
||||||
@@ -526,8 +548,8 @@ const {
|
|||||||
showTotalAmountInTrendsChart,
|
showTotalAmountInTrendsChart,
|
||||||
showStackedInTrendsChart,
|
showStackedInTrendsChart,
|
||||||
translateNameInTrendsChart,
|
translateNameInTrendsChart,
|
||||||
|
categoricalOverviewAnalysisData,
|
||||||
categoricalAnalysisData,
|
categoricalAnalysisData,
|
||||||
categoricalAllAnalysisData,
|
|
||||||
trendsAnalysisData,
|
trendsAnalysisData,
|
||||||
canShowCustomDateRange,
|
canShowCustomDateRange,
|
||||||
getTransactionCategoricalAnalysisDataItemDisplayColor,
|
getTransactionCategoricalAnalysisDataItemDisplayColor,
|
||||||
|
|||||||
Reference in New Issue
Block a user