Refactor category budget setting

The category budget setting is now based on the monthly budget amount, which is then divided into daily and weekly budgets.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-18 02:22:58 +00:00
parent 0be5154e02
commit 42c9355e76
5 changed files with 78 additions and 115 deletions

View File

@@ -1,4 +1,3 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { CirclePlus, Save, Check } from 'lucide-react'; import { CirclePlus, Save, Check } from 'lucide-react';
import BudgetInputCard from './BudgetInputCard'; import BudgetInputCard from './BudgetInputCard';
@@ -94,17 +93,23 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
// 예산 여부에 따른 텍스트 결정 // 예산 여부에 따른 텍스트 결정
const budgetButtonText = targetAmount > 0 ? "예산 수정하기" : "예산 입력하기"; const budgetButtonText = targetAmount > 0 ? "예산 수정하기" : "예산 입력하기";
return <div>
{targetAmount > 0 ? <> return (
<div>
{targetAmount > 0 ? (
<>
<div className="flex justify-between items-center mb-3"> <div className="flex justify-between items-center mb-3">
<div className="text-2xl font-bold">{formatCurrency(spentAmount)}</div> <div className="text-2xl font-bold">{formatCurrency(spentAmount)}</div>
<div className="text-sm text-gray-500">/ {formatCurrency(targetAmount)}</div> <div className="text-sm text-gray-500">/ {formatCurrency(targetAmount)}</div>
</div> </div>
<div className="w-full h-2 neuro-pressed overflow-hidden mb-3"> <div className="w-full h-2 neuro-pressed overflow-hidden mb-3">
<div className={`h-full ${progressBarColor} transition-all duration-700 ease-out`} style={{ <div
width: `${Math.min(percentage, 100)}%` className={`h-full ${progressBarColor} transition-all duration-700 ease-out`}
}} /> style={{
width: `${Math.min(percentage, 100)}%`,
}}
/>
</div> </div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
@@ -125,15 +130,19 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
<span className="text-base font-semibold">{budgetButtonText}</span> <span className="text-base font-semibold">{budgetButtonText}</span>
</button> </button>
</div> </div>
</> : <div className="py-4 text-center"> </>
) : (
<div className="py-4 text-center">
<div className="text-gray-400 mb-4"> </div> <div className="text-gray-400 mb-4"> </div>
<Button onClick={toggleBudgetInput} variant="default" className="bg-neuro-income hover:bg-neuro-income/90 animate-pulse shadow-lg"> <Button onClick={toggleBudgetInput} variant="default" className="bg-neuro-income hover:bg-neuro-income/90 animate-pulse shadow-lg">
<CirclePlus className="mr-2" size={24} /> <CirclePlus className="mr-2" size={24} />
<span className="animate-pulse">{budgetButtonText}</span> <span className="animate-pulse">{budgetButtonText}</span>
</Button> </Button>
</div>} </div>
)}
{showBudgetInput && <div className="mt-4"> {showBudgetInput && (
<div className="mt-4">
<div className="neuro-card p-4"> <div className="neuro-card p-4">
<div> <div>
<h3 className="text-base font-medium mb-3"> </h3> <h3 className="text-base font-medium mb-3"> </h3>
@@ -155,7 +164,9 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
</div> </div>
</div> </div>
</div> </div>
</div>} </div>
</div>; )}
</div>
);
}; };
export default BudgetTabContent; export default BudgetTabContent;

View File

@@ -18,8 +18,8 @@ const DataResetSection = () => {
await resetAllData(); await resetAllData();
setIsResetDialogOpen(false); setIsResetDialogOpen(false);
// 데이터 초기화 후 애플리케이션 리로드
// toast 알림은 useDataReset.ts에서 처리하므로 여기서는 제거 // toast 알림은 useDataReset.ts에서 처리하므로 여기서는 제거
// 페이지 새로고침 코드 제거 (navigate 사용으로 대체)
}; };
return ( return (

View File

@@ -1,4 +1,3 @@
import { BudgetData, BudgetPeriod, CategoryBudget, Transaction } from './types'; import { BudgetData, BudgetPeriod, CategoryBudget, Transaction } from './types';
import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons'; import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
@@ -53,7 +52,7 @@ export const calculateCategorySpending = (
})); }));
}; };
// 예산 데이터 업데이트 계산 // 예산 데이터 업데이트 계산 - 수정된 함수
export const calculateUpdatedBudgetData = ( export const calculateUpdatedBudgetData = (
prevBudgetData: BudgetData, prevBudgetData: BudgetData,
type: BudgetPeriod, type: BudgetPeriod,
@@ -61,79 +60,35 @@ export const calculateUpdatedBudgetData = (
): BudgetData => { ): BudgetData => {
console.log(`예산 업데이트 계산: 타입=${type}, 금액=${amount}`); console.log(`예산 업데이트 계산: 타입=${type}, 금액=${amount}`);
if (type === 'monthly') { // 카테고리 예산은 항상 월간 기준이므로, monthly 계산 방식 사용
// 문제 수정: 일일 예산은 월간/30, 주간 예산은 월간/4.3으로 계산 // 월간→주간→일간 순서로 변환
const dailyAmount = Math.round(amount / 30); const monthlyAmount = type === 'monthly' ? amount :
const weeklyAmount = Math.round(amount / 4.3); type === 'weekly' ? Math.round(amount * 4.3) :
Math.round(amount * 30);
console.log(`월간 예산 ${amount}원으로 설정 → 일일: ${dailyAmount}원, 주간: ${weeklyAmount}`); // 월간 금액에서 주간, 일간 계산
const weeklyAmount = Math.round(monthlyAmount / 4.3);
const dailyAmount = Math.round(monthlyAmount / 30);
return { console.log(`예산 변환: 월간=${monthlyAmount}원, 주간=${weeklyAmount}원, 일간=${dailyAmount}`);
daily: {
targetAmount: dailyAmount,
spentAmount: prevBudgetData.daily.spentAmount,
remainingAmount: Math.max(0, dailyAmount - prevBudgetData.daily.spentAmount)
},
weekly: {
targetAmount: weeklyAmount,
spentAmount: prevBudgetData.weekly.spentAmount,
remainingAmount: Math.max(0, weeklyAmount - prevBudgetData.weekly.spentAmount)
},
monthly: {
targetAmount: amount,
spentAmount: prevBudgetData.monthly.spentAmount,
remainingAmount: Math.max(0, amount - prevBudgetData.monthly.spentAmount)
}
};
} else if (type === 'weekly') {
// 문제 수정: 월간 예산은 주간*4.3, 일일 예산은 주간/7로 계산
const monthlyAmount = Math.round(amount * 4.3);
const dailyAmount = Math.round(amount / 7);
console.log(`주간 예산 ${amount}원으로 설정 → 월간: ${monthlyAmount}원, 일일: ${dailyAmount}`); return {
daily: {
return { targetAmount: dailyAmount,
daily: { spentAmount: prevBudgetData.daily.spentAmount,
targetAmount: dailyAmount, remainingAmount: Math.max(0, dailyAmount - prevBudgetData.daily.spentAmount)
spentAmount: prevBudgetData.daily.spentAmount, },
remainingAmount: Math.max(0, dailyAmount - prevBudgetData.daily.spentAmount) weekly: {
}, targetAmount: weeklyAmount,
weekly: { spentAmount: prevBudgetData.weekly.spentAmount,
targetAmount: amount, remainingAmount: Math.max(0, weeklyAmount - prevBudgetData.weekly.spentAmount)
spentAmount: prevBudgetData.weekly.spentAmount, },
remainingAmount: Math.max(0, amount - prevBudgetData.weekly.spentAmount) monthly: {
}, targetAmount: monthlyAmount,
monthly: { spentAmount: prevBudgetData.monthly.spentAmount,
targetAmount: monthlyAmount, remainingAmount: Math.max(0, monthlyAmount - prevBudgetData.monthly.spentAmount)
spentAmount: prevBudgetData.monthly.spentAmount, }
remainingAmount: Math.max(0, monthlyAmount - prevBudgetData.monthly.spentAmount) };
}
};
} else {
// 문제 수정: 주간 예산은 일일*7, 월간 예산은 일일*30으로 계산
const weeklyAmount = Math.round(amount * 7);
const monthlyAmount = Math.round(amount * 30);
console.log(`일일 예산 ${amount}원으로 설정 → 주간: ${weeklyAmount}원, 월간: ${monthlyAmount}`);
return {
daily: {
targetAmount: amount,
spentAmount: prevBudgetData.daily.spentAmount,
remainingAmount: Math.max(0, amount - prevBudgetData.daily.spentAmount)
},
weekly: {
targetAmount: weeklyAmount,
spentAmount: prevBudgetData.weekly.spentAmount,
remainingAmount: Math.max(0, weeklyAmount - prevBudgetData.weekly.spentAmount)
},
monthly: {
targetAmount: monthlyAmount,
spentAmount: prevBudgetData.monthly.spentAmount,
remainingAmount: Math.max(0, monthlyAmount - prevBudgetData.monthly.spentAmount)
}
};
}
}; };
// 지출액 계산 (일일, 주간, 월간) // 지출액 계산 (일일, 주간, 월간)

View File

@@ -23,18 +23,18 @@ export const resetAllData = (): void => {
'categoryBudgets', 'categoryBudgets',
'budgetData', 'budgetData',
'budget', 'budget',
'monthlyExpenses', // 월간 지출 데이터 'monthlyExpenses',
'categorySpending', // 카테고리별 지출 데이터 'categorySpending',
'expenseAnalytics', // 지출 분석 데이터 'expenseAnalytics',
'expenseHistory', // 지출 이력 'expenseHistory',
'budgetHistory', // 예산 이력 'budgetHistory',
'analyticsCache', // 분석 캐시 데이터 'analyticsCache',
'monthlyTotals', // 월간 합계 데이터 'monthlyTotals',
'analytics', // 분석 페이지 데이터 'analytics',
'dailyBudget', // 일일 예산 'dailyBudget',
'weeklyBudget', // 주간 예산 'weeklyBudget',
'monthlyBudget', // 월간 예산 'monthlyBudget',
'chartData', // 차트 데이터 'chartData',
]; ];
try { try {
@@ -42,6 +42,7 @@ export const resetAllData = (): void => {
dataKeys.forEach(key => { dataKeys.forEach(key => {
console.log(`삭제 중: ${key}`); console.log(`삭제 중: ${key}`);
localStorage.removeItem(key); localStorage.removeItem(key);
localStorage.removeItem(`${key}_backup`); // 백업 키도 함께 삭제
}); });
// 파일별 초기화 함수 호출 // 파일별 초기화 함수 호출
@@ -61,19 +62,15 @@ export const resetAllData = (): void => {
localStorage.setItem('transactions_backup', JSON.stringify([])); localStorage.setItem('transactions_backup', JSON.stringify([]));
// 이벤트 발생시켜 데이터 로드 트리거 - 이벤트 순서 최적화 // 이벤트 발생시켜 데이터 로드 트리거 - 이벤트 순서 최적화
try { const events = [
// 한 번에 모든 이벤트 발생 new Event('transactionUpdated'),
const events = [ new Event('budgetDataUpdated'),
new Event('transactionUpdated'), new Event('categoryBudgetsUpdated'),
new Event('budgetDataUpdated'), new StorageEvent('storage')
new Event('categoryBudgetsUpdated'), ];
new StorageEvent('storage')
];
events.forEach(event => window.dispatchEvent(event)); // 모든 이벤트 동시에 발생
} catch (e) { events.forEach(event => window.dispatchEvent(event));
console.error('이벤트 발생 오류:', e);
}
// 중요: 사용자 설정 값 복원 (백업한 값이 있는 경우) // 중요: 사용자 설정 값 복원 (백업한 값이 있는 경우)
if (dontShowWelcomeValue) { if (dontShowWelcomeValue) {

View File

@@ -65,7 +65,7 @@ export const loadBudgetFromStorage = (): number => {
return 0; return 0;
}; };
// 모든 데이터 완전히 초기화 // 모든 데이터 완전히 초기화 - 성능 최적화
export const resetAllStorageData = (): void => { export const resetAllStorageData = (): void => {
console.log('완전 초기화 시작 - resetAllStorageData'); console.log('완전 초기화 시작 - resetAllStorageData');
@@ -100,7 +100,7 @@ export const resetAllStorageData = (): void => {
'budgetHistory', 'budgetHistory',
'transactionHistory', 'transactionHistory',
'lastSync', 'lastSync',
'syncEnabled', // 동기화 설정도 초기화 'syncEnabled'
]; ];
// 키 동시에 삭제 (성능 최적화) // 키 동시에 삭제 (성능 최적화)