import { useState, useEffect, useMemo } from 'react'; import { useRouter } from 'next/router'; import { isLoggedIn, getRecords, getStats, createRecord, updateRecord, deleteRecord, getCategories } from '../lib/api'; import { DEFAULT_CATEGORIES } from '../lib/categories'; import AppShell from '../components/AppShell'; import { Icon } from '../components/icons'; import RecordModal from '../components/RecordModal'; import { useToast } from '../components/ToastProvider'; import { useConfirm } from '../components/ConfirmProvider'; export default function Home() { const router = useRouter(); const { showToast } = useToast(); const { confirm } = useConfirm(); const [currentMonth, setCurrentMonth] = useState(new Date()); const [typeFilter, setTypeFilter] = useState('all'); const [records, setRecords] = useState([]); const [stats, setStats] = useState({ income: 0, expense: 0, balance: 0 }); const [modalOpen, setModalOpen] = useState(false); const [editingRecord, setEditingRecord] = useState(null); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [deleting, setDeleting] = useState(false); const [formError, setFormError] = useState(''); const [categories, setCategories] = useState(DEFAULT_CATEGORIES); const [formData, setFormData] = useState({ type: 'expense', amount: '', category: '', date: new Date().toISOString().split('T')[0], time: new Date().toTimeString().slice(0, 5), note: '', }); useEffect(() => { if (!isLoggedIn()) { router.push('/login'); return; } loadData(); }, [router, currentMonth, typeFilter]); const getMonthStr = (date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; const formatMoney = (num) => (num || 0).toFixed(2); const formatMonth = (date) => `${date.getFullYear()}年${date.getMonth() + 1}月`; const resetForm = () => ({ type: 'expense', amount: '', category: '', date: new Date().toISOString().split('T')[0], time: new Date().toTimeString().slice(0, 5), note: '', }); const loadData = async () => { setLoading(true); try { const [recordsData, statsData, categoriesData] = await Promise.all([ getRecords(getMonthStr(currentMonth), typeFilter), getStats(getMonthStr(currentMonth)), getCategories(), ]); setRecords(recordsData); setStats(statsData); // 合并默认分类和自定义分类 setCategories({ expense: [ ...DEFAULT_CATEGORIES.expense, ...(categoriesData.expense || []).filter(c => !DEFAULT_CATEGORIES.expense.some(d => d.name === c.name)) ], income: [ ...DEFAULT_CATEGORIES.income, ...(categoriesData.income || []).filter(c => !DEFAULT_CATEGORIES.income.some(d => d.name === c.name)) ], }); } catch (err) { console.error(err); showToast(err.message, { tone: 'error' }); } finally { setLoading(false); } }; const prevMonth = () => { const newDate = new Date(currentMonth); newDate.setMonth(newDate.getMonth() - 1); setCurrentMonth(newDate); }; const nextMonth = () => { const newDate = new Date(currentMonth); newDate.setMonth(newDate.getMonth() + 1); setCurrentMonth(newDate); }; const handleCardClick = (type) => { setTypeFilter(type === typeFilter ? 'all' : type); }; const openAddModal = () => { setEditingRecord(null); setFormData(resetForm()); setFormError(''); setModalOpen(true); }; const openEditModal = (record) => { const [datePart, timePart] = record.date.split(' '); setEditingRecord(record); setFormData({ type: record.type, amount: record.amount.toString(), category: record.category, date: datePart, time: timePart || '00:00', note: record.note || '', }); setFormError(''); setModalOpen(true); }; const closeModal = () => { if (submitting || deleting) return; setModalOpen(false); setEditingRecord(null); setFormError(''); }; const handleSubmit = async (e) => { e.preventDefault(); if (!formData.amount || !formData.category || !formData.date) { setFormError('请填写完整信息'); return; } setSubmitting(true); setFormError(''); try { const data = { type: formData.type, amount: parseFloat(formData.amount), category: formData.category, date: `${formData.date} ${formData.time || '00:00'}`, note: formData.note, }; if (editingRecord) { await updateRecord(editingRecord.id, data); showToast('记录已更新', { tone: 'success' }); } else { await createRecord(data); showToast('记录已添加', { tone: 'success' }); } closeModal(); await loadData(); } catch (err) { setFormError(err.message); showToast(err.message, { tone: 'error' }); } finally { setSubmitting(false); } }; const handleDelete = async () => { if (!editingRecord) return; const shouldDelete = await confirm({ title: '删除这条记录?', description: '删除后将无法恢复,请确认是否继续。', confirmText: '删除', }); if (!shouldDelete) return; setDeleting(true); try { await deleteRecord(editingRecord.id); closeModal(); showToast('记录已删除', { tone: 'success' }); await loadData(); } catch (err) { setFormError(err.message); showToast(err.message, { tone: 'error' }); } finally { setDeleting(false); } }; const groupedRecords = useMemo(() => { return records.reduce((groups, record) => { const date = record.date.split(' ')[0]; if (!groups[date]) groups[date] = []; groups[date].push(record); return groups; }, {}); }, [records]); const sortedDates = useMemo( () => Object.keys(groupedRecords).sort((a, b) => b.localeCompare(a)), [groupedRecords] ); const getDayExpense = (date) => { const dayRecords = groupedRecords[date] || []; return dayRecords .filter((record) => record.type === 'expense') .reduce((sum, record) => sum + record.amount, 0); }; const homeHeaderContent = (
记录列表
暂无记录,点击上方按钮添加。