Emphasize initial budget setting
Add visual emphasis to the budget setting prompt on the home screen to guide new users.
This commit is contained in:
@@ -1,12 +1,8 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Plus, Wallet } from 'lucide-react';
|
||||
import BudgetInputCard from './BudgetInputCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check, ChevronDown, ChevronUp, Calculator } from 'lucide-react';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import BudgetProgress from './BudgetProgress';
|
||||
import CategoryBudgetInputs from './CategoryBudgetInputs';
|
||||
import { toast } from '@/hooks/useToast.wrapper'; // 래퍼 함수 사용
|
||||
import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons';
|
||||
|
||||
interface BudgetData {
|
||||
targetAmount: number;
|
||||
@@ -27,109 +23,77 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
||||
calculatePercentage,
|
||||
onSaveBudget
|
||||
}) => {
|
||||
const percentage = calculatePercentage(data.spentAmount, data.targetAmount);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [budgetInput, setBudgetInput] = useState(data.targetAmount > 0 ? data.targetAmount.toString() : '');
|
||||
|
||||
// 저장된 카테고리 예산을 불러옵니다
|
||||
const savedCategoryBudgets = localStorage.getItem('categoryBudgets');
|
||||
const defaultCategoryAmount = data.targetAmount > 0 ? Math.round(data.targetAmount / EXPENSE_CATEGORIES.length) : 0;
|
||||
const initialCategoryBudgets = savedCategoryBudgets ? JSON.parse(savedCategoryBudgets) : EXPENSE_CATEGORIES.reduce((acc, category) => {
|
||||
acc[category] = defaultCategoryAmount;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
const [showBudgetInput, setShowBudgetInput] = useState(false);
|
||||
|
||||
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>(initialCategoryBudgets);
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
// Remove all non-numeric characters
|
||||
const numericValue = value.replace(/[^0-9]/g, '');
|
||||
setBudgetInput(numericValue);
|
||||
};
|
||||
|
||||
const handleCategoryInputChange = (value: string, category: string) => {
|
||||
// Remove all non-numeric characters
|
||||
const numericValue = value.replace(/[^0-9]/g, '');
|
||||
setCategoryBudgets(prev => ({
|
||||
...prev,
|
||||
[category]: parseInt(numericValue) || 0
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
// Calculate total from all categories
|
||||
const totalAmount = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0);
|
||||
|
||||
// 버튼 중복 클릭 방지를 위해 비활성화 처리 (setTimeout 사용)
|
||||
const button = document.querySelector('button[type="button"]') as HTMLButtonElement;
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
setTimeout(() => {
|
||||
button.disabled = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 카테고리 예산도 함께 전달합니다
|
||||
onSaveBudget(totalAmount, categoryBudgets);
|
||||
|
||||
// 단일 토스트만 표시하고 즉시 패널 닫음
|
||||
setIsOpen(false);
|
||||
|
||||
// 토스트는 이벤트 발생 후 처리되므로 여기서는 호출하지 않음
|
||||
// 이 함수에서 직접 toast 호출하지 않고 budgetStorage에서 처리되도록 함
|
||||
};
|
||||
|
||||
// Format with commas for display
|
||||
const formatWithCommas = (amount: string) => {
|
||||
return amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
};
|
||||
const spentAmount = data.spentAmount;
|
||||
const targetAmount = data.targetAmount;
|
||||
const percentage = calculatePercentage(spentAmount, targetAmount);
|
||||
const isFirstBudget = targetAmount === 0;
|
||||
|
||||
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">
|
||||
<span className="text-gray-500 text-sm">남은 예산</span>
|
||||
<span className="font-semibold text-neuro-income">{formatCurrency(data.remainingAmount)}</span>
|
||||
</div>
|
||||
|
||||
<div className="pt-2 border-t border-gray-100">
|
||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
||||
<CollapsibleTrigger className="flex items-center justify-between w-full px-1 text-left py-[10px]">
|
||||
<span className="text-sm text-gray-600 font-bold">예산 설정하기</span>
|
||||
{isOpen ? <ChevronUp size={16} className="text-gray-500" /> : <ChevronDown size={16} className="text-gray-500" />}
|
||||
</CollapsibleTrigger>
|
||||
<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>
|
||||
|
||||
<CollapsibleContent className="pt-2 space-y-3">
|
||||
<CategoryBudgetInputs
|
||||
categoryBudgets={categoryBudgets}
|
||||
handleCategoryInputChange={handleCategoryInputChange}
|
||||
<div className="w-full h-2 neuro-pressed overflow-hidden mb-3">
|
||||
<div
|
||||
className="h-full bg-neuro-income transition-all duration-700 ease-out"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
|
||||
<div className="flex items-center gap-1">
|
||||
<Calculator size={16} className="text-gray-500" />
|
||||
<span className="text-sm font-medium">총 예산:</span>
|
||||
</div>
|
||||
<span className="font-semibold">{formatCurrency(Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0))}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-sm font-medium text-gray-500">
|
||||
남은 예산: {formatCurrency(Math.max(0, targetAmount - spentAmount))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleSave} size="sm" className="neuro-flat text-slate-50 bg-neuro-income hover:bg-neuro-income/90">
|
||||
<Check size={16} className="mr-1" />
|
||||
저장하기
|
||||
</Button>
|
||||
<div className="text-sm font-medium text-gray-500">
|
||||
{percentage}%
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-500 text-center py-[6px]">카테고리 예산을 설정하면 일일, 주간, 월간 예산이 자동으로 합산됩니다.</p>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<button
|
||||
onClick={() => setShowBudgetInput(!showBudgetInput)}
|
||||
className="text-neuro-income text-sm font-medium hover:underline flex items-center"
|
||||
>
|
||||
<Plus size={16} className="mr-1" /> 예산 수정하기
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="py-4 text-center">
|
||||
<div className="text-gray-400 mb-4">아직 예산이 설정되지 않았습니다</div>
|
||||
<Button
|
||||
onClick={() => setShowBudgetInput(true)}
|
||||
variant="default"
|
||||
className="bg-neuro-income hover:bg-neuro-income/90 animate-pulse shadow-lg"
|
||||
>
|
||||
<Wallet className="mr-2" size={18} />
|
||||
지금 예산 설정하기
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showBudgetInput && (
|
||||
<div className="mt-4">
|
||||
<BudgetInputCard
|
||||
initialBudgets={{
|
||||
daily: isFirstBudget ? 0 : data.targetAmount,
|
||||
weekly: isFirstBudget ? 0 : data.targetAmount,
|
||||
monthly: isFirstBudget ? 0 : data.targetAmount
|
||||
}}
|
||||
onSave={(type, amount) => {
|
||||
onSaveBudget(amount);
|
||||
setShowBudgetInput(false);
|
||||
}}
|
||||
highlight={isFirstBudget}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user