From 846a7bb165e635409fae15f55d381d581b419b52 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 12:26:59 +0000 Subject: [PATCH] Refactor ExpenseForm component Refactor ExpenseForm.tsx into smaller components to improve code organization and maintainability. --- src/components/expenses/ExpenseForm.tsx | 206 ++---------------- src/components/expenses/ExpenseFormFields.tsx | 185 ++++++++++++++++ .../expenses/ExpenseSubmitActions.tsx | 41 ++++ 3 files changed, 241 insertions(+), 191 deletions(-) create mode 100644 src/components/expenses/ExpenseFormFields.tsx create mode 100644 src/components/expenses/ExpenseSubmitActions.tsx diff --git a/src/components/expenses/ExpenseForm.tsx b/src/components/expenses/ExpenseForm.tsx index 87f5955..3f67686 100644 --- a/src/components/expenses/ExpenseForm.tsx +++ b/src/components/expenses/ExpenseForm.tsx @@ -1,14 +1,9 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; -import { Form, FormField, FormItem, FormLabel } from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import { Button } from '@/components/ui/button'; -import { Loader2, CreditCard, Banknote } from 'lucide-react'; -import ExpenseCategorySelector from './ExpenseCategorySelector'; -import { Badge } from '@/components/ui/badge'; -import { getPersonalizedTitleSuggestions } from '@/utils/userTitlePreferences'; -import { Separator } from '@/components/ui/separator'; +import { Form } from '@/components/ui/form'; +import ExpenseFormFields from './ExpenseFormFields'; +import ExpenseSubmitActions from './ExpenseSubmitActions'; export interface ExpenseFormValues { title: string; @@ -23,7 +18,11 @@ interface ExpenseFormProps { isSubmitting?: boolean; } -const ExpenseForm: React.FC = ({ onSubmit, onCancel, isSubmitting = false }) => { +const ExpenseForm: React.FC = ({ + onSubmit, + onCancel, + isSubmitting = false +}) => { const form = useForm({ defaultValues: { title: '', @@ -33,193 +32,18 @@ const ExpenseForm: React.FC = ({ onSubmit, onCancel, isSubmitt } }); - // 상태 관리 추가 - const [showTitleSuggestions, setShowTitleSuggestions] = useState(false); - const [showPaymentMethod, setShowPaymentMethod] = useState(false); - - // 현재 선택된 카테고리 가져오기 - const selectedCategory = form.watch('category'); - const selectedPaymentMethod = form.watch('paymentMethod'); - - // 선택된 카테고리에 대한 개인화된 제목 제안 목록 상태 - const [titleSuggestions, setTitleSuggestions] = useState([]); - - // 카테고리가 변경될 때마다 개인화된 제목 목록 업데이트 및 제목 추천 표시 - useEffect(() => { - if (selectedCategory) { - const suggestions = getPersonalizedTitleSuggestions(selectedCategory); - setTitleSuggestions(suggestions); - // 약간의 지연 후 제목 추천 표시 (애니메이션을 위해) - setTimeout(() => { - setShowTitleSuggestions(true); - }, 100); - } else { - setShowTitleSuggestions(false); - } - }, [selectedCategory]); - - // 제안된 제목 클릭 시 제목 필드에 설정 - const handleTitleSuggestionClick = (suggestion: string) => { - form.setValue('title', suggestion); - }; - - // Format number with commas - const formatWithCommas = (value: string): string => { - // Remove commas first to avoid duplicates when typing - 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); - }; - return (
- {/* 카테고리 필드를 가장 먼저 배치 */} - ( - - 카테고리 - field.onChange(value)} - /> - - )} + - {/* 카테고리별 제목 제안 - 카테고리 선택 후에만 표시, 서랍처럼 열리는 애니메이션 적용 */} - {selectedCategory && ( -
- {titleSuggestions.length > 0 && ( -
- {titleSuggestions.map((suggestion) => ( - handleTitleSuggestionClick(suggestion)} - > - {suggestion} - - ))} -
- )} -
- )} - - {/* 제목 필드를 두 번째로 배치 */} - ( - - 제목 - - - )} + - - {/* 금액 필드를 세 번째로 배치 */} - ( - - 금액 - setShowPaymentMethod(true)} - disabled={isSubmitting} - /> - - )} - /> - - {/* 지출 방법 필드는 금액 입력 시에만 표시 - 서랍처럼 열리는 애니메이션 적용 */} -
- - - ( - - 지출 방법 -
-
!isSubmitting && form.setValue('paymentMethod', '신용카드')} - > - - 신용카드 -
-
!isSubmitting && form.setValue('paymentMethod', '현금')} - > - - 현금 -
-
-
- )} - /> -
- -
- - -
); diff --git a/src/components/expenses/ExpenseFormFields.tsx b/src/components/expenses/ExpenseFormFields.tsx new file mode 100644 index 0000000..56d75e4 --- /dev/null +++ b/src/components/expenses/ExpenseFormFields.tsx @@ -0,0 +1,185 @@ + +import React, { useEffect, useState } from 'react'; +import { useForm, UseFormReturn } from 'react-hook-form'; +import { FormField, FormItem, FormLabel } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { CreditCard, Banknote } from 'lucide-react'; +import ExpenseCategorySelector from './ExpenseCategorySelector'; +import { Badge } from '@/components/ui/badge'; +import { getPersonalizedTitleSuggestions } from '@/utils/userTitlePreferences'; +import { Separator } from '@/components/ui/separator'; +import { ExpenseFormValues } from './ExpenseForm'; + +interface ExpenseFormFieldsProps { + form: UseFormReturn; + isSubmitting?: boolean; +} + +const ExpenseFormFields: React.FC = ({ form, isSubmitting = false }) => { + // 상태 관리 추가 + const [showTitleSuggestions, setShowTitleSuggestions] = useState(false); + const [showPaymentMethod, setShowPaymentMethod] = useState(false); + + // 현재 선택된 카테고리 가져오기 + const selectedCategory = form.watch('category'); + + // 선택된 카테고리에 대한 개인화된 제목 제안 목록 상태 + const [titleSuggestions, setTitleSuggestions] = useState([]); + + // 카테고리가 변경될 때마다 개인화된 제목 목록 업데이트 및 제목 추천 표시 + useEffect(() => { + if (selectedCategory) { + const suggestions = getPersonalizedTitleSuggestions(selectedCategory); + setTitleSuggestions(suggestions); + // 약간의 지연 후 제목 추천 표시 (애니메이션을 위해) + setTimeout(() => { + setShowTitleSuggestions(true); + }, 100); + } else { + setShowTitleSuggestions(false); + } + }, [selectedCategory]); + + // 제안된 제목 클릭 시 제목 필드에 설정 + const handleTitleSuggestionClick = (suggestion: string) => { + form.setValue('title', suggestion); + }; + + // Format number with commas + const formatWithCommas = (value: string): string => { + // Remove commas first to avoid duplicates when typing + 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); + }; + + return ( + <> + {/* 카테고리 필드를 가장 먼저 배치 */} + ( + + 카테고리 + field.onChange(value)} + /> + + )} + /> + + {/* 카테고리별 제목 제안 - 카테고리 선택 후에만 표시, 서랍처럼 열리는 애니메이션 적용 */} + {selectedCategory && ( +
+ {titleSuggestions.length > 0 && ( +
+ {titleSuggestions.map((suggestion) => ( + handleTitleSuggestionClick(suggestion)} + > + {suggestion} + + ))} +
+ )} +
+ )} + + {/* 제목 필드를 두 번째로 배치 */} + ( + + 제목 + + + )} + /> + + {/* 금액 필드를 세 번째로 배치 */} + ( + + 금액 + setShowPaymentMethod(true)} + disabled={isSubmitting} + /> + + )} + /> + + {/* 지출 방법 필드는 금액 입력 시에만 표시 - 서랍처럼 열리는 애니메이션 적용 */} +
+ + + ( + + 지출 방법 +
+
!isSubmitting && form.setValue('paymentMethod', '신용카드')} + > + + 신용카드 +
+
!isSubmitting && form.setValue('paymentMethod', '현금')} + > + + 현금 +
+
+
+ )} + /> +
+ + ); +}; + +export default ExpenseFormFields; diff --git a/src/components/expenses/ExpenseSubmitActions.tsx b/src/components/expenses/ExpenseSubmitActions.tsx new file mode 100644 index 0000000..70f6d3a --- /dev/null +++ b/src/components/expenses/ExpenseSubmitActions.tsx @@ -0,0 +1,41 @@ + +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Loader2 } from 'lucide-react'; + +interface ExpenseSubmitActionsProps { + onCancel: () => void; + isSubmitting: boolean; +} + +const ExpenseSubmitActions: React.FC = ({ + onCancel, + isSubmitting +}) => { + return ( +
+ + +
+ ); +}; + +export default ExpenseSubmitActions;