/** * 거래처 등록/수정 컴포넌트 * * ResponsiveFormTemplate 적용 * - 데스크톱/태블릿/모바일 통합 폼 레이아웃 * - 섹션 기반 정보 입력 * - 유효성 검사 및 에러 표시 */ "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 { ResponsiveFormTemplate, FormSection, FormFieldGrid, } from "../templates/ResponsiveFormTemplate"; import { FormField } from "../molecules/FormField"; import { ClientFormData, INITIAL_CLIENT_FORM, ClientType, } from "../../hooks/useClientList"; 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); // 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(); console.log("[ClientRegistration] businessNo 검증:", { 원본: formData.businessNo, 하이픈제거: businessNoDigits, 길이: businessNoDigits.length, 숫자만: /^\d{10}$/.test(businessNoDigits), }); 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 = async () => { if (!validateForm()) { toast.error("입력 내용을 확인해주세요."); return; } setIsSaving(true); try { await onSave(formData); toast.success( editingClient ? "거래처가 수정되었습니다." : "거래처가 등록되었습니다." ); onBack(); } catch (error) { toast.error("저장 중 오류가 발생했습니다."); } finally { setIsSaving(false); } }; const handleFieldChange = ( field: keyof ClientFormData, value: string | boolean ) => { console.log("[ClientRegistration] handleFieldChange:", field, value); setFormData({ ...formData, [field]: value }); if (errors[field]) { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[field]; return newErrors; }); } }; return ( {/* 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) } /> {/* TODO: 기획 확정 후 활성화 (2025-12-09) - 발주처 설정: 계정ID, 비밀번호, 매입/매출 결제일 - 약정 세금: 약정 여부, 금액, 시작/종료일 - 악성채권 정보: 악성채권 여부, 금액, 발생/만료일, 진행상태 백엔드 API에서는 이미 지원됨 (nullable 필드) */} {/* 4. 기타 */}