diff --git a/src/components/SearchFilterExample.tsx b/src/components/SearchFilterExample.tsx deleted file mode 100644 index d5d59a1..0000000 --- a/src/components/SearchFilterExample.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { useSearchFilter } from '@/hooks/useSearchFilter'; -import SearchFilterButton from './SearchFilterButton'; -import { SearchFilter, formatDateRange } from '@/types/filter'; - -interface SearchFilterExampleProps { - onFilterChange?: (filter: SearchFilter) => void; - className?: string; -} - -const SearchFilterExample: React.FC = ({ - onFilterChange, - className = '' -}) => { - const { t } = useTranslation(); - const searchFilterOptions = onFilterChange ? { - storageKey: 'transaction_search_filter', - onFilterChange - } : { - storageKey: 'transaction_search_filter' - }; - - const { - filter, - isModalOpen, - openModal, - closeModal, - handleFilterConfirm, - getDisplayText, - isFilterActive, - resetFilter - } = useSearchFilter(searchFilterOptions); - - return ( -
- {/* 조회조건 버튼과 필터 정보 */} -
-
- - - {/* 초기화 버튼 (필터가 활성화된 경우만 표시) */} - {isFilterActive && ( - - )} -
- - {/* 현재 필터 요약 정보 */} -
- {filter.dateRange && ( - - {formatDateRange(filter.dateRange)} - - )} -
-
- - {/* 현재 적용된 필터 상세 정보 (디버깅용) */} -
-

{t('filter.currentConditions')}

-
-
- {t('filter.period')}: {filter.period === 'custom' ? t('filter.periods.custom') : t(`filter.periods.${filter.period}`)} -
- {filter.dateRange && ( -
- {t('filter.dateRange')}: {formatDateRange(filter.dateRange)} -
- )} -
- {t('filter.transactionType')}: {t(`filter.transactionTypes.${filter.transactionType}`)} -
-
- {t('filter.sortOrder')}: {t(`filter.sortOrders.${filter.sortOrder}`)} -
-
-
- - {/* 조회조건 설정 모달 - - */} -
- ); -}; - -export default SearchFilterExample; \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index 8870408..19b495e 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -6,5 +6,4 @@ export { default as Services } from './Services'; export { default as TabBar } from './TabBar'; export { default as SlideMenu } from './SlideMenu'; export { default as SearchFilterButton } from './SearchFilterButton'; -export { default as SearchFilterExample } from './SearchFilterExample'; export { default as LanguageSwitcher } from './LanguageSwitcher'; \ No newline at end of file diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 57c5be0..85f634a 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,5 +1,3 @@ export { useAuth } from './useAuth'; -export { useLoginForm } from './useLoginForm'; export { useAppBridge } from './useAppBridge'; export { useScrollDirection } from './useScrollDirection'; -export { useSearchFilter } from './useSearchFilter'; \ No newline at end of file diff --git a/src/hooks/useLoginForm.tsx b/src/hooks/useLoginForm.tsx deleted file mode 100644 index abb5515..0000000 --- a/src/hooks/useLoginForm.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import { useState, useCallback } from 'react'; -import { LoginCredentials, RegisterData } from '@/types'; -import { useTranslation } from 'react-i18next'; -import { t } from 'i18next'; - -interface FormErrors { - email?: string; - password?: string; - name?: string; - phone?: string; - confirmPassword?: string; - general?: string; -} - -interface UseLoginFormReturn { - formData: LoginCredentials; - errors: FormErrors; - isLoading: boolean; - handleChange: (e: React.ChangeEvent) => void; - handleSubmit: (onSubmit: (data: LoginCredentials) => Promise) => (e: React.FormEvent) => Promise; - resetForm: () => void; - setError: (field: keyof FormErrors, message: string) => void; - clearErrors: () => void; -} - -interface UseRegisterFormReturn { - formData: RegisterData & { confirmPassword: string }; - errors: FormErrors; - isLoading: boolean; - handleChange: (e: React.ChangeEvent) => void; - handleSubmit: (onSubmit: (data: RegisterData) => Promise) => (e: React.FormEvent) => Promise; - resetForm: () => void; - setError: (field: keyof FormErrors, message: string) => void; - clearErrors: () => void; -} - -export const useLoginForm = (): UseLoginFormReturn => { - const { t } = useTranslation(); - const [formData, setFormData] = useState({ - email: '', - password: '', - }); - const [errors, setErrors] = useState({}); - const [isLoading, setIsLoading] = useState(false); - - const validateLoginForm = useCallback((data: LoginCredentials): FormErrors => { - const newErrors: FormErrors = {}; - - if (!data.email.trim()) { - newErrors.email = t('login.emailPlaceholder'); - } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { - newErrors.email = t('login.emailPlaceholder'); - } - - if (!data.password.trim()) { - newErrors.password = t('login.passwordPlaceholder'); - } else if (data.password.length < 6) { - newErrors.password = t('login.passwordMinLength'); - } - - return newErrors; - }, [t]); - - const handleChange = useCallback((e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); - - // 해당 필드의 에러 제거 - if (errors[name as keyof FormErrors]) { - setErrors(prev => ({ ...prev, [name]: undefined })); - } - }, [errors]); - - const handleSubmit = useCallback((onSubmit: (data: LoginCredentials) => Promise) => { - return async (e: React.FormEvent) => { - e.preventDefault(); - - const validationErrors = validateLoginForm(formData); - if (Object.keys(validationErrors).length > 0) { - setErrors(validationErrors); - return; - } - - setIsLoading(true); - setErrors({}); - - try { - await onSubmit(formData); - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : t('login.generalError'); - setErrors({ general: errorMessage }); - } finally { - setIsLoading(false); - } - }; - }, [formData, validateLoginForm, t]); - - const resetForm = useCallback(() => { - setFormData({ email: '', password: '' }); - setErrors({}); - setIsLoading(false); - }, []); - - const setError = useCallback((field: keyof FormErrors, message: string) => { - setErrors(prev => ({ ...prev, [field]: message })); - }, []); - - const clearErrors = useCallback(() => { - setErrors({}); - }, []); - - return { - formData, - errors, - isLoading, - handleChange, - handleSubmit, - resetForm, - setError, - clearErrors, - }; -}; - -export const useRegisterForm = (): UseRegisterFormReturn => { - const [formData, setFormData] = useState({ - email: '', - password: '', - name: '', - phone: '', - confirmPassword: '', - }); - const [errors, setErrors] = useState({}); - const [isLoading, setIsLoading] = useState(false); - - const validateRegisterForm = useCallback((data: RegisterData & { confirmPassword: string }): FormErrors => { - const newErrors: FormErrors = {}; - - if (!data.email.trim()) { - newErrors.email = '이메일을 입력해주세요.'; - } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { - newErrors.email = '올바른 이메일 형식을 입력해주세요.'; - } - - if (!data.password.trim()) { - newErrors.password = t('login.passwordPlaceholder'); - } else if (data.password.length < 8) { - newErrors.password = t('login.passwordMinLength'); - } else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(data.password)) { - newErrors.password = t('login.passwordComplexity'); - } - - if (!data.confirmPassword.trim()) { - newErrors.confirmPassword = t('login.confirmPasswordPlaceholder'); - } else if (data.password !== data.confirmPassword) { - newErrors.confirmPassword = t('login.confirmPasswordMismatch'); - } - - if (!data.name.trim()) { - newErrors.name = t('login.namePlaceholder'); - } else if (data.name.length < 2) { - newErrors.name = t('login.nameMinLength'); - } - - if (data.phone && !/^[0-9-+\s()]+$/.test(data.phone)) { - newErrors.phone = t('login.phoneFormat'); - } - - return newErrors; - }, []); - - const handleChange = useCallback((e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); - - // 해당 필드의 에러 제거 - if (errors[name as keyof FormErrors]) { - setErrors(prev => ({ ...prev, [name]: undefined })); - } - }, [errors]); - - const handleSubmit = useCallback((onSubmit: (data: RegisterData) => Promise) => { - return async (e: React.FormEvent) => { - e.preventDefault(); - - const validationErrors = validateRegisterForm(formData); - if (Object.keys(validationErrors).length > 0) { - setErrors(validationErrors); - return; - } - - setIsLoading(true); - setErrors({}); - - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { confirmPassword: _confirmPassword, ...registerData } = formData; - await onSubmit(registerData); - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : '회원가입 중 오류가 발생했습니다.'; - setErrors({ general: errorMessage }); - } finally { - setIsLoading(false); - } - }; - }, [formData, validateRegisterForm]); - - const resetForm = useCallback(() => { - setFormData({ - email: '', - password: '', - name: '', - phone: '', - confirmPassword: '', - }); - setErrors({}); - setIsLoading(false); - }, []); - - const setError = useCallback((field: keyof FormErrors, message: string) => { - setErrors(prev => ({ ...prev, [field]: message })); - }, []); - - const clearErrors = useCallback(() => { - setErrors({}); - }, []); - - return { - formData, - errors, - isLoading, - handleChange, - handleSubmit, - resetForm, - setError, - clearErrors, - }; -}; \ No newline at end of file diff --git a/src/hooks/useSearchFilter.tsx b/src/hooks/useSearchFilter.tsx deleted file mode 100644 index 1cf629d..0000000 --- a/src/hooks/useSearchFilter.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { useState, useCallback, useEffect, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; -import { - SearchFilter, - DEFAULT_SEARCH_FILTER, - getDateRangeFromPeriod, - getFilterDisplayText -} from '@/types/filter'; - -interface UseSearchFilterOptions { - initialFilter?: SearchFilter; - storageKey?: string; // 로컬 스토리지에 저장할 키 - onFilterChange?: (filter: SearchFilter) => void; -} - -interface UseSearchFilterReturn { - filter: SearchFilter; - setFilter: (filter: SearchFilter) => void; - resetFilter: () => void; - isModalOpen: boolean; - openModal: () => void; - closeModal: () => void; - handleFilterConfirm: (newFilter: SearchFilter) => void; - getDisplayText: () => string; - isFilterActive: boolean; // 기본값과 다른지 확인 -} - -export const useSearchFilter = (options: UseSearchFilterOptions = {}): UseSearchFilterReturn => { - const { t } = useTranslation(); - const { - initialFilter = DEFAULT_SEARCH_FILTER, - storageKey, - onFilterChange - } = options; - - const isInitialMount = useRef(true); - - // 로컬 스토리지에서 저장된 필터 로드 - const loadStoredFilter = useCallback((): SearchFilter => { - if (!storageKey) return initialFilter; - - try { - const stored = localStorage.getItem(storageKey); - if (stored) { - const parsedFilter = JSON.parse(stored); - // Date 객체 복원 - if (parsedFilter.dateRange) { - parsedFilter.dateRange = { - startDate: new Date(parsedFilter.dateRange.startDate), - endDate: new Date(parsedFilter.dateRange.endDate) - }; - } - return { ...initialFilter, ...parsedFilter }; - } - } catch (error) { - console.warn('Failed to load stored filter:', error); - } - - return initialFilter; - }, [initialFilter, storageKey]); - - const [filter, setFilterState] = useState(loadStoredFilter); - const [isModalOpen, setIsModalOpen] = useState(false); - - // 필터 저장 - const saveFilter = useCallback((newFilter: SearchFilter) => { - if (storageKey) { - try { - localStorage.setItem(storageKey, JSON.stringify(newFilter)); - } catch (error) { - console.warn('Failed to save filter to storage:', error); - } - } - }, [storageKey]); - - // 필터 설정 - const setFilter = useCallback((newFilter: SearchFilter) => { - // 날짜 범위가 없고 period가 custom이 아닌 경우 자동 계산 - const finalFilter = { - ...newFilter, - dateRange: newFilter.dateRange || getDateRangeFromPeriod(newFilter.period) - }; - - //setFilterState(finalFilter); - //saveFilter(finalFilter); - //onFilterChange?.(finalFilter); - }, [saveFilter, onFilterChange]); - - // 필터 초기화 - const resetFilter = useCallback(() => { - setFilter(initialFilter); - }, [initialFilter, setFilter]); - - // 모달 제어 - const openModal = useCallback(() => { - setIsModalOpen(true); - }, []); - - const closeModal = useCallback(() => { - setIsModalOpen(false); - }, []); - - // 모달에서 확인 버튼 클릭 시 - const handleFilterConfirm = useCallback((newFilter: SearchFilter) => { - setFilter(newFilter); - closeModal(); - }, [setFilter, closeModal]); - - // 표시 텍스트 가져오기 - const getDisplayText = useCallback(() => { - return getFilterDisplayText(filter, t); - }, [filter, t]); - - // 기본값과 다른지 확인 - const isFilterActive = useCallback(() => { - return JSON.stringify(filter) !== JSON.stringify(DEFAULT_SEARCH_FILTER); - }, [filter])(); - - // 초기 로드 시 필터 변경 콜백 호출 - useEffect(() => { - if (isInitialMount.current && onFilterChange) { - onFilterChange(filter); - isInitialMount.current = false; - } - }, [filter, onFilterChange]); - - return { - filter, - setFilter, - resetFilter, - isModalOpen, - openModal, - closeModal, - handleFilterConfirm, - getDisplayText, - isFilterActive - }; -}; \ No newline at end of file diff --git a/src/pages/Test.tsx b/src/pages/Test.tsx index 3f74385..0f1fb96 100644 --- a/src/pages/Test.tsx +++ b/src/pages/Test.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { appBridge } from '../utils/appBridge'; -import { SearchFilterExample, LanguageSwitcher } from '../components'; +import { LanguageSwitcher } from '../components'; import { SearchFilter } from '../types/filter'; interface TestResult { @@ -191,12 +191,7 @@ const Test: React.FC = () => { )} - {/* 조회조건 테스트 섹션 */} -
-

조회조건 테스트

- -
- + {/* 검색 결과 표시 */} {searchData.length > 0 && (