add transaction calendar

This commit is contained in:
MaysWind
2025-05-06 00:33:45 +08:00
parent ab6f9839ef
commit 81812bb31d
16 changed files with 365 additions and 31 deletions
+45
View File
@@ -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 });
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -1508,6 +1508,7 @@
"Income and Expense Trends": "収入と支出の傾向",
"View Details": "詳細を表示",
"Transaction List": "取引リスト",
"Transaction Calendar": "Transaction Calendar",
"Transaction Details": "取引の詳細",
"Statistics & Analysis": "統計と分析",
"Account List": "口座リスト",
+1
View File
@@ -1508,6 +1508,7 @@
"Income and Expense Trends": "Тенденции доходов и расходов",
"View Details": "Просмотреть детали",
"Transaction List": "Список транзакций",
"Transaction Calendar": "Transaction Calendar",
"Transaction Details": "Детали транзакции",
"Statistics & Analysis": "Статистика и анализ",
"Account List": "Список счетов",
+1
View File
@@ -1508,6 +1508,7 @@
"Income and Expense Trends": "Тренди доходів і витрат",
"View Details": "Переглянути деталі",
"Transaction List": "Список транзакцій",
"Transaction Calendar": "Transaction Calendar",
"Transaction Details": "Деталі по транзакціях",
"Statistics & Analysis": "Статистика та аналіз",
"Account List": "Список рахунків",
+1
View File
@@ -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",
+1
View File
@@ -1508,6 +1508,7 @@
"Income and Expense Trends": "收入与支出趋势",
"View Details": "查看详情",
"Transaction List": "交易列表",
"Transaction Calendar": "交易日历",
"Transaction Details": "交易详情",
"Statistics & Analysis": "统计分析",
"Account List": "账户列表",
+1
View File
@@ -1508,6 +1508,7 @@
"Income and Expense Trends": "收入與支出趨勢",
"View Details": "查看詳情",
"Transaction List": "交易清單",
"Transaction Calendar": "交易日曆",
"Transaction Details": "交易詳情",
"Statistics & Analysis": "統計分析",
"Account List": "帳戶清單",
+1
View File
@@ -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'],
+56 -8
View File
@@ -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,
+1 -1
View File
@@ -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"
+200 -22
View File
@@ -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>