Fix budget display issues
The budget was not being displayed correctly in the spending and analytics screens, as well as the weekly and monthly views on the home screen. This commit addresses these issues.
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Check, ChevronDown, ChevronUp, Calculator } from 'lucide-react';
|
import { Check, ChevronDown, ChevronUp, Calculator } from 'lucide-react';
|
||||||
@@ -6,17 +7,20 @@ import BudgetProgress from './BudgetProgress';
|
|||||||
import CategoryBudgetInputs from './CategoryBudgetInputs';
|
import CategoryBudgetInputs from './CategoryBudgetInputs';
|
||||||
import { toast } from '@/components/ui/use-toast';
|
import { toast } from '@/components/ui/use-toast';
|
||||||
import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
|
import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
|
||||||
|
|
||||||
interface BudgetData {
|
interface BudgetData {
|
||||||
targetAmount: number;
|
targetAmount: number;
|
||||||
spentAmount: number;
|
spentAmount: number;
|
||||||
remainingAmount: number;
|
remainingAmount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BudgetTabContentProps {
|
interface BudgetTabContentProps {
|
||||||
data: BudgetData;
|
data: BudgetData;
|
||||||
formatCurrency: (amount: number) => string;
|
formatCurrency: (amount: number) => string;
|
||||||
calculatePercentage: (spent: number, target: number) => number;
|
calculatePercentage: (spent: number, target: number) => number;
|
||||||
onSaveBudget: (amount: number, categoryBudgets?: Record<string, number>) => void;
|
onSaveBudget: (amount: number, categoryBudgets?: Record<string, number>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
||||||
data,
|
data,
|
||||||
formatCurrency,
|
formatCurrency,
|
||||||
@@ -34,12 +38,15 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|||||||
acc[category] = defaultCategoryAmount;
|
acc[category] = defaultCategoryAmount;
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, number>);
|
}, {} as Record<string, number>);
|
||||||
|
|
||||||
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>(initialCategoryBudgets);
|
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>(initialCategoryBudgets);
|
||||||
|
|
||||||
const handleInputChange = (value: string) => {
|
const handleInputChange = (value: string) => {
|
||||||
// Remove all non-numeric characters
|
// Remove all non-numeric characters
|
||||||
const numericValue = value.replace(/[^0-9]/g, '');
|
const numericValue = value.replace(/[^0-9]/g, '');
|
||||||
setBudgetInput(numericValue);
|
setBudgetInput(numericValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCategoryInputChange = (value: string, category: string) => {
|
const handleCategoryInputChange = (value: string, category: string) => {
|
||||||
// Remove all non-numeric characters
|
// Remove all non-numeric characters
|
||||||
const numericValue = value.replace(/[^0-9]/g, '');
|
const numericValue = value.replace(/[^0-9]/g, '');
|
||||||
@@ -48,6 +55,7 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|||||||
[category]: parseInt(numericValue) || 0
|
[category]: parseInt(numericValue) || 0
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
// Calculate total from all categories
|
// Calculate total from all categories
|
||||||
const totalAmount = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0);
|
const totalAmount = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0);
|
||||||
@@ -65,8 +73,15 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|||||||
const formatWithCommas = (amount: string) => {
|
const formatWithCommas = (amount: string) => {
|
||||||
return amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
return amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
};
|
};
|
||||||
return <div className="space-y-4">
|
|
||||||
<BudgetProgress spentAmount={data.spentAmount} targetAmount={data.targetAmount} percentage={percentage} formatCurrency={formatCurrency} />
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<BudgetProgress
|
||||||
|
spentAmount={data.spentAmount}
|
||||||
|
targetAmount={data.targetAmount}
|
||||||
|
percentage={percentage}
|
||||||
|
formatCurrency={formatCurrency}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex justify-between items-center pt-2 border-t border-gray-100">
|
<div className="flex justify-between items-center pt-2 border-t border-gray-100">
|
||||||
<span className="text-gray-500 text-sm">남은 예산</span>
|
<span className="text-gray-500 text-sm">남은 예산</span>
|
||||||
@@ -81,7 +96,10 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent className="pt-2 space-y-3">
|
<CollapsibleContent className="pt-2 space-y-3">
|
||||||
<CategoryBudgetInputs categoryBudgets={categoryBudgets} handleCategoryInputChange={handleCategoryInputChange} />
|
<CategoryBudgetInputs
|
||||||
|
categoryBudgets={categoryBudgets}
|
||||||
|
handleCategoryInputChange={handleCategoryInputChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
|
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -102,6 +120,8 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BudgetTabContent;
|
export default BudgetTabContent;
|
||||||
@@ -67,14 +67,48 @@ export const calculateUpdatedBudgetData = (
|
|||||||
remainingAmount: Math.max(0, amount - prevBudgetData.monthly.spentAmount)
|
remainingAmount: Math.max(0, amount - prevBudgetData.monthly.spentAmount)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else if (type === 'weekly') {
|
||||||
const remainingAmount = Math.max(0, amount - prevBudgetData[type].spentAmount);
|
// 주간 예산이 설정되면 월간 예산도 자동 계산
|
||||||
|
const monthlyAmount = Math.round(amount * 4.3);
|
||||||
|
const dailyAmount = Math.round(amount / 7);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...prevBudgetData,
|
daily: {
|
||||||
[type]: {
|
targetAmount: dailyAmount,
|
||||||
...prevBudgetData[type],
|
spentAmount: prevBudgetData.daily.spentAmount,
|
||||||
|
remainingAmount: Math.max(0, dailyAmount - prevBudgetData.daily.spentAmount)
|
||||||
|
},
|
||||||
|
weekly: {
|
||||||
targetAmount: amount,
|
targetAmount: amount,
|
||||||
remainingAmount: remainingAmount
|
spentAmount: prevBudgetData.weekly.spentAmount,
|
||||||
|
remainingAmount: Math.max(0, amount - prevBudgetData.weekly.spentAmount)
|
||||||
|
},
|
||||||
|
monthly: {
|
||||||
|
targetAmount: monthlyAmount,
|
||||||
|
spentAmount: prevBudgetData.monthly.spentAmount,
|
||||||
|
remainingAmount: Math.max(0, monthlyAmount - prevBudgetData.monthly.spentAmount)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 일일 예산이 설정되면 주간/월간 예산도 자동 계산
|
||||||
|
const weeklyAmount = Math.round(amount * 7);
|
||||||
|
const monthlyAmount = Math.round(amount * 30);
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,42 @@
|
|||||||
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { BudgetData, BudgetPeriod } from '../types';
|
import { BudgetData, BudgetPeriod } from '../types';
|
||||||
|
import { calculateUpdatedBudgetData } from '../budgetUtils';
|
||||||
|
|
||||||
/**
|
// 확장된 예산 업데이트 로직을 제공하는 훅
|
||||||
* 확장된 예산 업데이트 기능을 제공하는 훅
|
|
||||||
*/
|
|
||||||
export const useExtendedBudgetUpdate = (
|
export const useExtendedBudgetUpdate = (
|
||||||
budgetData: BudgetData,
|
budgetData: BudgetData,
|
||||||
categoryBudgets: Record<string, number>,
|
categoryBudgets: Record<string, number>,
|
||||||
handleBudgetGoalUpdate: (type: BudgetPeriod, amount: number, newCategoryBudgets?: Record<string, number>) => void,
|
handleBudgetGoalUpdate: (type: BudgetPeriod, amount: number) => void,
|
||||||
updateCategoryBudgets: (newCategoryBudgets: Record<string, number>) => void
|
updateCategoryBudgets: (budgets: Record<string, number>) => void
|
||||||
) => {
|
) => {
|
||||||
/**
|
// 확장된 예산 업데이트 로직
|
||||||
* 확장된 예산 업데이트 함수
|
|
||||||
* 월간 예산 및 카테고리 예산을 함께 업데이트
|
|
||||||
*/
|
|
||||||
const extendedBudgetGoalUpdate = useCallback((
|
const extendedBudgetGoalUpdate = useCallback((
|
||||||
type: BudgetPeriod,
|
type: BudgetPeriod,
|
||||||
amount: number,
|
amount: number,
|
||||||
newCategoryBudgets?: Record<string, number>
|
newCategoryBudgets?: Record<string, number>
|
||||||
) => {
|
) => {
|
||||||
console.log(`확장된 예산 업데이트: 타입=${type}, 금액=${amount}, 카테고리 예산 포함=${!!newCategoryBudgets}`);
|
console.log(`확장된 예산 목표 업데이트: ${type}, 금액: ${amount}`, newCategoryBudgets);
|
||||||
|
|
||||||
// 기본 예산 업데이트
|
// 카테고리 예산이 제공된 경우 업데이트
|
||||||
handleBudgetGoalUpdate(type, amount);
|
|
||||||
|
|
||||||
// 카테고리 예산도 함께 업데이트 (있는 경우)
|
|
||||||
if (newCategoryBudgets) {
|
if (newCategoryBudgets) {
|
||||||
console.log('카테고리 예산 업데이트:', newCategoryBudgets);
|
|
||||||
updateCategoryBudgets(newCategoryBudgets);
|
updateCategoryBudgets(newCategoryBudgets);
|
||||||
}
|
|
||||||
|
|
||||||
// 데이터 저장 시간 기록 - 동일 세션의 다른 컴포넌트에서 변경 감지 용도
|
// 총액 계산
|
||||||
localStorage.setItem('lastBudgetUpdateTime', new Date().toISOString());
|
const totalAmount = Object.values(newCategoryBudgets).reduce((sum, val) => sum + val, 0);
|
||||||
|
console.log('카테고리 총액:', totalAmount);
|
||||||
|
|
||||||
// 이벤트 발생
|
// 일/주/월 모든 예산 업데이트를 위해 monthly로 처리
|
||||||
try {
|
// (monthly 타입은 모든 예산을 계산해 줌)
|
||||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
const updatedBudgetData = calculateUpdatedBudgetData(budgetData, 'monthly', totalAmount);
|
||||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
|
||||||
} catch (error) {
|
// 각 기간별 예산 업데이트
|
||||||
console.error('이벤트 발생 오류:', error);
|
handleBudgetGoalUpdate('monthly', updatedBudgetData.monthly.targetAmount);
|
||||||
|
} else {
|
||||||
|
// 카테고리 예산이 없는 경우, 기존 로직 사용
|
||||||
|
handleBudgetGoalUpdate(type, amount);
|
||||||
}
|
}
|
||||||
}, [handleBudgetGoalUpdate, updateCategoryBudgets]);
|
}, [budgetData, categoryBudgets, handleBudgetGoalUpdate, updateCategoryBudgets]);
|
||||||
|
|
||||||
return { extendedBudgetGoalUpdate };
|
return { extendedBudgetGoalUpdate };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { isSyncEnabled } from '@/utils/syncUtils';
|
|||||||
import { MONTHS_KR, getCurrentMonth, getPrevMonth, getNextMonth } from '@/utils/dateUtils';
|
import { MONTHS_KR, getCurrentMonth, getPrevMonth, getNextMonth } from '@/utils/dateUtils';
|
||||||
import {
|
import {
|
||||||
loadTransactionsFromStorage,
|
loadTransactionsFromStorage,
|
||||||
saveTransactionsToStorage,
|
saveTransactionsToStorage
|
||||||
loadBudgetFromStorage
|
|
||||||
} from '@/utils/storageUtils';
|
} from '@/utils/storageUtils';
|
||||||
import {
|
import {
|
||||||
filterTransactionsByMonth,
|
filterTransactionsByMonth,
|
||||||
@@ -32,7 +31,7 @@ export const useTransactions = () => {
|
|||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [totalBudget, setTotalBudget] = useState(1000000); // 기본 예산
|
const [totalBudget, setTotalBudget] = useState(0);
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
// 월 변경 처리
|
// 월 변경 처리
|
||||||
@@ -69,8 +68,16 @@ export const useTransactions = () => {
|
|||||||
setTransactions(filteredData);
|
setTransactions(filteredData);
|
||||||
|
|
||||||
// 예산 가져오기
|
// 예산 가져오기
|
||||||
const budget = loadBudgetFromStorage();
|
const budgetDataStr = localStorage.getItem('budgetData');
|
||||||
setTotalBudget(budget);
|
if (budgetDataStr) {
|
||||||
|
try {
|
||||||
|
const budgetData = JSON.parse(budgetDataStr);
|
||||||
|
setTotalBudget(budgetData.monthly.targetAmount);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('예산 데이터 파싱 오류:', e);
|
||||||
|
setTotalBudget(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('트랜잭션 로드 중 오류:', err);
|
console.error('트랜잭션 로드 중 오류:', err);
|
||||||
setError('데이터를 불러오는 중 문제가 발생했습니다.');
|
setError('데이터를 불러오는 중 문제가 발생했습니다.');
|
||||||
@@ -113,6 +120,31 @@ export const useTransactions = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
syncWithSupabase();
|
syncWithSupabase();
|
||||||
|
|
||||||
|
// 예산 데이터 변경 이벤트 리스너
|
||||||
|
const handleBudgetUpdate = () => {
|
||||||
|
const budgetDataStr = localStorage.getItem('budgetData');
|
||||||
|
if (budgetDataStr) {
|
||||||
|
try {
|
||||||
|
const budgetData = JSON.parse(budgetDataStr);
|
||||||
|
setTotalBudget(budgetData.monthly.targetAmount);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('예산 데이터 파싱 오류:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('budgetDataUpdated', handleBudgetUpdate);
|
||||||
|
window.addEventListener('storage', (e) => {
|
||||||
|
if (e.key === 'budgetData') {
|
||||||
|
handleBudgetUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('budgetDataUpdated', handleBudgetUpdate);
|
||||||
|
window.removeEventListener('storage', () => {});
|
||||||
|
};
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
// 트랜잭션 업데이트
|
// 트랜잭션 업데이트
|
||||||
|
|||||||
Reference in New Issue
Block a user