diff --git a/src/components/BudgetTabContent.tsx b/src/components/BudgetTabContent.tsx index 19fe599..e1b9c53 100644 --- a/src/components/BudgetTabContent.tsx +++ b/src/components/BudgetTabContent.tsx @@ -1,16 +1,18 @@ -import React, { useState, useEffect } from 'react'; -import { CirclePlus, Save, Check } from 'lucide-react'; -import BudgetInputCard from './BudgetInputCard'; -import { Button } from '@/components/ui/button'; -import CategoryBudgetInputs from './CategoryBudgetInputs'; -import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons'; +import React from 'react'; +import { useBudgetTabContent } from '@/hooks/budget/useBudgetTabContent'; +import BudgetHeader from './budget/BudgetHeader'; +import BudgetProgressBar from './budget/BudgetProgressBar'; +import BudgetStatusDisplay from './budget/BudgetStatusDisplay'; +import BudgetInputButton from './budget/BudgetInputButton'; +import BudgetInputForm from './budget/BudgetInputForm'; interface BudgetData { targetAmount: number; spentAmount: number; remainingAmount: number; } + interface BudgetTabContentProps { data: BudgetData; formatCurrency: (amount: number) => string; @@ -24,202 +26,66 @@ const BudgetTabContent: React.FC = ({ calculatePercentage, onSaveBudget }) => { - const [categoryBudgets, setCategoryBudgets] = useState>({}); - const [showBudgetInput, setShowBudgetInput] = useState(false); - const spentAmount = data.spentAmount; - const targetAmount = data.targetAmount; - - // 로그 추가 - 받은 데이터 확인 - useEffect(() => { - console.log(`BudgetTabContent 수신 데이터:`, data); - }, [data]); - - // 전역 예산 데이터가 변경되었을 때 로컬 상태 갱신 - useEffect(() => { - const handleBudgetDataUpdated = () => { - console.log(`BudgetTabContent: 전역 예산 데이터 이벤트 감지, 현재 targetAmount=${targetAmount}`); - // 입력 폼이 열려있고 예산이 설정된 경우 폼 닫기 - if (showBudgetInput && targetAmount > 0) { - console.log('예산이 설정되어 입력 폼을 닫습니다.'); - setShowBudgetInput(false); - } - }; - - window.addEventListener('budgetDataUpdated', handleBudgetDataUpdated); - return () => window.removeEventListener('budgetDataUpdated', handleBudgetDataUpdated); - }, [showBudgetInput, targetAmount]); - - // 예산 설정 여부 확인 - 데이터 targetAmount가 실제로 0보다 큰지 확인 - const isBudgetSet = targetAmount > 0; - - useEffect(() => { - if (isBudgetSet && showBudgetInput) { - console.log('예산이 설정되었으므로 입력 폼을 닫습니다.'); - setShowBudgetInput(false); - } - }, [isBudgetSet, showBudgetInput]); - - // 실제 백분율 계산 (초과해도 실제 퍼센트로 표시) - const actualPercentage = targetAmount > 0 ? Math.round((spentAmount / targetAmount) * 100) : 0; - const percentage = Math.min(actualPercentage, 100); // 대시보드 표시용으로는 100% 제한 - - // 예산 초과 여부 계산 - const isOverBudget = spentAmount > targetAmount && targetAmount > 0; - // 예산이 얼마 남지 않은 경우 (10% 미만) - const isLowBudget = targetAmount > 0 && actualPercentage >= 90 && actualPercentage < 100; - - // 프로그레스 바 색상 결정 - const progressBarColor = isOverBudget ? 'bg-red-500' : isLowBudget ? 'bg-yellow-400' : 'bg-neuro-income'; - - // 남은 예산 또는 초과 예산 텍스트 및 금액 - const budgetStatusText = isOverBudget ? '예산 초과: ' : '남은 예산: '; - const budgetAmount = isOverBudget ? formatCurrency(Math.abs(targetAmount - spentAmount)) : formatCurrency(Math.max(0, targetAmount - spentAmount)); - - const handleCategoryInputChange = (value: string, category: string) => { - const numValue = parseInt(value.replace(/,/g, ''), 10) || 0; - setCategoryBudgets(prev => ({ - ...prev, - [category]: numValue - })); - }; - - // 카테고리별 예산 합계 계산 - const calculateTotalBudget = () => { - // 모든 EXPENSE_CATEGORIES에 있는 카테고리 포함해서 합계 계산 - let total = 0; - EXPENSE_CATEGORIES.forEach(category => { - total += categoryBudgets[category] || 0; - }); - console.log('카테고리 예산 총합:', total, categoryBudgets); - return total; - }; - - // 카테고리 예산 저장 - const handleSaveCategoryBudgets = () => { - // 카테고리 예산 기본값 설정 - 모든 카테고리 포함 - const updatedCategoryBudgets: Record = {}; - EXPENSE_CATEGORIES.forEach(category => { - updatedCategoryBudgets[category] = categoryBudgets[category] || 0; - }); - - const totalBudget = calculateTotalBudget(); - console.log('카테고리 예산 저장 및 총 예산 설정:', totalBudget, updatedCategoryBudgets); - - // 총액이 0이 아닐 때만 저장 처리 - if (totalBudget > 0) { - onSaveBudget(totalBudget, updatedCategoryBudgets); - setShowBudgetInput(false); - - // 이벤트 발생 추가 (데이터 저장 후 즉시 UI 업데이트를 위해) - setTimeout(() => { - window.dispatchEvent(new Event('budgetDataUpdated')); - }, 200); - } else { - alert('예산을 입력해주세요.'); - } - }; - - // 기존 카테고리 예산 불러오기 - useEffect(() => { - if (showBudgetInput) { - // 로컬 스토리지에서 카테고리 예산 불러오기 - try { - const storedCategoryBudgets = localStorage.getItem('categoryBudgets'); - if (storedCategoryBudgets) { - const parsedBudgets = JSON.parse(storedCategoryBudgets); - console.log('저장된 카테고리 예산 불러옴:', parsedBudgets); - setCategoryBudgets(parsedBudgets); - } - } catch (error) { - console.error('카테고리 예산 불러오기 오류:', error); - } - } - }, [showBudgetInput]); - - // 예산 버튼 클릭 핸들러 - 토글 기능 추가 - const toggleBudgetInput = () => { - console.log('예산 입력 폼 토글. 현재 상태:', showBudgetInput, '예산 설정 여부:', isBudgetSet); - setShowBudgetInput(prev => !prev); - }; - - // 예산 여부에 따른 텍스트 결정 - const budgetButtonText = isBudgetSet ? "예산 수정하기" : "예산 입력하기"; - - // 화면에 표시할 내용 - 디버깅을 위한 로그 추가 - console.log(`BudgetTabContent 렌더링: targetAmount=${targetAmount}, isBudgetSet=${isBudgetSet}, 표시될 화면:`, isBudgetSet ? "예산 진행 상황" : "예산 입력하기 버튼"); + const { + categoryBudgets, + showBudgetInput, + toggleBudgetInput, + handleCategoryInputChange, + handleSaveCategoryBudgets, + isBudgetSet, + actualPercentage, + percentage, + isOverBudget, + isLowBudget, + progressBarColor, + budgetStatusText, + budgetAmount, + budgetButtonText, + calculateTotalBudget + } = useBudgetTabContent({ + data, + calculatePercentage, + onSaveBudget + }); return (
{isBudgetSet ? ( <> -
-
{formatCurrency(spentAmount)}
-
/ {formatCurrency(targetAmount)}
-
+ -
-
-
+ -
-
- {budgetStatusText}{budgetAmount} -
-
- {actualPercentage}% -
-
- -
- -
+ - ) : ( -
-
아직 예산이 설정되지 않았습니다
- -
- )} + ) : null} - {showBudgetInput && ( -
-
-
-

카테고리별 월간 예산 설정

-

카테고리별로 월간 예산을 설정하세요. 일일, 주간 예산은 자동으로 계산됩니다.

- + -
-
-

월간 총 예산:

-

{formatCurrency(calculateTotalBudget())}

-
- -
- -
-
-
-
-
- )} +
); }; diff --git a/src/components/budget/BudgetHeader.tsx b/src/components/budget/BudgetHeader.tsx new file mode 100644 index 0000000..25fecd1 --- /dev/null +++ b/src/components/budget/BudgetHeader.tsx @@ -0,0 +1,23 @@ + +import React from 'react'; + +interface BudgetHeaderProps { + spentAmount: number; + targetAmount: number; + formatCurrency: (amount: number) => string; +} + +const BudgetHeader: React.FC = ({ + spentAmount, + targetAmount, + formatCurrency +}) => { + return ( +
+
{formatCurrency(spentAmount)}
+
/ {formatCurrency(targetAmount)}
+
+ ); +}; + +export default BudgetHeader; diff --git a/src/components/budget/BudgetInputButton.tsx b/src/components/budget/BudgetInputButton.tsx new file mode 100644 index 0000000..dce7585 --- /dev/null +++ b/src/components/budget/BudgetInputButton.tsx @@ -0,0 +1,42 @@ + +import React from 'react'; +import { CirclePlus } from 'lucide-react'; +import { Button } from '@/components/ui/button'; + +interface BudgetInputButtonProps { + isBudgetSet: boolean; + budgetButtonText: string; + toggleBudgetInput: () => void; +} + +const BudgetInputButton: React.FC = ({ + isBudgetSet, + budgetButtonText, + toggleBudgetInput +}) => { + if (isBudgetSet) { + return ( +
+ +
+ ); + } + + return ( +
+
아직 예산이 설정되지 않았습니다
+ +
+ ); +}; + +export default BudgetInputButton; diff --git a/src/components/budget/BudgetInputForm.tsx b/src/components/budget/BudgetInputForm.tsx new file mode 100644 index 0000000..ce7109a --- /dev/null +++ b/src/components/budget/BudgetInputForm.tsx @@ -0,0 +1,60 @@ + +import React from 'react'; +import { Check } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import CategoryBudgetInputs from '../CategoryBudgetInputs'; + +interface BudgetInputFormProps { + showBudgetInput: boolean; + categoryBudgets: Record; + handleCategoryInputChange: (value: string, category: string) => void; + handleSaveCategoryBudgets: () => void; + calculateTotalBudget: () => number; + formatCurrency: (amount: number) => string; +} + +const BudgetInputForm: React.FC = ({ + showBudgetInput, + categoryBudgets, + handleCategoryInputChange, + handleSaveCategoryBudgets, + calculateTotalBudget, + formatCurrency +}) => { + if (!showBudgetInput) return null; + + return ( +
+
+
+

카테고리별 월간 예산 설정

+

카테고리별로 월간 예산을 설정하세요. 일일, 주간 예산은 자동으로 계산됩니다.

+ + +
+
+

월간 총 예산:

+

{formatCurrency(calculateTotalBudget())}

+
+ +
+ +
+
+
+
+
+ ); +}; + +export default BudgetInputForm; diff --git a/src/components/budget/BudgetProgressBar.tsx b/src/components/budget/BudgetProgressBar.tsx new file mode 100644 index 0000000..7f1621f --- /dev/null +++ b/src/components/budget/BudgetProgressBar.tsx @@ -0,0 +1,25 @@ + +import React from 'react'; + +interface BudgetProgressBarProps { + percentage: number; + progressBarColor: string; +} + +const BudgetProgressBar: React.FC = ({ + percentage, + progressBarColor +}) => { + return ( +
+
+
+ ); +}; + +export default BudgetProgressBar; diff --git a/src/components/budget/BudgetStatusDisplay.tsx b/src/components/budget/BudgetStatusDisplay.tsx new file mode 100644 index 0000000..9271073 --- /dev/null +++ b/src/components/budget/BudgetStatusDisplay.tsx @@ -0,0 +1,29 @@ + +import React from 'react'; + +interface BudgetStatusDisplayProps { + budgetStatusText: string; + budgetAmount: string; + actualPercentage: number; + isOverBudget: boolean; +} + +const BudgetStatusDisplay: React.FC = ({ + budgetStatusText, + budgetAmount, + actualPercentage, + isOverBudget +}) => { + return ( +
+
+ {budgetStatusText}{budgetAmount}원 +
+
+ {actualPercentage}% +
+
+ ); +}; + +export default BudgetStatusDisplay; diff --git a/src/hooks/budget/useBudgetTabContent.ts b/src/hooks/budget/useBudgetTabContent.ts new file mode 100644 index 0000000..9c74c88 --- /dev/null +++ b/src/hooks/budget/useBudgetTabContent.ts @@ -0,0 +1,183 @@ + +import { useState, useEffect } from 'react'; +import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons'; + +interface BudgetData { + targetAmount: number; + spentAmount: number; + remainingAmount: number; +} + +interface UseBudgetTabContentProps { + data: BudgetData; + calculatePercentage: (spent: number, target: number) => number; + onSaveBudget: (amount: number, categoryBudgets?: Record) => void; +} + +interface UseBudgetTabContentReturn { + categoryBudgets: Record; + showBudgetInput: boolean; + toggleBudgetInput: () => void; + handleCategoryInputChange: (value: string, category: string) => void; + handleSaveCategoryBudgets: () => void; + isBudgetSet: boolean; + actualPercentage: number; + percentage: number; + isOverBudget: boolean; + isLowBudget: boolean; + progressBarColor: string; + budgetStatusText: string; + budgetAmount: string; + budgetButtonText: string; + calculateTotalBudget: () => number; +} + +export const useBudgetTabContent = ({ + data, + calculatePercentage, + onSaveBudget +}: UseBudgetTabContentProps): UseBudgetTabContentReturn => { + const [categoryBudgets, setCategoryBudgets] = useState>({}); + const [showBudgetInput, setShowBudgetInput] = useState(false); + const spentAmount = data.spentAmount; + const targetAmount = data.targetAmount; + + // 로그 추가 - 받은 데이터 확인 + useEffect(() => { + console.log(`BudgetTabContent 수신 데이터:`, data); + }, [data]); + + // 전역 예산 데이터가 변경되었을 때 로컬 상태 갱신 + useEffect(() => { + const handleBudgetDataUpdated = () => { + console.log(`BudgetTabContent: 전역 예산 데이터 이벤트 감지, 현재 targetAmount=${targetAmount}`); + // 입력 폼이 열려있고 예산이 설정된 경우 폼 닫기 + if (showBudgetInput && targetAmount > 0) { + console.log('예산이 설정되어 입력 폼을 닫습니다.'); + setShowBudgetInput(false); + } + }; + + window.addEventListener('budgetDataUpdated', handleBudgetDataUpdated); + return () => window.removeEventListener('budgetDataUpdated', handleBudgetDataUpdated); + }, [showBudgetInput, targetAmount]); + + // 예산 설정 여부 확인 - 데이터 targetAmount가 실제로 0보다 큰지 확인 + const isBudgetSet = targetAmount > 0; + + useEffect(() => { + if (isBudgetSet && showBudgetInput) { + console.log('예산이 설정되었으므로 입력 폼을 닫습니다.'); + setShowBudgetInput(false); + } + }, [isBudgetSet, showBudgetInput]); + + // 실제 백분율 계산 (초과해도 실제 퍼센트로 표시) + const actualPercentage = targetAmount > 0 ? Math.round((spentAmount / targetAmount) * 100) : 0; + const percentage = Math.min(actualPercentage, 100); // 대시보드 표시용으로는 100% 제한 + + // 예산 초과 여부 계산 + const isOverBudget = spentAmount > targetAmount && targetAmount > 0; + // 예산이 얼마 남지 않은 경우 (10% 미만) + const isLowBudget = targetAmount > 0 && actualPercentage >= 90 && actualPercentage < 100; + + // 프로그레스 바 색상 결정 + const progressBarColor = isOverBudget ? 'bg-red-500' : isLowBudget ? 'bg-yellow-400' : 'bg-neuro-income'; + + // 남은 예산 또는 초과 예산 텍스트 및 금액 + const budgetStatusText = isOverBudget ? '예산 초과: ' : '남은 예산: '; + const budgetAmount = isOverBudget ? + Math.abs(targetAmount - spentAmount).toLocaleString() : + Math.max(0, targetAmount - spentAmount).toLocaleString(); + + const handleCategoryInputChange = (value: string, category: string) => { + const numValue = parseInt(value.replace(/,/g, ''), 10) || 0; + setCategoryBudgets(prev => ({ + ...prev, + [category]: numValue + })); + }; + + // 카테고리별 예산 합계 계산 + const calculateTotalBudget = () => { + // 모든 EXPENSE_CATEGORIES에 있는 카테고리 포함해서 합계 계산 + let total = 0; + EXPENSE_CATEGORIES.forEach(category => { + total += categoryBudgets[category] || 0; + }); + console.log('카테고리 예산 총합:', total, categoryBudgets); + return total; + }; + + // 카테고리 예산 저장 + const handleSaveCategoryBudgets = () => { + // 카테고리 예산 기본값 설정 - 모든 카테고리 포함 + const updatedCategoryBudgets: Record = {}; + EXPENSE_CATEGORIES.forEach(category => { + updatedCategoryBudgets[category] = categoryBudgets[category] || 0; + }); + + const totalBudget = calculateTotalBudget(); + console.log('카테고리 예산 저장 및 총 예산 설정:', totalBudget, updatedCategoryBudgets); + + // 총액이 0이 아닐 때만 저장 처리 + if (totalBudget > 0) { + onSaveBudget(totalBudget, updatedCategoryBudgets); + setShowBudgetInput(false); + + // 이벤트 발생 추가 (데이터 저장 후 즉시 UI 업데이트를 위해) + setTimeout(() => { + window.dispatchEvent(new Event('budgetDataUpdated')); + }, 200); + } else { + alert('예산을 입력해주세요.'); + } + }; + + // 기존 카테고리 예산 불러오기 + useEffect(() => { + if (showBudgetInput) { + // 로컬 스토리지에서 카테고리 예산 불러오기 + try { + const storedCategoryBudgets = localStorage.getItem('categoryBudgets'); + if (storedCategoryBudgets) { + const parsedBudgets = JSON.parse(storedCategoryBudgets); + console.log('저장된 카테고리 예산 불러옴:', parsedBudgets); + setCategoryBudgets(parsedBudgets); + } + } catch (error) { + console.error('카테고리 예산 불러오기 오류:', error); + } + } + }, [showBudgetInput]); + + // 예산 버튼 클릭 핸들러 - 토글 기능 추가 + const toggleBudgetInput = () => { + console.log('예산 입력 폼 토글. 현재 상태:', showBudgetInput, '예산 설정 여부:', isBudgetSet); + setShowBudgetInput(prev => !prev); + }; + + // 예산 여부에 따른 텍스트 결정 + const budgetButtonText = isBudgetSet ? "예산 수정하기" : "예산 입력하기"; + + // 화면에 표시할 내용 - 디버깅을 위한 로그 추가 + console.log(`BudgetTabContent 렌더링: targetAmount=${targetAmount}, isBudgetSet=${isBudgetSet}, 표시될 화면:`, isBudgetSet ? "예산 진행 상황" : "예산 입력하기 버튼"); + + return { + categoryBudgets, + showBudgetInput, + toggleBudgetInput, + handleCategoryInputChange, + handleSaveCategoryBudgets, + isBudgetSet, + actualPercentage, + percentage, + isOverBudget, + isLowBudget, + progressBarColor, + budgetStatusText, + budgetAmount, + budgetButtonText, + calculateTotalBudget + }; +};