Fix budget input and display
- Fix issue where budget input for transportation and other categories were not saved correctly. - Fix issue where total budget amount was calculated incorrectly. - Fix issue where daily and weekly budgets were not displayed correctly.
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import BudgetTabContent from './BudgetTabContent';
|
import BudgetTabContent from './BudgetTabContent';
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -15,6 +14,7 @@ interface BudgetTabContentProps {
|
|||||||
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,
|
||||||
@@ -94,19 +94,25 @@ 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-base font-bold">{formatCurrency(spentAmount)}</div>
|
<div className="text-base 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">
|
||||||
<div className={`text-sm font-medium ${isOverBudget ? 'text-red-500' : 'text-gray-500'}`}>
|
<div className={`text-sm font-medium ${isOverBudget ? 'text-red-500' : 'text-gray-500'}`}>
|
||||||
{budgetStatusText}{budgetAmount}
|
{budgetStatusText}{budgetAmount}
|
||||||
@@ -115,34 +121,41 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|||||||
{percentage}%
|
{percentage}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<button onClick={toggleBudgetInput} className="text-neuro-income hover:underline flex items-center text-base font-semibold group">
|
<button
|
||||||
|
onClick={toggleBudgetInput}
|
||||||
|
className="text-neuro-income hover:underline flex items-center text-base font-semibold group"
|
||||||
|
>
|
||||||
<CirclePlus size={26} className="mr-2 text-neuro-income transition-transform group-hover:scale-110" />
|
<CirclePlus size={26} className="mr-2 text-neuro-income transition-transform group-hover:scale-110" />
|
||||||
<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>
|
||||||
<p className="text-sm text-gray-500 mb-4">카테고리별로 월간 예산을 설정하세요. 일일, 주간 예산은 자동으로 계산됩니다.</p>
|
<p className="text-sm text-gray-500 mb-4">카테고리별로 월간 예산을 설정하세요. 일일, 주간 예산은 자동으로 계산됩니다.</p>
|
||||||
<CategoryBudgetInputs categoryBudgets={categoryBudgets} handleCategoryInputChange={handleCategoryInputChange} />
|
<CategoryBudgetInputs categoryBudgets={categoryBudgets} handleCategoryInputChange={handleCategoryInputChange} />
|
||||||
|
|
||||||
<div className="mt-4 border-t border-gray-300 pt-3">
|
<div className="mt-4 border-t border-gray-300 pt-3">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="font-medium text-sm px-[34px]">월간 총 예산:</h3>
|
<h3 className="font-medium text-sm px-[34px]">월간 총 예산:</h3>
|
||||||
<p className="font-bold text-neuro-income text-base px-[10px]">{formatCurrency(calculateTotalBudget())}</p>
|
<p className="font-bold text-neuro-income text-base px-[10px]">{formatCurrency(calculateTotalBudget())}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button onClick={handleSaveCategoryBudgets} size="sm" className="bg-neuro-income hover:bg-neuro-income/90 text-white mx-[6px]">
|
<Button onClick={handleSaveCategoryBudgets} size="sm" className="bg-neuro-income hover:bg-neuro-income/90 text-white mx-[6px]">
|
||||||
<Check size={18} className="mr-1" />
|
<Check size={18} className="mr-1" />
|
||||||
@@ -152,7 +165,10 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>
|
||||||
</div>;
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BudgetTabContent;
|
export default BudgetTabContent;
|
||||||
|
|||||||
@@ -1,45 +1,31 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Coffee, Home, Car, Package, Banknote } from 'lucide-react';
|
import { ShoppingBag, Coffee, Bus, Landmark, MoreHorizontal } from 'lucide-react';
|
||||||
|
|
||||||
// 카테고리와 아이콘 매핑 정의
|
// 지출 카테고리 정의
|
||||||
|
export const EXPENSE_CATEGORIES = [
|
||||||
|
'음식',
|
||||||
|
'쇼핑',
|
||||||
|
'교통비',
|
||||||
|
'교통',
|
||||||
|
'기타'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 카테고리별 아이콘 매핑
|
||||||
export const categoryIcons: Record<string, React.ReactNode> = {
|
export const categoryIcons: Record<string, React.ReactNode> = {
|
||||||
음식: <Coffee size={18} />,
|
'음식': <Coffee className="h-4 w-4" />,
|
||||||
쇼핑: <Home size={18} />,
|
'쇼핑': <ShoppingBag className="h-4 w-4" />,
|
||||||
교통: <Car size={18} />,
|
'교통비': <Bus className="h-4 w-4" />,
|
||||||
기타: <Package size={18} />,
|
'교통': <Bus className="h-4 w-4" />,
|
||||||
수입: <Banknote size={18} />,
|
'기타': <MoreHorizontal className="h-4 w-4" />,
|
||||||
|
'수입': <Landmark className="h-4 w-4" />
|
||||||
};
|
};
|
||||||
|
|
||||||
// 지출 카테고리 목록 - 4개로 확장
|
// 기본 카테고리 설정 (신규 사용자용)
|
||||||
export const EXPENSE_CATEGORIES = ['음식', '쇼핑', '교통', '기타'];
|
export const DEFAULT_CATEGORIES = {
|
||||||
|
'음식': 0,
|
||||||
// 카테고리 부가 설명 정의
|
'쇼핑': 0,
|
||||||
export const CATEGORY_DESCRIPTIONS: Record<string, string> = {
|
'교통비': 0,
|
||||||
음식: '(식비, 음료)',
|
'교통': 0,
|
||||||
쇼핑: '',
|
'기타': 0
|
||||||
교통: '(차량 유지비)',
|
|
||||||
기타: '(기타 지출)',
|
|
||||||
식비: '(식비, 음료)', // 이전 이름과의 호환성 유지
|
|
||||||
생활비: '', // 이전 이름과의 호환성 유지
|
|
||||||
교통비: '(차량 유지비)', // 이전 이름과의 호환성 유지
|
|
||||||
};
|
|
||||||
|
|
||||||
// 기본 카테고리 예산 설정
|
|
||||||
export const DEFAULT_CATEGORY_BUDGETS = {
|
|
||||||
음식: 400000,
|
|
||||||
쇼핑: 600000,
|
|
||||||
교통: 200000,
|
|
||||||
기타: 100000
|
|
||||||
};
|
|
||||||
|
|
||||||
// 기본 월간 예산
|
|
||||||
export const DEFAULT_MONTHLY_BUDGET = 1300000;
|
|
||||||
|
|
||||||
// 카테고리별 대표 제목 목록 정의 - 자주 사용하는 순서로 재정렬
|
|
||||||
export const CATEGORY_TITLE_SUGGESTIONS: Record<string, string[]> = {
|
|
||||||
음식: ['점심', '저녁', '간식', '아침', '음료', '외식'],
|
|
||||||
쇼핑: ['생활용품', '의류', '화장품', '가전제품', '운동용품'],
|
|
||||||
교통: ['택시', '주유', '지하철', '버스', '주차', '교통카드'],
|
|
||||||
기타: ['통신비', '구독', '관리비', '의료비', '전기요금', '취미']
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
|
|||||||
export const DEFAULT_CATEGORY_BUDGETS: Record<string, number> = {
|
export const DEFAULT_CATEGORY_BUDGETS: Record<string, number> = {
|
||||||
음식: 0,
|
음식: 0,
|
||||||
쇼핑: 0,
|
쇼핑: 0,
|
||||||
교통비: 0
|
교통비: 0,
|
||||||
|
교통: 0,
|
||||||
|
기타: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_MONTHLY_BUDGET = 0;
|
export const DEFAULT_MONTHLY_BUDGET = 0;
|
||||||
|
|||||||
@@ -26,11 +26,7 @@ export const useExtendedBudgetUpdate = (
|
|||||||
const totalAmount = Object.values(newCategoryBudgets).reduce((sum, val) => sum + val, 0);
|
const totalAmount = Object.values(newCategoryBudgets).reduce((sum, val) => sum + val, 0);
|
||||||
console.log('카테고리 총액:', totalAmount);
|
console.log('카테고리 총액:', totalAmount);
|
||||||
|
|
||||||
// 일/주/월 모든 예산 업데이트를 위해 monthly로 처리
|
// 예산 데이터 업데이트를 위해 월간 금액으로 처리 (type은 'monthly'로 설정)
|
||||||
// (monthly 타입은 모든 예산을 계산해 줌)
|
|
||||||
const updatedBudgetData = calculateUpdatedBudgetData(budgetData, 'monthly', totalAmount);
|
|
||||||
|
|
||||||
// 각 기간별 예산 업데이트
|
|
||||||
handleBudgetGoalUpdate('monthly', totalAmount);
|
handleBudgetGoalUpdate('monthly', totalAmount);
|
||||||
} else {
|
} else {
|
||||||
// 카테고리 예산이 없는 경우, 기존 로직 사용
|
// 카테고리 예산이 없는 경우, 기존 로직 사용
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const loadCategoryBudgetsFromStorage = (): Record<string, number> => {
|
|||||||
const parsed = JSON.parse(storedCategoryBudgets);
|
const parsed = JSON.parse(storedCategoryBudgets);
|
||||||
console.log('카테고리 예산 로드 완료:', parsed);
|
console.log('카테고리 예산 로드 완료:', parsed);
|
||||||
|
|
||||||
// 4개 카테고리만 유지
|
// 모든 허용된 카테고리 포함
|
||||||
const filteredBudgets: Record<string, number> = {};
|
const filteredBudgets: Record<string, number> = {};
|
||||||
EXPENSE_CATEGORIES.forEach(category => {
|
EXPENSE_CATEGORIES.forEach(category => {
|
||||||
// 이전 카테고리명이 있을 경우 새 카테고리명으로 값 이전
|
// 이전 카테고리명이 있을 경우 새 카테고리명으로 값 이전
|
||||||
@@ -36,7 +36,7 @@ export const loadCategoryBudgetsFromStorage = (): Record<string, number> => {
|
|||||||
const parsedBackup = JSON.parse(backupCategoryBudgets);
|
const parsedBackup = JSON.parse(backupCategoryBudgets);
|
||||||
console.log('백업에서 카테고리 예산 복구:', parsedBackup);
|
console.log('백업에서 카테고리 예산 복구:', parsedBackup);
|
||||||
|
|
||||||
// 4개 카테고리만 유지
|
// 모든 허용된 카테고리 포함
|
||||||
const filteredBudgets: Record<string, number> = {};
|
const filteredBudgets: Record<string, number> = {};
|
||||||
EXPENSE_CATEGORIES.forEach(category => {
|
EXPENSE_CATEGORIES.forEach(category => {
|
||||||
// 이전 카테고리명이 있을 경우 새 카테고리명으로 값 이전
|
// 이전 카테고리명이 있을 경우 새 카테고리명으로 값 이전
|
||||||
@@ -83,7 +83,7 @@ export const saveCategoryBudgetsToStorage = (categoryBudgets: Record<string, num
|
|||||||
console.error('이전 카테고리 예산 비교 오류:', e);
|
console.error('이전 카테고리 예산 비교 오류:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4개 카테고리만 유지하고 나머지는 제거
|
// 모든 허용된 카테고리 포함하여 유지
|
||||||
const filteredBudgets: Record<string, number> = {};
|
const filteredBudgets: Record<string, number> = {};
|
||||||
EXPENSE_CATEGORIES.forEach(category => {
|
EXPENSE_CATEGORIES.forEach(category => {
|
||||||
filteredBudgets[category] = categoryBudgets[category] || 0;
|
filteredBudgets[category] = categoryBudgets[category] || 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user