"use client"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { LoadingSpinner } from "@/components/common/LoadingSpinner"; import { ValidationMessage } from "@/components/common/ValidationMessage"; import { useCreateCode, useUpdateCode } from "@/hooks/queries/useCodes"; import { useCheckCodeDuplicate } from "@/hooks/queries/useValidation"; import { createCodeSchema, updateCodeSchema, type CreateCodeData, type UpdateCodeData } from "@/lib/schemas/commonCode"; import type { CodeInfo } from "@/types/commonCode"; import type { FieldError } from "react-hook-form"; interface CodeFormModalProps { isOpen: boolean; onClose: () => void; categoryCode: string; editingCode?: CodeInfo | null; codes: CodeInfo[]; defaultParentCode?: string; // 하위 코드 추가 시 기본 부모 코드 } // 에러 메시지를 안전하게 문자열로 변환하는 헬퍼 함수 const getErrorMessage = (error: FieldError | undefined): string => { if (!error) return ""; if (typeof error === "string") return error; return error.message || ""; }; // 코드값 자동 생성 함수 (UUID 기반 짧은 코드) const generateCodeValue = (): string => { const timestamp = Date.now().toString(36).toUpperCase(); const random = Math.random().toString(36).substring(2, 6).toUpperCase(); return `${timestamp}${random}`; }; export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, codes, defaultParentCode, }: CodeFormModalProps) { const createCodeMutation = useCreateCode(); const updateCodeMutation = useUpdateCode(); const isEditing = !!editingCode; // 검증 상태 관리 (코드명만 중복 검사) const [validationStates, setValidationStates] = useState({ codeName: { enabled: false, value: "" }, }); // 코드명 중복 검사 const codeNameCheck = useCheckCodeDuplicate( categoryCode, "codeName", validationStates.codeName.value, isEditing ? editingCode?.codeValue || editingCode?.code_value : undefined, validationStates.codeName.enabled, ); // 중복 검사 결과 확인 const hasDuplicateErrors = codeNameCheck.data?.isDuplicate && validationStates.codeName.enabled; // 중복 검사 로딩 중인지 확인 const isDuplicateChecking = codeNameCheck.isLoading; // 폼 스키마 선택 (생성/수정에 따라) const schema = isEditing ? updateCodeSchema : createCodeSchema; const form = useForm({ resolver: zodResolver(schema), mode: "onChange", // 실시간 검증 활성화 defaultValues: { codeValue: "", codeName: "", codeNameEng: "", description: "", sortOrder: 1, parentCodeValue: "" as string | undefined, ...(isEditing && { isActive: "Y" as const }), }, }); // 편집 모드일 때 기존 데이터 로드 useEffect(() => { if (isOpen) { if (isEditing && editingCode) { // 수정 모드: 기존 데이터 로드 (codeValue는 표시용으로만 설정) const parentValue = editingCode.parentCodeValue || editingCode.parent_code_value || ""; form.reset({ codeName: editingCode.codeName || editingCode.code_name, codeNameEng: editingCode.codeNameEng || editingCode.code_name_eng || "", description: editingCode.description || "", sortOrder: editingCode.sortOrder || editingCode.sort_order, isActive: (editingCode.isActive || editingCode.is_active) as "Y" | "N", parentCodeValue: parentValue, }); // codeValue는 별도로 설정 (표시용) form.setValue("codeValue" as any, editingCode.codeValue || editingCode.code_value); } else { // 새 코드 모드: 자동 순서 계산 const maxSortOrder = codes.length > 0 ? Math.max(...codes.map((c) => c.sortOrder || c.sort_order || 0)) : 0; // 기본 부모 코드가 있으면 설정 (하위 코드 추가 시) const parentValue = defaultParentCode || ""; // 코드값 자동 생성 const autoCodeValue = generateCodeValue(); form.reset({ codeValue: autoCodeValue, codeName: "", codeNameEng: "", description: "", sortOrder: maxSortOrder + 1, parentCodeValue: parentValue, }); } } }, [isOpen, isEditing, editingCode, codes, defaultParentCode]); const handleSubmit = form.handleSubmit(async (data) => { try { if (isEditing && editingCode) { // 수정 await updateCodeMutation.mutateAsync({ categoryCode, codeValue: editingCode.codeValue || editingCode.code_value || "", data: data as UpdateCodeData, }); } else { // 생성 await createCodeMutation.mutateAsync({ categoryCode, data: data as CreateCodeData, }); } onClose(); form.reset(); } catch (error) { console.error("코드 저장 실패:", error); } }); const isLoading = createCodeMutation.isPending || updateCodeMutation.isPending; return ( {isEditing ? "코드 수정" : defaultParentCode ? "하위 코드 추가" : "새 코드"}
{/* 코드값 (자동 생성, 수정 시에만 표시) */} {isEditing && (
{form.watch("codeValue")}

코드값은 변경할 수 없습니다

)} {/* 코드명 */}
{ const value = e.target.value.trim(); if (value) { setValidationStates((prev) => ({ ...prev, codeName: { enabled: true, value }, })); } }} /> {form.formState.errors.codeName && (

{getErrorMessage(form.formState.errors.codeName)}

)} {!form.formState.errors.codeName && ( )}
{/* 영문명 (선택) */}
{/* 설명 (선택) */}