import React, { useState, useEffect, useRef } from 'react'; import { Wallet, Trash2, TrendingUp, TrendingDown, History, X, Plus, Repeat, ArrowRightCircle, Search, Filter, Calendar, RefreshCw, Zap, Tag, Copy, PieChart, Calculator, CalendarDays, List } from 'lucide-react'; export default function App() { // --- 預設資料 --- const defaultExpenseCategories = [ { id: 'food', name: '食', icon: '🍜', color: 'bg-orange-100 text-orange-600 border-orange-200' }, { id: 'clothing', name: '衣', icon: '👕', color: 'bg-pink-100 text-pink-600 border-pink-200' }, { id: 'housing', name: '住', icon: '🏠', color: 'bg-blue-100 text-blue-600 border-blue-200' }, { id: 'transport', name: '行', icon: '🚗', color: 'bg-green-100 text-green-600 border-green-200' }, { id: 'education', name: '育', icon: '📚', color: 'bg-indigo-100 text-indigo-600 border-indigo-200' }, { id: 'entertainment', name: '樂', icon: '🎮', color: 'bg-purple-100 text-purple-600 border-purple-200' }, ]; const defaultIncomeCategories = [ { id: 'amway', name: '安麗獎金', icon: '💎', color: 'bg-emerald-100 text-emerald-600 border-emerald-200' }, { id: 'stock', name: '股票', icon: '📈', color: 'bg-red-100 text-red-600 border-red-200' }, { id: 'card', name: '牌卡', icon: '🎴', color: 'bg-violet-100 text-violet-600 border-violet-200' }, { id: 'crystal', name: '水晶', icon: '✨', color: 'bg-cyan-100 text-cyan-600 border-cyan-200' }, { id: 'other', name: '其他收入', icon: '💰', color: 'bg-gray-100 text-gray-600 border-gray-200' }, ]; // --- 狀態管理 --- const [transactions, setTransactions] = useState(() => { const saved = localStorage.getItem('my-simple-account-book'); return saved ? JSON.parse(saved) : []; }); const [expenseCats, setExpenseCats] = useState(() => { const saved = localStorage.getItem('my-expense-cats'); return saved ? JSON.parse(saved) : defaultExpenseCategories; }); const [incomeCats, setIncomeCats] = useState(() => { const saved = localStorage.getItem('my-income-cats'); return saved ? JSON.parse(saved) : defaultIncomeCategories; }); const [fixedItems, setFixedItems] = useState(() => { const saved = localStorage.getItem('my-fixed-items'); return saved ? JSON.parse(saved) : []; }); // 輸入狀態 const [quickAmount, setQuickAmount] = useState(''); const [quickNote, setQuickNote] = useState(''); const [quickCategory, setQuickCategory] = useState(''); const [quickType, setQuickType] = useState('expense'); // 篩選與搜尋狀態 const [searchTerm, setSearchTerm] = useState(''); const [filterMonth, setFilterMonth] = useState(''); const [filterCategory, setFilterCategory] = useState(''); // 介面狀態 const [showAddModal, setShowAddModal] = useState(false); const [showFixedModal, setShowFixedModal] = useState(false); // 固定收支預估選擇的月份 const [forecastDate, setForecastDate] = useState(new Date().toISOString().slice(0, 7)); // 新增分類輸入 const [newCatName, setNewCatName] = useState(''); // 新增固定收支輸入 const [newFixed, setNewFixed] = useState({ name: '', amount: '', type: 'expense', category: '', frequency: 'monthly', startMonth: String(new Date().getMonth() + 1) // 預設當前月份 (1-12字串) }); // 長按刪除邏輯 Refs const timerRef = useRef(null); const isLongPressRef = useRef(false); // --- 副作用:自動儲存 --- useEffect(() => { localStorage.setItem('my-simple-account-book', JSON.stringify(transactions)); }, [transactions]); useEffect(() => { localStorage.setItem('my-expense-cats', JSON.stringify(expenseCats)); localStorage.setItem('my-income-cats', JSON.stringify(incomeCats)); }, [expenseCats, incomeCats]); useEffect(() => { localStorage.setItem('my-fixed-items', JSON.stringify(fixedItems)); }, [fixedItems]); // --- 計算邏輯 --- const filteredTransactions = transactions.filter(t => { const matchSearch = searchTerm === '' || (t.note && t.note.includes(searchTerm)) || t.category.includes(searchTerm); const matchMonth = filterMonth === '' || t.date.startsWith(filterMonth); const matchCategory = filterCategory === '' || t.category === filterCategory; return matchSearch && matchMonth && matchCategory; }); const totalIncome = filteredTransactions .filter(t => t.type === 'income') .reduce((acc, curr) => acc + curr.amount, 0); const totalExpense = filteredTransactions .filter(t => t.type === 'expense') .reduce((acc, curr) => acc + curr.amount, 0); const balance = totalIncome - totalExpense; // --- 核心邏輯:計算特定月份的固定收支 --- const getMonthlyForecastDetails = (targetYearMonth) => { const [tYear, tMonth] = targetYearMonth.split('-').map(Number); const items = []; let mIncome = 0; let mExpense = 0; fixedItems.forEach(item => { let isOccurring = false; let startMonthNum; if (item.startMonth && item.startMonth.includes('-')) { startMonthNum = parseInt(item.startMonth.split('-')[1], 10); } else { startMonthNum = parseInt(item.startMonth || '1', 10); } switch(item.frequency) { case 'monthly': isOccurring = true; break; case 'quarterly': const monthDiff = tMonth - startMonthNum; isOccurring = (monthDiff % 3 === 0); break; case 'yearly': isOccurring = (tMonth === startMonthNum); break; default: isOccurring = true; } if (isOccurring) { items.push(item); if (item.type === 'income') mIncome += item.amount; else mExpense += item.amount; } }); return { items, mIncome, mExpense }; }; const { items: forecastItems, mIncome: forecastIncome, mExpense: forecastExpense } = getMonthlyForecastDetails(forecastDate); const forecastBalance = forecastIncome - forecastExpense; // --- 年度固定收支總計 (概覽) --- const calculateAnnualFixed = () => { let annualIncome = 0; let annualExpense = 0; fixedItems.forEach(item => { let multiplier = 0; switch(item.frequency) { case 'monthly': multiplier = 12; break; case 'quarterly': multiplier = 4; break; case 'yearly': multiplier = 1; break; default: multiplier = 12; } const totalAmount = item.amount * multiplier; if (item.type === 'income') annualIncome += totalAmount; else annualExpense += totalAmount; }); return { annualIncome, annualExpense }; }; const { annualIncome: annualFixedIncome, annualExpense: annualFixedExpense } = calculateAnnualFixed(); const annualFixedBalance = annualFixedIncome - annualFixedExpense; // --- 事件處理 --- const handleQuickAdd = (e) => { e.preventDefault(); if (!quickAmount || !quickCategory) return; addTransactionRecord(quickType, quickAmount, quickCategory, quickNote, new Date().toISOString().split('T')[0]); setQuickAmount(''); setQuickNote(''); }; const addTransactionRecord = (tType, tAmount, tCategory, tNote, tDate) => { const newTransaction = { id: Date.now(), type: tType, amount: parseFloat(tAmount), category: tCategory, date: tDate, note: tNote }; setTransactions([newTransaction, ...transactions]); }; const handleDeleteTransaction = (e, id) => { e.stopPropagation(); if (window.confirm('確定要刪除這筆紀錄嗎?')) { setTransactions(transactions.filter(t => t.id !== id)); } }; const handleReuseTransaction = (t) => { setQuickType(t.type); setQuickAmount(t.amount.toString()); setQuickCategory(t.category); setQuickNote(t.note || ''); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const handleAddFixedItem = (e) => { e.preventDefault(); if (!newFixed.name || !newFixed.amount || !newFixed.category) return; const newItem = { id: Date.now(), ...newFixed, amount: parseFloat(newFixed.amount) }; setFixedItems([...fixedItems, newItem]); setNewFixed({ ...newFixed, name: '', amount: '' }); }; const handleDeleteFixedItem = (id) => { if (window.confirm('確定要刪除這個固定項目嗎?')) { setFixedItems(fixedItems.filter(item => item.id !== id)); } }; const handleRecordFixedItem = (item) => { addTransactionRecord( item.type, item.amount, item.category, `${item.name} (${getFrequencyLabel(item.frequency)})`, new Date().toISOString().split('T')[0] ); setShowFixedModal(false); alert(`已新增:${item.name}`); }; const startPress = (catId) => { isLongPressRef.current = false; timerRef.current = setTimeout(() => { isLongPressRef.current = true; if (window.confirm('【刪除分類】\n確定要刪除這個分類嗎?\n(這不會影響已存在的記帳紀錄)')) { handleDeleteCategory(catId); if (quickCategory === catId) setQuickCategory(''); } }, 600); }; const endPress = () => { if (timerRef.current) { clearTimeout(timerRef.current); } }; const handleQuickCategoryClick = (catName) => { if (isLongPressRef.current) return; setQuickCategory(catName); }; const getRandomColorClass = () => { const colors = [ 'bg-red-100 text-red-600 border-red-200', 'bg-orange-100 text-orange-600 border-orange-200', 'bg-amber-100 text-amber-600 border-amber-200', 'bg-yellow-100 text-yellow-600 border-yellow-200', 'bg-lime-100 text-lime-600 border-lime-200', 'bg-green-100 text-green-600 border-green-200', 'bg-emerald-100 text-emerald-600 border-emerald-200', 'bg-teal-100 text-teal-600 border-teal-200', 'bg-cyan-100 text-cyan-600 border-cyan-200', 'bg-sky-100 text-sky-600 border-sky-200', 'bg-blue-100 text-blue-600 border-blue-200', 'bg-indigo-100 text-indigo-600 border-indigo-200', 'bg-violet-100 text-violet-600 border-violet-200', 'bg-purple-100 text-purple-600 border-purple-200', 'bg-fuchsia-100 text-fuchsia-600 border-fuchsia-200', 'bg-pink-100 text-pink-600 border-pink-200', 'bg-rose-100 text-rose-600 border-rose-200', ]; return colors[Math.floor(Math.random() * colors.length)]; }; const handleConfirmAddCategory = (e) => { e.preventDefault(); if (newCatName && newCatName.trim()) { const newCat = { id: Date.now().toString(), name: newCatName.trim(), icon: '🏷️', color: getRandomColorClass() }; if (quickType === 'expense') { setExpenseCats([...expenseCats, newCat]); } else { setIncomeCats([...incomeCats, newCat]); } setNewCatName(''); setShowAddModal(false); } }; const handleDeleteCategory = (catId) => { if (quickType === 'expense') { setExpenseCats(expenseCats.filter(c => c.id !== catId)); } else { setIncomeCats(incomeCats.filter(c => c.id !== catId)); } if (quickCategory === expenseCats.find(c => c.id === catId)?.name || quickCategory === incomeCats.find(c => c.id === catId)?.name) { setQuickCategory(''); } }; const getFrequencyLabel = (freq) => { switch(freq) { case 'monthly': return '每月'; case 'quarterly': return '每季'; case 'yearly': return '每年'; default: return '每月'; } }; const currentCategories = quickType === 'expense' ? expenseCats : incomeCats; const allCategoryNames = Array.from(new Set([...expenseCats.map(c => c.name), ...incomeCats.map(c => c.name)])); const formatMoney = (num) => { return new Intl.NumberFormat('zh-TW', { style: 'currency', currency: 'TWD', maximumFractionDigits: 0 }).format(num); }; const renderPieChart = (income, expense) => { if (income === 0 && expense === 0) return null; const expensePercent = income === 0 ? 100 : Math.min((expense / income) * 100, 100); const dashExpense = expensePercent; return (
支出佔比 100 ? 'text-rose-600' : 'text-gray-700'}`}> {expensePercent.toFixed(0)}%
); }; return (
{/* --- Header / 總覽卡片 (亮橘色主題) --- */}

{filterMonth ? `${filterMonth}` : '豐盛記錄'}

{(searchTerm || filterMonth || filterCategory) && ( )}

{filterMonth ? '本月結餘' : '目前總資產餘額'}

{formatMoney(balance)}

{filterMonth ? '本月支出' : '總支出'} {formatMoney(totalExpense)}
{filterMonth ? '本月收入' : '總收入'} {formatMoney(totalIncome)}
{/* --- 搜尋與篩選列 --- */}
setSearchTerm(e.target.value)} className="w-full pl-8 pr-2 py-1.5 bg-gray-50 rounded-lg text-xs outline-none focus:ring-2 focus:ring-orange-500/20" />
setFilterMonth(e.target.value)} className="pl-7 pr-1 py-1.5 bg-gray-50 rounded-lg text-xs outline-none focus:ring-2 focus:ring-orange-500/20 text-gray-600 w-28" />
{/* --- 記帳區域 (極速記帳列 - 亮橘色) --- */}

極速記帳

{/* 第一排:類型 + 備註(前) + 金額(後) */}
{/* 備註 */} setQuickNote(e.target.value)} className="flex-1 min-w-0 px-2 py-1.5 bg-gray-50 rounded-lg text-sm outline-none focus:ring-2 focus:ring-orange-500/20 text-gray-700 placeholder-gray-300" /> {/* 金額 */} setQuickAmount(e.target.value)} className="w-20 px-2 py-1.5 bg-gray-50 rounded-lg text-base font-bold outline-none focus:ring-2 focus:ring-orange-500/20 text-gray-800 placeholder-gray-300" />
{/* 第二排:田字狀分類按鈕 (亮橘色主題) */}
類別 ({quickType === 'expense' ? '支出' : '收入'})
{currentCategories.map(c => ( ))}
{/* --- 歷史紀錄 --- */}

{filterMonth ? `${filterMonth} 明細` : '近期明細'}
共 {filteredTransactions.length} 筆

{filteredTransactions.length === 0 ? (
{searchTerm || filterMonth || filterCategory ? '🔍' : '📝'}

{searchTerm || filterMonth || filterCategory ? '沒有符合條件的紀錄' : '還沒有紀錄,快記一筆吧!'}

) : ( filteredTransactions.map((t) => (
handleReuseTransaction(t)} className="bg-white p-3 rounded-xl shadow-sm border border-gray-100 flex items-center justify-between group cursor-pointer hover:border-orange-300 hover:shadow-md transition-all relative overflow-hidden active:scale-[0.99]" title="點擊以帶入資料" >
{t.type === 'income' ? '收' : '支'}
{t.category} {t.date}
{t.note &&
{t.note}
}
{t.type === 'income' ? '+' : '-'}{t.amount}
)) )}
{/* --- 新增分類視窗 --- */} {showAddModal && (

新增{quickType === 'expense' ? '支出' : '收入'}分類

setNewCatName(e.target.value)} placeholder="輸入名稱 (例如: 飲料)" className="w-full bg-gray-50 border border-gray-200 rounded-xl px-3 py-2 text-base mb-3 focus:ring-2 focus:ring-orange-500 outline-none" />
)} {/* --- 固定收支 & 預算管理視窗 (亮橘色主題) --- */} {showFixedModal && (

固定收支 & 預算

{/* --- 1. 月度收支預估 & 佔比 --- */}

月度收支預估 & 佔比

{/* 月份選擇器 */}
setForecastDate(e.target.value)} className="text-xs font-bold text-gray-700 outline-none bg-transparent flex-1" />
{/* 收支金額 */}
固定收入 {formatMoney(forecastIncome)}
固定支出 {formatMoney(forecastExpense)}
預估結餘 = 0 ? 'text-orange-600' : 'text-rose-600'}`}> {formatMoney(forecastBalance)}
{/* 圓餅圖佔比 */}
{renderPieChart(forecastIncome, forecastExpense)}
{/* 預估明細列表 */} {forecastItems.length > 0 && (
該月份明細 ({forecastItems.length}筆)
{forecastItems.map(item => (
{item.name}
{item.type === 'income' ? '+' : '-'}{item.amount}
))}
)} {forecastItems.length === 0 && (

該月份無固定收支項目

)}
{/* --- 2. 年度固定收支預估 --- */}

年度固定收支總覽

年度總收

{formatMoney(annualFixedIncome)}

年度總支

{formatMoney(annualFixedExpense)}

年度固定結餘

= 0 ? 'text-emerald-600' : 'text-rose-600'}`}> {formatMoney(annualFixedBalance)}

{/* --- 3. 固定收支區塊 --- */}

固定項目列表

{/* 新增固定項目表單 */}
setNewFixed({...newFixed, name: e.target.value})} className="flex-1 p-1.5 rounded-lg border border-gray-200 text-xs outline-none focus:ring-2 focus:ring-orange-300" />
setNewFixed({...newFixed, amount: e.target.value})} className="w-1/2 p-1.5 rounded-lg border border-gray-200 text-xs outline-none focus:ring-2 focus:ring-orange-300" />
{/* 起始月份選擇 (優化:下拉選單) */} {(newFixed.frequency === 'quarterly' || newFixed.frequency === 'yearly') && (
{newFixed.frequency === 'yearly' ? '發生月份:' : '起始月份:'}
)}
{/* 固定項目列表 */}
{fixedItems.length === 0 ? (
目前沒有固定項目
) : ( fixedItems.map(item => (
{item.type === 'income' ? '收' : '支'} {item.name}
{getFrequencyLabel(item.frequency)} {/* 顯示起始月 (針對非每月項目) */} {item.frequency !== 'monthly' && ( {item.frequency === 'yearly' ? `每年${item.startMonth ? item.startMonth.replace(/^[0]*([1-9][0-9]*)/, '$1') : '1'}月` : `${item.startMonth ? item.startMonth.replace(/^[0]*([1-9][0-9]*)/, '$1') : '1'}月起` } )} {item.category} • ${item.amount}
)) )}
)}
); }