Refactor budgetUtils.ts
Refactor budgetUtils.ts to improve code organization and maintainability by splitting the logic into multiple files. The functionality remains the same.
This commit is contained in:
@@ -1,232 +1,24 @@
|
|||||||
|
|
||||||
import { BudgetData, BudgetPeriod, CategoryBudget, Transaction } from './types';
|
// 분리된 유틸리티 파일들에서 함수와 상수를 재내보내기
|
||||||
import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
|
export {
|
||||||
|
DEFAULT_CATEGORY_BUDGETS,
|
||||||
|
DEFAULT_MONTHLY_BUDGET,
|
||||||
|
getInitialBudgetData
|
||||||
|
} from './utils/constants';
|
||||||
|
|
||||||
// 기본 데이터 상수 (기본값을 0으로 설정)
|
export {
|
||||||
export const DEFAULT_CATEGORY_BUDGETS: Record<string, number> = {
|
calculateCategorySpending
|
||||||
음식: 0,
|
} from './utils/categoryUtils';
|
||||||
쇼핑: 0,
|
|
||||||
교통: 0,
|
|
||||||
기타: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DEFAULT_MONTHLY_BUDGET = 0;
|
export {
|
||||||
|
calculateUpdatedBudgetData
|
||||||
|
} from './utils/budgetCalculation';
|
||||||
|
|
||||||
// 카테고리별 지출 계산
|
export {
|
||||||
export const calculateCategorySpending = (
|
calculateSpentAmounts
|
||||||
transactions: Transaction[],
|
} from './utils/spendingCalculation';
|
||||||
categoryBudgets: Record<string, number>
|
|
||||||
): CategoryBudget[] => {
|
|
||||||
const expenseTransactions = transactions.filter(t => t.type === 'expense');
|
|
||||||
const categorySpending: Record<string, number> = {};
|
|
||||||
|
|
||||||
// 모든 카테고리에 대해 초기값 0 설정
|
export {
|
||||||
Object.keys(categoryBudgets).forEach(category => {
|
safelyLoadBudgetData,
|
||||||
// 정의된 카테고리만 유지
|
safeStorage
|
||||||
if (EXPENSE_CATEGORIES.includes(category)) {
|
} from './utils/storageUtils';
|
||||||
categorySpending[category] = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 지원되는 카테고리가 없을 경우 기본값 설정
|
|
||||||
if (Object.keys(categorySpending).length === 0) {
|
|
||||||
EXPENSE_CATEGORIES.forEach(category => {
|
|
||||||
categorySpending[category] = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
expenseTransactions.forEach(t => {
|
|
||||||
if (t.category in categorySpending) {
|
|
||||||
categorySpending[t.category] += t.amount;
|
|
||||||
} else if (EXPENSE_CATEGORIES.includes(t.category)) {
|
|
||||||
// 지원되는 카테고리이지만 초기화되지 않은 경우
|
|
||||||
categorySpending[t.category] = t.amount;
|
|
||||||
} else if (t.category === '교통비') {
|
|
||||||
// 예전 카테고리명 '교통비'를 '교통'으로 매핑
|
|
||||||
categorySpending['교통'] = (categorySpending['교통'] || 0) + t.amount;
|
|
||||||
} else {
|
|
||||||
// 지원되지 않는 카테고리는 '기타'로 집계
|
|
||||||
categorySpending['기타'] = (categorySpending['기타'] || 0) + t.amount;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return EXPENSE_CATEGORIES.map(category => ({
|
|
||||||
title: category,
|
|
||||||
current: categorySpending[category] || 0,
|
|
||||||
total: categoryBudgets[category] || 0
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 예산 데이터 업데이트 계산 - 수정된 함수
|
|
||||||
export const calculateUpdatedBudgetData = (
|
|
||||||
prevBudgetData: BudgetData,
|
|
||||||
type: BudgetPeriod,
|
|
||||||
amount: number
|
|
||||||
): BudgetData => {
|
|
||||||
console.log(`예산 업데이트 계산 시작: 타입=${type}, 금액=${amount}`);
|
|
||||||
|
|
||||||
// 값이 없거나 유효하지 않은 경우 로깅
|
|
||||||
if (!prevBudgetData) {
|
|
||||||
console.error('이전 예산 데이터가 없습니다. 기본값 사용.');
|
|
||||||
prevBudgetData = getInitialBudgetData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 선택된 타입에 따라 다른 타입의 예산도 자동으로 계산
|
|
||||||
let monthlyAmount: number, weeklyAmount: number, dailyAmount: number;
|
|
||||||
|
|
||||||
if (type === 'monthly') {
|
|
||||||
// 월간 예산이 직접 입력된 경우
|
|
||||||
monthlyAmount = amount;
|
|
||||||
// 월 30일 기준 (실제 사용 시 값 확인)
|
|
||||||
dailyAmount = Math.round(monthlyAmount / 30);
|
|
||||||
// 월 4.3주 기준 (실제 사용 시 값 확인)
|
|
||||||
weeklyAmount = Math.round(monthlyAmount / 4.3);
|
|
||||||
} else if (type === 'weekly') {
|
|
||||||
// 주간 예산이 직접 입력된 경우
|
|
||||||
weeklyAmount = amount;
|
|
||||||
monthlyAmount = Math.round(weeklyAmount * 4.3);
|
|
||||||
dailyAmount = Math.round(weeklyAmount / 7);
|
|
||||||
} else { // 'daily'
|
|
||||||
// 일일 예산이 직접 입력된 경우
|
|
||||||
dailyAmount = amount;
|
|
||||||
weeklyAmount = Math.round(dailyAmount * 7);
|
|
||||||
monthlyAmount = Math.round(dailyAmount * 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 모든 금액이 최소한 0 이상이 되도록 보장
|
|
||||||
monthlyAmount = Math.max(0, monthlyAmount);
|
|
||||||
weeklyAmount = Math.max(0, weeklyAmount);
|
|
||||||
dailyAmount = Math.max(0, dailyAmount);
|
|
||||||
|
|
||||||
console.log(`최종 예산 계산 결과: 월간=${monthlyAmount}원, 주간=${weeklyAmount}원, 일일=${dailyAmount}원`);
|
|
||||||
|
|
||||||
// 로그에 이전 예산 데이터 출력
|
|
||||||
console.log("이전 예산 데이터:", JSON.stringify(prevBudgetData));
|
|
||||||
|
|
||||||
// 이전 지출 데이터 보존
|
|
||||||
const dailySpent = prevBudgetData.daily?.spentAmount || 0;
|
|
||||||
const weeklySpent = prevBudgetData.weekly?.spentAmount || 0;
|
|
||||||
const monthlySpent = prevBudgetData.monthly?.spentAmount || 0;
|
|
||||||
|
|
||||||
// 새 예산 데이터 생성 (spentAmount는 이전 값 유지)
|
|
||||||
const updatedBudgetData = {
|
|
||||||
daily: {
|
|
||||||
targetAmount: dailyAmount,
|
|
||||||
spentAmount: dailySpent,
|
|
||||||
remainingAmount: Math.max(0, dailyAmount - dailySpent)
|
|
||||||
},
|
|
||||||
weekly: {
|
|
||||||
targetAmount: weeklyAmount,
|
|
||||||
spentAmount: weeklySpent,
|
|
||||||
remainingAmount: Math.max(0, weeklyAmount - weeklySpent)
|
|
||||||
},
|
|
||||||
monthly: {
|
|
||||||
targetAmount: monthlyAmount,
|
|
||||||
spentAmount: monthlySpent,
|
|
||||||
remainingAmount: Math.max(0, monthlyAmount - monthlySpent)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("새 예산 데이터:", JSON.stringify(updatedBudgetData));
|
|
||||||
|
|
||||||
return updatedBudgetData;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 지출액 계산 (일일, 주간, 월간) - 문제 수정
|
|
||||||
export const calculateSpentAmounts = (
|
|
||||||
transactions: Transaction[],
|
|
||||||
prevBudgetData: BudgetData
|
|
||||||
): BudgetData => {
|
|
||||||
console.log("지출액 계산 시작, 트랜잭션 수:", transactions.length);
|
|
||||||
|
|
||||||
// 지출 거래 필터링
|
|
||||||
const expenseTransactions = transactions.filter(t => t.type === 'expense');
|
|
||||||
|
|
||||||
// 오늘 지출 계산
|
|
||||||
const todayExpenses = expenseTransactions.filter(t => {
|
|
||||||
if (t.date.includes('오늘')) return true;
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
const dailySpent = todayExpenses.reduce((sum, t) => sum + t.amount, 0);
|
|
||||||
|
|
||||||
// 이번 주 지출 계산 (단순화된 버전)
|
|
||||||
const weeklyExpenses = expenseTransactions.filter(t => {
|
|
||||||
if (t.date.includes('오늘') || t.date.includes('어제') || t.date.includes('이번주')) return true;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
const weeklySpent = weeklyExpenses.reduce((sum, t) => sum + t.amount, 0);
|
|
||||||
|
|
||||||
// 이번 달 총 지출 계산
|
|
||||||
const monthlySpent = expenseTransactions.reduce((sum, t) => sum + t.amount, 0);
|
|
||||||
|
|
||||||
// 기존 예산 목표 유지 (없으면 기본값 0)
|
|
||||||
const dailyTarget = prevBudgetData?.daily?.targetAmount || 0;
|
|
||||||
const weeklyTarget = prevBudgetData?.weekly?.targetAmount || 0;
|
|
||||||
const monthlyTarget = prevBudgetData?.monthly?.targetAmount || 0;
|
|
||||||
|
|
||||||
// 예산 데이터 업데이트
|
|
||||||
const updatedBudget = {
|
|
||||||
daily: {
|
|
||||||
targetAmount: dailyTarget,
|
|
||||||
spentAmount: dailySpent,
|
|
||||||
remainingAmount: Math.max(0, dailyTarget - dailySpent)
|
|
||||||
},
|
|
||||||
weekly: {
|
|
||||||
targetAmount: weeklyTarget,
|
|
||||||
spentAmount: weeklySpent,
|
|
||||||
remainingAmount: Math.max(0, weeklyTarget - weeklySpent)
|
|
||||||
},
|
|
||||||
monthly: {
|
|
||||||
targetAmount: monthlyTarget,
|
|
||||||
spentAmount: monthlySpent,
|
|
||||||
remainingAmount: Math.max(0, monthlyTarget - monthlySpent)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("지출액 계산 결과:", updatedBudget);
|
|
||||||
|
|
||||||
return updatedBudget;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 초기 예산 데이터 생성
|
|
||||||
export const getInitialBudgetData = (): BudgetData => {
|
|
||||||
return {
|
|
||||||
daily: {
|
|
||||||
targetAmount: 0,
|
|
||||||
spentAmount: 0,
|
|
||||||
remainingAmount: 0
|
|
||||||
},
|
|
||||||
weekly: {
|
|
||||||
targetAmount: 0,
|
|
||||||
spentAmount: 0,
|
|
||||||
remainingAmount: 0
|
|
||||||
},
|
|
||||||
monthly: {
|
|
||||||
targetAmount: 0,
|
|
||||||
spentAmount: 0,
|
|
||||||
remainingAmount: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 스토리지에서 안전하게 예산 데이터 가져오기
|
|
||||||
export const safelyLoadBudgetData = (defaultData: BudgetData = getInitialBudgetData()): BudgetData => {
|
|
||||||
try {
|
|
||||||
const budgetDataStr = localStorage.getItem('budgetData');
|
|
||||||
if (budgetDataStr) {
|
|
||||||
const parsed = JSON.parse(budgetDataStr);
|
|
||||||
|
|
||||||
// 데이터 구조 검증 (daily, weekly, monthly 키 존재 확인)
|
|
||||||
if (parsed && parsed.daily && parsed.weekly && parsed.monthly) {
|
|
||||||
return parsed;
|
|
||||||
} else {
|
|
||||||
console.warn('저장된 예산 데이터 구조가 유효하지 않습니다. 기본값 사용.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('예산 데이터 로드 오류:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 오류 발생 또는 데이터 없음 시 기본값 반환
|
|
||||||
return defaultData;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,50 +1,17 @@
|
|||||||
|
|
||||||
import { BudgetData } from '../types';
|
import { BudgetData } from '../types';
|
||||||
import { getInitialBudgetData } from '../budgetUtils';
|
import { getInitialBudgetData, safelyLoadBudgetData, safeStorage } from '../budgetUtils';
|
||||||
import { toast } from '@/hooks/useToast.wrapper'; // 래퍼 사용
|
import { toast } from '@/hooks/useToast.wrapper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 예산 데이터 불러오기
|
* 예산 데이터 불러오기
|
||||||
*/
|
*/
|
||||||
export const loadBudgetDataFromStorage = (): BudgetData => {
|
export const loadBudgetDataFromStorage = (): BudgetData => {
|
||||||
try {
|
try {
|
||||||
const storedBudgetData = localStorage.getItem('budgetData');
|
// 새로운 safelyLoadBudgetData 함수 사용
|
||||||
if (storedBudgetData) {
|
const budgetData = safelyLoadBudgetData();
|
||||||
try {
|
console.log('예산 데이터 로드 완료', budgetData);
|
||||||
const parsed = JSON.parse(storedBudgetData);
|
return budgetData;
|
||||||
console.log('예산 데이터 로드 완료', parsed);
|
|
||||||
|
|
||||||
// 데이터 유효성 검사 추가
|
|
||||||
if (!parsed || !parsed.monthly || !parsed.daily || !parsed.weekly) {
|
|
||||||
console.warn('불완전한 예산 데이터, 백업에서 복구 시도');
|
|
||||||
throw new Error('잘못된 형식의 예산 데이터');
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('예산 데이터 파싱 오류:', error);
|
|
||||||
|
|
||||||
// 백업에서 복구 시도
|
|
||||||
const backupData = localStorage.getItem('budgetData_backup');
|
|
||||||
if (backupData) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(backupData);
|
|
||||||
console.log('백업에서 예산 데이터 복구 완료', parsed);
|
|
||||||
|
|
||||||
// 백업 데이터 유효성 검사
|
|
||||||
if (!parsed || !parsed.monthly || !parsed.daily || !parsed.weekly) {
|
|
||||||
console.warn('백업 예산 데이터도 불완전함, 새 데이터 생성');
|
|
||||||
throw new Error('백업 데이터도 잘못된 형식');
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem('budgetData', backupData); // 메인 스토리지에 복구
|
|
||||||
return parsed;
|
|
||||||
} catch (backupError) {
|
|
||||||
console.error('백업 데이터 복구 실패:', backupError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('예산 데이터 로드 중 오류:', error);
|
console.error('예산 데이터 로드 중 오류:', error);
|
||||||
}
|
}
|
||||||
@@ -73,9 +40,8 @@ export const saveBudgetDataToStorage = (budgetData: BudgetData): void => {
|
|||||||
// 이전 예산과 비교하여 변경 여부 확인
|
// 이전 예산과 비교하여 변경 여부 확인
|
||||||
let hasChanged = true;
|
let hasChanged = true;
|
||||||
try {
|
try {
|
||||||
const oldDataString = localStorage.getItem('budgetData');
|
const oldData = safeStorage.get('budgetData');
|
||||||
if (oldDataString) {
|
if (oldData) {
|
||||||
const oldData = JSON.parse(oldDataString);
|
|
||||||
// 월간 예산이 동일하면 변경되지 않은 것으로 판단
|
// 월간 예산이 동일하면 변경되지 않은 것으로 판단
|
||||||
hasChanged = oldData.monthly.targetAmount !== budgetData.monthly.targetAmount;
|
hasChanged = oldData.monthly.targetAmount !== budgetData.monthly.targetAmount;
|
||||||
}
|
}
|
||||||
@@ -84,12 +50,12 @@ export const saveBudgetDataToStorage = (budgetData: BudgetData): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 로컬 스토리지에 저장
|
// 로컬 스토리지에 저장
|
||||||
localStorage.setItem('budgetData', dataString);
|
safeStorage.set('budgetData', budgetData);
|
||||||
console.log('예산 데이터 저장 완료', budgetData);
|
console.log('예산 데이터 저장 완료', budgetData);
|
||||||
|
|
||||||
// 중요: 즉시 자동 백업 (데이터 손실 방지)
|
// 중요: 즉시 자동 백업 (데이터 손실 방지)
|
||||||
localStorage.setItem('budgetData_backup', dataString);
|
safeStorage.set('budgetData_backup', budgetData);
|
||||||
localStorage.setItem('lastBudgetSaveTime', new Date().toISOString());
|
safeStorage.set('lastBudgetSaveTime', new Date().toISOString());
|
||||||
|
|
||||||
// 이벤트 발생 (단일 이벤트로 통합)
|
// 이벤트 발생 (단일 이벤트로 통합)
|
||||||
const event = new CustomEvent('budgetChanged', {
|
const event = new CustomEvent('budgetChanged', {
|
||||||
@@ -121,14 +87,13 @@ export const saveBudgetDataToStorage = (budgetData: BudgetData): void => {
|
|||||||
*/
|
*/
|
||||||
export const clearAllBudgetData = (): void => {
|
export const clearAllBudgetData = (): void => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem('budgetData');
|
safeStorage.remove('budgetData');
|
||||||
localStorage.removeItem('budgetData_backup');
|
safeStorage.remove('budgetData_backup');
|
||||||
|
|
||||||
// 기본값으로 재설정
|
// 기본값으로 재설정
|
||||||
const initialData = getInitialBudgetData();
|
const initialData = getInitialBudgetData();
|
||||||
const dataString = JSON.stringify(initialData);
|
safeStorage.set('budgetData', initialData);
|
||||||
localStorage.setItem('budgetData', dataString);
|
safeStorage.set('budgetData_backup', initialData);
|
||||||
localStorage.setItem('budgetData_backup', dataString);
|
|
||||||
|
|
||||||
console.log('예산 데이터가 초기화되었습니다.');
|
console.log('예산 데이터가 초기화되었습니다.');
|
||||||
|
|
||||||
@@ -136,7 +101,7 @@ export const clearAllBudgetData = (): void => {
|
|||||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
window.dispatchEvent(new StorageEvent('storage', {
|
window.dispatchEvent(new StorageEvent('storage', {
|
||||||
key: 'budgetData',
|
key: 'budgetData',
|
||||||
newValue: dataString
|
newValue: JSON.stringify(initialData)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 토스트 알림 (사용자가 직접 초기화한 경우만)
|
// 토스트 알림 (사용자가 직접 초기화한 경우만)
|
||||||
|
|||||||
78
src/contexts/budget/utils/budgetCalculation.ts
Normal file
78
src/contexts/budget/utils/budgetCalculation.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
|
||||||
|
import { BudgetData, BudgetPeriod } from '../types';
|
||||||
|
import { getInitialBudgetData } from './constants';
|
||||||
|
|
||||||
|
// 예산 데이터 업데이트 계산
|
||||||
|
export const calculateUpdatedBudgetData = (
|
||||||
|
prevBudgetData: BudgetData,
|
||||||
|
type: BudgetPeriod,
|
||||||
|
amount: number
|
||||||
|
): BudgetData => {
|
||||||
|
console.log(`예산 업데이트 계산 시작: 타입=${type}, 금액=${amount}`);
|
||||||
|
|
||||||
|
// 값이 없거나 유효하지 않은 경우 로깅
|
||||||
|
if (!prevBudgetData) {
|
||||||
|
console.error('이전 예산 데이터가 없습니다. 기본값 사용.');
|
||||||
|
prevBudgetData = getInitialBudgetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 선택된 타입에 따라 다른 타입의 예산도 자동으로 계산
|
||||||
|
let monthlyAmount: number, weeklyAmount: number, dailyAmount: number;
|
||||||
|
|
||||||
|
if (type === 'monthly') {
|
||||||
|
// 월간 예산이 직접 입력된 경우
|
||||||
|
monthlyAmount = amount;
|
||||||
|
// 월 30일 기준 (실제 사용 시 값 확인)
|
||||||
|
dailyAmount = Math.round(monthlyAmount / 30);
|
||||||
|
// 월 4.3주 기준 (실제 사용 시 값 확인)
|
||||||
|
weeklyAmount = Math.round(monthlyAmount / 4.3);
|
||||||
|
} else if (type === 'weekly') {
|
||||||
|
// 주간 예산이 직접 입력된 경우
|
||||||
|
weeklyAmount = amount;
|
||||||
|
monthlyAmount = Math.round(weeklyAmount * 4.3);
|
||||||
|
dailyAmount = Math.round(weeklyAmount / 7);
|
||||||
|
} else { // 'daily'
|
||||||
|
// 일일 예산이 직접 입력된 경우
|
||||||
|
dailyAmount = amount;
|
||||||
|
weeklyAmount = Math.round(dailyAmount * 7);
|
||||||
|
monthlyAmount = Math.round(dailyAmount * 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 금액이 최소한 0 이상이 되도록 보장
|
||||||
|
monthlyAmount = Math.max(0, monthlyAmount);
|
||||||
|
weeklyAmount = Math.max(0, weeklyAmount);
|
||||||
|
dailyAmount = Math.max(0, dailyAmount);
|
||||||
|
|
||||||
|
console.log(`최종 예산 계산 결과: 월간=${monthlyAmount}원, 주간=${weeklyAmount}원, 일일=${dailyAmount}원`);
|
||||||
|
|
||||||
|
// 로그에 이전 예산 데이터 출력
|
||||||
|
console.log("이전 예산 데이터:", JSON.stringify(prevBudgetData));
|
||||||
|
|
||||||
|
// 이전 지출 데이터 보존
|
||||||
|
const dailySpent = prevBudgetData.daily?.spentAmount || 0;
|
||||||
|
const weeklySpent = prevBudgetData.weekly?.spentAmount || 0;
|
||||||
|
const monthlySpent = prevBudgetData.monthly?.spentAmount || 0;
|
||||||
|
|
||||||
|
// 새 예산 데이터 생성 (spentAmount는 이전 값 유지)
|
||||||
|
const updatedBudgetData = {
|
||||||
|
daily: {
|
||||||
|
targetAmount: dailyAmount,
|
||||||
|
spentAmount: dailySpent,
|
||||||
|
remainingAmount: Math.max(0, dailyAmount - dailySpent)
|
||||||
|
},
|
||||||
|
weekly: {
|
||||||
|
targetAmount: weeklyAmount,
|
||||||
|
spentAmount: weeklySpent,
|
||||||
|
remainingAmount: Math.max(0, weeklyAmount - weeklySpent)
|
||||||
|
},
|
||||||
|
monthly: {
|
||||||
|
targetAmount: monthlyAmount,
|
||||||
|
spentAmount: monthlySpent,
|
||||||
|
remainingAmount: Math.max(0, monthlyAmount - monthlySpent)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("새 예산 데이터:", JSON.stringify(updatedBudgetData));
|
||||||
|
|
||||||
|
return updatedBudgetData;
|
||||||
|
};
|
||||||
48
src/contexts/budget/utils/categoryUtils.ts
Normal file
48
src/contexts/budget/utils/categoryUtils.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
import { CategoryBudget, Transaction } from '../types';
|
||||||
|
import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
|
||||||
|
|
||||||
|
// 카테고리별 지출 계산
|
||||||
|
export const calculateCategorySpending = (
|
||||||
|
transactions: Transaction[],
|
||||||
|
categoryBudgets: Record<string, number>
|
||||||
|
): CategoryBudget[] => {
|
||||||
|
const expenseTransactions = transactions.filter(t => t.type === 'expense');
|
||||||
|
const categorySpending: Record<string, number> = {};
|
||||||
|
|
||||||
|
// 모든 카테고리에 대해 초기값 0 설정
|
||||||
|
Object.keys(categoryBudgets).forEach(category => {
|
||||||
|
// 정의된 카테고리만 유지
|
||||||
|
if (EXPENSE_CATEGORIES.includes(category)) {
|
||||||
|
categorySpending[category] = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 지원되는 카테고리가 없을 경우 기본값 설정
|
||||||
|
if (Object.keys(categorySpending).length === 0) {
|
||||||
|
EXPENSE_CATEGORIES.forEach(category => {
|
||||||
|
categorySpending[category] = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
expenseTransactions.forEach(t => {
|
||||||
|
if (t.category in categorySpending) {
|
||||||
|
categorySpending[t.category] += t.amount;
|
||||||
|
} else if (EXPENSE_CATEGORIES.includes(t.category)) {
|
||||||
|
// 지원되는 카테고리이지만 초기화되지 않은 경우
|
||||||
|
categorySpending[t.category] = t.amount;
|
||||||
|
} else if (t.category === '교통비') {
|
||||||
|
// 예전 카테고리명 '교통비'를 '교통'으로 매핑
|
||||||
|
categorySpending['교통'] = (categorySpending['교통'] || 0) + t.amount;
|
||||||
|
} else {
|
||||||
|
// 지원되지 않는 카테고리는 '기타'로 집계
|
||||||
|
categorySpending['기타'] = (categorySpending['기타'] || 0) + t.amount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return EXPENSE_CATEGORIES.map(category => ({
|
||||||
|
title: category,
|
||||||
|
current: categorySpending[category] || 0,
|
||||||
|
total: categoryBudgets[category] || 0
|
||||||
|
}));
|
||||||
|
};
|
||||||
33
src/contexts/budget/utils/constants.ts
Normal file
33
src/contexts/budget/utils/constants.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
import { BudgetData } from '../types';
|
||||||
|
|
||||||
|
// 기본 데이터 상수 (기본값을 0으로 설정)
|
||||||
|
export const DEFAULT_CATEGORY_BUDGETS: Record<string, number> = {
|
||||||
|
음식: 0,
|
||||||
|
쇼핑: 0,
|
||||||
|
교통: 0,
|
||||||
|
기타: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_MONTHLY_BUDGET = 0;
|
||||||
|
|
||||||
|
// 초기 예산 데이터 생성
|
||||||
|
export const getInitialBudgetData = (): BudgetData => {
|
||||||
|
return {
|
||||||
|
daily: {
|
||||||
|
targetAmount: 0,
|
||||||
|
spentAmount: 0,
|
||||||
|
remainingAmount: 0
|
||||||
|
},
|
||||||
|
weekly: {
|
||||||
|
targetAmount: 0,
|
||||||
|
spentAmount: 0,
|
||||||
|
remainingAmount: 0
|
||||||
|
},
|
||||||
|
monthly: {
|
||||||
|
targetAmount: 0,
|
||||||
|
spentAmount: 0,
|
||||||
|
remainingAmount: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
58
src/contexts/budget/utils/spendingCalculation.ts
Normal file
58
src/contexts/budget/utils/spendingCalculation.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
import { BudgetData, Transaction } from '../types';
|
||||||
|
|
||||||
|
// 지출액 계산 (일일, 주간, 월간)
|
||||||
|
export const calculateSpentAmounts = (
|
||||||
|
transactions: Transaction[],
|
||||||
|
prevBudgetData: BudgetData
|
||||||
|
): BudgetData => {
|
||||||
|
console.log("지출액 계산 시작, 트랜잭션 수:", transactions.length);
|
||||||
|
|
||||||
|
// 지출 거래 필터링
|
||||||
|
const expenseTransactions = transactions.filter(t => t.type === 'expense');
|
||||||
|
|
||||||
|
// 오늘 지출 계산
|
||||||
|
const todayExpenses = expenseTransactions.filter(t => {
|
||||||
|
if (t.date.includes('오늘')) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
const dailySpent = todayExpenses.reduce((sum, t) => sum + t.amount, 0);
|
||||||
|
|
||||||
|
// 이번 주 지출 계산 (단순화된 버전)
|
||||||
|
const weeklyExpenses = expenseTransactions.filter(t => {
|
||||||
|
if (t.date.includes('오늘') || t.date.includes('어제') || t.date.includes('이번주')) return true;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
const weeklySpent = weeklyExpenses.reduce((sum, t) => sum + t.amount, 0);
|
||||||
|
|
||||||
|
// 이번 달 총 지출 계산
|
||||||
|
const monthlySpent = expenseTransactions.reduce((sum, t) => sum + t.amount, 0);
|
||||||
|
|
||||||
|
// 기존 예산 목표 유지 (없으면 기본값 0)
|
||||||
|
const dailyTarget = prevBudgetData?.daily?.targetAmount || 0;
|
||||||
|
const weeklyTarget = prevBudgetData?.weekly?.targetAmount || 0;
|
||||||
|
const monthlyTarget = prevBudgetData?.monthly?.targetAmount || 0;
|
||||||
|
|
||||||
|
// 예산 데이터 업데이트
|
||||||
|
const updatedBudget = {
|
||||||
|
daily: {
|
||||||
|
targetAmount: dailyTarget,
|
||||||
|
spentAmount: dailySpent,
|
||||||
|
remainingAmount: Math.max(0, dailyTarget - dailySpent)
|
||||||
|
},
|
||||||
|
weekly: {
|
||||||
|
targetAmount: weeklyTarget,
|
||||||
|
spentAmount: weeklySpent,
|
||||||
|
remainingAmount: Math.max(0, weeklyTarget - weeklySpent)
|
||||||
|
},
|
||||||
|
monthly: {
|
||||||
|
targetAmount: monthlyTarget,
|
||||||
|
spentAmount: monthlySpent,
|
||||||
|
remainingAmount: Math.max(0, monthlyTarget - monthlySpent)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("지출액 계산 결과:", updatedBudget);
|
||||||
|
|
||||||
|
return updatedBudget;
|
||||||
|
};
|
||||||
65
src/contexts/budget/utils/storageUtils.ts
Normal file
65
src/contexts/budget/utils/storageUtils.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
import { BudgetData } from '../types';
|
||||||
|
import { getInitialBudgetData } from './constants';
|
||||||
|
import { toast } from '@/hooks/useToast.wrapper';
|
||||||
|
|
||||||
|
// 스토리지에서 안전하게 예산 데이터 가져오기
|
||||||
|
export const safelyLoadBudgetData = (defaultData: BudgetData = getInitialBudgetData()): BudgetData => {
|
||||||
|
try {
|
||||||
|
const budgetDataStr = localStorage.getItem('budgetData');
|
||||||
|
if (budgetDataStr) {
|
||||||
|
const parsed = JSON.parse(budgetDataStr);
|
||||||
|
|
||||||
|
// 데이터 구조 검증 (daily, weekly, monthly 키 존재 확인)
|
||||||
|
if (parsed && parsed.daily && parsed.weekly && parsed.monthly) {
|
||||||
|
return parsed;
|
||||||
|
} else {
|
||||||
|
console.warn('저장된 예산 데이터 구조가 유효하지 않습니다. 기본값 사용.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('예산 데이터 로드 오류:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 오류 발생 또는 데이터 없음 시 기본값 반환
|
||||||
|
return defaultData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 안전한 스토리지 접근
|
||||||
|
export const safeStorage = {
|
||||||
|
get: (key: string, defaultValue: any = null): any => {
|
||||||
|
try {
|
||||||
|
const value = localStorage.getItem(key);
|
||||||
|
if (value === null) return defaultValue;
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`스토리지 읽기 오류 (${key}):`, error);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
set: (key: string, value: any): boolean => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`스토리지 쓰기 오류 (${key}):`, error);
|
||||||
|
toast({
|
||||||
|
title: "저장 오류",
|
||||||
|
description: "데이터를 저장하는 중 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: (key: string): boolean => {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`스토리지 삭제 오류 (${key}):`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user