Fixes an issue where entering a monthly budget resulted in incorrect daily, weekly, and monthly budget calculations, leading to incorrect display on the spending and analytics screens.
159 lines
6.8 KiB
TypeScript
159 lines
6.8 KiB
TypeScript
|
|
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';
|
|
interface BudgetData {
|
|
targetAmount: number;
|
|
spentAmount: number;
|
|
remainingAmount: number;
|
|
}
|
|
interface BudgetTabContentProps {
|
|
data: BudgetData;
|
|
formatCurrency: (amount: number) => string;
|
|
calculatePercentage: (spent: number, target: number) => number;
|
|
onSaveBudget: (amount: number, categoryBudgets?: Record<string, number>) => void;
|
|
}
|
|
const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|
data,
|
|
formatCurrency,
|
|
calculatePercentage,
|
|
onSaveBudget
|
|
}) => {
|
|
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>({});
|
|
const [showBudgetInput, setShowBudgetInput] = useState(false);
|
|
const spentAmount = data.spentAmount;
|
|
const targetAmount = data.targetAmount;
|
|
|
|
// 실제 백분율 계산 (초과해도 실제 퍼센트로 표시)
|
|
const actualPercentage = targetAmount > 0 ? Math.round(spentAmount / targetAmount * 100) : 0;
|
|
const percentage = actualPercentage;
|
|
const isFirstBudget = targetAmount === 0;
|
|
|
|
// 예산 초과 여부 계산
|
|
const isOverBudget = spentAmount > targetAmount;
|
|
// 예산이 얼마 남지 않은 경우 (10% 미만)
|
|
const isLowBudget = targetAmount > 0 && percentage >= 90 && percentage < 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, 10) || 0;
|
|
setCategoryBudgets(prev => ({
|
|
...prev,
|
|
[category]: numValue
|
|
}));
|
|
};
|
|
|
|
// 카테고리별 예산 합계 계산
|
|
const calculateTotalBudget = () => {
|
|
const total = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0);
|
|
console.log('카테고리 예산 총합:', total, categoryBudgets);
|
|
return total;
|
|
};
|
|
|
|
// 카테고리 예산 저장
|
|
const handleSaveCategoryBudgets = () => {
|
|
const totalBudget = calculateTotalBudget();
|
|
console.log('카테고리 예산 저장 및 총 예산 설정:', totalBudget, categoryBudgets);
|
|
// 총액이 0이 아닐 때만 저장 처리
|
|
if (totalBudget > 0) {
|
|
onSaveBudget(totalBudget, categoryBudgets);
|
|
setShowBudgetInput(false);
|
|
} 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 = () => {
|
|
setShowBudgetInput(prev => !prev);
|
|
};
|
|
|
|
// 예산 여부에 따른 텍스트 결정
|
|
const budgetButtonText = targetAmount > 0 ? "예산 수정하기" : "예산 입력하기";
|
|
return <div>
|
|
{targetAmount > 0 ? <>
|
|
<div className="flex justify-between items-center mb-3">
|
|
<div className="text-2xl font-bold">{formatCurrency(spentAmount)}</div>
|
|
<div className="text-sm text-gray-500">/ {formatCurrency(targetAmount)}</div>
|
|
</div>
|
|
|
|
<div className="w-full h-2 neuro-pressed overflow-hidden mb-3">
|
|
<div className={`h-full ${progressBarColor} transition-all duration-700 ease-out`} style={{
|
|
width: `${Math.min(percentage, 100)}%`
|
|
}} />
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<div className={`text-sm font-medium ${isOverBudget ? 'text-red-500' : 'text-gray-500'}`}>
|
|
{budgetStatusText}{budgetAmount}
|
|
</div>
|
|
<div className="text-sm font-medium text-gray-500">
|
|
{percentage}%
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6">
|
|
<button onClick={toggleBudgetInput} className="text-neuro-income hover:underline flex items-center text-lg font-bold group">
|
|
<CirclePlus size={26} className="mr-2 text-neuro-income transition-transform group-hover:scale-110" />
|
|
<span className="text-base font-semibold">{budgetButtonText}</span>
|
|
</button>
|
|
</div>
|
|
</> : <div className="py-4 text-center">
|
|
<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">
|
|
<CirclePlus className="mr-2" size={24} />
|
|
<span className="animate-pulse">{budgetButtonText}</span>
|
|
</Button>
|
|
</div>}
|
|
|
|
{showBudgetInput && <div className="mt-4">
|
|
<div className="neuro-card p-4">
|
|
<div>
|
|
<h3 className="text-base font-medium mb-3">카테고리별 월간 예산 설정</h3>
|
|
<p className="text-sm text-gray-500 mb-4">카테고리별로 월간 예산을 설정하세요. 일일, 주간 예산은 자동으로 계산됩니다.</p>
|
|
<CategoryBudgetInputs categoryBudgets={categoryBudgets} handleCategoryInputChange={handleCategoryInputChange} />
|
|
|
|
<div className="mt-4 border-t border-gray-300 pt-3">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h3 className="font-medium text-sm px-[34px]">월간 총 예산:</h3>
|
|
<p className="font-bold text-neuro-income text-base px-[10px]">{formatCurrency(calculateTotalBudget())}</p>
|
|
</div>
|
|
|
|
<div className="flex justify-end">
|
|
<Button onClick={handleSaveCategoryBudgets} size="sm" className="bg-neuro-income hover:bg-neuro-income/90 text-white mx-[6px]">
|
|
<Check size={18} className="mr-1" />
|
|
저장하기
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>}
|
|
</div>;
|
|
};
|
|
export default BudgetTabContent;
|