import React, { useState, useMemo, useEffect } from 'react'; import { Transaction, TransactionType, BillReminder } from '../types'; import { Solar } from 'lunar-javascript'; interface CalendarViewProps { transactions: Transaction[]; reminders?: BillReminder[]; viewDate?: string; // Format: YYYY-MM } const formatAmount = (num: number): string => { // 如果金额超过 10 万(十万单位),显示为 X.Y万,最多显示小数点后一位 if (num >= 100000) { return (num / 10000).toFixed(1).replace(/\.0$/, '') + '万'; } // 否则显示完整数字(四舍五入) return Math.round(num).toString(); }; const CalendarView: React.FC = ({ transactions, reminders = [], viewDate }) => { // Initialize from prop or current date const [currentDate, setCurrentDate] = useState(() => { if (viewDate) { const [y, m] = viewDate.split('-').map(Number); return new Date(y, m - 1, 1); } return new Date(); }); // Sync internal state when prop changes useEffect(() => { if (viewDate) { const [y, m] = viewDate.split('-').map(Number); setCurrentDate(new Date(y, m - 1, 1)); } }, [viewDate]); const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const firstDayOfMonth = new Date(year, month, 1).getDay(); // 0 is Sunday const handlePrevMonth = () => setCurrentDate(new Date(year, month - 1, 1)); const handleNextMonth = () => setCurrentDate(new Date(year, month + 1, 1)); // Helper to get Lunar Date text const getLunarText = (y: number, m: number, d: number) => { try { const solar = Solar.fromYmd(y, m + 1, d); const lunar = solar.getLunar(); const day = lunar.getDayInChinese(); if (day === '初一') { return lunar.getMonthInChinese() + '月'; } return day; } catch (e) { return ''; } }; // Aggregate stats by date const dailyStats = useMemo(() => { const map = new Map(); // Process Transactions transactions.forEach(t => { const existing = map.get(t.date) || { income: 0, expense: 0 }; if (t.type === TransactionType.EXPENSE) { existing.expense += t.amount; } else { existing.income += t.amount; } map.set(t.date, existing); }); // Process Reminders reminders.forEach(r => { let dateKey = ''; if (r.frequency === 'monthly') { const day = parseInt(r.targetDate); if (day <= daysInMonth) { dateKey = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; } } else { dateKey = r.targetDate; } if (dateKey) { const existing = map.get(dateKey) || { income: 0, expense: 0 }; if (!r.isCompleted && !existing.reminderTitle) { existing.reminderTitle = r.title; } map.set(dateKey, existing); } }); return map; }, [transactions, reminders, year, month, daysInMonth]); const renderDays = () => { const days = []; for (let i = 0; i < firstDayOfMonth; i++) { days.push(
); } for (let d = 1; d <= daysInMonth; d++) { const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`; const stat = dailyStats.get(dateStr) || { income: 0, expense: 0 }; const isToday = new Date().toISOString().split('T')[0] === dateStr; const lunarText = getLunarText(year, month, d); days.push(
{d} {lunarText}
{stat.reminderTitle && (
{stat.reminderTitle.slice(0, 4)}
)}
{stat.income > 0 && (
¥{formatAmount(stat.income)}
)} {stat.expense > 0 && (
¥{formatAmount(stat.expense)}
)}
); } return days; }; return (

{year} / {String(month + 1).padStart(2, '0')}

{['日', '一', '二', '三', '四', '五', '六'].map(day => (
{day}
))}
{renderDays()}
); }; export default CalendarView;