/** * 거래처 등록/수정 컴포넌트 * * IntegratedDetailTemplate 마이그레이션 (2026-01-20) * - 데스크톱/태블릿/모바일 통합 폼 레이아웃 * - 섹션 기반 정보 입력 * - 유효성 검사 및 에러 표시 */ 'use client'; 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'; // 필드명 매핑 const FIELD_NAME_MAP: Record = { name: '거래처명', businessNo: '사업자등록번호', representative: '대표자명', phone: '전화번호', email: '이메일', }; interface ClientRegistrationProps { onBack: () => void; onSave: (client: ClientFormData) => Promise; editingClient?: ClientFormData | null; isLoading?: boolean; } // 4자리 영문+숫자 조합 코드 생성 (중복 방지를 위해 타임스탬프 기반) const generateClientCode = (): string => { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const timestamp = Date.now().toString(36).toUpperCase().slice(-2); // 타임스탬프 2자리 let random = ''; for (let i = 0; i < 2; i++) { random += chars.charAt(Math.floor(Math.random() * chars.length)); } return timestamp + random; }; export function ClientRegistration({ onBack, onSave, editingClient, isLoading = false, }: ClientRegistrationProps) { const [formData, setFormData] = useState(() => { if (editingClient) { return editingClient; } // 신규 등록 시 클라이언트 코드 자동 생성 return { ...INITIAL_CLIENT_FORM, clientCode: generateClientCode(), }; }); const [errors, setErrors] = useState>({}); const [isSaving, setIsSaving] = useState(false); const isEditMode = !!editingClient; // editingClient가 변경되면 formData 업데이트 useEffect(() => { if (editingClient) { setFormData(editingClient); } }, [editingClient]); // 유효성 검사 const validateForm = (): boolean => { const newErrors: Record = {}; if (!formData.name || formData.name.length < 2) { newErrors.name = '거래처명은 2자 이상 입력해주세요'; } // 하이픈 제거 후 10자리 숫자 검증 (123-45-67890 또는 1234567890 허용) const businessNoDigits = formData.businessNo.replace(/-/g, '').trim(); if (!formData.businessNo || !/^\d{10}$/.test(businessNoDigits)) { newErrors.businessNo = '사업자등록번호는 10자리 숫자여야 합니다'; } if (!formData.representative || formData.representative.length < 2) { newErrors.representative = '대표자명은 2자 이상 입력해주세요'; } // 전화번호 형식 검사 (선택적) const phonePattern = /^[0-9-]+$/; if (formData.phone && !phonePattern.test(formData.phone)) { newErrors.phone = '올바른 전화번호 형식이 아닙니다'; } if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { newErrors.email = '올바른 이메일 형식이 아닙니다'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = useCallback(async () => { if (!validateForm()) { // 페이지 상단으로 스크롤 window.scrollTo({ top: 0, behavior: 'smooth' }); return; } // 에러 초기화 setErrors({}); setIsSaving(true); try { await onSave(formData); toast.success( editingClient ? '거래처가 수정되었습니다.' : '거래처가 등록되었습니다.' ); onBack(); } catch (error) { toast.error('저장 중 오류가 발생했습니다.'); } finally { setIsSaving(false); } }, [formData, editingClient, onSave, onBack]); const handleFieldChange = ( field: keyof ClientFormData, value: string | boolean ) => { setFormData({ ...formData, [field]: value }); if (errors[field]) { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[field]; return newErrors; }); } }; // 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. 기본 정보 */} 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('address', e.target.value)} /> handleFieldChange('phone', 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('systemManager', e.target.value) } /> {/* 4. 기타 */}