add transaction calendar
This commit is contained in:
@@ -317,6 +317,15 @@ 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();
|
||||
}
|
||||
@@ -792,6 +801,28 @@ export function getRecentDateRangeType(allRecentMonthDateRanges: LocalizedRecent
|
||||
return getRecentDateRangeTypeByDateType(allRecentMonthDateRanges, DateRange.Custom.type);
|
||||
}
|
||||
|
||||
export function getFullMonthDateRange(minTime: number, maxTime: number, firstDayOfWeek: number): TimeRangeAndDateType | null {
|
||||
if (isDateRangeMatchOneMonth(minTime, maxTime)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!minTime) {
|
||||
return getDateRangeByDateType(DateRange.ThisMonth.type, firstDayOfWeek);
|
||||
}
|
||||
|
||||
const monthFirstUnixTime = getMonthFirstUnixTimeBySpecifiedUnixTime(minTime);
|
||||
const monthLastUnixTime = getMonthLastUnixTimeBySpecifiedUnixTime(minTime);
|
||||
const dateType = getDateTypeByDateRange(monthFirstUnixTime, monthLastUnixTime, firstDayOfWeek, DateRangeScene.Normal);
|
||||
|
||||
const dateRange: TimeRangeAndDateType = {
|
||||
dateType: dateType,
|
||||
maxTime: monthLastUnixTime,
|
||||
minTime: monthFirstUnixTime
|
||||
};
|
||||
|
||||
return dateRange;
|
||||
}
|
||||
|
||||
export function getTimeValues(date: Date, is24Hour: boolean, isMeridiemIndicatorFirst: boolean): string[] {
|
||||
const hourMinuteSeconds = [
|
||||
getTwoDigitsString(is24Hour ? date.getHours() : getHourIn12HourFormat(date.getHours())),
|
||||
@@ -849,6 +880,20 @@ export function getCombinedDateAndTimeValues(date: Date, timeValues: string[], i
|
||||
return newDateTime;
|
||||
}
|
||||
|
||||
export function getMonthFirstDayOrCurrentDayShortDate(unixTime: number): string {
|
||||
const currentTime = moment();
|
||||
let dateTime = moment.unix(unixTime);
|
||||
|
||||
if (dateTime.year() === currentTime.year() && dateTime.month() === currentTime.month()) {
|
||||
return getShortDate(currentTime);
|
||||
}
|
||||
|
||||
dateTime = dateTime.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
|
||||
dateTime = dateTime.subtract(dateTime.date() - 1, 'days');
|
||||
|
||||
return getShortDate(dateTime);
|
||||
}
|
||||
|
||||
export function isDateRangeMatchFullYears(minTime: number, maxTime: number): boolean {
|
||||
const minDateTime = parseDateFromUnixTime(minTime).set({ millisecond: 0 });
|
||||
const maxDateTime = parseDateFromUnixTime(maxTime).set({ millisecond: 999 });
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "Einkommens- und Ausgabentrends",
|
||||
"View Details": "Details anzeigen",
|
||||
"Transaction List": "Transaktionsliste",
|
||||
"Transaction Calendar": "Transaction Calendar",
|
||||
"Transaction Details": "Transaktionsdetails",
|
||||
"Statistics & Analysis": "Statistiken & Analysen",
|
||||
"Account List": "Kontoliste",
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "Income and Expense Trends",
|
||||
"View Details": "View Details",
|
||||
"Transaction List": "Transaction List",
|
||||
"Transaction Calendar": "Transaction Calendar",
|
||||
"Transaction Details": "Transaction Details",
|
||||
"Statistics & Analysis": "Statistics & Analysis",
|
||||
"Account List": "Account List",
|
||||
|
||||
@@ -1507,6 +1507,7 @@
|
||||
"Income and Expense Trends": "Tendencias de ingresos y gastos",
|
||||
"View Details": "Ver detalles",
|
||||
"Transaction List": "Lista de transacciones",
|
||||
"Transaction Calendar": "Transaction Calendar",
|
||||
"Transaction Details": "Detalles de la transacción",
|
||||
"Statistics & Analysis": "Estadísticas y análisis",
|
||||
"Account List": "Lista de cuentas",
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "Andamento entrate e uscite",
|
||||
"View Details": "Visualizza dettagli",
|
||||
"Transaction List": "Elenco transazioni",
|
||||
"Transaction Calendar": "Transaction Calendar",
|
||||
"Transaction Details": "Dettagli transazione",
|
||||
"Statistics & Analysis": "Statistiche e analisi",
|
||||
"Account List": "Elenco account",
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "収入と支出の傾向",
|
||||
"View Details": "詳細を表示",
|
||||
"Transaction List": "取引リスト",
|
||||
"Transaction Calendar": "Transaction Calendar",
|
||||
"Transaction Details": "取引の詳細",
|
||||
"Statistics & Analysis": "統計と分析",
|
||||
"Account List": "口座リスト",
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "Тенденции доходов и расходов",
|
||||
"View Details": "Просмотреть детали",
|
||||
"Transaction List": "Список транзакций",
|
||||
"Transaction Calendar": "Transaction Calendar",
|
||||
"Transaction Details": "Детали транзакции",
|
||||
"Statistics & Analysis": "Статистика и анализ",
|
||||
"Account List": "Список счетов",
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "Тренди доходів і витрат",
|
||||
"View Details": "Переглянути деталі",
|
||||
"Transaction List": "Список транзакцій",
|
||||
"Transaction Calendar": "Transaction Calendar",
|
||||
"Transaction Details": "Деталі по транзакціях",
|
||||
"Statistics & Analysis": "Статистика та аналіз",
|
||||
"Account List": "Список рахунків",
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "Xu hướng thu nhập và chi tiêu",
|
||||
"View Details": "Xem chi tiết",
|
||||
"Transaction List": "Danh sách giao dịch",
|
||||
"Transaction Calendar": "Transaction Calendar",
|
||||
"Transaction Details": "Chi tiết giao dịch",
|
||||
"Statistics & Analysis": "Thống kê & Phân tích",
|
||||
"Account List": "Danh sách tài khoản",
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "收入与支出趋势",
|
||||
"View Details": "查看详情",
|
||||
"Transaction List": "交易列表",
|
||||
"Transaction Calendar": "交易日历",
|
||||
"Transaction Details": "交易详情",
|
||||
"Statistics & Analysis": "统计分析",
|
||||
"Account List": "账户列表",
|
||||
|
||||
@@ -1508,6 +1508,7 @@
|
||||
"Income and Expense Trends": "收入與支出趨勢",
|
||||
"View Details": "查看詳情",
|
||||
"Transaction List": "交易清單",
|
||||
"Transaction Calendar": "交易日曆",
|
||||
"Transaction Details": "交易詳情",
|
||||
"Statistics & Analysis": "統計分析",
|
||||
"Account List": "帳戶清單",
|
||||
|
||||
@@ -103,6 +103,7 @@ const router = createRouter({
|
||||
component: TransactionListPage,
|
||||
beforeEnter: checkLogin,
|
||||
props: route => ({
|
||||
initPageType: route.query['pageType'],
|
||||
initDateType: route.query['dateType'],
|
||||
initMaxTime: route.query['maxTime'],
|
||||
initMinTime: route.query['minTime'],
|
||||
|
||||
@@ -87,18 +87,21 @@ export interface TransactionListFilter extends TransactionListPartialFilter {
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
export interface TransactionTotalAmount {
|
||||
expense: number;
|
||||
incompleteExpense: boolean;
|
||||
income: number;
|
||||
incompleteIncome: boolean;
|
||||
}
|
||||
|
||||
export interface TransactionMonthList {
|
||||
readonly year: number;
|
||||
readonly month: number;
|
||||
readonly yearMonth: string;
|
||||
opened: boolean;
|
||||
readonly items: Transaction[];
|
||||
readonly totalAmount: {
|
||||
expense: number;
|
||||
incompleteExpense: boolean;
|
||||
income: number;
|
||||
incompleteIncome: boolean;
|
||||
};
|
||||
readonly totalAmount: TransactionTotalAmount;
|
||||
readonly dailyTotalAmounts: Record<string, TransactionTotalAmount>;
|
||||
}
|
||||
|
||||
export const useTransactionsStore = defineStore('transactions', () => {
|
||||
@@ -216,7 +219,8 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
incompleteExpense: true,
|
||||
income: 0,
|
||||
incompleteIncome: true
|
||||
}
|
||||
},
|
||||
dailyTotalAmounts: {}
|
||||
};
|
||||
|
||||
transactions.value.push(monthList);
|
||||
@@ -319,6 +323,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
let totalIncome = 0;
|
||||
let hasUnCalculatedTotalExpense = false;
|
||||
let hasUnCalculatedTotalIncome = false;
|
||||
const dailyTotalAmounts: Record<string, TransactionTotalAmount> = {};
|
||||
|
||||
const allAccountIdsMap: Record<string, boolean> = {};
|
||||
let totalAccountIdsCount = 0;
|
||||
@@ -336,6 +341,18 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
|
||||
for (let i = 0; i < transactionMonthList.items.length; i++) {
|
||||
const transaction = transactionMonthList.items[i];
|
||||
const transactionDay = isNumber(transaction.day) ? transaction.day.toString() : '0';
|
||||
let dailyTotalAmount = dailyTotalAmounts[transactionDay];
|
||||
|
||||
if (!dailyTotalAmount) {
|
||||
dailyTotalAmount = {
|
||||
expense: 0,
|
||||
incompleteExpense: false,
|
||||
income: 0,
|
||||
incompleteIncome: false
|
||||
};
|
||||
dailyTotalAmounts[transactionDay] = dailyTotalAmount;
|
||||
}
|
||||
|
||||
let amount = transaction.sourceAmount;
|
||||
let account = transaction.sourceAccount;
|
||||
@@ -357,8 +374,10 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
if (!isNumber(balance)) {
|
||||
if (transaction.type === TransactionType.Expense) {
|
||||
hasUnCalculatedTotalExpense = true;
|
||||
dailyTotalAmount.incompleteExpense = true;
|
||||
} else if (transaction.type === TransactionType.Income) {
|
||||
hasUnCalculatedTotalIncome = true;
|
||||
dailyTotalAmount.incompleteIncome = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -369,8 +388,10 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
|
||||
if (transaction.type === TransactionType.Expense) {
|
||||
totalExpense += amount;
|
||||
dailyTotalAmount.expense += amount;
|
||||
} else if (transaction.type === TransactionType.Income) {
|
||||
totalIncome += amount;
|
||||
dailyTotalAmount.income += amount;
|
||||
} else if (transaction.type === TransactionType.Transfer && totalAccountIdsCount > 0) {
|
||||
if (allAccountIdsMap[transaction.sourceAccountId] && allAccountIdsMap[transaction.destinationAccountId]) {
|
||||
// Do Nothing
|
||||
@@ -382,8 +403,10 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
// Do Nothing
|
||||
} else if (allAccountIdsMap[transaction.sourceAccountId] || (transaction.sourceAccount && allAccountIdsMap[transaction.sourceAccount.parentId])) {
|
||||
totalExpense += amount;
|
||||
dailyTotalAmount.expense += amount;
|
||||
} else if (allAccountIdsMap[transaction.destinationAccountId] || (transaction.destinationAccount && allAccountIdsMap[transaction.destinationAccount.parentId])) {
|
||||
totalIncome += amount;
|
||||
dailyTotalAmount.income += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -392,6 +415,29 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
transactionMonthList.totalAmount.incompleteExpense = incomplete || hasUnCalculatedTotalExpense;
|
||||
transactionMonthList.totalAmount.income = Math.floor(totalIncome);
|
||||
transactionMonthList.totalAmount.incompleteIncome = incomplete || hasUnCalculatedTotalIncome;
|
||||
|
||||
for (const day in transactionMonthList.dailyTotalAmounts) {
|
||||
if (!Object.prototype.hasOwnProperty.call(transactionMonthList.dailyTotalAmounts, day)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
delete transactionMonthList.dailyTotalAmounts[day];
|
||||
}
|
||||
|
||||
for (const day in dailyTotalAmounts) {
|
||||
if (!Object.prototype.hasOwnProperty.call(dailyTotalAmounts, day)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dailyTotalAmount = dailyTotalAmounts[day];
|
||||
|
||||
transactionMonthList.dailyTotalAmounts[day] = {
|
||||
expense: Math.floor(dailyTotalAmount.expense),
|
||||
incompleteExpense: incomplete || dailyTotalAmount.incompleteExpense,
|
||||
income: Math.floor(dailyTotalAmount.income),
|
||||
incompleteIncome: incomplete || dailyTotalAmount.incompleteIncome
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function fillTransactionObject(transaction: Transaction, currentUtcOffset: number): void {
|
||||
@@ -691,9 +737,11 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
return changed;
|
||||
}
|
||||
|
||||
function getTransactionListPageParams(): string {
|
||||
function getTransactionListPageParams(pageType: number): string {
|
||||
const querys: string[] = [];
|
||||
|
||||
querys.push('pageType=' + pageType);
|
||||
|
||||
if (transactionsFilter.value.type) {
|
||||
querys.push('type=' + transactionsFilter.value.type);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
|
||||
import { type TransactionListFilter, type TransactionMonthList, useTransactionsStore } from '@/stores/transaction.ts';
|
||||
|
||||
import type { TypeAndName } from '@/core/base.ts';
|
||||
import { type LocalizedDateRange, DateRange, DateRangeScene } from '@/core/datetime.ts';
|
||||
import { AccountType } from '@/core/account.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
@@ -18,6 +19,10 @@ import type { TransactionCategory } from '@/models/transaction_category.ts';
|
||||
import type { TransactionTag } from '@/models/transaction_tag.ts';
|
||||
import type { Transaction } from '@/models/transaction.ts';
|
||||
|
||||
import {
|
||||
arrangeArrayWithNewStartIndex
|
||||
} from '@/lib/common.ts';
|
||||
|
||||
import {
|
||||
getUtcOffsetByUtcOffsetMinutes,
|
||||
getTimezoneOffset,
|
||||
@@ -35,9 +40,39 @@ import {
|
||||
categoryTypeToTransactionType
|
||||
} from '@/lib/category.ts';
|
||||
|
||||
export class TransactionListPageType implements TypeAndName {
|
||||
private static readonly allInstances: TransactionListPageType[] = [];
|
||||
private static readonly allInstancesByType: Record<number, TransactionListPageType> = {};
|
||||
|
||||
public static readonly List = new TransactionListPageType(0, 'Transaction List');
|
||||
public static readonly Calendar = new TransactionListPageType(1, 'Transaction Calendar');
|
||||
|
||||
public static readonly Default = TransactionListPageType.List;
|
||||
|
||||
public readonly type: number;
|
||||
public readonly name: string;
|
||||
|
||||
private constructor(type: number, name: string) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
|
||||
TransactionListPageType.allInstances.push(this);
|
||||
TransactionListPageType.allInstancesByType[type] = this;
|
||||
}
|
||||
|
||||
public static values(): TransactionListPageType[] {
|
||||
return TransactionListPageType.allInstances;
|
||||
}
|
||||
|
||||
public static valueOf(type: number): TransactionListPageType | undefined {
|
||||
return TransactionListPageType.allInstancesByType[type];
|
||||
}
|
||||
}
|
||||
|
||||
export function useTransactionListPageBase() {
|
||||
const {
|
||||
tt,
|
||||
getAllLongWeekdayNames,
|
||||
getAllDateRanges,
|
||||
formatUnixTimeToLongDateTime,
|
||||
formatUnixTimeToLongDate,
|
||||
@@ -54,12 +89,15 @@ export function useTransactionListPageBase() {
|
||||
const transactionTagsStore = useTransactionTagsStore();
|
||||
const transactionsStore = useTransactionsStore();
|
||||
|
||||
const pageType = ref<number>(TransactionListPageType.List.type);
|
||||
const loading = ref<boolean>(true);
|
||||
const customMinDatetime = ref<number>(0);
|
||||
const customMaxDatetime = ref<number>(0);
|
||||
const currentCalendarDate = ref<string>('');
|
||||
|
||||
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
|
||||
const firstDayOfWeek = computed<number>(() => userStore.currentUserFirstDayOfWeek);
|
||||
const dayNames = computed<string[]>(() => arrangeArrayWithNewStartIndex(getAllLongWeekdayNames(), firstDayOfWeek.value));
|
||||
const defaultCurrency = computed<string>(() => getUnifiedSelectedAccountsCurrencyOrDefaultCurrency(allAccountsMap.value, queryAllFilterAccountIds.value, userStore.currentUserDefaultCurrency));
|
||||
const showTotalAmountInTransactionListPage = computed<boolean>(() => settingsStore.appSettings.showTotalAmountInTransactionListPage);
|
||||
const showTagInTransactionListPage = computed<boolean>(() => settingsStore.appSettings.showTagInTransactionListPage);
|
||||
@@ -110,6 +148,16 @@ export function useTransactionListPageBase() {
|
||||
const allTransactionTags = computed<Record<string, TransactionTag>>(() => transactionTagsStore.allTransactionTagsMap);
|
||||
const allAvailableTagsCount = computed<number>(() => transactionTagsStore.allAvailableTagsCount);
|
||||
|
||||
const displayPageTypeName = computed<string>(() => {
|
||||
const type = TransactionListPageType.valueOf(pageType.value);
|
||||
|
||||
if (type) {
|
||||
return tt(type.name);
|
||||
}
|
||||
|
||||
return tt(TransactionListPageType.List.name);
|
||||
});
|
||||
|
||||
const query = computed<TransactionListFilter>(() => transactionsStore.transactionsFilter);
|
||||
const queryDateRangeName = computed<string>(() => {
|
||||
if (query.value.dateType === DateRange.All.type) {
|
||||
@@ -268,12 +316,15 @@ export function useTransactionListPageBase() {
|
||||
|
||||
return {
|
||||
// states
|
||||
pageType,
|
||||
loading,
|
||||
customMinDatetime,
|
||||
customMaxDatetime,
|
||||
currentCalendarDate,
|
||||
// computed states
|
||||
currentTimezoneOffsetMinutes,
|
||||
firstDayOfWeek,
|
||||
dayNames,
|
||||
defaultCurrency,
|
||||
showTotalAmountInTransactionListPage,
|
||||
showTagInTransactionListPage,
|
||||
@@ -286,6 +337,7 @@ export function useTransactionListPageBase() {
|
||||
allAvailableCategoriesCount,
|
||||
allTransactionTags,
|
||||
allAvailableTagsCount,
|
||||
displayPageTypeName,
|
||||
query,
|
||||
queryDateRangeName,
|
||||
queryMinTime,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<router-link to="/transaction/list?dateType=7">
|
||||
<router-link to="/transaction/list?pageType=0&dateType=7">
|
||||
<v-icon class="nav-item-icon" :icon="mdiListBoxOutline"/>
|
||||
<span class="nav-item-title d-inline-block">{{ tt('Transaction Details') }}</span>
|
||||
<v-btn density="compact" color="secondary" variant="text" size="22"
|
||||
|
||||
@@ -5,16 +5,33 @@
|
||||
<v-layout>
|
||||
<v-navigation-drawer :permanent="alwaysShowNav" v-model="showNav">
|
||||
<div class="mx-6 my-4">
|
||||
<btn-vertical-group :disabled="loading" :buttons="[
|
||||
{ name: tt('All Types'), value: 0 },
|
||||
{ name: tt('Modify Balance'), value: 1 },
|
||||
{ name: tt('Income'), value: 2 },
|
||||
{ name: tt('Expense'), value: 3 },
|
||||
{ name: tt('Transfer'), value: 4 }
|
||||
]" v-model="queryType" />
|
||||
<btn-vertical-group :disabled="loading" :buttons="TransactionListPageType.values().map(item => {
|
||||
return {
|
||||
name: tt(item.name),
|
||||
value: item.type
|
||||
}
|
||||
})" v-model="queryPageType" />
|
||||
</div>
|
||||
<v-divider />
|
||||
<div class="mx-6 mt-4">
|
||||
<span class="text-subtitle-2">{{ tt('Transaction Type') }}</span>
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="type"
|
||||
class="mt-2"
|
||||
density="compact"
|
||||
:disabled="loading"
|
||||
:items="[
|
||||
{ displayName: tt('All Types'), type: 0 },
|
||||
{ displayName: tt('Modify Balance'), type: 1 },
|
||||
{ displayName: tt('Income'), type: 2 },
|
||||
{ displayName: tt('Expense'), type: 3 },
|
||||
{ displayName: tt('Transfer'), type: 4 }
|
||||
]"
|
||||
v-model="queryType"
|
||||
/>
|
||||
</div>
|
||||
<div class="mx-6 mt-4" v-if="pageType === TransactionListPageType.List.type">
|
||||
<span class="text-subtitle-2">{{ tt('Transactions Per Page') }}</span>
|
||||
<v-select class="mt-2" density="compact" :disabled="loading"
|
||||
:items="[ 5, 10, 15, 20, 25, 30, 50 ]"
|
||||
@@ -119,6 +136,39 @@
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text class="transaction-calendar-container pt-0" v-if="pageType === TransactionListPageType.Calendar.type">
|
||||
<vue-date-picker inline auto-apply model-type="yyyy-M-d"
|
||||
month-name-format="long"
|
||||
class="justify-content-center"
|
||||
:disable-month-year-select="true"
|
||||
:month-change-on-scroll="false"
|
||||
:month-change-on-arrows="false"
|
||||
:enable-time-picker="false"
|
||||
:hide-offset-dates="true"
|
||||
:min-date="getShortDate(parseDateFromUnixTime(query.minTime))"
|
||||
:max-date="getShortDate(parseDateFromUnixTime(query.maxTime))"
|
||||
:disabled-dates="noTransactionInMonthDay"
|
||||
:clearable="false"
|
||||
:dark="isDarkMode"
|
||||
:week-start="firstDayOfWeek"
|
||||
:day-names="dayNames"
|
||||
v-model="currentCalendarDate">
|
||||
<template #month="{ text }">
|
||||
{{ getMonthShortName(text) }}
|
||||
</template>
|
||||
<template #month-overlay-value="{ text }">
|
||||
{{ getMonthShortName(text) }}
|
||||
</template>
|
||||
<template #day="{ day }">
|
||||
<div class="block">
|
||||
<div :class="{ 'font-weight-bold': currentMonthTransactionData && currentMonthTransactionData.dailyTotalAmounts[day] }">{{ day }}</div>
|
||||
<div class="text-income" v-if="currentMonthTransactionData && currentMonthTransactionData.dailyTotalAmounts[day] && currentMonthTransactionData.dailyTotalAmounts[day].income">{{ getDisplayMonthTotalAmount(currentMonthTransactionData.dailyTotalAmounts[day].income, defaultCurrency, '', currentMonthTransactionData.dailyTotalAmounts[day].incompleteIncome) }}</div>
|
||||
<div class="text-expense" v-if="currentMonthTransactionData && currentMonthTransactionData.dailyTotalAmounts[day] && currentMonthTransactionData.dailyTotalAmounts[day].expense">{{ getDisplayMonthTotalAmount(currentMonthTransactionData.dailyTotalAmounts[day].expense, defaultCurrency, '', currentMonthTransactionData.dailyTotalAmounts[day].incompleteExpense) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</vue-date-picker>
|
||||
</v-card-text>
|
||||
|
||||
<v-table class="transaction-table" :hover="!loading">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -471,7 +521,7 @@
|
||||
:class="{ 'disabled': loading, 'has-bottom-border': idx < transactions.length - 1 }"
|
||||
v-for="(transaction, idx) in transactions">
|
||||
<tr class="transaction-list-row-date no-hover text-sm"
|
||||
v-if="idx === 0 || (idx > 0 && (transaction.date !== transactions[idx - 1].date))">
|
||||
v-if="pageType === TransactionListPageType.List.type && (idx === 0 || (idx > 0 && (transaction.date !== transactions[idx - 1].date)))">
|
||||
<td :colspan="showTagInTransactionListPage ? 6 : 5" class="font-weight-bold">
|
||||
<div class="d-flex align-center">
|
||||
<span>{{ getDisplayLongDate(transaction) }}</span>
|
||||
@@ -538,7 +588,7 @@
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
<div class="mt-2 mb-4">
|
||||
<div class="mt-2 mb-4" v-if="pageType === TransactionListPageType.List.type">
|
||||
<pagination-buttons :totalPageCount="totalPageCount"
|
||||
v-model="paginationCurrentPage"></pagination-buttons>
|
||||
</div>
|
||||
@@ -593,10 +643,10 @@ import { TransactionEditPageType } from '@/views/base/transactions/TransactionEd
|
||||
|
||||
import { ref, computed, useTemplateRef, watch, nextTick } from 'vue';
|
||||
import { useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useDisplay } from 'vuetify';
|
||||
import { useDisplay, useTheme } from 'vuetify';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
import { useTransactionListPageBase } from '@/views/base/transactions/TransactionListPageBase.ts';
|
||||
import { TransactionListPageType, useTransactionListPageBase } from '@/views/base/transactions/TransactionListPageBase.ts';
|
||||
|
||||
import { useSettingsStore } from '@/stores/setting.ts';
|
||||
import { useAccountsStore } from '@/stores/account.ts';
|
||||
@@ -614,6 +664,7 @@ import {
|
||||
DateRange
|
||||
} from '@/core/datetime.ts';
|
||||
import { AmountFilterType } from '@/core/numeral.ts';
|
||||
import { ThemeType } from '@/core/theme.ts';
|
||||
import { TransactionType, TransactionTagFilterType } from '@/core/transaction.ts';
|
||||
import { TemplateType } from '@/core/template.ts';
|
||||
import type { TransactionCategory } from '@/models/transaction_category.ts';
|
||||
@@ -628,8 +679,10 @@ import {
|
||||
import {
|
||||
getCurrentUnixTime,
|
||||
parseDateFromUnixTime,
|
||||
getShortDate,
|
||||
getYear,
|
||||
getMonth,
|
||||
getDay,
|
||||
getSpecifiedDayFirstUnixTime,
|
||||
getBrowserTimezoneOffsetMinutes,
|
||||
getActualUnixTimeForStore,
|
||||
@@ -640,6 +693,8 @@ import {
|
||||
getDateRangeByDateType,
|
||||
getDateRangeByBillingCycleDateType,
|
||||
getRecentDateRangeType,
|
||||
getFullMonthDateRange,
|
||||
getMonthFirstDayOrCurrentDayShortDate,
|
||||
isDateRangeMatchOneMonth
|
||||
} from '@/lib/datetime.ts';
|
||||
import {
|
||||
@@ -671,6 +726,7 @@ import {
|
||||
} from '@mdi/js';
|
||||
|
||||
interface TransactionListProps {
|
||||
initPageType?: string;
|
||||
initDateType?: string,
|
||||
initMaxTime?: string,
|
||||
initMinTime?: string,
|
||||
@@ -703,20 +759,25 @@ interface TransactionListDisplayTotalAmount {
|
||||
|
||||
const router = useRouter();
|
||||
const display = useDisplay();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
tt,
|
||||
getAllRecentMonthDateRanges,
|
||||
getAllTransactionTagFilterTypes,
|
||||
getMonthShortName,
|
||||
getWeekdayLongName
|
||||
} = useI18n();
|
||||
|
||||
const {
|
||||
pageType,
|
||||
loading,
|
||||
customMinDatetime,
|
||||
customMaxDatetime,
|
||||
currentCalendarDate,
|
||||
currentTimezoneOffsetMinutes,
|
||||
firstDayOfWeek,
|
||||
dayNames,
|
||||
defaultCurrency,
|
||||
showTotalAmountInTransactionListPage,
|
||||
showTagInTransactionListPage,
|
||||
@@ -796,7 +857,9 @@ const showFilterAccountDialog = ref<boolean>(false);
|
||||
const showFilterCategoryDialog = ref<boolean>(false);
|
||||
const showFilterTagDialog = ref<boolean>(false);
|
||||
|
||||
const recentMonthDateRanges = computed<LocalizedRecentMonthDateRange[]>(() => getAllRecentMonthDateRanges(true, true));
|
||||
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
|
||||
|
||||
const recentMonthDateRanges = computed<LocalizedRecentMonthDateRange[]>(() => getAllRecentMonthDateRanges(pageType.value === TransactionListPageType.List.type, true));
|
||||
|
||||
const allTransactionTemplates = computed<TransactionTemplate[]>(() => {
|
||||
const allTemplates = transactionTemplatesStore.allVisibleTemplates;
|
||||
@@ -827,19 +890,45 @@ const allowCategoryTypes = computed<string>(() => {
|
||||
});
|
||||
|
||||
const transactions = computed<Transaction[]>(() => {
|
||||
if (queryMonthlyData.value) {
|
||||
const transactionData = currentMonthTransactionData.value;
|
||||
if (pageType.value === TransactionListPageType.List.type) {
|
||||
if (queryMonthlyData.value) {
|
||||
const transactionData = currentMonthTransactionData.value;
|
||||
|
||||
if (!transactionData || !transactionData.items) {
|
||||
if (!transactionData || !transactionData.items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const firstIndex = (currentPage.value - 1) * countPerPage.value;
|
||||
const lastIndex = currentPage.value * countPerPage.value;
|
||||
|
||||
return transactionData.items.slice(firstIndex, lastIndex);
|
||||
} else {
|
||||
return currentPageTransactions.value;
|
||||
}
|
||||
} else if (pageType.value === TransactionListPageType.Calendar.type) {
|
||||
if (queryMonthlyData.value) {
|
||||
const transactionData = currentMonthTransactionData.value;
|
||||
|
||||
if (!transactionData || !transactionData.items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const transactions :Transaction[] = [];
|
||||
|
||||
for (let i = 0; i < transactionData.items.length; i++) {
|
||||
const transaction = transactionData.items[i];
|
||||
|
||||
if (transaction.date === currentCalendarDate.value) {
|
||||
transactions.push(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
return transactions;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
const firstIndex = (currentPage.value - 1) * countPerPage.value;
|
||||
const lastIndex = currentPage.value * countPerPage.value;
|
||||
|
||||
return transactionData.items.slice(firstIndex, lastIndex);
|
||||
} else {
|
||||
return currentPageTransactions.value;
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -874,6 +963,11 @@ const recentDateRangeType = computed<number>({
|
||||
}
|
||||
});
|
||||
|
||||
const queryPageType = computed<number>({
|
||||
get: () => pageType.value,
|
||||
set: (value) => changePageType(value)
|
||||
});
|
||||
|
||||
const queryType = computed<number>({
|
||||
get: () => query.value.type,
|
||||
set: (value) => changeTypeFilter(value)
|
||||
@@ -974,6 +1068,10 @@ const currentMonthTotalAmount = computed<TransactionListDisplayTotalAmount | nul
|
||||
}
|
||||
});
|
||||
|
||||
function noTransactionInMonthDay(date: Date): boolean {
|
||||
return !currentMonthTransactionData.value || !currentMonthTransactionData.value.dailyTotalAmounts || !currentMonthTransactionData.value.dailyTotalAmounts[getDay(date)];
|
||||
}
|
||||
|
||||
function getCategoryListItemCheckedClass(category: TransactionCategory, queryCategoryIds: Record<string, boolean>): Record<string, boolean> {
|
||||
if (queryCategoryIds && queryCategoryIds[category.id]) {
|
||||
return {
|
||||
@@ -1006,7 +1104,7 @@ function updateUrlWhenChanged(changed: boolean): void {
|
||||
loading.value = true;
|
||||
currentPageTransactions.value = [];
|
||||
transactionsStore.clearTransactions();
|
||||
router.push(`/transaction/list?${transactionsStore.getTransactionListPageParams()}`);
|
||||
router.push(`/transaction/list?${transactionsStore.getTransactionListPageParams(pageType.value)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,6 +1134,32 @@ function init(initProps: TransactionListProps): void {
|
||||
keyword: initProps.initKeyword || ''
|
||||
});
|
||||
|
||||
if (initProps.initPageType) {
|
||||
const type = TransactionListPageType.valueOf(parseInt(initProps.initPageType));
|
||||
|
||||
if (type) {
|
||||
pageType.value = type.type;
|
||||
currentCalendarDate.value = getMonthFirstDayOrCurrentDayShortDate(query.value.minTime);
|
||||
|
||||
if (pageType.value === TransactionListPageType.Calendar.type) {
|
||||
const dateRange = getFullMonthDateRange(query.value.minTime, query.value.maxTime, firstDayOfWeek.value);
|
||||
|
||||
if (dateRange) {
|
||||
const changed = transactionsStore.updateTransactionListFilter({
|
||||
dateType: dateRange.dateType,
|
||||
maxTime: dateRange.maxTime,
|
||||
minTime: dateRange.minTime
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
updateUrlWhenChanged(changed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
searchKeyword.value = initProps.initKeyword || '';
|
||||
currentAmountFilterType.value = '';
|
||||
|
||||
@@ -1108,6 +1232,25 @@ function reload(force: boolean, init: boolean): void {
|
||||
});
|
||||
}
|
||||
|
||||
function changePageType(type: number): void {
|
||||
pageType.value = type;
|
||||
currentCalendarDate.value = getMonthFirstDayOrCurrentDayShortDate(query.value.minTime);
|
||||
|
||||
if (pageType.value === TransactionListPageType.Calendar.type) {
|
||||
const dateRange = getFullMonthDateRange(query.value.minTime, query.value.maxTime, firstDayOfWeek.value);
|
||||
|
||||
if (dateRange) {
|
||||
transactionsStore.updateTransactionListFilter({
|
||||
dateType: dateRange.dateType,
|
||||
maxTime: dateRange.maxTime,
|
||||
minTime: dateRange.minTime
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateUrlWhenChanged(true);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1134,6 +1277,15 @@ function changeDateFilter(dateRange: TimeRangeAndDateType | number | null): void
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageType.value === TransactionListPageType.Calendar.type) {
|
||||
currentCalendarDate.value = getMonthFirstDayOrCurrentDayShortDate(dateRange.minTime);
|
||||
const fullMonthDateRange = getFullMonthDateRange(dateRange.minTime, dateRange.maxTime, firstDayOfWeek.value);
|
||||
|
||||
if (fullMonthDateRange) {
|
||||
dateRange = fullMonthDateRange;
|
||||
}
|
||||
}
|
||||
|
||||
if (query.value.dateType === dateRange.dateType && query.value.maxTime === dateRange.maxTime && query.value.minTime === dateRange.minTime) {
|
||||
return;
|
||||
}
|
||||
@@ -1158,6 +1310,17 @@ function changeCustomDateFilter(minTime: number, maxTime: number): void {
|
||||
dateType = getDateTypeByDateRange(minTime, maxTime, firstDayOfWeek.value, DateRangeScene.Normal);
|
||||
}
|
||||
|
||||
if (pageType.value === TransactionListPageType.Calendar.type) {
|
||||
currentCalendarDate.value = getMonthFirstDayOrCurrentDayShortDate(minTime);
|
||||
const dateRange = getFullMonthDateRange(minTime, maxTime, firstDayOfWeek.value);
|
||||
|
||||
if (dateRange) {
|
||||
minTime = dateRange.minTime;
|
||||
maxTime = dateRange.maxTime;
|
||||
dateType = dateRange.dateType;
|
||||
}
|
||||
}
|
||||
|
||||
if (query.value.dateType === dateType && query.value.maxTime === maxTime && query.value.minTime === minTime) {
|
||||
showCustomDateRangeDialog.value = false;
|
||||
return;
|
||||
@@ -1188,6 +1351,15 @@ function shiftDateRange(startTime: number, endTime: number, scale: number): void
|
||||
newDateRange = getShiftedDateRangeAndDateType(startTime, endTime, scale, firstDayOfWeek.value, DateRangeScene.Normal);
|
||||
}
|
||||
|
||||
if (pageType.value === TransactionListPageType.Calendar.type) {
|
||||
currentCalendarDate.value = getMonthFirstDayOrCurrentDayShortDate(newDateRange.minTime);
|
||||
const fullMonthDateRange = getFullMonthDateRange(newDateRange.minTime, newDateRange.maxTime, firstDayOfWeek.value);
|
||||
|
||||
if (fullMonthDateRange) {
|
||||
newDateRange = fullMonthDateRange;
|
||||
}
|
||||
}
|
||||
|
||||
const changed = transactionsStore.updateTransactionListFilter({
|
||||
dateType: newDateRange.dateType,
|
||||
maxTime: newDateRange.maxTime,
|
||||
@@ -1600,4 +1772,10 @@ init(props);
|
||||
.transaction-tag-menu .item-in-multiple-selection span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.transaction-calendar-container .dp__calendar .dp__calendar_row {
|
||||
--dp-cell-size: 80px;
|
||||
--dp-primary-color: rgba(var(--v-theme-primary), var(--v-activated-opacity));
|
||||
--dp-primary-text-color: rgb(var(--v-theme-primary));
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user