diff --git a/src/components/clients/ClientRegistration.tsx b/src/components/clients/ClientRegistration.tsx index 3ad4d479..9ca5bfab 100644 --- a/src/components/clients/ClientRegistration.tsx +++ b/src/components/clients/ClientRegistration.tsx @@ -1,47 +1,41 @@ /** * 거래처 등록/수정 컴포넌트 * - * ResponsiveFormTemplate 적용 + * IntegratedDetailTemplate 마이그레이션 (2026-01-20) * - 데스크톱/태블릿/모바일 통합 폼 레이아웃 * - 섹션 기반 정보 입력 * - 유효성 검사 및 에러 표시 */ -"use client"; +'use client'; -import { useState, useEffect } from "react"; -import { Input } from "../ui/input"; -import { Textarea } from "../ui/textarea"; -import { RadioGroup, RadioGroupItem } from "../ui/radio-group"; -import { Label } from "../ui/label"; -import { - Building2, - UserCircle, - Phone, - FileText, -} from "lucide-react"; -import { toast } from "sonner"; -import { Alert, AlertDescription } from "../ui/alert"; - -// 필드명 매핑 -const FIELD_NAME_MAP: Record = { - name: "거래처명", - businessNo: "사업자등록번호", - representative: "대표자명", - phone: "전화번호", - email: "이메일", -}; -import { - ResponsiveFormTemplate, - FormSection, - FormFieldGrid, -} from "../templates/ResponsiveFormTemplate"; -import { FormField } from "../molecules/FormField"; +import { useState, useEffect, useCallback } from 'react'; +import { Input } from '../ui/input'; +import { Textarea } from '../ui/textarea'; +import { RadioGroup, RadioGroupItem } from '../ui/radio-group'; +import { Label } from '../ui/label'; +import { Building2, UserCircle, Phone, FileText } from 'lucide-react'; +import { toast } from 'sonner'; +import { Alert, AlertDescription } from '../ui/alert'; +import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; +import { clientCreateConfig, clientEditConfig } from './clientConfig'; +import { FormSection } from '../organisms/FormSection'; +import { FormFieldGrid } from '../organisms/FormFieldGrid'; +import { FormField } from '../molecules/FormField'; import { ClientFormData, INITIAL_CLIENT_FORM, ClientType, -} from "../../hooks/useClientList"; +} from '../../hooks/useClientList'; + +// 필드명 매핑 +const FIELD_NAME_MAP: Record = { + name: '거래처명', + businessNo: '사업자등록번호', + representative: '대표자명', + phone: '전화번호', + email: '이메일', +}; interface ClientRegistrationProps { onBack: () => void; @@ -52,9 +46,9 @@ interface ClientRegistrationProps { // 4자리 영문+숫자 조합 코드 생성 (중복 방지를 위해 타임스탬프 기반) const generateClientCode = (): string => { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const timestamp = Date.now().toString(36).toUpperCase().slice(-2); // 타임스탬프 2자리 - let random = ""; + let random = ''; for (let i = 0; i < 2; i++) { random += chars.charAt(Math.floor(Math.random() * chars.length)); } @@ -80,6 +74,8 @@ export function ClientRegistration({ const [errors, setErrors] = useState>({}); const [isSaving, setIsSaving] = useState(false); + const isEditMode = !!editingClient; + // editingClient가 변경되면 formData 업데이트 useEffect(() => { if (editingClient) { @@ -92,40 +88,34 @@ export function ClientRegistration({ const newErrors: Record = {}; if (!formData.name || formData.name.length < 2) { - newErrors.name = "거래처명은 2자 이상 입력해주세요"; + newErrors.name = '거래처명은 2자 이상 입력해주세요'; } // 하이픈 제거 후 10자리 숫자 검증 (123-45-67890 또는 1234567890 허용) - const businessNoDigits = formData.businessNo.replace(/-/g, "").trim(); - console.log("[ClientRegistration] businessNo 검증:", { - 원본: formData.businessNo, - 하이픈제거: businessNoDigits, - 길이: businessNoDigits.length, - 숫자만: /^\d{10}$/.test(businessNoDigits), - }); + const businessNoDigits = formData.businessNo.replace(/-/g, '').trim(); if (!formData.businessNo || !/^\d{10}$/.test(businessNoDigits)) { - newErrors.businessNo = "사업자등록번호는 10자리 숫자여야 합니다"; + newErrors.businessNo = '사업자등록번호는 10자리 숫자여야 합니다'; } if (!formData.representative || formData.representative.length < 2) { - newErrors.representative = "대표자명은 2자 이상 입력해주세요"; + newErrors.representative = '대표자명은 2자 이상 입력해주세요'; } // 전화번호 형식 검사 (선택적) const phonePattern = /^[0-9-]+$/; if (formData.phone && !phonePattern.test(formData.phone)) { - newErrors.phone = "올바른 전화번호 형식이 아닙니다"; + newErrors.phone = '올바른 전화번호 형식이 아닙니다'; } if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { - newErrors.email = "올바른 이메일 형식이 아닙니다"; + newErrors.email = '올바른 이메일 형식이 아닙니다'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; - const handleSubmit = async () => { + const handleSubmit = useCallback(async () => { if (!validateForm()) { // 페이지 상단으로 스크롤 window.scrollTo({ top: 0, behavior: 'smooth' }); @@ -138,21 +128,20 @@ export function ClientRegistration({ try { await onSave(formData); toast.success( - editingClient ? "거래처가 수정되었습니다." : "거래처가 등록되었습니다." + editingClient ? '거래처가 수정되었습니다.' : '거래처가 등록되었습니다.' ); onBack(); } catch (error) { - toast.error("저장 중 오류가 발생했습니다."); + toast.error('저장 중 오류가 발생했습니다.'); } finally { setIsSaving(false); } - }; + }, [formData, editingClient, onSave, onBack]); const handleFieldChange = ( field: keyof ClientFormData, value: string | boolean ) => { - console.log("[ClientRegistration] handleFieldChange:", field, value); setFormData({ ...formData, [field]: value }); if (errors[field]) { setErrors((prev) => { @@ -163,304 +152,314 @@ export function ClientRegistration({ } }; - return ( - - {/* Validation 에러 표시 */} - {Object.keys(errors).length > 0 && ( - - -
- ⚠️ -
- - 입력 내용을 확인해주세요 ({Object.keys(errors).length}개 오류) - -
    - {Object.entries(errors).map(([field, message]) => { - const fieldName = FIELD_NAME_MAP[field] || field; - return ( -
  • - - - {fieldName}: {message} - -
  • - ); - })} -
+ // Config 선택 + const config = isEditMode ? clientEditConfig : clientCreateConfig; + + // 폼 콘텐츠 렌더링 + const renderFormContent = useCallback( + () => ( +
+ {/* Validation 에러 표시 */} + {Object.keys(errors).length > 0 && ( + + +
+ ⚠️ +
+ + 입력 내용을 확인해주세요 ({Object.keys(errors).length}개 오류) + +
    + {Object.entries(errors).map(([field, message]) => { + const fieldName = FIELD_NAME_MAP[field] || field; + return ( +
  • + + + {fieldName}: {message} + +
  • + ); + })} +
+
-
- - - )} + + + )} - {/* 1. 기본 정보 */} - - - + {/* 1. 기본 정보 */} + + + + handleFieldChange('businessNo', e.target.value)} + /> + + + + + + + + + + handleFieldChange('name', e.target.value)} + /> + + + + + handleFieldChange('representative', e.target.value) + } + /> + + + + + + handleFieldChange('clientType', value as ClientType) + } + className="flex gap-4" + > +
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + handleFieldChange('businessType', e.target.value) + } + /> + + + + + handleFieldChange('businessItem', e.target.value) + } + /> + + +
+ + {/* 2. 연락처 정보 */} + + handleFieldChange("businessNo", e.target.value)} + id="address" + placeholder="주소 입력" + value={formData.address} + onChange={(e) => handleFieldChange('address', e.target.value)} /> - - - -
+ + + handleFieldChange('phone', e.target.value)} + /> + - - - handleFieldChange("name", e.target.value)} - /> - + + handleFieldChange('mobile', e.target.value)} + /> + + + + handleFieldChange('fax', e.target.value)} + /> + + handleFieldChange('email', e.target.value)} + /> + +
+ + {/* 3. 담당자 정보 */} + + + + handleFieldChange('managerName', e.target.value)} + /> + + + + handleFieldChange('managerTel', e.target.value)} + /> + + + + + - handleFieldChange("representative", e.target.value) + handleFieldChange('systemManager', e.target.value) } /> - + - - - handleFieldChange("clientType", value as ClientType) - } - className="flex gap-4" - > -
- - -
-
- - -
-
- - -
-
-
+ {/* 4. 기타 */} + + +