feat: 实现首页组件,展示账户概览、交易趋势、预算信息及提供快捷操作。

This commit is contained in:
2026-01-29 07:49:29 +08:00
parent ca2c2073fb
commit 732793c5c3

View File

@@ -69,7 +69,7 @@ function Home() {
setLoading(true); setLoading(true);
setError(null); setError(null);
// Helper for local date string YYYY-MM-DD to fix timezone issues // Helper for local date string YYYY-MM-DD
const toLocalDate = (d: Date) => { const toLocalDate = (d: Date) => {
const year = d.getFullYear(); const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0'); const month = String(d.getMonth() + 1).padStart(2, '0');
@@ -77,56 +77,74 @@ function Home() {
return `${year}-${month}-${day}`; return `${year}-${month}-${day}`;
}; };
// Calculate dates for today, yesterday, and last week same day // Helper to check if two dates are the same day (using local time)
const isSameDay = (d1: Date, d2: Date) => {
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate();
};
const today = new Date(); const today = new Date();
const yesterday = new Date(today); const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
const lastWeek = new Date(today); const lastWeekSameDay = new Date(today);
lastWeek.setDate(lastWeek.getDate() - 7); lastWeekSameDay.setDate(lastWeekSameDay.getDate() - 7);
const todayStr = toLocalDate(today); // Calculate range for fetching: 8 days ago to Tomorrow (to cover timezone shifts)
const yesterdayStr = toLocalDate(yesterday); const rangeStart = new Date(today);
const lastWeekStr = toLocalDate(lastWeek); rangeStart.setDate(rangeStart.getDate() - 8);
const rangeEnd = new Date(today);
rangeEnd.setDate(rangeEnd.getDate() + 1);
// Generate last 7 days date strings (from 6 days ago to today) const rangeStartStr = toLocalDate(rangeStart);
const last7Days: string[] = []; const rangeEndStr = toLocalDate(rangeEnd);
for (let i = 6; i >= 0; i--) {
const d = new Date(today);
d.setDate(d.getDate() - i);
last7Days.push(toLocalDate(d));
}
// Load accounts, recent transactions, today/yesterday stats... AND last 7 days // Parallel fetch optimized: Single request for transaction history
const [accountsData, transactionsData, categoriesData, ledgersData, settingsData, budgetsData, todayData, yesterdayData, lastWeekData, streakData, ...last7DaysData] = await Promise.all([ const [accountsData, recentTxData, categoriesData, ledgersData, settingsData, budgetsData, streakData, rangeTxData] = await Promise.all([
getAccounts(), getAccounts(),
getTransactions({ page: 1, pageSize: 5 }), // Recent getTransactions({ page: 1, pageSize: 5 }), // Recent list (server sort)
getCategories(), getCategories(),
getLedgers().catch(() => []), getLedgers().catch(() => []),
getSettings().catch(() => null), getSettings().catch(() => null),
getBudgets().catch(() => []), getBudgets().catch(() => []),
getTransactions({ startDate: todayStr, endDate: todayStr, type: 'expense', pageSize: 100 }),
getTransactions({ startDate: yesterdayStr, endDate: yesterdayStr, type: 'expense', pageSize: 100 }),
getTransactions({ startDate: lastWeekStr, endDate: lastWeekStr, type: 'expense', pageSize: 100 }), // Last week same day
getStreakInfo().catch(() => null), getStreakInfo().catch(() => null),
// Fetch last 7 days data getTransactions({ startDate: rangeStartStr, endDate: rangeEndStr, type: 'expense', pageSize: 1000 }), // Bulk fetch
...last7Days.map(dateStr =>
getTransactions({ startDate: dateStr, endDate: dateStr, type: 'expense', pageSize: 100 })
),
]); ]);
setAccounts(accountsData || []); setAccounts(accountsData || []);
setRecentTransactions(transactionsData?.items || []); setRecentTransactions(recentTxData?.items || []);
setCategories(categoriesData || []); setCategories(categoriesData || []);
setLedgers(ledgersData || []); setLedgers(ledgersData || []);
setSettings(settingsData); setSettings(settingsData);
setStreakInfo(streakData);
// Calculate daily spends // In-Memory Aggregation for Daily Spend (Fixes Timezone Issues)
setTodaySpend(calculateTotalExpense(todayData.items)); const allRangeTx = rangeTxData?.items || [];
setTodayTransactions(todayData.items || []);
setYesterdaySpend(calculateTotalExpense(yesterdayData.items));
setLastWeekSpend(calculateTotalExpense(lastWeekData.items));
// Calculate monthly budget stats (Normalized) // Today
const todayTxItems = allRangeTx.filter(t => isSameDay(new Date(t.transactionDate), today));
setTodayTransactions(todayTxItems);
setTodaySpend(calculateTotalExpense(todayTxItems));
// Yesterday
const yesterdayTxItems = allRangeTx.filter(t => isSameDay(new Date(t.transactionDate), yesterday));
setYesterdaySpend(calculateTotalExpense(yesterdayTxItems));
// Last Week Same Day
const lastWeekTxItems = allRangeTx.filter(t => isSameDay(new Date(t.transactionDate), lastWeekSameDay));
setLastWeekSpend(calculateTotalExpense(lastWeekTxItems));
// Last 7 Days Array (for chart)
const last7DaysSpendArray = [];
for (let i = 6; i >= 0; i--) {
const d = new Date(today);
d.setDate(d.getDate() - i);
const dayTx = allRangeTx.filter(t => isSameDay(new Date(t.transactionDate), d));
last7DaysSpendArray.push(calculateTotalExpense(dayTx));
}
setLast7DaysSpend(last7DaysSpendArray);
// Monthly Budget Stats
let mTotal = 0; let mTotal = 0;
let mSpent = 0; let mSpent = 0;
(budgetsData || []).forEach((b: any) => { (budgetsData || []).forEach((b: any) => {
@@ -136,22 +154,11 @@ function Home() {
else if (b.periodType === 'yearly') multiplier = 1 / 12; else if (b.periodType === 'yearly') multiplier = 1 / 12;
mTotal += b.amount * multiplier; mTotal += b.amount * multiplier;
// We add the actual spent amount regardless of period, as it contributes to "Monthly Spending" in a general sense
// But strictly speaking, Daily spent resets daily.
// For the purpose of "Monthly Overview", we just want to know if there IS a budget.
mSpent += (b.spent || 0); mSpent += (b.spent || 0);
}); });
setMonthlyBudgetTotal(mTotal); setMonthlyBudgetTotal(mTotal);
setMonthlyBudgetSpentTotal(mSpent); setMonthlyBudgetSpentTotal(mSpent);
// Set streak info
setStreakInfo(streakData);
// Calculate last 7 days spending array
const last7DaysSpendArray = last7DaysData.map(dayData =>
calculateTotalExpense(dayData?.items || [])
);
setLast7DaysSpend(last7DaysSpendArray);
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : '加载数据失败'); setError(err instanceof Error ? err.message : '加载数据失败');
console.error('Failed to load home page data:', err); console.error('Failed to load home page data:', err);