reconciliation statement page / dialog supports account balance trends chart (#184)
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
|
||||
import {
|
||||
type UnixTimeRange,
|
||||
type YearUnixTime,
|
||||
type YearQuarterUnixTime,
|
||||
type YearMonthUnixTime,
|
||||
YearMonthDayUnixTime,
|
||||
} from '@/core/datetime.ts';
|
||||
import type { FiscalYearUnixTime } from '@/core/fiscalyear.ts';
|
||||
import { ChartDateAggregationType } from '@/core/statistics.ts';
|
||||
import type { TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts';
|
||||
|
||||
import { isDefined, isArray } from '@/lib/common.ts';
|
||||
import {
|
||||
getYearAndMonthFromUnixTime,
|
||||
getYearFirstUnixTimeBySpecifiedUnixTime,
|
||||
getQuarterFirstUnixTimeBySpecifiedUnixTime,
|
||||
getMonthFirstUnixTimeBySpecifiedUnixTime,
|
||||
getDayFirstUnixTimeBySpecifiedUnixTime,
|
||||
getAllDaysStartAndEndUnixTimes,
|
||||
getFiscalYearStartUnixTime
|
||||
} from '@/lib/datetime.ts';
|
||||
import { getAllDateRangesByYearMonthRange } from '@/lib/statistics.ts';
|
||||
|
||||
export interface AccountBalanceUnixTimeAndBalanceRange extends UnixTimeRange {
|
||||
minUnixTimeBalance: number;
|
||||
maxUnixTimeBalance: number;
|
||||
}
|
||||
|
||||
export interface AccountBalanceTrendsChartItem {
|
||||
displayDate: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface CommonAccountBalanceTrendsChartProps {
|
||||
items: TransactionReconciliationStatementResponseItem[] | undefined;
|
||||
dateAggregationType?: number;
|
||||
fiscalYearStart: number;
|
||||
accountCurrency: string;
|
||||
}
|
||||
|
||||
export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTrendsChartProps) {
|
||||
const { formatUnixTimeToShortDate, formatUnixTimeToShortYear, formatUnixTimeToShortYearMonth, formatUnixTimeToYearQuarter, formatUnixTimeToFiscalYear } = useI18n();
|
||||
|
||||
const dataDateRange = computed<AccountBalanceUnixTimeAndBalanceRange | null>(() => {
|
||||
if (!props.items || props.items.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let minUnixTime = Number.MAX_SAFE_INTEGER, maxUnixTime = 0;
|
||||
let minUnixTimeBalance = 0, maxUnixTimeBalance = 0;
|
||||
|
||||
for (let i = 0; i < props.items.length; i++) {
|
||||
const item = props.items[i];
|
||||
|
||||
if (item.time < minUnixTime) {
|
||||
minUnixTime = item.time;
|
||||
minUnixTimeBalance = item.accountBalance;
|
||||
}
|
||||
|
||||
if (item.time > maxUnixTime) {
|
||||
maxUnixTime = item.time;
|
||||
maxUnixTimeBalance = item.accountBalance;
|
||||
}
|
||||
}
|
||||
|
||||
if (minUnixTime >= Number.MAX_SAFE_INTEGER || maxUnixTime <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
minUnixTime: minUnixTime,
|
||||
maxUnixTime: maxUnixTime,
|
||||
minUnixTimeBalance: minUnixTimeBalance,
|
||||
maxUnixTimeBalance: maxUnixTimeBalance
|
||||
};
|
||||
});
|
||||
|
||||
const allDateRanges = computed<YearUnixTime[] | FiscalYearUnixTime[] | YearQuarterUnixTime[] | YearMonthUnixTime[] | YearMonthDayUnixTime[]>(() => {
|
||||
if (!dataDateRange.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isDefined(props.dateAggregationType)) {
|
||||
return getAllDaysStartAndEndUnixTimes(dataDateRange.value.minUnixTime, dataDateRange.value.maxUnixTime);
|
||||
} else {
|
||||
const startYearMonth = getYearAndMonthFromUnixTime(dataDateRange.value.minUnixTime);
|
||||
const endYearMonth = getYearAndMonthFromUnixTime(dataDateRange.value.maxUnixTime);
|
||||
return getAllDateRangesByYearMonthRange(startYearMonth, endYearMonth, props.fiscalYearStart, props.dateAggregationType);
|
||||
}
|
||||
});
|
||||
|
||||
const allDataItems = computed<AccountBalanceTrendsChartItem[]>(() => {
|
||||
const ret: AccountBalanceTrendsChartItem[] = [];
|
||||
|
||||
if (!dataDateRange.value || !allDateRanges.value || allDateRanges.value.length < 1 || !props.items || props.items.length < 1) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const dayDataItemsMap: Record<number, TransactionReconciliationStatementResponseItem[]> = {};
|
||||
|
||||
for (let i = 0; i < props.items.length; i++) {
|
||||
const dateItem = props.items[i];
|
||||
let dateRangeMinUnixTime = 0;
|
||||
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
|
||||
dateRangeMinUnixTime = getYearFirstUnixTimeBySpecifiedUnixTime(dateItem.time);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
|
||||
dateRangeMinUnixTime = getFiscalYearStartUnixTime(dateItem.time, props.fiscalYearStart);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
|
||||
dateRangeMinUnixTime = getQuarterFirstUnixTimeBySpecifiedUnixTime(dateItem.time);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
dateRangeMinUnixTime = getMonthFirstUnixTimeBySpecifiedUnixTime(dateItem.time);
|
||||
} else {
|
||||
dateRangeMinUnixTime = getDayFirstUnixTimeBySpecifiedUnixTime(dateItem.time);
|
||||
}
|
||||
|
||||
const dataItems: TransactionReconciliationStatementResponseItem[] = dayDataItemsMap[dateRangeMinUnixTime] || [];
|
||||
dataItems.push(dateItem);
|
||||
|
||||
dayDataItemsMap[dateRangeMinUnixTime] = dataItems;
|
||||
}
|
||||
|
||||
let lastAmount = dataDateRange.value.minUnixTimeBalance;
|
||||
|
||||
for (let i = 0; i < allDateRanges.value.length; i++) {
|
||||
const dateRange = allDateRanges.value[i];
|
||||
const dataItems = dayDataItemsMap[dateRange.minUnixTime];
|
||||
|
||||
let displayDate = '';
|
||||
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
|
||||
displayDate = formatUnixTimeToShortYear(dateRange.minUnixTime);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
|
||||
displayDate = formatUnixTimeToFiscalYear(dateRange.minUnixTime);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
|
||||
displayDate = formatUnixTimeToYearQuarter(dateRange.minUnixTime);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
displayDate = formatUnixTimeToShortYearMonth(dateRange.minUnixTime);
|
||||
} else {
|
||||
displayDate = formatUnixTimeToShortDate(dateRange.minUnixTime);
|
||||
}
|
||||
|
||||
if (isArray(dataItems)) {
|
||||
let lastUnixTime = 0;
|
||||
|
||||
for (let i = 0; i < dataItems.length; i++) {
|
||||
const dataItem = dataItems[i];
|
||||
|
||||
if (dataItem.time >= lastUnixTime) {
|
||||
lastUnixTime = dataItem.time;
|
||||
lastAmount = dataItem.accountBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret.push({
|
||||
displayDate: displayDate,
|
||||
amount: lastAmount
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
const allDisplayDateRanges = computed<string[]>(() => {
|
||||
if (!allDataItems.value || allDataItems.value.length < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return allDataItems.value.map(item => item.displayDate);
|
||||
});
|
||||
|
||||
return {
|
||||
// computed states
|
||||
allDateRanges,
|
||||
allDataItems,
|
||||
allDisplayDateRanges
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<v-chart autoresize class="account-balance-trends-chart-container" :class="{ 'transition-in': skeleton }" :option="chartOptions"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useTheme } from 'vuetify';
|
||||
import type { CallbackDataParams } from 'echarts/types/dist/shared';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
import { type CommonAccountBalanceTrendsChartProps, useAccountBalanceTrendsChartBase } from '@/components/base/AccountBalanceTrendsChartBase.ts'
|
||||
|
||||
import type { ColorValue } from '@/core/color.ts';
|
||||
import { ThemeType } from '@/core/theme.ts';
|
||||
import { TrendChartType } from '@/core/statistics.ts';
|
||||
import { DEFAULT_CHART_COLORS } from '@/consts/color.ts';
|
||||
|
||||
interface DesktopAccountBalanceTrendsChartProps extends CommonAccountBalanceTrendsChartProps {
|
||||
legendName: string;
|
||||
skeleton?: boolean;
|
||||
type?: number;
|
||||
}
|
||||
|
||||
interface AccountBalanceTrendsChartDataItem {
|
||||
id: string;
|
||||
name: string;
|
||||
itemStyle: {
|
||||
color: ColorValue;
|
||||
};
|
||||
selected: boolean;
|
||||
type: string;
|
||||
areaStyle?: object;
|
||||
stack: string;
|
||||
animation: boolean;
|
||||
data: number[];
|
||||
}
|
||||
|
||||
const props = defineProps<DesktopAccountBalanceTrendsChartProps>();
|
||||
|
||||
const theme = useTheme();
|
||||
const { formatAmountWithCurrency } = useI18n();
|
||||
const { allDataItems, allDisplayDateRanges } = useAccountBalanceTrendsChartBase(props);
|
||||
|
||||
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
|
||||
|
||||
const allSeries = computed<AccountBalanceTrendsChartDataItem[]>(() => {
|
||||
const series: AccountBalanceTrendsChartDataItem = {
|
||||
id: 'accountBalance',
|
||||
name: props.legendName,
|
||||
itemStyle: {
|
||||
color: `#${DEFAULT_CHART_COLORS[0]}`
|
||||
},
|
||||
selected: true,
|
||||
type: 'line',
|
||||
stack: 'a',
|
||||
animation: !props.skeleton,
|
||||
data: []
|
||||
};
|
||||
|
||||
if (props.type === TrendChartType.Area.type) {
|
||||
series.areaStyle = {};
|
||||
} else if (props.type === TrendChartType.Column.type) {
|
||||
series.type = 'bar';
|
||||
}
|
||||
|
||||
for (let i = 0; i < allDataItems.value.length; i++) {
|
||||
const item = allDataItems.value[i];
|
||||
series.data.push(item.amount);
|
||||
}
|
||||
|
||||
return [series];
|
||||
});
|
||||
|
||||
const yAxisWidth = computed<number>(() => {
|
||||
let maxValue = Number.MIN_SAFE_INTEGER;
|
||||
let minValue = Number.MAX_SAFE_INTEGER;
|
||||
let width = 90;
|
||||
|
||||
if (!allSeries.value || !allSeries.value.length) {
|
||||
return width;
|
||||
}
|
||||
|
||||
for (let i = 0; i < allSeries.value.length; i++) {
|
||||
for (let j = 0; j < allSeries.value[i].data.length; j++) {
|
||||
const value = allSeries.value[i].data[j];
|
||||
|
||||
if (value > maxValue) {
|
||||
maxValue = value;
|
||||
}
|
||||
|
||||
if (value < minValue) {
|
||||
minValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const maxValueText = formatAmountWithCurrency(maxValue, props.accountCurrency);
|
||||
const minValueText = formatAmountWithCurrency(minValue, props.accountCurrency);
|
||||
const maxLengthText = maxValueText.length > minValueText.length ? maxValueText : minValueText;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if (context) {
|
||||
context.font = '12px Arial';
|
||||
|
||||
const textMetrics = context.measureText(maxLengthText);
|
||||
const actualWidth = Math.round(textMetrics.width) + 20;
|
||||
|
||||
if (actualWidth >= 200) {
|
||||
width = 200;
|
||||
} if (actualWidth > 90) {
|
||||
width = actualWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
});
|
||||
|
||||
const chartOptions = computed<object>(() => {
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: isDarkMode.value ? '#333' : '#fff',
|
||||
color: isDarkMode.value ? '#eee' : '#333'
|
||||
},
|
||||
},
|
||||
backgroundColor: isDarkMode.value ? '#333' : '#fff',
|
||||
borderColor: isDarkMode.value ? '#333' : '#fff',
|
||||
textStyle: {
|
||||
color: isDarkMode.value ? '#eee' : '#333'
|
||||
},
|
||||
formatter: (params: CallbackDataParams[]) => {
|
||||
const amount = params[0].data as number;
|
||||
const value = formatAmountWithCurrency(amount, props.accountCurrency);
|
||||
|
||||
return `${params[0].name}<br/>`
|
||||
+ '<div><span class="chart-pointer" style="background-color: #' + DEFAULT_CHART_COLORS[0] + '"></span>'
|
||||
+ `<span>${props.legendName}</span><span style="margin-left: 20px; float: right">${value}</span><br/>`
|
||||
+ '</div>';
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: yAxisWidth.value,
|
||||
right: 20
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: allDisplayDateRanges.value
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: (value: string) => {
|
||||
return formatAmountWithCurrency(value, props.accountCurrency);
|
||||
}
|
||||
},
|
||||
axisPointer: {
|
||||
label: {
|
||||
formatter: (params: CallbackDataParams) => {
|
||||
return formatAmountWithCurrency(Math.floor(params.value as number), props.accountCurrency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
series: allSeries.value
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.account-balance-trends-chart-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<f7-list class="skeleton-text margin-top-half" media-list v-if="loading">
|
||||
<f7-list-item class="account-balance-trends-list-item" title="Date Range" after="0.00 USD"
|
||||
:key="itemIdx" v-for="itemIdx in [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]">
|
||||
<template #media>
|
||||
<f7-icon f7="app_fill"></f7-icon>
|
||||
</template>
|
||||
<template #inner>
|
||||
<div class="display-flex padding-top-half">
|
||||
<div class="account-balance-percent-line width-100">
|
||||
<f7-progressbar :progress="0"></f7-progressbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
<f7-list v-else-if="!loading && (!allVirtualListItems || !allVirtualListItems.length)">
|
||||
<f7-list-item :title="tt('No transaction data')"></f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
<f7-list class="margin-top-half" media-list virtual-list :virtual-list-params="{ items: allVirtualListItems, renderExternal, height: 'auto' }"
|
||||
:key="`account-balance-trends-${dateAggregationType}`"
|
||||
v-else-if="!loading && allVirtualListItems && allVirtualListItems.length > 0">
|
||||
<ul>
|
||||
<f7-list-item class="account-balance-trends-list-item"
|
||||
:key="item.index"
|
||||
:style="`top: ${virtualDataItems.topPosition}px`"
|
||||
:virtual-list-index="item.index"
|
||||
:title="item.displayDate"
|
||||
:after="formatAmountWithCurrency(item.amount, accountCurrency)"
|
||||
v-for="item in virtualDataItems.items"
|
||||
>
|
||||
<template #media>
|
||||
<f7-icon f7="calendar"></f7-icon>
|
||||
</template>
|
||||
<template #inner>
|
||||
<div class="display-flex padding-top-half">
|
||||
<div class="account-balance-percent-line" :style="{ 'width': item.percent + '%' }">
|
||||
<f7-progressbar :progress="100" :style="{ '--f7-progressbar-progress-color': (item.color ? item.color : '') } "></f7-progressbar>
|
||||
</div>
|
||||
<div class="account-balance-percent-line" :style="{ 'width': (100.0 - item.percent) + '%' }"
|
||||
v-if="item.percent < 100.0">
|
||||
<f7-progressbar :progress="0"></f7-progressbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</ul>
|
||||
</f7-list>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
import {
|
||||
type AccountBalanceTrendsChartItem,
|
||||
type CommonAccountBalanceTrendsChartProps,
|
||||
useAccountBalanceTrendsChartBase
|
||||
} from '@/components/base/AccountBalanceTrendsChartBase.ts'
|
||||
|
||||
import type { ColorValue } from '@/core/color.ts';
|
||||
import { DEFAULT_CHART_COLORS } from '@/consts/color.ts';
|
||||
|
||||
interface MobileAccountBalanceTrendsChartItem extends AccountBalanceTrendsChartItem {
|
||||
index: number;
|
||||
percent: number;
|
||||
color: ColorValue;
|
||||
}
|
||||
|
||||
interface MobileAccountBalanceTrendsChartProps extends CommonAccountBalanceTrendsChartProps {
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
interface MobileAccountBalanceTrendsChartVirtualListData {
|
||||
items: MobileAccountBalanceTrendsChartItem[],
|
||||
topPosition: number
|
||||
}
|
||||
|
||||
const props = defineProps<MobileAccountBalanceTrendsChartProps>();
|
||||
|
||||
const { tt, formatAmountWithCurrency } = useI18n();
|
||||
const { allDataItems } = useAccountBalanceTrendsChartBase(props);
|
||||
|
||||
const virtualDataItems = ref<MobileAccountBalanceTrendsChartVirtualListData>({
|
||||
items: [],
|
||||
topPosition: 0
|
||||
});
|
||||
|
||||
const allVirtualListItems = computed<MobileAccountBalanceTrendsChartItem[]>(() => {
|
||||
const ret: MobileAccountBalanceTrendsChartItem[] = [];
|
||||
let maxAmount = 0;
|
||||
|
||||
for (let i = 0; i < allDataItems.value.length; i++) {
|
||||
const dataItem = allDataItems.value[i];
|
||||
|
||||
if (dataItem.amount > maxAmount) {
|
||||
maxAmount = dataItem.amount;
|
||||
}
|
||||
|
||||
const finalDataItem: MobileAccountBalanceTrendsChartItem = {
|
||||
index: i,
|
||||
displayDate: dataItem.displayDate,
|
||||
amount: dataItem.amount,
|
||||
color: `#${DEFAULT_CHART_COLORS[0]}`,
|
||||
percent: 0.0
|
||||
};
|
||||
|
||||
ret.push(finalDataItem);
|
||||
}
|
||||
|
||||
for (let i = 0; i < ret.length; i++) {
|
||||
if (maxAmount > 0 && ret[i].amount > 0) {
|
||||
ret[i].percent = 100.0 * ret[i].amount / maxAmount;
|
||||
} else {
|
||||
ret[i].percent = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
function renderExternal(vl: unknown, vlData: MobileAccountBalanceTrendsChartVirtualListData): void {
|
||||
virtualDataItems.value = vlData;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.account-balance-trends-list-item .account-balance-percent-line {
|
||||
--f7-progressbar-bg-color: #f8f8f8;
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,12 @@ export interface YearMonthRange {
|
||||
readonly endYearMonth: Year0BasedMonth;
|
||||
}
|
||||
|
||||
export interface YearMonthDay {
|
||||
readonly year: number;
|
||||
readonly month: number; // 1-based (1 = January, 12 = December)
|
||||
readonly day: number;
|
||||
}
|
||||
|
||||
export interface TimeRange {
|
||||
readonly minTime: number;
|
||||
readonly maxTime: number;
|
||||
@@ -136,6 +142,26 @@ export class YearMonthUnixTime implements Year0BasedMonth, UnixTimeRange {
|
||||
}
|
||||
}
|
||||
|
||||
export class YearMonthDayUnixTime implements YearMonthDay, UnixTimeRange {
|
||||
public readonly year: number;
|
||||
public readonly month: number;
|
||||
public readonly day: number;
|
||||
public readonly minUnixTime: number;
|
||||
public readonly maxUnixTime: number;
|
||||
|
||||
private constructor(year: number, month: number, day: number, minUnixTime: number, maxUnixTime: number) {
|
||||
this.year = year;
|
||||
this.month = month;
|
||||
this.day = day
|
||||
this.minUnixTime = minUnixTime;
|
||||
this.maxUnixTime = maxUnixTime;
|
||||
}
|
||||
|
||||
public static of(yearMonthDay: YearMonthDay, minUnixTime: number, maxUnixTime: number): YearMonthDayUnixTime {
|
||||
return new YearMonthDayUnixTime(yearMonthDay.year, yearMonthDay.month, yearMonthDay.day, minUnixTime, maxUnixTime);
|
||||
}
|
||||
}
|
||||
|
||||
export type MonthValue = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
|
||||
export class Month {
|
||||
|
||||
+10
-8
@@ -151,23 +151,25 @@ export class ChartSortingType implements TypeAndName {
|
||||
}
|
||||
}
|
||||
|
||||
export class ChartDateAggregationType implements TypeAndName {
|
||||
export class ChartDateAggregationType {
|
||||
private static readonly allInstances: ChartDateAggregationType[] = [];
|
||||
private static readonly allInstancesByType: Record<number, ChartDateAggregationType> = {};
|
||||
|
||||
public static readonly Month = new ChartDateAggregationType(0, 'Aggregate by Month');
|
||||
public static readonly Quarter = new ChartDateAggregationType(1, 'Aggregate by Quarter');
|
||||
public static readonly Year = new ChartDateAggregationType(2, 'Aggregate by Year');
|
||||
public static readonly FiscalYear = new ChartDateAggregationType(3, 'Aggregate by Fiscal Year');
|
||||
public static readonly Month = new ChartDateAggregationType(0, 'Monthly', 'Aggregate by Month');
|
||||
public static readonly Quarter = new ChartDateAggregationType(1, 'Quarterly', 'Aggregate by Quarter');
|
||||
public static readonly Year = new ChartDateAggregationType(2, 'Yearly', 'Aggregate by Year');
|
||||
public static readonly FiscalYear = new ChartDateAggregationType(3, 'FiscalYearly', 'Aggregate by Fiscal Year');
|
||||
|
||||
public static readonly Default = ChartDateAggregationType.Month;
|
||||
|
||||
public readonly type: number;
|
||||
public readonly name: string;
|
||||
public readonly shortName: string;
|
||||
public readonly fullName: string;
|
||||
|
||||
private constructor(type: number, name: string) {
|
||||
private constructor(type: number, shortName: string, fullName: string) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.shortName = shortName;
|
||||
this.fullName = fullName;
|
||||
|
||||
ChartDateAggregationType.allInstances.push(this);
|
||||
ChartDateAggregationType.allInstancesByType[type] = this;
|
||||
|
||||
@@ -99,6 +99,7 @@ import MonthlyTrendsChart from '@/components/desktop/MonthlyTrendsChart.vue';
|
||||
import DateRangeSelectionDialog from '@/components/desktop/DateRangeSelectionDialog.vue';
|
||||
import MonthSelectionDialog from '@/components/desktop/MonthSelectionDialog.vue';
|
||||
import MonthRangeSelectionDialog from '@/components/desktop/MonthRangeSelectionDialog.vue';
|
||||
import AccountBalanceTrendsChart from '@/components/desktop/AccountBalanceTrendsChart.vue';
|
||||
import SwitchToMobileDialog from '@/components/desktop/SwitchToMobileDialog.vue';
|
||||
|
||||
import '@/styles/desktop/template/vuetify/index.scss';
|
||||
@@ -525,6 +526,7 @@ app.component('MonthlyTrendsChart', MonthlyTrendsChart);
|
||||
app.component('DateRangeSelectionDialog', DateRangeSelectionDialog);
|
||||
app.component('MonthSelectionDialog', MonthSelectionDialog);
|
||||
app.component('MonthRangeSelectionDialog', MonthRangeSelectionDialog);
|
||||
app.component('AccountBalanceTrendsChart', AccountBalanceTrendsChart);
|
||||
app.component('SwitchToMobileDialog', SwitchToMobileDialog);
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
+65
-10
@@ -17,6 +17,7 @@ import {
|
||||
type TimeFormat,
|
||||
YearQuarterUnixTime,
|
||||
YearMonthUnixTime,
|
||||
YearMonthDayUnixTime,
|
||||
Month,
|
||||
WeekDay,
|
||||
MeridiemIndicator,
|
||||
@@ -222,6 +223,10 @@ export function getYear(date: SupportedDate): number {
|
||||
return moment(date).year();
|
||||
}
|
||||
|
||||
export function getQuarter(date: SupportedDate): number {
|
||||
return moment(date).quarter();
|
||||
}
|
||||
|
||||
export function getMonth(date: SupportedDate): number {
|
||||
return moment(date).month() + 1;
|
||||
}
|
||||
@@ -323,15 +328,6 @@ export function getThisMonthLastUnixTime(): number {
|
||||
return moment.unix(getThisMonthFirstUnixTime()).add(1, 'months').subtract(1, 'seconds').unix();
|
||||
}
|
||||
|
||||
export function getMonthFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
const date = moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
|
||||
return date.subtract(date.date() - 1, 'days').unix();
|
||||
}
|
||||
|
||||
export function getMonthLastUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
return moment.unix(getMonthFirstUnixTimeBySpecifiedUnixTime(unixTime)).add(1, 'months').subtract(1, 'seconds').unix();
|
||||
}
|
||||
|
||||
export function getThisMonthSpecifiedDayFirstUnixTime(date: number): number {
|
||||
return moment().set({ date: date, hour: 0, minute: 0, second: 0, millisecond: 0 }).unix();
|
||||
}
|
||||
@@ -349,10 +345,43 @@ export function getThisYearLastUnixTime(): number {
|
||||
return moment.unix(getThisYearFirstUnixTime()).add(1, 'years').subtract(1, 'seconds').unix();
|
||||
}
|
||||
|
||||
export function getSpecifiedDayFirstUnixTime(unixTime: number): number {
|
||||
export function getYearFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
const date = moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
|
||||
return date.subtract(date.dayOfYear() - 1, 'days').unix();
|
||||
}
|
||||
|
||||
export function getYearLastUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
return moment.unix(getYearFirstUnixTimeBySpecifiedUnixTime(unixTime)).add(1, 'years').subtract(1, 'seconds').unix();
|
||||
}
|
||||
|
||||
export function getQuarterFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
const date = moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
|
||||
const month = date.month();
|
||||
const quarterStartMonth = Math.floor(month / 3) * 3;
|
||||
return date.set({ month: quarterStartMonth, date: 1 }).unix();
|
||||
}
|
||||
|
||||
export function getQuarterLastUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
return moment.unix(getQuarterFirstUnixTimeBySpecifiedUnixTime(unixTime)).add(3, 'months').subtract(1, 'seconds').unix();
|
||||
}
|
||||
|
||||
export function getMonthFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
const date = moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
|
||||
return date.subtract(date.date() - 1, 'days').unix();
|
||||
}
|
||||
|
||||
export function getMonthLastUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
return moment.unix(getMonthFirstUnixTimeBySpecifiedUnixTime(unixTime)).add(1, 'months').subtract(1, 'seconds').unix();
|
||||
}
|
||||
|
||||
export function getDayFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
return moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).unix();
|
||||
}
|
||||
|
||||
export function getDayLastUnixTimeBySpecifiedUnixTime(unixTime: number): number {
|
||||
return moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).add(1, 'days').subtract(1, 'seconds').unix();
|
||||
}
|
||||
|
||||
export function getYearFirstUnixTime(year: number): number {
|
||||
return moment().set({ year: year, month: 0, date: 1, hour: 0, minute: 0, second: 0, millisecond: 0 }).unix();
|
||||
}
|
||||
@@ -581,6 +610,32 @@ export function getAllMonthsStartAndEndUnixTimes(startYearMonth: Year0BasedMonth
|
||||
return allYearMonthTimes;
|
||||
}
|
||||
|
||||
export function getAllDaysStartAndEndUnixTimes(startUnixTime: number, endUnixTime: number): YearMonthDayUnixTime[] {
|
||||
const allYearMonthDayTimes: YearMonthDayUnixTime[] = [];
|
||||
|
||||
if (!startUnixTime || !endUnixTime) {
|
||||
return allYearMonthDayTimes;
|
||||
}
|
||||
|
||||
let unixTime: number = startUnixTime;
|
||||
|
||||
while (unixTime <= endUnixTime) {
|
||||
const currentDay = parseDateFromUnixTime(unixTime);
|
||||
const currentDayMinUnixTime = getDayFirstUnixTimeBySpecifiedUnixTime(unixTime);
|
||||
const currentDayMaxUnixTime = getDayLastUnixTimeBySpecifiedUnixTime(unixTime);
|
||||
|
||||
allYearMonthDayTimes.push(YearMonthDayUnixTime.of({
|
||||
year: currentDay.year(),
|
||||
month: currentDay.month() + 1,
|
||||
day: currentDay.date()
|
||||
}, currentDayMinUnixTime, currentDayMaxUnixTime));
|
||||
|
||||
unixTime = currentDayMaxUnixTime + 1;
|
||||
}
|
||||
|
||||
return allYearMonthDayTimes;
|
||||
}
|
||||
|
||||
export function getDateTimeFormatType<T extends DateFormat | TimeFormat>(allFormatMap: Record<string, T>, allFormatArray: T[], formatTypeValue: number, languageDefaultTypeName: string, systemDefaultFormatType: T): T {
|
||||
if (formatTypeValue > LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE && allFormatArray[formatTypeValue - 1] && allFormatArray[formatTypeValue - 1].key) {
|
||||
return allFormatArray[formatTypeValue - 1];
|
||||
|
||||
@@ -74,6 +74,10 @@ export function getAllDateRanges<T extends Year1BasedMonth>(items: YearMonthItem
|
||||
endYearMonth = `${maxYear}-${maxMonth}`;
|
||||
}
|
||||
|
||||
return getAllDateRangesByYearMonthRange(startYearMonth, endYearMonth, fiscalYearStart, dateAggregationType);
|
||||
}
|
||||
|
||||
export function getAllDateRangesByYearMonthRange(startYearMonth: Year1BasedMonth | string, endYearMonth: Year1BasedMonth | string, fiscalYearStart: number, dateAggregationType: number): YearUnixTime[] | FiscalYearUnixTime[] | YearQuarterUnixTime[] | YearMonthUnixTime[] {
|
||||
if (!startYearMonth || !endYearMonth) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31."
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "Punkt",
|
||||
"Comma": "Komma",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "Konto kann nicht gelöscht werden",
|
||||
"Unable to delete this sub-account": "Unable to delete this sub-account",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "Transaktion",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "Nach Betrag sortieren",
|
||||
"Sort by Display Order": "Nach Anzeigereihenfolge sortieren",
|
||||
"Sort by Name": "Nach Name sortieren",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "Nach Monat aggregieren",
|
||||
"Aggregate by Quarter": "Nach Quartal aggregieren",
|
||||
"Aggregate by Year": "Nach Jahr aggregieren",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31th"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "Dot",
|
||||
"Comma": "Comma",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "Unable to delete this account",
|
||||
"Unable to delete this sub-account": "Unable to delete this sub-account",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "Transaction",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "Sort by Amount",
|
||||
"Sort by Display Order": "Sort by Display Order",
|
||||
"Sort by Name": "Sort by Name",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "Aggregate by Month",
|
||||
"Aggregate by Quarter": "Aggregate by Quarter",
|
||||
"Aggregate by Year": "Aggregate by Year",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "Punto",
|
||||
"Comma": "Coma",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "No se puede eliminar esta cuenta",
|
||||
"Unable to delete this sub-account": "Unable to delete this sub-account",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "Transacción",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "Ordenar por Importe",
|
||||
"Sort by Display Order": "Ordenar por orden de visualización",
|
||||
"Sort by Name": "Ordenar por Nombre",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "Agregado por mes",
|
||||
"Aggregate by Quarter": "Agregado por trimestre",
|
||||
"Aggregate by Year": "Agregado por año",
|
||||
|
||||
+27
-1
@@ -147,6 +147,7 @@ import {
|
||||
getTimezoneOffset,
|
||||
getTimezoneOffsetMinutes,
|
||||
getYear,
|
||||
getQuarter,
|
||||
isDateRangeMatchFullMonths,
|
||||
isDateRangeMatchFullYears,
|
||||
isPM,
|
||||
@@ -496,6 +497,22 @@ export function useI18n() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getLocalizedChartDateAggregationTypeAndDisplayName(fullName: boolean): TypeAndDisplayName[] {
|
||||
const ret: TypeAndDisplayName[] = [];
|
||||
const allTypes: ChartDateAggregationType[] = ChartDateAggregationType.values();
|
||||
|
||||
for (let i = 0; i < allTypes.length; i++) {
|
||||
const type = allTypes[i];
|
||||
|
||||
ret.push({
|
||||
type: type.type,
|
||||
displayName: t(fullName ? type.fullName : `granularity.${type.shortName}`)
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getAllMonthNames(type: string): string[] {
|
||||
const ret = [];
|
||||
const allMonths = Month.values();
|
||||
@@ -1472,6 +1489,13 @@ export function useI18n() {
|
||||
return formatMonthDay(monthDay, getLocalizedLongMonthDayFormat());
|
||||
}
|
||||
|
||||
function formatUnixTimeToYearQuarter(unixTime: number): string {
|
||||
const date = parseDateFromUnixTime(unixTime);
|
||||
const year = getYear(date);
|
||||
const quarter = getQuarter(date);
|
||||
return formatYearQuarter(year, quarter);
|
||||
}
|
||||
|
||||
function formatYearQuarter(year: number, quarter: number): string {
|
||||
if (1 <= quarter && quarter <= 4) {
|
||||
return t('format.yearQuarter.q' + quarter, {
|
||||
@@ -1912,7 +1936,8 @@ export function useI18n() {
|
||||
getAllTrendChartTypes: () => getLocalizedDisplayNameAndType(TrendChartType.values()),
|
||||
getAllStatisticsChartDataTypes: (analysisType: StatisticsAnalysisType) => getLocalizedDisplayNameAndType(ChartDataType.values(analysisType)),
|
||||
getAllStatisticsSortingTypes: () => getLocalizedDisplayNameAndType(ChartSortingType.values()),
|
||||
getAllStatisticsDateAggregationTypes: () => getLocalizedDisplayNameAndType(ChartDateAggregationType.values()),
|
||||
getAllStatisticsDateAggregationTypes: () => getLocalizedChartDateAggregationTypeAndDisplayName(true),
|
||||
getAllStatisticsDateAggregationTypesWithShortName: () => getLocalizedChartDateAggregationTypeAndDisplayName(false),
|
||||
getAllTransactionEditScopeTypes: () => getLocalizedDisplayNameAndType(TransactionEditScopeType.values()),
|
||||
getAllTransactionTagFilterTypes: () => getLocalizedDisplayNameAndType(TransactionTagFilterType.values()),
|
||||
getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()),
|
||||
@@ -1961,6 +1986,7 @@ export function useI18n() {
|
||||
formatUnixTimeToShortTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortTimeFormat(), utcOffset, currentUtcOffset),
|
||||
formatDateToLongDate,
|
||||
formatMonthDayToLongDay,
|
||||
formatUnixTimeToYearQuarter,
|
||||
formatYearQuarter,
|
||||
formatDateRange,
|
||||
formatFiscalYearStartToLongDay,
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "Punto",
|
||||
"Comma": "Virgola",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "Impossibile eliminare questo account",
|
||||
"Unable to delete this sub-account": "Impossibile eliminare questo sotto-account",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "Transazione",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "Ordina per importo",
|
||||
"Sort by Display Order": "Ordina per ordine di visualizzazione",
|
||||
"Sort by Name": "Ordina per nome",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "Aggrega per mese",
|
||||
"Aggregate by Quarter": "Aggrega per trimestre",
|
||||
"Aggregate by Year": "Aggrega per anno",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "ドット",
|
||||
"Comma": "コンマ",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "この口座を削除できません",
|
||||
"Unable to delete this sub-account": "Unable to delete this sub-account",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "取引",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "金額で並べ替え",
|
||||
"Sort by Display Order": "表示で並べ替え",
|
||||
"Sort by Name": "名前で並べ替え",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "月ごとに集計",
|
||||
"Aggregate by Quarter": "四半期ごとに集計",
|
||||
"Aggregate by Year": "年ごとに集計",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31º"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "Ponto",
|
||||
"Comma": "Vírgula",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "Não foi possível deletar esta conta",
|
||||
"Unable to delete this sub-account": "Não foi possível deletar esta subconta",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "Transação",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "Classificar por Montante",
|
||||
"Sort by Display Order": "Classificar por Ordem de Exibição",
|
||||
"Sort by Name": "Classificar por Nome",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "Agregado por Mês",
|
||||
"Aggregate by Quarter": "Agregado por Trimestre",
|
||||
"Aggregate by Year": "Agregado por Ano",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31-й"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "Точка",
|
||||
"Comma": "Запятая",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "Не удалось удалить этот счет",
|
||||
"Unable to delete this sub-account": "Unable to delete this sub-account",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "Транзакция",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "Сортировать по сумме",
|
||||
"Sort by Display Order": "Сортировать по порядку отображения",
|
||||
"Sort by Name": "Сортировать по имени",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "Агрегировать по месяцам",
|
||||
"Aggregate by Quarter": "Агрегировать по кварталам",
|
||||
"Aggregate by Year": "Агрегировать по годам",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31-й"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "Крапка",
|
||||
"Comma": "Кома",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "Не вдалося видалити цей рахунок",
|
||||
"Unable to delete this sub-account": "Не вдалося видалити цей субрахунок",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "Транзакція",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "Сортувати за сумою",
|
||||
"Sort by Display Order": "Сортувати за порядком відображення",
|
||||
"Sort by Name": "Сортувати за назвою",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "Агрегувати за місяцями",
|
||||
"Aggregate by Quarter": "Агрегувати за кварталами",
|
||||
"Aggregate by Year": "Агрегувати за роками",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "Ngày 31"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "Fiscal Yearly",
|
||||
"Yearly": "Yearly",
|
||||
"Quarterly": "Quarterly",
|
||||
"Monthly": "Monthly",
|
||||
"Daily": "Daily"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "Dấu chấm",
|
||||
"Comma": "Dấu phẩy",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "Không thể xóa tài khoản này",
|
||||
"Unable to delete this sub-account": "Unable to delete this sub-account",
|
||||
"Reconciliation Statement": "Reconciliation Statement",
|
||||
"Show Account Balance Trends": "Show Account Balance Trends",
|
||||
"Show Transaction List": "Show Transaction List",
|
||||
"Update Closing Balance": "Update Closing Balance",
|
||||
"Please enter the new closing balance for the account": "Please enter the new closing balance for the account",
|
||||
"Transaction": "Giao dịch",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "Sắp xếp theo số tiền",
|
||||
"Sort by Display Order": "Sắp xếp theo thứ tự hiển thị",
|
||||
"Sort by Name": "Sắp xếp theo tên",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Month": "Tổng hợp theo tháng",
|
||||
"Aggregate by Quarter": "Tổng hợp theo quý",
|
||||
"Aggregate by Year": "Tổng hợp theo năm",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "按财年",
|
||||
"Yearly": "按年",
|
||||
"Quarterly": "按季度",
|
||||
"Monthly": "按月",
|
||||
"Daily": "按天"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "句点",
|
||||
"Comma": "逗号",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "无法删除该账户",
|
||||
"Unable to delete this sub-account": "无法删除该子账户",
|
||||
"Reconciliation Statement": "对账单",
|
||||
"Show Account Balance Trends": "显示账户余额趋势",
|
||||
"Show Transaction List": "显示交易列表",
|
||||
"Update Closing Balance": "更新期末余额",
|
||||
"Please enter the new closing balance for the account": "请输入账户的新期末余额",
|
||||
"Transaction": "交易",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "按金额排序",
|
||||
"Sort by Display Order": "按显示顺序排序",
|
||||
"Sort by Name": "按名称排序",
|
||||
"Time Granularity": "时间粒度",
|
||||
"Aggregate by Month": "按月聚合",
|
||||
"Aggregate by Quarter": "按季度聚合",
|
||||
"Aggregate by Year": "按年聚合",
|
||||
|
||||
@@ -251,6 +251,13 @@
|
||||
"31": "31"
|
||||
}
|
||||
},
|
||||
"granularity": {
|
||||
"FiscalYearly": "依財政年度",
|
||||
"Yearly": "依年份",
|
||||
"Quarterly": "依季度",
|
||||
"Monthly": "依月份",
|
||||
"Daily": "依日期"
|
||||
},
|
||||
"numeral": {
|
||||
"Dot": "句點",
|
||||
"Comma": "逗號",
|
||||
@@ -1635,6 +1642,8 @@
|
||||
"Unable to delete this account": "無法刪除此帳戶",
|
||||
"Unable to delete this sub-account": "無法刪除此子帳戶",
|
||||
"Reconciliation Statement": "對帳單",
|
||||
"Show Account Balance Trends": "顯示帳戶餘額趨勢",
|
||||
"Show Transaction List": "顯示交易清單",
|
||||
"Update Closing Balance": "更新期末餘額",
|
||||
"Please enter the new closing balance for the account": "請輸入帳戶的新期末餘額",
|
||||
"Transaction": "交易",
|
||||
@@ -1873,6 +1882,7 @@
|
||||
"Sort by Amount": "依金額排序",
|
||||
"Sort by Display Order": "依顯示順序排序",
|
||||
"Sort by Name": "依名稱排序",
|
||||
"Time Granularity": "時間粒度",
|
||||
"Aggregate by Month": "依月份彙整",
|
||||
"Aggregate by Quarter": "依季度彙整",
|
||||
"Aggregate by Year": "依年份彙整",
|
||||
|
||||
@@ -109,6 +109,7 @@ import NumberPadSheet from '@/components/mobile/NumberPadSheet.vue';
|
||||
import MapSheet from '@/components/mobile/MapSheet.vue';
|
||||
import TransactionTagSelectionSheet from '@/components/mobile/TransactionTagSelectionSheet.vue';
|
||||
import ScheduleFrequencySheet from '@/components/mobile/ScheduleFrequencySheet.vue';
|
||||
import AccountBalanceTrendsBarChart from '@/components/mobile/AccountBalanceTrendsBarChart.vue';
|
||||
|
||||
import TextareaAutoSize from '@/directives/mobile/textareaAutoSize.ts';
|
||||
|
||||
@@ -197,6 +198,7 @@ app.component('InformationSheet', InformationSheet);
|
||||
app.component('NumberPadSheet', NumberPadSheet);
|
||||
app.component('MapSheet', MapSheet);
|
||||
app.component('TransactionTagSelectionSheet', TransactionTagSelectionSheet);
|
||||
app.component('AccountBalanceTrendsBarChart', AccountBalanceTrendsBarChart);
|
||||
app.component('ScheduleFrequencySheet', ScheduleFrequencySheet);
|
||||
|
||||
app.directive('TextareaAutoSize', TextareaAutoSize);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useUserStore } from '@/stores/user.ts';
|
||||
import { useAccountsStore } from '@/stores/account.ts';
|
||||
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||
|
||||
import type { TypeAndDisplayName } from '@/core/base.ts';
|
||||
import { type WeekDayValue, KnownDateTimeFormat } from '@/core/datetime.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
import { KnownFileType } from '@/core/file.ts';
|
||||
@@ -33,6 +34,8 @@ import {
|
||||
export function useReconciliationStatementPageBase() {
|
||||
const {
|
||||
tt,
|
||||
getAllTrendChartTypes,
|
||||
getAllStatisticsDateAggregationTypesWithShortName,
|
||||
getCurrentDigitGroupingSymbol,
|
||||
formatUnixTimeToLongDateTime,
|
||||
formatUnixTimeToLongDate,
|
||||
@@ -56,6 +59,9 @@ export function useReconciliationStatementPageBase() {
|
||||
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
|
||||
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
|
||||
|
||||
const allChartTypes = computed<TypeAndDisplayName[]>(() => getAllTrendChartTypes());
|
||||
const allDateAggregationTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsDateAggregationTypesWithShortName());
|
||||
|
||||
const currentAccount = computed(() => allAccountsMap.value[accountId.value]);
|
||||
const currentAccountCurrency = computed<string>(() => currentAccount.value?.currency ?? defaultCurrency.value);
|
||||
const isCurrentLiabilityAccount = computed<boolean>(() => currentAccount.value?.isLiability ?? false);
|
||||
@@ -266,6 +272,8 @@ export function useReconciliationStatementPageBase() {
|
||||
fiscalYearStart,
|
||||
currentTimezoneOffsetMinutes,
|
||||
defaultCurrency,
|
||||
allChartTypes,
|
||||
allDateAggregationTypes,
|
||||
currentAccount,
|
||||
currentAccountCurrency,
|
||||
isCurrentLiabilityAccount,
|
||||
|
||||
@@ -14,6 +14,34 @@
|
||||
<v-tooltip activator="parent">{{ tt('Refresh') }}</v-tooltip>
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn density="comfortable" color="default" variant="text" class="ml-2"
|
||||
:icon="true" :disabled="loading"
|
||||
v-if="showAccountBalanceTrendsCharts">
|
||||
<v-icon :icon="mdiTuneVertical" />
|
||||
<v-menu activator="parent">
|
||||
<v-list>
|
||||
<v-list-subheader :title="tt('Chart Type')"/>
|
||||
<v-list-item :key="type.type"
|
||||
:prepend-icon="chartTypeIconMap[type.type]"
|
||||
:append-icon="chartType === type.type ? mdiCheck : undefined"
|
||||
:title="type.displayName"
|
||||
@click="chartType = type.type"
|
||||
v-for="type in allChartTypes"></v-list-item>
|
||||
<v-divider class="my-2"/>
|
||||
<v-list-subheader :title="tt('Time Granularity')"/>
|
||||
<v-list-item :prepend-icon="mdiCalendarTodayOutline"
|
||||
:append-icon="chartDataDateAggregationType === undefined ? mdiCheck : undefined"
|
||||
:title="tt('granularity.Daily')"
|
||||
@click="chartDataDateAggregationType = undefined"></v-list-item>
|
||||
<v-list-item :key="dateAggregationType.type"
|
||||
:prepend-icon="chartDataDateAggregationTypeIconMap[dateAggregationType.type]"
|
||||
:append-icon="chartDataDateAggregationType === dateAggregationType.type ? mdiCheck : undefined"
|
||||
:title="dateAggregationType.displayName"
|
||||
@click="chartDataDateAggregationType = dateAggregationType.type"
|
||||
v-for="dateAggregationType in allDateAggregationTypes"></v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn density="comfortable" color="default" variant="text" class="ml-2"
|
||||
:icon="true" :disabled="loading">
|
||||
<v-icon :icon="mdiDotsVertical" />
|
||||
@@ -27,6 +55,15 @@
|
||||
:title="tt('Update Closing Balance')"
|
||||
@click="updateClosingBalance()"></v-list-item>
|
||||
<v-divider class="my-2"/>
|
||||
<v-list-item :prepend-icon="mdiChartBoxOutline"
|
||||
:title="tt('Show Account Balance Trends')"
|
||||
@click="showAccountBalanceTrendsCharts = true"
|
||||
v-if="!showAccountBalanceTrendsCharts"></v-list-item>
|
||||
<v-list-item :prepend-icon="mdiListBoxOutline"
|
||||
:title="tt('Show Transaction List')"
|
||||
@click="showAccountBalanceTrendsCharts = false"
|
||||
v-if="showAccountBalanceTrendsCharts"></v-list-item>
|
||||
<v-divider class="my-2"/>
|
||||
<v-list-item :prepend-icon="mdiComma"
|
||||
:disabled="!reconciliationStatements || !reconciliationStatements.transactions || reconciliationStatements.transactions.length < 1"
|
||||
@click="exportReconciliationStatements(KnownFileType.CSV)">
|
||||
@@ -109,6 +146,7 @@
|
||||
:no-data-text="loading ? '' : tt('No transaction data')"
|
||||
v-model:items-per-page="countPerPage"
|
||||
v-model:page="currentPage"
|
||||
v-if="!showAccountBalanceTrendsCharts"
|
||||
>
|
||||
<template #item.time="{ item }">
|
||||
<span>{{ getDisplayDateTime(item) }}</span>
|
||||
@@ -187,6 +225,27 @@
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
<account-balance-trends-chart
|
||||
:type="chartType"
|
||||
:date-aggregation-type="chartDataDateAggregationType"
|
||||
:fiscal-year-start="fiscalYearStart"
|
||||
:items="[]"
|
||||
:legend-name="isCurrentLiabilityAccount ? tt('Account Outstanding Balance') : tt('Account Balance')"
|
||||
:account-currency="currentAccountCurrency"
|
||||
:skeleton="true"
|
||||
v-if="showAccountBalanceTrendsCharts && loading"
|
||||
/>
|
||||
|
||||
<account-balance-trends-chart
|
||||
:type="chartType"
|
||||
:date-aggregation-type="chartDataDateAggregationType"
|
||||
:fiscal-year-start="fiscalYearStart"
|
||||
:items="reconciliationStatements?.transactions"
|
||||
:legend-name="isCurrentLiabilityAccount ? tt('Account Outstanding Balance') : tt('Account Balance')"
|
||||
:account-currency="currentAccountCurrency"
|
||||
v-if="showAccountBalanceTrendsCharts && !loading"
|
||||
/>
|
||||
|
||||
<v-card-text class="overflow-y-visible">
|
||||
<div class="w-100 d-flex justify-center mt-2 mt-sm-4 mt-md-6 gap-4">
|
||||
<v-btn color="secondary" variant="tonal"
|
||||
@@ -219,6 +278,7 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||
import { useTransactionsStore } from '@/stores/transaction.ts';
|
||||
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
import { TrendChartType, ChartDateAggregationType } from '@/core/statistics.ts';
|
||||
import { KnownFileType } from '@/core/file.ts';
|
||||
import { Transaction, type TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts';
|
||||
|
||||
@@ -229,7 +289,16 @@ import { startDownloadFile } from '@/lib/ui/common.ts';
|
||||
import {
|
||||
mdiRefresh,
|
||||
mdiArrowRight,
|
||||
mdiTuneVertical,
|
||||
mdiDotsVertical,
|
||||
mdiCheck,
|
||||
mdiChartBoxOutline,
|
||||
mdiListBoxOutline,
|
||||
mdiChartBar,
|
||||
mdiChartAreasplineVariant,
|
||||
mdiCalendarTodayOutline,
|
||||
mdiCalendarMonthOutline,
|
||||
mdiLayersTripleOutline,
|
||||
mdiInvoiceTextPlusOutline,
|
||||
mdiInvoiceTextEditOutline,
|
||||
mdiComma,
|
||||
@@ -258,10 +327,13 @@ const {
|
||||
endTime,
|
||||
reconciliationStatements,
|
||||
currentTimezoneOffsetMinutes,
|
||||
allAccountsMap,
|
||||
allCategoriesMap,
|
||||
fiscalYearStart,
|
||||
allChartTypes,
|
||||
allDateAggregationTypes,
|
||||
currentAccountCurrency,
|
||||
isCurrentLiabilityAccount,
|
||||
allAccountsMap,
|
||||
allCategoriesMap,
|
||||
exportFileName,
|
||||
displayStartDateTime,
|
||||
displayEndDateTime,
|
||||
@@ -283,6 +355,18 @@ const accountsStore = useAccountsStore();
|
||||
const transactionCategoriesStore = useTransactionCategoriesStore();
|
||||
const transactionsStore = useTransactionsStore();
|
||||
|
||||
const chartTypeIconMap = {
|
||||
[TrendChartType.Column.type]: mdiChartBar,
|
||||
[TrendChartType.Area.type]: mdiChartAreasplineVariant,
|
||||
};
|
||||
|
||||
const chartDataDateAggregationTypeIconMap = {
|
||||
[ChartDateAggregationType.Month.type]: mdiCalendarMonthOutline,
|
||||
[ChartDateAggregationType.Quarter.type]: mdiLayersTripleOutline,
|
||||
[ChartDateAggregationType.Year.type]: mdiLayersTripleOutline,
|
||||
[ChartDateAggregationType.FiscalYear.type]: mdiLayersTripleOutline,
|
||||
};
|
||||
|
||||
const amountInputDialog = useTemplateRef<AmountInputDialogType>('amountInputDialog');
|
||||
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||
const editDialog = useTemplateRef<EditDialogType>('editDialog');
|
||||
@@ -291,6 +375,9 @@ const showState = ref<boolean>(false);
|
||||
const loading = ref<boolean>(false);
|
||||
const currentPage = ref<number>(1);
|
||||
const countPerPage = ref<number>(10);
|
||||
const showAccountBalanceTrendsCharts = ref<boolean>(false);
|
||||
const chartType = ref<number>(TrendChartType.Default.type);
|
||||
const chartDataDateAggregationType = ref<number | undefined>(undefined);
|
||||
|
||||
let rejectFunc: ((reason?: unknown) => void) | null = null;
|
||||
|
||||
@@ -371,6 +458,9 @@ function open(options: { accountId: string, startTime: number, endTime: number }
|
||||
reconciliationStatements.value = undefined;
|
||||
currentPage.value = 1;
|
||||
countPerPage.value = 10;
|
||||
showAccountBalanceTrendsCharts.value = false;
|
||||
chartType.value = TrendChartType.Default.type;
|
||||
chartDataDateAggregationType.value = undefined;
|
||||
showState.value = true;
|
||||
loading.value = true;
|
||||
|
||||
|
||||
@@ -716,7 +716,7 @@ import {
|
||||
getMonth,
|
||||
getBrowserTimezoneOffsetMinutes,
|
||||
getActualUnixTimeForStore,
|
||||
getSpecifiedDayFirstUnixTime,
|
||||
getDayFirstUnixTimeBySpecifiedUnixTime,
|
||||
getYearMonthFirstUnixTime,
|
||||
getYearMonthLastUnixTime,
|
||||
getShiftedDateRangeAndDateType,
|
||||
@@ -1275,7 +1275,7 @@ function changeDateFilter(dateRange: TimeRangeAndDateType | number | null): void
|
||||
if (dateRange === DateRange.Custom.type || (isObject(dateRange) && dateRange.dateType === DateRange.Custom.type && !dateRange.minTime && !dateRange.maxTime)) { // Custom
|
||||
if (!query.value.minTime || !query.value.maxTime) {
|
||||
customMaxDatetime.value = getActualUnixTimeForStore(getCurrentUnixTime(), currentTimezoneOffsetMinutes.value, getBrowserTimezoneOffsetMinutes());
|
||||
customMinDatetime.value = getSpecifiedDayFirstUnixTime(customMaxDatetime.value);
|
||||
customMinDatetime.value = getDayFirstUnixTimeBySpecifiedUnixTime(customMaxDatetime.value);
|
||||
} else {
|
||||
customMaxDatetime.value = query.value.maxTime;
|
||||
customMinDatetime.value = query.value.minTime;
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<f7-list strong inset dividers media-list
|
||||
class="skeleton-text margin-vertical transaction-info-list reconciliation-statement-list"
|
||||
v-if="finishQuery && loading">
|
||||
v-if="finishQuery && !showAccountBalanceTrendsCharts && loading">
|
||||
<ul>
|
||||
<f7-list-item chevron-center
|
||||
:key="index"
|
||||
@@ -130,14 +130,14 @@
|
||||
</f7-list>
|
||||
|
||||
<f7-list strong inset dividers class="margin-vertical"
|
||||
v-if="finishQuery && !loading && (!allReconciliationStatementVirtualListItems || !allReconciliationStatementVirtualListItems.length)">
|
||||
v-if="finishQuery && !showAccountBalanceTrendsCharts && !loading && (!allReconciliationStatementVirtualListItems || !allReconciliationStatementVirtualListItems.length)">
|
||||
<f7-list-item :title="tt('No transaction data')"></f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
<f7-list strong inset dividers media-list virtual-list
|
||||
class="margin-vertical transaction-info-list reconciliation-statement-list"
|
||||
:virtual-list-params="{ items: allReconciliationStatementVirtualListItems, renderExternal, height: 'auto' }"
|
||||
v-if="finishQuery && !loading && allReconciliationStatementVirtualListItems && allReconciliationStatementVirtualListItems.length">
|
||||
v-if="finishQuery && !showAccountBalanceTrendsCharts && !loading && allReconciliationStatementVirtualListItems && allReconciliationStatementVirtualListItems.length">
|
||||
<ul>
|
||||
<f7-list-item chevron-center
|
||||
:key="item.index"
|
||||
@@ -231,6 +231,50 @@
|
||||
</ul>
|
||||
</f7-list>
|
||||
|
||||
<f7-card v-if="finishQuery && showAccountBalanceTrendsCharts">
|
||||
<f7-card-header class="no-border display-block">
|
||||
<div class="statistics-chart-header display-flex full-line justify-content-space-between">
|
||||
<div></div>
|
||||
<div class="align-self-flex-end">
|
||||
<span style="margin-right: 4px;">{{ tt('Time Granularity') }}</span>
|
||||
<f7-link :class="{ 'disabled': loading }" href="#" popover-open=".chart-data-date-aggregation-type-popover-menu">{{ chartDataDateAggregationTypeDisplayName }}</f7-link>
|
||||
</div>
|
||||
</div>
|
||||
</f7-card-header>
|
||||
<f7-card-content style="margin-top: -14px" :padding="false">
|
||||
<account-balance-trends-bar-chart
|
||||
:loading="loading"
|
||||
:date-aggregation-type="chartDataDateAggregationType"
|
||||
:fiscal-year-start="fiscalYearStart"
|
||||
:items="reconciliationStatements?.transactions"
|
||||
:account-currency="currentAccountCurrency"
|
||||
/>
|
||||
</f7-card-content>
|
||||
</f7-card>
|
||||
|
||||
<f7-popover class="chart-data-date-aggregation-type-popover-menu"
|
||||
v-model:opened="showChartDataDateAggregationTypePopover">
|
||||
<f7-list dividers>
|
||||
<f7-list-item :title="tt('granularity.Daily')"
|
||||
:class="{ 'list-item-selected': chartDataDateAggregationType === undefined }"
|
||||
key="daily"
|
||||
@click="setChartDataDateAggregationType(undefined)">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="chartDataDateAggregationType === undefined"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
<f7-list-item :title="dateAggregationType.displayName"
|
||||
:class="{ 'list-item-selected': chartDataDateAggregationType === dateAggregationType.type }"
|
||||
:key="dateAggregationType.type"
|
||||
v-for="dateAggregationType in allDateAggregationTypes"
|
||||
@click="setChartDataDateAggregationType(dateAggregationType.type)">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="chartDataDateAggregationType === dateAggregationType.type"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-popover>
|
||||
|
||||
<date-range-selection-sheet :title="tt('Custom Date Range')"
|
||||
:min-time="startTime"
|
||||
:max-time="endTime"
|
||||
@@ -256,6 +300,8 @@
|
||||
</f7-actions-group>
|
||||
<f7-actions-group>
|
||||
<f7-actions-button :class="{ 'disabled': loading }" @click="reload(true)">{{ tt('Refresh') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': loading }" @click="showAccountBalanceTrendsCharts = true" v-if="!showAccountBalanceTrendsCharts">{{ tt('Show Account Balance Trends') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': loading }" @click="showAccountBalanceTrendsCharts = false" v-if="showAccountBalanceTrendsCharts">{{ tt('Show Transaction List') }}</f7-actions-button>
|
||||
</f7-actions-group>
|
||||
<f7-actions-group>
|
||||
<f7-actions-button bold close>{{ tt('Cancel') }}</f7-actions-button>
|
||||
@@ -292,7 +338,7 @@ import { TransactionType } from '@/core/transaction.ts';
|
||||
import { TRANSACTION_MIN_AMOUNT, TRANSACTION_MAX_AMOUNT } from '@/consts/transaction.ts';
|
||||
import { type TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts';
|
||||
|
||||
import { isDefined, isEquals } from '@/lib/common.ts';
|
||||
import { isDefined, isEquals, findDisplayNameByType } from '@/lib/common.ts';
|
||||
import {
|
||||
getCurrentUnixTime,
|
||||
getDateTypeByDateRange,
|
||||
@@ -330,6 +376,7 @@ const {
|
||||
reconciliationStatements,
|
||||
firstDayOfWeek,
|
||||
fiscalYearStart,
|
||||
allDateAggregationTypes,
|
||||
currentTimezoneOffsetMinutes,
|
||||
isCurrentLiabilityAccount,
|
||||
allCategoriesMap,
|
||||
@@ -358,12 +405,15 @@ const finishQuery = ref<boolean>(false);
|
||||
const loading = ref<boolean>(false);
|
||||
const loadingError = ref<unknown | null>(null);
|
||||
const queryDateRangeType = ref<number>(DateRange.ThisMonth.type);
|
||||
const showAccountBalanceTrendsCharts = ref<boolean>(false);
|
||||
const chartDataDateAggregationType = ref<number | undefined>(undefined);
|
||||
const transactionToDelete = ref<TransactionReconciliationStatementResponseItem | null>(null);
|
||||
const newClosingBalance = ref<number>(0);
|
||||
const showCustomDateRangeSheet = ref<boolean>(false);
|
||||
const showNewClosingBalanceSheet = ref<boolean>(false);
|
||||
const showMoreActionSheet = ref<boolean>(false);
|
||||
const showDeleteActionSheet = ref<boolean>(false);
|
||||
const showChartDataDateAggregationTypePopover = ref<boolean>(false);
|
||||
const virtualDataItems = ref<ReconciliationStatementVirtualListData>({
|
||||
items: [],
|
||||
topPosition: 0
|
||||
@@ -407,6 +457,14 @@ const allReconciliationStatementVirtualListItems = computed<ReconciliationStatem
|
||||
return ret;
|
||||
});
|
||||
|
||||
const chartDataDateAggregationTypeDisplayName = computed<string>(() => {
|
||||
if (chartDataDateAggregationType.value === undefined) {
|
||||
return tt('granularity.Daily');
|
||||
}
|
||||
|
||||
return findDisplayNameByType(allDateAggregationTypes.value, chartDataDateAggregationType.value) || tt('Unknown');
|
||||
});
|
||||
|
||||
function getTransactionDomId(transaction: TransactionReconciliationStatementResponseItem): string {
|
||||
return 'transaction_' + transaction.id;
|
||||
}
|
||||
@@ -592,6 +650,11 @@ function removeTransaction(transaction: TransactionReconciliationStatementRespon
|
||||
});
|
||||
}
|
||||
|
||||
function setChartDataDateAggregationType(type: number | undefined): void {
|
||||
chartDataDateAggregationType.value = type;
|
||||
showChartDataDateAggregationTypePopover.value = false;
|
||||
}
|
||||
|
||||
function renderExternal(vl: unknown, vlData: ReconciliationStatementVirtualListData): void {
|
||||
virtualDataItems.value = vlData;
|
||||
}
|
||||
|
||||
@@ -638,7 +638,7 @@ import {
|
||||
getActualUnixTimeForStore,
|
||||
getYear,
|
||||
getMonth,
|
||||
getSpecifiedDayFirstUnixTime,
|
||||
getDayFirstUnixTimeBySpecifiedUnixTime,
|
||||
getYearMonthFirstUnixTime,
|
||||
getYearMonthLastUnixTime,
|
||||
getShiftedDateRangeAndDateType,
|
||||
@@ -1066,7 +1066,7 @@ function changeDateFilter(dateType: number): void {
|
||||
if (dateType === DateRange.Custom.type) { // Custom
|
||||
if (!query.value.minTime || !query.value.maxTime) {
|
||||
customMaxDatetime.value = getActualUnixTimeForStore(getCurrentUnixTime(), currentTimezoneOffsetMinutes.value, getBrowserTimezoneOffsetMinutes());
|
||||
customMinDatetime.value = getSpecifiedDayFirstUnixTime(customMaxDatetime.value);
|
||||
customMinDatetime.value = getDayFirstUnixTimeBySpecifiedUnixTime(customMaxDatetime.value);
|
||||
} else {
|
||||
customMaxDatetime.value = query.value.maxTime;
|
||||
customMinDatetime.value = query.value.minTime;
|
||||
|
||||
Reference in New Issue
Block a user