Adds a payment method selection (Credit Card, Cash) to the expense form and includes a line separator. Also requests to add a graph showing the proportion of credit card and cash usage in expense analytics, but this part is not implemented in this commit.
149 lines
4.8 KiB
TypeScript
149 lines
4.8 KiB
TypeScript
import React, { createContext, useState, useContext, useEffect } from 'react';
|
|
import { BudgetContextType, BudgetData, BudgetPeriod, Transaction, CategoryBudget } from './budget/types';
|
|
import { loadTransactionsFromStorage, saveTransactionsToStorage } from '@/hooks/transactions/storageUtils';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
// BudgetContext 생성
|
|
const BudgetContext = createContext<BudgetContextType | undefined>(undefined);
|
|
|
|
export const BudgetProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
// 로컬 스토리지에서 초기 트랜잭션 데이터 로드
|
|
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
|
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>({});
|
|
const [budgetData, setBudgetData] = useState<BudgetData>({
|
|
daily: { targetAmount: 0, spentAmount: 0, remainingAmount: 0 },
|
|
weekly: { targetAmount: 0, spentAmount: 0, remainingAmount: 0 },
|
|
monthly: { targetAmount: 0, spentAmount: 0, remainingAmount: 0 },
|
|
});
|
|
const [selectedTab, setSelectedTab] = useState<BudgetPeriod>('monthly');
|
|
|
|
useEffect(() => {
|
|
const storedTransactions = loadTransactionsFromStorage();
|
|
setTransactions(storedTransactions);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// 트랜잭션 변경 시 로컬 스토리지에 저장
|
|
saveTransactionsToStorage(transactions);
|
|
}, [transactions]);
|
|
|
|
// 트랜잭션 추가
|
|
const addTransaction = (transaction: Transaction) => {
|
|
const newTransaction = { ...transaction, id: uuidv4() };
|
|
setTransactions(prevTransactions => [...prevTransactions, newTransaction]);
|
|
};
|
|
|
|
// 트랜잭션 업데이트
|
|
const updateTransaction = (updatedTransaction: Transaction) => {
|
|
setTransactions(prevTransactions =>
|
|
prevTransactions.map(transaction =>
|
|
transaction.id === updatedTransaction.id ? updatedTransaction : transaction
|
|
)
|
|
);
|
|
};
|
|
|
|
// 트랜잭션 삭제
|
|
const deleteTransaction = (id: string) => {
|
|
setTransactions(prevTransactions =>
|
|
prevTransactions.filter(transaction => transaction.id !== id)
|
|
);
|
|
};
|
|
|
|
// 예산 목표 업데이트
|
|
const handleBudgetGoalUpdate = (type: BudgetPeriod, amount: number, newCategoryBudgets?: Record<string, number>) => {
|
|
setBudgetData(prev => ({
|
|
...prev,
|
|
[type]: {
|
|
...prev[type],
|
|
targetAmount: amount,
|
|
},
|
|
}));
|
|
if (newCategoryBudgets) {
|
|
setCategoryBudgets(newCategoryBudgets);
|
|
}
|
|
};
|
|
|
|
// 카테고리별 지출 계산
|
|
const getCategorySpending = () => {
|
|
const categorySpending: { [key: string]: { total: number; current: number } } = {};
|
|
|
|
// 초기화
|
|
['음식', '쇼핑', '교통', '기타'].forEach(category => {
|
|
categorySpending[category] = { total: categoryBudgets[category] || 0, current: 0 };
|
|
});
|
|
|
|
// 지출 합산
|
|
transactions.filter(tx => tx.type === 'expense').forEach(tx => {
|
|
categorySpending[tx.category].current += tx.amount;
|
|
});
|
|
|
|
// 배열로 변환
|
|
return Object.entries(categorySpending).map(([title, { total, current }]) => ({
|
|
title,
|
|
total,
|
|
current,
|
|
}));
|
|
};
|
|
|
|
// 결제 방법 통계 계산 함수 추가
|
|
const getPaymentMethodStats = () => {
|
|
// 지출 트랜잭션 필터링
|
|
const expenseTransactions = transactions.filter(t => t.type === 'expense');
|
|
|
|
// 총 지출 계산
|
|
const totalExpense = expenseTransactions.reduce((acc, curr) => acc + curr.amount, 0);
|
|
|
|
// 결제 방법별 금액 계산
|
|
const cardExpense = expenseTransactions
|
|
.filter(t => t.paymentMethod === '신용카드' || !t.paymentMethod) // paymentMethod가 없으면 신용카드로 간주
|
|
.reduce((acc, curr) => acc + curr.amount, 0);
|
|
|
|
const cashExpense = expenseTransactions
|
|
.filter(t => t.paymentMethod === '현금')
|
|
.reduce((acc, curr) => acc + curr.amount, 0);
|
|
|
|
// 결과 배열 생성 - 금액이 큰 순서대로 정렬
|
|
const result = [
|
|
{
|
|
method: '신용카드',
|
|
amount: cardExpense,
|
|
percentage: totalExpense > 0 ? (cardExpense / totalExpense) * 100 : 0
|
|
},
|
|
{
|
|
method: '현금',
|
|
amount: cashExpense,
|
|
percentage: totalExpense > 0 ? (cashExpense / totalExpense) * 100 : 0
|
|
}
|
|
].sort((a, b) => b.amount - a.amount);
|
|
|
|
return result;
|
|
};
|
|
|
|
return (
|
|
<BudgetContext.Provider value={{
|
|
transactions,
|
|
categoryBudgets,
|
|
budgetData,
|
|
selectedTab,
|
|
setSelectedTab,
|
|
updateTransaction,
|
|
handleBudgetGoalUpdate,
|
|
getCategorySpending,
|
|
getPaymentMethodStats, // 추가된 메서드
|
|
addTransaction,
|
|
deleteTransaction,
|
|
}}>
|
|
{children}
|
|
</BudgetContext.Provider>
|
|
);
|
|
};
|
|
|
|
// useContext Hook
|
|
export const useBudget = () => {
|
|
const context = useContext(BudgetContext);
|
|
if (context === undefined) {
|
|
throw new Error("useBudget must be used within a BudgetProvider");
|
|
}
|
|
return context;
|
|
};
|