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:
gpt-engineer-app[bot]
2025-03-15 04:02:43 +00:00
parent 2626e35924
commit a5fac97a95
2 changed files with 160 additions and 32 deletions

View File

@@ -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;