Implement budget input per category
Implement budget input fields for each category in the monthly budget settings, and automatically calculate and populate daily, weekly, and monthly budgets based on the sum of the category inputs.
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { Check, ChevronDown, ChevronUp, Calculator } from 'lucide-react';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
|
||||
interface BudgetData {
|
||||
targetAmount: number;
|
||||
spentAmount: number;
|
||||
remainingAmount: number;
|
||||
}
|
||||
|
||||
interface CategoryBudget {
|
||||
식비: number;
|
||||
생활비: number;
|
||||
교통비: number;
|
||||
}
|
||||
|
||||
interface BudgetProgressCardProps {
|
||||
budgetData: {
|
||||
daily: BudgetData;
|
||||
@@ -21,6 +30,7 @@ interface BudgetProgressCardProps {
|
||||
calculatePercentage: (spent: number, target: number) => number;
|
||||
onSaveBudget: (type: 'daily' | 'weekly' | 'monthly', amount: number) => void;
|
||||
}
|
||||
|
||||
const BudgetProgressCard: React.FC<BudgetProgressCardProps> = ({
|
||||
budgetData,
|
||||
selectedTab,
|
||||
@@ -59,12 +69,14 @@ const BudgetProgressCard: React.FC<BudgetProgressCardProps> = ({
|
||||
</Tabs>
|
||||
</div>;
|
||||
};
|
||||
|
||||
interface BudgetTabContentProps {
|
||||
data: BudgetData;
|
||||
formatCurrency: (amount: number) => string;
|
||||
calculatePercentage: (spent: number, target: number) => number;
|
||||
onSaveBudget: (amount: number) => void;
|
||||
}
|
||||
|
||||
const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
||||
data,
|
||||
formatCurrency,
|
||||
@@ -74,14 +86,33 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
||||
const percentage = calculatePercentage(data.spentAmount, data.targetAmount);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [budgetInput, setBudgetInput] = useState(data.targetAmount.toString());
|
||||
|
||||
const [categoryBudgets, setCategoryBudgets] = useState<CategoryBudget>({
|
||||
식비: Math.round(data.targetAmount * 0.4),
|
||||
생활비: Math.round(data.targetAmount * 0.4),
|
||||
교통비: Math.round(data.targetAmount * 0.2)
|
||||
});
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
// Remove all non-numeric characters
|
||||
const numericValue = value.replace(/[^0-9]/g, '');
|
||||
setBudgetInput(numericValue);
|
||||
};
|
||||
|
||||
const handleCategoryInputChange = (value: string, category: keyof CategoryBudget) => {
|
||||
// Remove all non-numeric characters
|
||||
const numericValue = value.replace(/[^0-9]/g, '');
|
||||
|
||||
setCategoryBudgets(prev => ({
|
||||
...prev,
|
||||
[category]: parseInt(numericValue) || 0
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const amount = parseInt(budgetInput, 10) || 0;
|
||||
onSaveBudget(amount);
|
||||
// Calculate total from all categories
|
||||
const totalAmount = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0);
|
||||
onSaveBudget(totalAmount);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
@@ -89,6 +120,7 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
||||
const formatWithCommas = (amount: string) => {
|
||||
return amount.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
};
|
||||
|
||||
return <div className="space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
@@ -117,21 +149,60 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
||||
<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 font-medium text-gray-600">월간 예산 설정하기</span>
|
||||
<span className="text-sm font-medium text-gray-600">카테고리별 예산 설정하기</span>
|
||||
{isOpen ? <ChevronUp size={16} className="text-gray-500" /> : <ChevronDown size={16} className="text-gray-500" />}
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent className="pt-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input value={budgetInput} onChange={e => handleInputChange(e.target.value)} placeholder="목표 금액 입력" className="neuro-pressed" />
|
||||
<Button onClick={handleSave} size="icon" className="neuro-flat text-slate-50 bg-slate-400 hover:bg-slate-300">
|
||||
<Check size={18} />
|
||||
<CollapsibleContent className="pt-2 space-y-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-600">식비</label>
|
||||
<Input
|
||||
value={categoryBudgets.식비.toString()}
|
||||
onChange={e => handleCategoryInputChange(e.target.value, '식비')}
|
||||
className="neuro-pressed max-w-[150px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-600">생활비</label>
|
||||
<Input
|
||||
value={categoryBudgets.생활비.toString()}
|
||||
onChange={e => handleCategoryInputChange(e.target.value, '생활비')}
|
||||
className="neuro-pressed max-w-[150px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-600">교통비</label>
|
||||
<Input
|
||||
value={categoryBudgets.교통비.toString()}
|
||||
onChange={e => handleCategoryInputChange(e.target.value, '교통비')}
|
||||
className="neuro-pressed max-w-[150px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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-end">
|
||||
<Button onClick={handleSave} size="sm" className="neuro-flat text-slate-50 bg-slate-400 hover:bg-slate-300">
|
||||
<Check size={16} className="mr-1" />
|
||||
저장하기
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1 text-center py-[6px]">월간 예산을 입력하면 일일, 주간 예산이 자동으로 입력됩니다.</p>
|
||||
|
||||
<p className="text-xs text-gray-500 text-center py-[6px]">총 예산을 설정하면 일일, 주간 예산이 자동으로 계산됩니다.</p>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
export default BudgetProgressCard;
|
||||
|
||||
export default BudgetProgressCard;
|
||||
|
||||
Reference in New Issue
Block a user