diff --git a/src/components/RecentTransactionsSection.tsx b/src/components/RecentTransactionsSection.tsx index 7b6cc7e..a821253 100644 --- a/src/components/RecentTransactionsSection.tsx +++ b/src/components/RecentTransactionsSection.tsx @@ -1,25 +1,45 @@ -import React from 'react'; +import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import TransactionCard, { Transaction } from '@/components/TransactionCard'; +import { toast } from '@/components/ui/use-toast'; interface RecentTransactionsSectionProps { transactions: Transaction[]; + onUpdateTransaction?: (updatedTransaction: Transaction) => void; } const RecentTransactionsSection: React.FC = ({ - transactions + transactions, + onUpdateTransaction }) => { const navigate = useNavigate(); const handleViewAllTransactions = () => { navigate('/transactions'); }; + + const handleUpdateTransaction = (updatedTransaction: Transaction) => { + if (onUpdateTransaction) { + onUpdateTransaction(updatedTransaction); + + toast({ + title: "지출이 수정되었습니다", + description: `${updatedTransaction.title} 항목이 업데이트되었습니다.`, + }); + } + }; return <>

최근 지출

- {transactions.map(transaction => )} + {transactions.map(transaction => ( + + ))}
diff --git a/src/components/TransactionCard.tsx b/src/components/TransactionCard.tsx index eda02ab..75d6ed3 100644 --- a/src/components/TransactionCard.tsx +++ b/src/components/TransactionCard.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import { ArrowDownIcon, ArrowUpIcon, Coffee, Home, Car } from 'lucide-react'; +import React, { useState } from 'react'; +import { ArrowDownIcon, Coffee, Home, Car } from 'lucide-react'; import { cn } from '@/lib/utils'; +import TransactionEditDialog from './TransactionEditDialog'; export type Transaction = { id: string; @@ -20,9 +21,14 @@ const categoryIcons: Record = { interface TransactionCardProps { transaction: Transaction; + onUpdate?: (updatedTransaction: Transaction) => void; } -const TransactionCard: React.FC = ({ transaction }) => { +const TransactionCard: React.FC = ({ + transaction, + onUpdate +}) => { + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const { title, amount, date, category, type } = transaction; const formattedAmount = new Intl.NumberFormat('ko-KR', { @@ -30,29 +36,47 @@ const TransactionCard: React.FC = ({ transaction }) => { currency: 'KRW', maximumFractionDigits: 0 }).format(amount); + + const handleSaveTransaction = (updatedTransaction: Transaction) => { + if (onUpdate) { + onUpdate(updatedTransaction); + } + }; return ( -
-
-
-
- {categoryIcons[category] || } + <> +
setIsEditDialogOpen(true)} + > +
+
+
+ {categoryIcons[category] || } +
+ +
+

{title}

+

{date}

+
-
-

{title}

-

{date}

+
+ + + {formattedAmount} +
- -
- - - {formattedAmount} - -
-
+ + + ); }; diff --git a/src/components/TransactionEditDialog.tsx b/src/components/TransactionEditDialog.tsx new file mode 100644 index 0000000..55d3cfd --- /dev/null +++ b/src/components/TransactionEditDialog.tsx @@ -0,0 +1,163 @@ + +import React from 'react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { Transaction } from '@/components/TransactionCard'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogClose +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; +import { Coffee, Home, Car } from 'lucide-react'; + +// Form schema for validation +const formSchema = z.object({ + title: z.string().min(1, '제목을 입력해주세요'), + amount: z.string().min(1, '금액을 입력해주세요'), + category: z.enum(['식비', '생활비', '교통비']), +}); + +interface TransactionEditDialogProps { + transaction: Transaction; + open: boolean; + onOpenChange: (open: boolean) => void; + onSave: (updatedTransaction: Transaction) => void; +} + +const TransactionEditDialog: React.FC = ({ + transaction, + open, + onOpenChange, + onSave +}) => { + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + title: transaction.title, + amount: transaction.amount.toString(), + category: transaction.category as any, + }, + }); + + const handleSubmit = (values: z.infer) => { + // Remove commas from amount string and convert to number + const cleanAmount = values.amount.replace(/,/g, ''); + + onSave({ + ...transaction, + title: values.title, + amount: Number(cleanAmount), + category: values.category, + }); + + onOpenChange(false); + }; + + // Function to format number with commas + const formatWithCommas = (value: string) => { + const numericValue = value.replace(/[^0-9]/g, ''); + return numericValue.replace(/\B(?=(\d{3})+(?!\d))/g, ','); + }; + + const handleAmountChange = (e: React.ChangeEvent) => { + const formattedValue = formatWithCommas(e.target.value); + form.setValue('amount', formattedValue); + }; + + const categoryIcons: Record = { + 식비: , + 생활비: , + 교통비: , + }; + + return ( + + + + 지출 수정 + + +
+ + ( + + 제목 + + + + + + )} + /> + + ( + + 금액 + + + + + + )} + /> + + ( + + 카테고리 +
+ {['식비', '생활비', '교통비'].map((category) => ( +
form.setValue('category', category as any)} + > +
+ {categoryIcons[category]} +
+ {category} +
+ ))} +
+ +
+ )} + /> + + + + + + + + + +
+
+ ); +}; + +export default TransactionEditDialog; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 1444c30..c797422 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -14,7 +14,7 @@ const Index = () => { const [selectedTab, setSelectedTab] = useState("daily"); // Sample data - in a real app, this would come from a data source - const transactions: Transaction[] = [{ + const [transactions, setTransactions] = useState([{ id: '1', title: '식료품 구매', amount: 25000, @@ -35,7 +35,7 @@ const Index = () => { date: '2일전, 9:00 AM', category: 'income', type: 'income' - }]; + }]); // 카테고리별 예산 const [categoryBudgets, setCategoryBudgets] = useState({ @@ -139,6 +139,15 @@ const Index = () => { description: `${type === 'daily' ? '일일' : type === 'weekly' ? '주간' : '월간'} 목표가 ${amount.toLocaleString()}원으로 설정되었습니다.` }); }; + + // Handle transaction updates + const handleUpdateTransaction = (updatedTransaction: Transaction) => { + setTransactions(prev => + prev.map(transaction => + transaction.id === updatedTransaction.id ? updatedTransaction : transaction + ) + ); + }; return (
@@ -160,7 +169,10 @@ const Index = () => { {/* 최근 지출 */} - +
diff --git a/src/pages/Transactions.tsx b/src/pages/Transactions.tsx index acf9cbc..3a165f6 100644 --- a/src/pages/Transactions.tsx +++ b/src/pages/Transactions.tsx @@ -4,12 +4,13 @@ import NavBar from '@/components/NavBar'; import TransactionCard, { Transaction } from '@/components/TransactionCard'; import AddTransactionButton from '@/components/AddTransactionButton'; import { Calendar, Search, ChevronLeft, ChevronRight } from 'lucide-react'; +import { toast } from '@/components/ui/use-toast'; const Transactions = () => { const [selectedMonth, setSelectedMonth] = useState('8월'); // Sample data - updated to use only the three specified categories - const transactions: Transaction[] = [{ + const [transactions, setTransactions] = useState([{ id: '1', title: '식료품 구매', amount: 25000, @@ -44,7 +45,7 @@ const Transactions = () => { date: '8월 12일, 2:15 PM', category: '식비', type: 'expense' - }]; + }]); // Filter only expense transactions const expenseTransactions = transactions.filter(t => t.type === 'expense'); @@ -65,6 +66,19 @@ const Transactions = () => { groupedTransactions[datePart].push(transaction); }); + const handleUpdateTransaction = (updatedTransaction: Transaction) => { + setTransactions(prev => + prev.map(transaction => + transaction.id === updatedTransaction.id ? updatedTransaction : transaction + ) + ); + + toast({ + title: "지출이 수정되었습니다", + description: `${updatedTransaction.title} 항목이 업데이트되었습니다.`, + }); + }; + return
{/* Header */} @@ -128,7 +142,13 @@ const Transactions = () => {
- {transactions.map(transaction => )} + {transactions.map(transaction => ( + + ))}
)}