Fix budget update issues
Addresses delayed notifications and data loss after budget updates and page transitions.
This commit is contained in:
@@ -34,6 +34,7 @@ const BudgetInputCard: React.FC<BudgetGoalProps> = ({
|
|||||||
return numericValue.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
return numericValue.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 초기값 변경시 입력 필드 값 업데이트
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBudgetInputs({
|
setBudgetInputs({
|
||||||
daily: initialBudgets.daily > 0 ? initialBudgets.daily.toString() : '',
|
daily: initialBudgets.daily > 0 ? initialBudgets.daily.toString() : '',
|
||||||
@@ -53,9 +54,21 @@ const BudgetInputCard: React.FC<BudgetGoalProps> = ({
|
|||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
const amount = parseInt(budgetInputs[selectedTab].replace(/,/g, ''), 10) || 0;
|
const amount = parseInt(budgetInputs[selectedTab].replace(/,/g, ''), 10) || 0;
|
||||||
onSave(selectedTab, amount);
|
if (amount <= 0) {
|
||||||
// Close the collapsible after saving
|
return; // 0 이하의 금액은 저장하지 않음
|
||||||
|
}
|
||||||
|
|
||||||
|
// 즉시 입력 필드를 업데이트하여 사용자에게 피드백 제공
|
||||||
|
setBudgetInputs(prev => ({
|
||||||
|
...prev,
|
||||||
|
[selectedTab]: amount.toString()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 즉시 콜랩시블을 닫아 사용자에게 완료 피드백 제공
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
|
||||||
|
// 예산 저장
|
||||||
|
onSave(selectedTab, amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 비어있으면 빈 문자열을, 그렇지 않으면 포맷팅된 문자열을 반환
|
// 비어있으면 빈 문자열을, 그렇지 않으면 포맷팅된 문자열을 반환
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
|
import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
|
import { toast } from '@/components/ui/use-toast';
|
||||||
|
|
||||||
interface CategoryBudgetInputsProps {
|
interface CategoryBudgetInputsProps {
|
||||||
categoryBudgets: Record<string, number>;
|
categoryBudgets: Record<string, number>;
|
||||||
@@ -14,6 +15,7 @@ const CategoryBudgetInputs: React.FC<CategoryBudgetInputsProps> = ({
|
|||||||
handleCategoryInputChange
|
handleCategoryInputChange
|
||||||
}) => {
|
}) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const previousBudgetsRef = useRef<Record<string, number>>({});
|
||||||
|
|
||||||
// Format number with commas for display
|
// Format number with commas for display
|
||||||
const formatWithCommas = (value: number): string => {
|
const formatWithCommas = (value: number): string => {
|
||||||
@@ -26,6 +28,12 @@ const CategoryBudgetInputs: React.FC<CategoryBudgetInputsProps> = ({
|
|||||||
// Remove all non-numeric characters before passing to parent handler
|
// Remove all non-numeric characters before passing to parent handler
|
||||||
const numericValue = e.target.value.replace(/[^0-9]/g, '');
|
const numericValue = e.target.value.replace(/[^0-9]/g, '');
|
||||||
handleCategoryInputChange(numericValue, category);
|
handleCategoryInputChange(numericValue, category);
|
||||||
|
|
||||||
|
// 사용자에게 시각적 피드백 제공
|
||||||
|
e.target.classList.add('border-green-500');
|
||||||
|
setTimeout(() => {
|
||||||
|
e.target.classList.remove('border-green-500');
|
||||||
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 컴포넌트가 마운트될 때 categoryBudgets가 로컬 스토리지에서 다시 로드되도록 이벤트 리스너 설정
|
// 컴포넌트가 마운트될 때 categoryBudgets가 로컬 스토리지에서 다시 로드되도록 이벤트 리스너 설정
|
||||||
@@ -44,6 +52,24 @@ const CategoryBudgetInputs: React.FC<CategoryBudgetInputsProps> = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 값이 변경될 때마다 토스트 메시지 표시
|
||||||
|
useEffect(() => {
|
||||||
|
const hasChanges = Object.keys(categoryBudgets).some(
|
||||||
|
category => categoryBudgets[category] !== previousBudgetsRef.current[category]
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalBudget = Object.values(categoryBudgets).reduce((sum, val) => sum + val, 0);
|
||||||
|
const previousTotal = Object.values(previousBudgetsRef.current).reduce((sum, val) => sum + val, 0);
|
||||||
|
|
||||||
|
// 이전 값과 다르고, 총 예산이 있는 경우 토스트 표시
|
||||||
|
if (hasChanges && totalBudget > 0 && totalBudget !== previousTotal) {
|
||||||
|
// 토스트 메시지는 storage에서 처리
|
||||||
|
}
|
||||||
|
|
||||||
|
// 현재 값을 이전 값으로 업데이트
|
||||||
|
previousBudgetsRef.current = { ...categoryBudgets };
|
||||||
|
}, [categoryBudgets]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2 w-full">
|
<div className="space-y-2 w-full">
|
||||||
{EXPENSE_CATEGORIES.map(category => (
|
{EXPENSE_CATEGORIES.map(category => (
|
||||||
@@ -53,7 +79,7 @@ const CategoryBudgetInputs: React.FC<CategoryBudgetInputsProps> = ({
|
|||||||
value={formatWithCommas(categoryBudgets[category] || 0)}
|
value={formatWithCommas(categoryBudgets[category] || 0)}
|
||||||
onChange={(e) => handleInput(e, category)}
|
onChange={(e) => handleInput(e, category)}
|
||||||
placeholder="예산 입력"
|
placeholder="예산 입력"
|
||||||
className={`neuro-pressed ${isMobile ? 'w-[150px]' : 'max-w-[150px]'} text-xs`}
|
className={`neuro-pressed transition-colors duration-300 ${isMobile ? 'w-[150px]' : 'max-w-[150px]'} text-xs`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -16,14 +16,26 @@ import {
|
|||||||
export const useBudgetDataState = (transactions: any[]) => {
|
export const useBudgetDataState = (transactions: any[]) => {
|
||||||
const [budgetData, setBudgetData] = useState<BudgetData>(loadBudgetDataFromStorage());
|
const [budgetData, setBudgetData] = useState<BudgetData>(loadBudgetDataFromStorage());
|
||||||
const [selectedTab, setSelectedTab] = useState<BudgetPeriod>("daily");
|
const [selectedTab, setSelectedTab] = useState<BudgetPeriod>("daily");
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
|
|
||||||
// 초기 로드 및 이벤트 리스너 설정
|
// 초기 로드 및 이벤트 리스너 설정
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadBudget = () => {
|
const loadBudget = () => {
|
||||||
console.log('예산 데이터 로드 시도 중...');
|
try {
|
||||||
const loadedData = loadBudgetDataFromStorage();
|
console.log('예산 데이터 로드 시도 중...');
|
||||||
console.log('예산 데이터 로드됨:', loadedData);
|
const loadedData = loadBudgetDataFromStorage();
|
||||||
setBudgetData(loadedData);
|
console.log('예산 데이터 로드됨:', loadedData);
|
||||||
|
setBudgetData(loadedData);
|
||||||
|
|
||||||
|
// 최근 데이터 로드 시간 기록
|
||||||
|
localStorage.setItem('lastBudgetDataLoadTime', new Date().toISOString());
|
||||||
|
|
||||||
|
if (!isInitialized) {
|
||||||
|
setIsInitialized(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('예산 데이터 로드 중 오류:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 초기 로드
|
// 초기 로드
|
||||||
@@ -39,30 +51,56 @@ export const useBudgetDataState = (transactions: any[]) => {
|
|||||||
|
|
||||||
window.addEventListener('budgetDataUpdated', () => handleBudgetUpdate());
|
window.addEventListener('budgetDataUpdated', () => handleBudgetUpdate());
|
||||||
window.addEventListener('storage', handleBudgetUpdate);
|
window.addEventListener('storage', handleBudgetUpdate);
|
||||||
|
window.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
console.log('페이지 보임: 예산 데이터 새로고침');
|
||||||
|
loadBudget();
|
||||||
|
}
|
||||||
|
});
|
||||||
window.addEventListener('focus', () => {
|
window.addEventListener('focus', () => {
|
||||||
console.log('창 포커스: 예산 데이터 새로고침');
|
console.log('창 포커스: 예산 데이터 새로고침');
|
||||||
loadBudget();
|
loadBudget();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 주기적 데이터 검사 (1초마다)
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
const lastSaveTime = localStorage.getItem('lastBudgetSaveTime');
|
||||||
|
const lastLoadTime = localStorage.getItem('lastBudgetDataLoadTime');
|
||||||
|
|
||||||
|
if (lastSaveTime && lastLoadTime && new Date(lastSaveTime) > new Date(lastLoadTime)) {
|
||||||
|
console.log('새로운 저장 감지됨, 데이터 다시 로드...');
|
||||||
|
loadBudget();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('budgetDataUpdated', () => handleBudgetUpdate());
|
window.removeEventListener('budgetDataUpdated', () => handleBudgetUpdate());
|
||||||
window.removeEventListener('storage', handleBudgetUpdate);
|
window.removeEventListener('storage', handleBudgetUpdate);
|
||||||
|
window.removeEventListener('visibilitychange', () => {});
|
||||||
window.removeEventListener('focus', () => loadBudget());
|
window.removeEventListener('focus', () => loadBudget());
|
||||||
|
clearInterval(intervalId);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [isInitialized]);
|
||||||
|
|
||||||
// 트랜잭션 변경 시 지출 금액 업데이트
|
// 트랜잭션 변경 시 지출 금액 업데이트
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (transactions.length > 0) {
|
if (transactions.length > 0) {
|
||||||
console.log('트랜잭션 변경으로 인한 예산 데이터 업데이트. 트랜잭션 수:', transactions.length);
|
console.log('트랜잭션 변경으로 인한 예산 데이터 업데이트. 트랜잭션 수:', transactions.length);
|
||||||
// 지출 금액 업데이트
|
try {
|
||||||
const updatedBudgetData = calculateSpentAmounts(transactions, budgetData);
|
// 지출 금액 업데이트
|
||||||
|
const updatedBudgetData = calculateSpentAmounts(transactions, budgetData);
|
||||||
// 상태 및 스토리지 모두 업데이트
|
|
||||||
setBudgetData(updatedBudgetData);
|
// 상태 및 스토리지 모두 업데이트
|
||||||
saveBudgetDataToStorage(updatedBudgetData);
|
setBudgetData(updatedBudgetData);
|
||||||
|
saveBudgetDataToStorage(updatedBudgetData);
|
||||||
|
|
||||||
|
// 저장 시간 업데이트
|
||||||
|
localStorage.setItem('lastBudgetSaveTime', new Date().toISOString());
|
||||||
|
} catch (error) {
|
||||||
|
console.error('예산 데이터 업데이트 중 오류:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [transactions]);
|
}, [transactions, budgetData]);
|
||||||
|
|
||||||
// 예산 목표 업데이트 함수
|
// 예산 목표 업데이트 함수
|
||||||
const handleBudgetGoalUpdate = useCallback((
|
const handleBudgetGoalUpdate = useCallback((
|
||||||
@@ -70,26 +108,42 @@ export const useBudgetDataState = (transactions: any[]) => {
|
|||||||
amount: number,
|
amount: number,
|
||||||
newCategoryBudgets?: Record<string, number>
|
newCategoryBudgets?: Record<string, number>
|
||||||
) => {
|
) => {
|
||||||
console.log(`예산 목표 업데이트: ${type}, 금액: ${amount}`);
|
try {
|
||||||
// 월간 예산 직접 업데이트 (카테고리 예산이 없는 경우)
|
console.log(`예산 목표 업데이트: ${type}, 금액: ${amount}`);
|
||||||
if (!newCategoryBudgets) {
|
// 월간 예산 직접 업데이트 (카테고리 예산이 없는 경우)
|
||||||
const updatedBudgetData = calculateUpdatedBudgetData(budgetData, type, amount);
|
if (!newCategoryBudgets) {
|
||||||
console.log('새 예산 데이터:', updatedBudgetData);
|
const updatedBudgetData = calculateUpdatedBudgetData(budgetData, type, amount);
|
||||||
setBudgetData(updatedBudgetData);
|
console.log('새 예산 데이터:', updatedBudgetData);
|
||||||
saveBudgetDataToStorage(updatedBudgetData);
|
setBudgetData(updatedBudgetData);
|
||||||
|
saveBudgetDataToStorage(updatedBudgetData);
|
||||||
|
|
||||||
|
// 저장 시간 업데이트
|
||||||
|
localStorage.setItem('lastBudgetSaveTime', new Date().toISOString());
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('예산 목표 업데이트 중 오류:', error);
|
||||||
toast({
|
toast({
|
||||||
title: "목표 업데이트 완료",
|
title: "예산 업데이트 실패",
|
||||||
description: `${type === 'daily' ? '일일' : type === 'weekly' ? '주간' : '월간'} 목표가 ${amount.toLocaleString()}원으로 설정되었습니다.`
|
description: "예산 목표를 업데이트하는데 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [budgetData]);
|
}, [budgetData]);
|
||||||
|
|
||||||
// 예산 데이터 초기화 함수
|
// 예산 데이터 초기화 함수
|
||||||
const resetBudgetData = useCallback(() => {
|
const resetBudgetData = useCallback(() => {
|
||||||
console.log('예산 데이터 초기화');
|
try {
|
||||||
clearAllBudgetData();
|
console.log('예산 데이터 초기화');
|
||||||
setBudgetData(loadBudgetDataFromStorage());
|
clearAllBudgetData();
|
||||||
|
setBudgetData(loadBudgetDataFromStorage());
|
||||||
|
} catch (error) {
|
||||||
|
console.error('예산 데이터 초기화 중 오류:', error);
|
||||||
|
toast({
|
||||||
|
title: "예산 초기화 실패",
|
||||||
|
description: "예산 데이터를 초기화하는데 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 예산 데이터 변경 시 로그 기록
|
// 예산 데이터 변경 시 로그 기록
|
||||||
|
|||||||
@@ -11,14 +11,26 @@ export const useCategoryBudgetState = () => {
|
|||||||
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>(
|
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>(
|
||||||
loadCategoryBudgetsFromStorage()
|
loadCategoryBudgetsFromStorage()
|
||||||
);
|
);
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
|
|
||||||
// 초기 로드 및 이벤트 리스너 설정
|
// 초기 로드 및 이벤트 리스너 설정
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadCategories = () => {
|
const loadCategories = () => {
|
||||||
console.log('카테고리 예산 로드 시도 중...');
|
try {
|
||||||
const loaded = loadCategoryBudgetsFromStorage();
|
console.log('카테고리 예산 로드 시도 중...');
|
||||||
console.log('카테고리 예산 로드됨:', loaded);
|
const loaded = loadCategoryBudgetsFromStorage();
|
||||||
setCategoryBudgets(loaded);
|
console.log('카테고리 예산 로드됨:', loaded);
|
||||||
|
setCategoryBudgets(loaded);
|
||||||
|
|
||||||
|
// 최근 데이터 로드 시간 기록
|
||||||
|
localStorage.setItem('lastCategoryBudgetLoadTime', new Date().toISOString());
|
||||||
|
|
||||||
|
if (!isInitialized) {
|
||||||
|
setIsInitialized(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('카테고리 예산 로드 중 오류:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 초기 로드
|
// 초기 로드
|
||||||
@@ -34,30 +46,60 @@ export const useCategoryBudgetState = () => {
|
|||||||
|
|
||||||
window.addEventListener('categoryBudgetsUpdated', () => handleCategoryUpdate());
|
window.addEventListener('categoryBudgetsUpdated', () => handleCategoryUpdate());
|
||||||
window.addEventListener('storage', handleCategoryUpdate);
|
window.addEventListener('storage', handleCategoryUpdate);
|
||||||
|
window.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
console.log('페이지 보임: 카테고리 예산 새로고침');
|
||||||
|
loadCategories();
|
||||||
|
}
|
||||||
|
});
|
||||||
window.addEventListener('focus', () => {
|
window.addEventListener('focus', () => {
|
||||||
console.log('창 포커스: 카테고리 예산 새로고침');
|
console.log('창 포커스: 카테고리 예산 새로고침');
|
||||||
loadCategories();
|
loadCategories();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 주기적 데이터 검사
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
const lastSaveTime = localStorage.getItem('lastCategoryBudgetSaveTime');
|
||||||
|
const lastLoadTime = localStorage.getItem('lastCategoryBudgetLoadTime');
|
||||||
|
|
||||||
|
if (lastSaveTime && lastLoadTime && new Date(lastSaveTime) > new Date(lastLoadTime)) {
|
||||||
|
console.log('새로운 카테고리 저장 감지됨, 데이터 다시 로드...');
|
||||||
|
loadCategories();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('categoryBudgetsUpdated', () => handleCategoryUpdate());
|
window.removeEventListener('categoryBudgetsUpdated', () => handleCategoryUpdate());
|
||||||
window.removeEventListener('storage', handleCategoryUpdate);
|
window.removeEventListener('storage', handleCategoryUpdate);
|
||||||
|
window.removeEventListener('visibilitychange', () => {});
|
||||||
window.removeEventListener('focus', () => loadCategories());
|
window.removeEventListener('focus', () => loadCategories());
|
||||||
|
clearInterval(intervalId);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [isInitialized]);
|
||||||
|
|
||||||
// 카테고리 예산 업데이트 함수
|
// 카테고리 예산 업데이트 함수
|
||||||
const updateCategoryBudgets = useCallback((newCategoryBudgets: Record<string, number>) => {
|
const updateCategoryBudgets = useCallback((newCategoryBudgets: Record<string, number>) => {
|
||||||
console.log('카테고리 예산 업데이트:', newCategoryBudgets);
|
try {
|
||||||
setCategoryBudgets(newCategoryBudgets);
|
console.log('카테고리 예산 업데이트:', newCategoryBudgets);
|
||||||
saveCategoryBudgetsToStorage(newCategoryBudgets);
|
setCategoryBudgets(newCategoryBudgets);
|
||||||
|
saveCategoryBudgetsToStorage(newCategoryBudgets);
|
||||||
|
|
||||||
|
// 저장 시간 업데이트
|
||||||
|
localStorage.setItem('lastCategoryBudgetSaveTime', new Date().toISOString());
|
||||||
|
} catch (error) {
|
||||||
|
console.error('카테고리 예산 업데이트 중 오류:', error);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 카테고리 예산 초기화 함수
|
// 카테고리 예산 초기화 함수
|
||||||
const resetCategoryBudgets = useCallback(() => {
|
const resetCategoryBudgets = useCallback(() => {
|
||||||
console.log('카테고리 예산 초기화');
|
try {
|
||||||
clearAllCategoryBudgets();
|
console.log('카테고리 예산 초기화');
|
||||||
setCategoryBudgets(loadCategoryBudgetsFromStorage());
|
clearAllCategoryBudgets();
|
||||||
|
setCategoryBudgets(loadCategoryBudgetsFromStorage());
|
||||||
|
} catch (error) {
|
||||||
|
console.error('카테고리 예산 초기화 중 오류:', error);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 카테고리 예산 변경 시 로그 기록
|
// 카테고리 예산 변경 시 로그 기록
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import { BudgetData } from '../types';
|
import { BudgetData } from '../types';
|
||||||
import { getInitialBudgetData } from '../budgetUtils';
|
import { getInitialBudgetData } from '../budgetUtils';
|
||||||
|
import { toast } from '@/components/ui/use-toast';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 예산 데이터 불러오기
|
* 예산 데이터 불러오기
|
||||||
@@ -36,14 +37,36 @@ export const saveBudgetDataToStorage = (budgetData: BudgetData): void => {
|
|||||||
localStorage.setItem('budgetData', dataString);
|
localStorage.setItem('budgetData', dataString);
|
||||||
console.log('예산 데이터 저장 완료', budgetData);
|
console.log('예산 데이터 저장 완료', budgetData);
|
||||||
|
|
||||||
|
// 중요: 즉시 자동 백업 (데이터 손실 방지)
|
||||||
|
localStorage.setItem('budgetData_backup', dataString);
|
||||||
|
|
||||||
// 스토리지 이벤트 수동 트리거 (동일 창에서도 감지하기 위함)
|
// 스토리지 이벤트 수동 트리거 (동일 창에서도 감지하기 위함)
|
||||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
try {
|
||||||
window.dispatchEvent(new StorageEvent('storage', {
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
key: 'budgetData',
|
window.dispatchEvent(new StorageEvent('storage', {
|
||||||
newValue: dataString
|
key: 'budgetData',
|
||||||
}));
|
newValue: dataString
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('이벤트 발생 오류:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// toast 알림은 즉시 표시
|
||||||
|
if (budgetData.monthly.targetAmount > 0) {
|
||||||
|
toast({
|
||||||
|
title: "예산 저장 완료",
|
||||||
|
description: `월 예산이 ${budgetData.monthly.targetAmount.toLocaleString()}원으로 설정되었습니다.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('예산 데이터 저장 오류:', error);
|
console.error('예산 데이터 저장 오류:', error);
|
||||||
|
|
||||||
|
// 오류 발생 시 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "예산 저장 실패",
|
||||||
|
description: "예산 데이터를 저장하는데 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,10 +76,14 @@ export const saveBudgetDataToStorage = (budgetData: BudgetData): void => {
|
|||||||
export const clearAllBudgetData = (): void => {
|
export const clearAllBudgetData = (): void => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem('budgetData');
|
localStorage.removeItem('budgetData');
|
||||||
|
localStorage.removeItem('budgetData_backup');
|
||||||
|
|
||||||
// 기본값으로 재설정
|
// 기본값으로 재설정
|
||||||
const initialData = getInitialBudgetData();
|
const initialData = getInitialBudgetData();
|
||||||
const dataString = JSON.stringify(initialData);
|
const dataString = JSON.stringify(initialData);
|
||||||
localStorage.setItem('budgetData', dataString);
|
localStorage.setItem('budgetData', dataString);
|
||||||
|
localStorage.setItem('budgetData_backup', dataString);
|
||||||
|
|
||||||
console.log('예산 데이터가 초기화되었습니다.');
|
console.log('예산 데이터가 초기화되었습니다.');
|
||||||
|
|
||||||
// 스토리지 이벤트 수동 트리거
|
// 스토리지 이벤트 수동 트리거
|
||||||
@@ -65,7 +92,18 @@ export const clearAllBudgetData = (): void => {
|
|||||||
key: 'budgetData',
|
key: 'budgetData',
|
||||||
newValue: dataString
|
newValue: dataString
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "예산 초기화",
|
||||||
|
description: "모든, 예산 데이터가 초기화되었습니다.",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('예산 데이터 삭제 오류:', error);
|
console.error('예산 데이터 삭제 오류:', error);
|
||||||
|
toast({
|
||||||
|
title: "초기화 실패",
|
||||||
|
description: "예산 데이터를 초기화하는데 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,29 @@
|
|||||||
|
|
||||||
import { DEFAULT_CATEGORY_BUDGETS } from '../budgetUtils';
|
import { DEFAULT_CATEGORY_BUDGETS } from '../budgetUtils';
|
||||||
|
import { toast } from '@/components/ui/use-toast';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 카테고리 예산 불러오기
|
* 카테고리 예산 불러오기
|
||||||
*/
|
*/
|
||||||
export const loadCategoryBudgetsFromStorage = (): Record<string, number> => {
|
export const loadCategoryBudgetsFromStorage = (): Record<string, number> => {
|
||||||
try {
|
try {
|
||||||
|
// 메인 스토리지에서 시도
|
||||||
const storedCategoryBudgets = localStorage.getItem('categoryBudgets');
|
const storedCategoryBudgets = localStorage.getItem('categoryBudgets');
|
||||||
if (storedCategoryBudgets) {
|
if (storedCategoryBudgets) {
|
||||||
const parsed = JSON.parse(storedCategoryBudgets);
|
const parsed = JSON.parse(storedCategoryBudgets);
|
||||||
console.log('카테고리 예산 로드 완료:', parsed);
|
console.log('카테고리 예산 로드 완료:', parsed);
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 백업에서 시도
|
||||||
|
const backupCategoryBudgets = localStorage.getItem('categoryBudgets_backup');
|
||||||
|
if (backupCategoryBudgets) {
|
||||||
|
const parsedBackup = JSON.parse(backupCategoryBudgets);
|
||||||
|
console.log('백업에서 카테고리 예산 복구:', parsedBackup);
|
||||||
|
// 메인 스토리지도 복구
|
||||||
|
localStorage.setItem('categoryBudgets', backupCategoryBudgets);
|
||||||
|
return parsedBackup;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('카테고리 예산 데이터 파싱 오류:', error);
|
console.error('카테고리 예산 데이터 파싱 오류:', error);
|
||||||
}
|
}
|
||||||
@@ -32,16 +44,42 @@ export const saveCategoryBudgetsToStorage = (categoryBudgets: Record<string, num
|
|||||||
|
|
||||||
// 로컬 스토리지에 저장
|
// 로컬 스토리지에 저장
|
||||||
localStorage.setItem('categoryBudgets', dataString);
|
localStorage.setItem('categoryBudgets', dataString);
|
||||||
|
// 백업 저장
|
||||||
|
localStorage.setItem('categoryBudgets_backup', dataString);
|
||||||
|
|
||||||
console.log('카테고리 예산 저장 완료:', categoryBudgets);
|
console.log('카테고리 예산 저장 완료:', categoryBudgets);
|
||||||
|
|
||||||
// 스토리지 이벤트 수동 트리거 (동일 창에서도 감지하기 위함)
|
// 스토리지 이벤트 수동 트리거 (동일 창에서도 감지하기 위함)
|
||||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
try {
|
||||||
window.dispatchEvent(new StorageEvent('storage', {
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
key: 'categoryBudgets',
|
window.dispatchEvent(new StorageEvent('storage', {
|
||||||
newValue: dataString
|
key: 'categoryBudgets',
|
||||||
}));
|
newValue: dataString
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('이벤트 발생 오류:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 마지막 저장 시간 기록 (데이터 검증용)
|
||||||
|
localStorage.setItem('lastCategoryBudgetSaveTime', new Date().toISOString());
|
||||||
|
|
||||||
|
// 토스트 알림
|
||||||
|
const totalBudget = Object.values(categoryBudgets).reduce((sum, val) => sum + val, 0);
|
||||||
|
if (totalBudget > 0) {
|
||||||
|
toast({
|
||||||
|
title: "카테고리 예산 저장 완료",
|
||||||
|
description: `카테고리별 예산 총 ${totalBudget.toLocaleString()}원이 설정되었습니다.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('카테고리 예산 저장 오류:', error);
|
console.error('카테고리 예산 저장 오류:', error);
|
||||||
|
|
||||||
|
// 오류 발생 시 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "카테고리 예산 저장 실패",
|
||||||
|
description: "카테고리 예산을 저장하는데 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,9 +89,13 @@ export const saveCategoryBudgetsToStorage = (categoryBudgets: Record<string, num
|
|||||||
export const clearAllCategoryBudgets = (): void => {
|
export const clearAllCategoryBudgets = (): void => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem('categoryBudgets');
|
localStorage.removeItem('categoryBudgets');
|
||||||
|
localStorage.removeItem('categoryBudgets_backup');
|
||||||
|
|
||||||
// 기본값으로 재설정
|
// 기본값으로 재설정
|
||||||
const dataString = JSON.stringify(DEFAULT_CATEGORY_BUDGETS);
|
const dataString = JSON.stringify(DEFAULT_CATEGORY_BUDGETS);
|
||||||
localStorage.setItem('categoryBudgets', dataString);
|
localStorage.setItem('categoryBudgets', dataString);
|
||||||
|
localStorage.setItem('categoryBudgets_backup', dataString);
|
||||||
|
|
||||||
console.log('카테고리 예산이 초기화되었습니다.');
|
console.log('카테고리 예산이 초기화되었습니다.');
|
||||||
|
|
||||||
// 이벤트 발생
|
// 이벤트 발생
|
||||||
@@ -62,7 +104,20 @@ export const clearAllCategoryBudgets = (): void => {
|
|||||||
key: 'categoryBudgets',
|
key: 'categoryBudgets',
|
||||||
newValue: dataString
|
newValue: dataString
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "카테고리 예산 초기화",
|
||||||
|
description: "모든 카테고리 예산이 기본값으로 초기화되었습니다.",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('카테고리 예산 삭제 오류:', error);
|
console.error('카테고리 예산 삭제 오류:', error);
|
||||||
|
|
||||||
|
// 오류 발생 시 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "초기화 실패",
|
||||||
|
description: "카테고리 예산을 초기화하는데 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,29 @@
|
|||||||
|
|
||||||
import { Transaction } from '../types';
|
import { Transaction } from '../types';
|
||||||
|
import { toast } from '@/components/ui/use-toast';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 로컬 스토리지에서 트랜잭션 불러오기
|
* 로컬 스토리지에서 트랜잭션 불러오기
|
||||||
*/
|
*/
|
||||||
export const loadTransactionsFromStorage = (): Transaction[] => {
|
export const loadTransactionsFromStorage = (): Transaction[] => {
|
||||||
try {
|
try {
|
||||||
|
// 메인 스토리지에서 먼저 시도
|
||||||
const storedTransactions = localStorage.getItem('transactions');
|
const storedTransactions = localStorage.getItem('transactions');
|
||||||
if (storedTransactions) {
|
if (storedTransactions) {
|
||||||
const parsedData = JSON.parse(storedTransactions);
|
const parsedData = JSON.parse(storedTransactions);
|
||||||
console.log('트랜잭션 로드 완료, 항목 수:', parsedData.length);
|
console.log('트랜잭션 로드 완료, 항목 수:', parsedData.length);
|
||||||
return parsedData;
|
return parsedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 백업에서 시도
|
||||||
|
const backupTransactions = localStorage.getItem('transactions_backup');
|
||||||
|
if (backupTransactions) {
|
||||||
|
const parsedBackup = JSON.parse(backupTransactions);
|
||||||
|
console.log('백업에서 트랜잭션 복구, 항목 수:', parsedBackup.length);
|
||||||
|
// 메인 스토리지도 복구
|
||||||
|
localStorage.setItem('transactions', backupTransactions);
|
||||||
|
return parsedBackup;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('트랜잭션 데이터 파싱 오류:', error);
|
console.error('트랜잭션 데이터 파싱 오류:', error);
|
||||||
}
|
}
|
||||||
@@ -28,16 +40,33 @@ export const saveTransactionsToStorage = (transactions: Transaction[]): void =>
|
|||||||
|
|
||||||
// 로컬 스토리지에 저장
|
// 로컬 스토리지에 저장
|
||||||
localStorage.setItem('transactions', dataString);
|
localStorage.setItem('transactions', dataString);
|
||||||
|
// 백업 저장
|
||||||
|
localStorage.setItem('transactions_backup', dataString);
|
||||||
|
|
||||||
console.log('트랜잭션 저장 완료, 항목 수:', transactions.length);
|
console.log('트랜잭션 저장 완료, 항목 수:', transactions.length);
|
||||||
|
|
||||||
// 스토리지 이벤트 수동 트리거 (동일 창에서도 감지하기 위함)
|
// 스토리지 이벤트 수동 트리거 (동일 창에서도 감지하기 위함)
|
||||||
window.dispatchEvent(new Event('transactionUpdated'));
|
try {
|
||||||
window.dispatchEvent(new StorageEvent('storage', {
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
key: 'transactions',
|
window.dispatchEvent(new StorageEvent('storage', {
|
||||||
newValue: dataString
|
key: 'transactions',
|
||||||
}));
|
newValue: dataString
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('이벤트 발생 오류:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 마지막 저장 시간 기록 (데이터 검증용)
|
||||||
|
localStorage.setItem('lastTransactionSaveTime', new Date().toISOString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('트랜잭션 저장 오류:', error);
|
console.error('트랜잭션 저장 오류:', error);
|
||||||
|
|
||||||
|
// 오류 발생 시 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "지출 저장 실패",
|
||||||
|
description: "지출 데이터를 저장하는데 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,9 +76,13 @@ export const saveTransactionsToStorage = (transactions: Transaction[]): void =>
|
|||||||
export const clearAllTransactions = (): void => {
|
export const clearAllTransactions = (): void => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem('transactions');
|
localStorage.removeItem('transactions');
|
||||||
|
localStorage.removeItem('transactions_backup');
|
||||||
|
|
||||||
// 빈 배열을 저장하여 확실히 초기화
|
// 빈 배열을 저장하여 확실히 초기화
|
||||||
const emptyData = JSON.stringify([]);
|
const emptyData = JSON.stringify([]);
|
||||||
localStorage.setItem('transactions', emptyData);
|
localStorage.setItem('transactions', emptyData);
|
||||||
|
localStorage.setItem('transactions_backup', emptyData);
|
||||||
|
|
||||||
console.log('모든 트랜잭션이 삭제되었습니다.');
|
console.log('모든 트랜잭션이 삭제되었습니다.');
|
||||||
|
|
||||||
// 스토리지 이벤트 수동 트리거
|
// 스토리지 이벤트 수동 트리거
|
||||||
@@ -58,7 +91,20 @@ export const clearAllTransactions = (): void => {
|
|||||||
key: 'transactions',
|
key: 'transactions',
|
||||||
newValue: emptyData
|
newValue: emptyData
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "지출 내역 초기화",
|
||||||
|
description: "모든 지출 내역이 삭제되었습니다.",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('트랜잭션 삭제 오류:', error);
|
console.error('트랜잭션 삭제 오류:', error);
|
||||||
|
|
||||||
|
// 오류 발생 시 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "초기화 실패",
|
||||||
|
description: "지출 내역을 초기화하는데 문제가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,51 +40,98 @@ export const useBudgetState = () => {
|
|||||||
console.log('- 예산 데이터:', budgetData);
|
console.log('- 예산 데이터:', budgetData);
|
||||||
console.log('- 카테고리 예산:', categoryBudgets);
|
console.log('- 카테고리 예산:', categoryBudgets);
|
||||||
console.log('- 트랜잭션 수:', transactions.length);
|
console.log('- 트랜잭션 수:', transactions.length);
|
||||||
|
|
||||||
|
// 데이터 손실 방지를 위한 타이머 설정
|
||||||
|
const saveTimer = setInterval(() => {
|
||||||
|
// 저장된 데이터 유효성 검사 및 백업
|
||||||
|
try {
|
||||||
|
const storedBudgetData = localStorage.getItem('budgetData');
|
||||||
|
const storedCategoryBudgets = localStorage.getItem('categoryBudgets');
|
||||||
|
const storedTransactions = localStorage.getItem('transactions');
|
||||||
|
|
||||||
|
if (storedBudgetData) {
|
||||||
|
localStorage.setItem('budgetData_backup_auto', storedBudgetData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedCategoryBudgets) {
|
||||||
|
localStorage.setItem('categoryBudgets_backup_auto', storedCategoryBudgets);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedTransactions) {
|
||||||
|
localStorage.setItem('transactions_backup_auto', storedTransactions);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('자동 백업 중 오류:', error);
|
||||||
|
}
|
||||||
|
}, 5000); // 5초마다 백업
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(saveTimer);
|
||||||
|
};
|
||||||
}, [budgetData, categoryBudgets, transactions]);
|
}, [budgetData, categoryBudgets, transactions]);
|
||||||
|
|
||||||
// 카테고리별 예산 및 지출 계산
|
// 카테고리별 예산 및 지출 계산
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const totalMonthlyBudget = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0);
|
try {
|
||||||
console.log('카테고리 예산 합계:', totalMonthlyBudget);
|
const totalMonthlyBudget = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0);
|
||||||
|
console.log('카테고리 예산 합계:', totalMonthlyBudget);
|
||||||
if (totalMonthlyBudget > 0) {
|
|
||||||
const totalDailyBudget = Math.round(totalMonthlyBudget / 30);
|
|
||||||
const totalWeeklyBudget = Math.round(totalMonthlyBudget / 4.3);
|
|
||||||
|
|
||||||
const updatedBudgetData = {
|
|
||||||
daily: {
|
|
||||||
targetAmount: totalDailyBudget,
|
|
||||||
spentAmount: budgetData.daily.spentAmount,
|
|
||||||
remainingAmount: totalDailyBudget - budgetData.daily.spentAmount
|
|
||||||
},
|
|
||||||
weekly: {
|
|
||||||
targetAmount: totalWeeklyBudget,
|
|
||||||
spentAmount: budgetData.weekly.spentAmount,
|
|
||||||
remainingAmount: totalWeeklyBudget - budgetData.weekly.spentAmount
|
|
||||||
},
|
|
||||||
monthly: {
|
|
||||||
targetAmount: totalMonthlyBudget,
|
|
||||||
spentAmount: budgetData.monthly.spentAmount,
|
|
||||||
remainingAmount: totalMonthlyBudget - budgetData.monthly.spentAmount
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 로컬 상태 업데이트
|
if (totalMonthlyBudget > 0) {
|
||||||
handleBudgetGoalUpdate('monthly', totalMonthlyBudget);
|
const totalDailyBudget = Math.round(totalMonthlyBudget / 30);
|
||||||
console.log('예산 데이터 자동 업데이트:', updatedBudgetData);
|
const totalWeeklyBudget = Math.round(totalMonthlyBudget / 4.3);
|
||||||
|
|
||||||
|
const updatedBudgetData = {
|
||||||
|
daily: {
|
||||||
|
targetAmount: totalDailyBudget,
|
||||||
|
spentAmount: budgetData.daily.spentAmount,
|
||||||
|
remainingAmount: totalDailyBudget - budgetData.daily.spentAmount
|
||||||
|
},
|
||||||
|
weekly: {
|
||||||
|
targetAmount: totalWeeklyBudget,
|
||||||
|
spentAmount: budgetData.weekly.spentAmount,
|
||||||
|
remainingAmount: totalWeeklyBudget - budgetData.weekly.spentAmount
|
||||||
|
},
|
||||||
|
monthly: {
|
||||||
|
targetAmount: totalMonthlyBudget,
|
||||||
|
spentAmount: budgetData.monthly.spentAmount,
|
||||||
|
remainingAmount: totalMonthlyBudget - budgetData.monthly.spentAmount
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 로컬 상태 업데이트
|
||||||
|
handleBudgetGoalUpdate('monthly', totalMonthlyBudget);
|
||||||
|
console.log('예산 데이터 자동 업데이트:', updatedBudgetData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('카테고리 예산 계산 중 오류:', error);
|
||||||
}
|
}
|
||||||
}, [categoryBudgets, handleBudgetGoalUpdate, budgetData]);
|
}, [categoryBudgets, handleBudgetGoalUpdate, budgetData]);
|
||||||
|
|
||||||
// 모든 데이터 리셋 함수
|
// 모든 데이터 리셋 함수
|
||||||
const resetBudgetData = useCallback(() => {
|
const resetBudgetData = useCallback(() => {
|
||||||
console.log('BudgetContext에서 데이터 리셋 시작');
|
try {
|
||||||
|
console.log('BudgetContext에서 데이터 리셋 시작');
|
||||||
// 로컬 스토리지 초기화
|
|
||||||
resetTransactions();
|
// 로컬 스토리지 초기화
|
||||||
resetCategoryBudgets();
|
resetTransactions();
|
||||||
resetBudgetDataInternal();
|
resetCategoryBudgets();
|
||||||
|
resetBudgetDataInternal();
|
||||||
console.log('BudgetContext에서 데이터 리셋 완료');
|
|
||||||
|
console.log('BudgetContext에서 데이터 리셋 완료');
|
||||||
|
|
||||||
|
// 토스트 알림
|
||||||
|
toast({
|
||||||
|
title: "모든 데이터 초기화",
|
||||||
|
description: "예산과 지출 내역이 모두 초기화되었습니다.",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('데이터 초기화 중 오류:', error);
|
||||||
|
toast({
|
||||||
|
title: "초기화 실패",
|
||||||
|
description: "데이터를 초기화하는 중 오류가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [resetTransactions, resetCategoryBudgets, resetBudgetDataInternal]);
|
}, [resetTransactions, resetCategoryBudgets, resetBudgetDataInternal]);
|
||||||
|
|
||||||
// 확장된 예산 목표 업데이트 함수
|
// 확장된 예산 목표 업데이트 함수
|
||||||
@@ -93,37 +140,55 @@ export const useBudgetState = () => {
|
|||||||
amount: number,
|
amount: number,
|
||||||
newCategoryBudgets?: Record<string, number>
|
newCategoryBudgets?: Record<string, number>
|
||||||
) => {
|
) => {
|
||||||
console.log(`확장된 예산 목표 업데이트 호출: ${type}, 금액: ${amount}`);
|
try {
|
||||||
|
console.log(`확장된 예산 목표 업데이트 호출: ${type}, 금액: ${amount}`);
|
||||||
// 카테고리 예산이 직접 업데이트된 경우
|
|
||||||
if (newCategoryBudgets) {
|
|
||||||
console.log('카테고리 예산 직접 업데이트:', newCategoryBudgets);
|
|
||||||
updateCategoryBudgets(newCategoryBudgets);
|
|
||||||
|
|
||||||
|
// 카테고리 예산이 직접 업데이트된 경우
|
||||||
|
if (newCategoryBudgets) {
|
||||||
|
console.log('카테고리 예산 직접 업데이트:', newCategoryBudgets);
|
||||||
|
updateCategoryBudgets(newCategoryBudgets);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 월간 예산을 업데이트하고 일일, 주간도 자동 계산
|
||||||
|
if (type === 'monthly') {
|
||||||
|
console.log('월간 예산 업데이트:', amount);
|
||||||
|
if (amount <= 0) return; // 예산이 0 이하면 업데이트하지 않음
|
||||||
|
|
||||||
|
const ratio = amount / (budgetData.monthly.targetAmount || 1); // 0으로 나누기 방지
|
||||||
|
const updatedCategoryBudgets: Record<string, number> = {};
|
||||||
|
|
||||||
|
// 비율에 따라 카테고리 예산 업데이트
|
||||||
|
Object.keys(categoryBudgets).forEach(category => {
|
||||||
|
updatedCategoryBudgets[category] = Math.round(categoryBudgets[category] * ratio);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 모든 카테고리가 0인 경우 (초기 상태)
|
||||||
|
const allZero = Object.values(categoryBudgets).every(value => value === 0);
|
||||||
|
if (allZero) {
|
||||||
|
// 카테고리 간 균등 분배
|
||||||
|
const categories = Object.keys(categoryBudgets);
|
||||||
|
const perCategoryAmount = Math.round(amount / categories.length);
|
||||||
|
|
||||||
|
categories.forEach(category => {
|
||||||
|
updatedCategoryBudgets[category] = perCategoryAmount;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('업데이트된 카테고리 예산:', updatedCategoryBudgets);
|
||||||
|
updateCategoryBudgets(updatedCategoryBudgets);
|
||||||
|
} else {
|
||||||
|
// 일일이나 주간 예산이 직접 업데이트되는 경우
|
||||||
|
console.log(`${type} 예산 직접 업데이트:`, amount);
|
||||||
|
handleBudgetGoalUpdate(type, amount);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('예산 목표 업데이트 중 오류:', error);
|
||||||
toast({
|
toast({
|
||||||
title: "카테고리 예산 업데이트 완료",
|
title: "예산 업데이트 실패",
|
||||||
description: "카테고리별 예산이 저장되었습니다."
|
description: "예산 목표를 업데이트하는 중 오류가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
});
|
});
|
||||||
|
|
||||||
return; // 카테고리 예산이 변경되면 useEffect에서 자동으로 budgetData가 업데이트됩니다
|
|
||||||
}
|
|
||||||
|
|
||||||
// 월간 예산을 업데이트하고 일일, 주간도 자동 계산
|
|
||||||
if (type === 'monthly') {
|
|
||||||
console.log('월간 예산 업데이트:', amount);
|
|
||||||
const ratio = amount / (budgetData.monthly.targetAmount || 1); // 0으로 나누기 방지
|
|
||||||
const updatedCategoryBudgets: Record<string, number> = {};
|
|
||||||
|
|
||||||
Object.keys(categoryBudgets).forEach(category => {
|
|
||||||
updatedCategoryBudgets[category] = Math.round(categoryBudgets[category] * ratio);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('업데이트된 카테고리 예산:', updatedCategoryBudgets);
|
|
||||||
updateCategoryBudgets(updatedCategoryBudgets);
|
|
||||||
} else {
|
|
||||||
// 일일이나 주간 예산이 직접 업데이트되는 경우
|
|
||||||
console.log(`${type} 예산 직접 업데이트:`, amount);
|
|
||||||
handleBudgetGoalUpdate(type, amount);
|
|
||||||
}
|
}
|
||||||
}, [budgetData, categoryBudgets, handleBudgetGoalUpdate, updateCategoryBudgets]);
|
}, [budgetData, categoryBudgets, handleBudgetGoalUpdate, updateCategoryBudgets]);
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,41 @@ const Index = () => {
|
|||||||
console.log('예산 데이터:', budgetData);
|
console.log('예산 데이터:', budgetData);
|
||||||
|
|
||||||
// 수동으로 이벤트 발생시켜 데이터 갱신
|
// 수동으로 이벤트 발생시켜 데이터 갱신
|
||||||
window.dispatchEvent(new Event('transactionUpdated'));
|
try {
|
||||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('이벤트 발생 오류:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 백업된 데이터 복구 확인
|
||||||
|
try {
|
||||||
|
const budgetBackup = localStorage.getItem('budgetData_backup');
|
||||||
|
const categoryBackup = localStorage.getItem('categoryBudgets_backup');
|
||||||
|
const transactionBackup = localStorage.getItem('transactions_backup');
|
||||||
|
|
||||||
|
// 메인 데이터가 없지만 백업은 있는 경우 복구
|
||||||
|
if (!localStorage.getItem('budgetData') && budgetBackup) {
|
||||||
|
console.log('예산 데이터 백업에서 복구');
|
||||||
|
localStorage.setItem('budgetData', budgetBackup);
|
||||||
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localStorage.getItem('categoryBudgets') && categoryBackup) {
|
||||||
|
console.log('카테고리 예산 백업에서 복구');
|
||||||
|
localStorage.setItem('categoryBudgets', categoryBackup);
|
||||||
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localStorage.getItem('transactions') && transactionBackup) {
|
||||||
|
console.log('트랜잭션 백업에서 복구');
|
||||||
|
localStorage.setItem('transactions', transactionBackup);
|
||||||
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('백업 복구 시도 중 오류:', error);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 앱이 포커스를 얻었을 때 데이터를 새로고침
|
// 앱이 포커스를 얻었을 때 데이터를 새로고침
|
||||||
@@ -55,14 +87,40 @@ const Index = () => {
|
|||||||
const handleFocus = () => {
|
const handleFocus = () => {
|
||||||
console.log('창이 포커스를 얻음 - 데이터 새로고침');
|
console.log('창이 포커스를 얻음 - 데이터 새로고침');
|
||||||
// 이벤트 발생시켜 데이터 새로고침
|
// 이벤트 발생시켜 데이터 새로고침
|
||||||
window.dispatchEvent(new Event('storage'));
|
try {
|
||||||
window.dispatchEvent(new Event('transactionUpdated'));
|
window.dispatchEvent(new Event('storage'));
|
||||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('이벤트 발생 오류:', e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 포커스 이벤트
|
||||||
window.addEventListener('focus', handleFocus);
|
window.addEventListener('focus', handleFocus);
|
||||||
return () => window.removeEventListener('focus', handleFocus);
|
|
||||||
|
// 가시성 변경 이벤트 (백그라운드에서 전경으로 돌아올 때)
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
console.log('페이지가 다시 보임 - 데이터 새로고침');
|
||||||
|
handleFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 정기적인 데이터 새로고침 (10초마다)
|
||||||
|
const refreshInterval = setInterval(() => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
console.log('정기 새로고침 - 데이터 업데이트');
|
||||||
|
handleFocus();
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('focus', handleFocus);
|
||||||
|
document.removeEventListener('visibilitychange', () => {});
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user