"use client"; import React, { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Switch } from "@/components/ui/switch"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Plus, X, Save, RotateCcw, AlertTriangle, CheckCircle } from "lucide-react"; import { toast } from "sonner"; import { useComponentDuplicateCheck } from "@/hooks/admin/useComponentDuplicateCheck"; import { Alert, AlertDescription } from "@/components/ui/alert"; // 컴포넌트 카테고리 정의 const COMPONENT_CATEGORIES = [ { id: "input", name: "입력", description: "사용자 입력을 받는 컴포넌트" }, { id: "action", name: "액션", description: "사용자 액션을 처리하는 컴포넌트" }, { id: "display", name: "표시", description: "정보를 표시하는 컴포넌트" }, { id: "layout", name: "레이아웃", description: "레이아웃을 구성하는 컴포넌트" }, { id: "other", name: "기타", description: "기타 컴포넌트" }, ]; // 컴포넌트 타입 정의 const COMPONENT_TYPES = [ { id: "widget", name: "위젯", description: "입력 양식 위젯" }, { id: "button", name: "버튼", description: "액션 버튼" }, { id: "card", name: "카드", description: "카드 컨테이너" }, { id: "container", name: "컨테이너", description: "일반 컨테이너" }, { id: "dashboard", name: "대시보드", description: "대시보드 그리드" }, { id: "alert", name: "알림", description: "알림 메시지" }, { id: "badge", name: "배지", description: "상태 배지" }, { id: "progress", name: "진행률", description: "진행률 표시" }, { id: "chart", name: "차트", description: "데이터 차트" }, ]; // 웹타입 정의 (위젯인 경우만) const WEB_TYPES = [ "text", "number", "decimal", "date", "datetime", "select", "dropdown", "textarea", "boolean", "checkbox", "radio", "code", "entity", "file", "email", "tel", "color", "range", "time", "week", "month", ]; interface ComponentFormData { component_code: string; component_name: string; description: string; category: string; component_config: { type: string; webType?: string; config_panel?: string; }; default_size: { width: number; height: number; }; icon_name: string; active: string; sort_order: number; } interface ComponentFormModalProps { isOpen: boolean; onClose: () => void; onSubmit: (data: ComponentFormData) => Promise; initialData?: any; mode?: "create" | "edit"; } export const ComponentFormModal: React.FC = ({ isOpen, onClose, onSubmit, initialData, mode = "create", }) => { const [formData, setFormData] = useState({ component_code: "", component_name: "", description: "", category: "other", component_config: { type: "widget", }, default_size: { width: 200, height: 40, }, icon_name: "", is_active: "Y", sort_order: 100, }); const [isSubmitting, setIsSubmitting] = useState(false); const [shouldCheckDuplicate, setShouldCheckDuplicate] = useState(false); // 중복 체크 쿼리 (생성 모드에서만 활성화) const duplicateCheck = useComponentDuplicateCheck( formData.component_code, mode === "create" && shouldCheckDuplicate && formData.component_code.length > 0, ); // 초기 데이터 설정 useEffect(() => { if (isOpen) { if (mode === "edit" && initialData) { setFormData({ component_code: initialData.component_code || "", component_name: initialData.component_name || "", description: initialData.description || "", category: initialData.category || "other", component_config: initialData.component_config || { type: "widget" }, default_size: initialData.default_size || { width: 200, height: 40 }, icon_name: initialData.icon_name || "", is_active: initialData.is_active || "Y", sort_order: initialData.sort_order || 100, }); } else { // 새 컴포넌트 생성 시 초기값 setFormData({ component_code: "", component_name: "", description: "", category: "other", component_config: { type: "widget", }, default_size: { width: 200, height: 40, }, icon_name: "", is_active: "Y", sort_order: 100, }); } } }, [isOpen, mode, initialData]); // 컴포넌트 코드 자동 생성 const generateComponentCode = (name: string, type: string) => { if (!name) return ""; // 한글을 영문으로 매핑 const koreanToEnglish: { [key: string]: string } = { 도움말: "help", 툴팁: "tooltip", 안내: "guide", 알림: "alert", 버튼: "button", 카드: "card", 대시보드: "dashboard", 패널: "panel", 입력: "input", 텍스트: "text", 선택: "select", 체크: "check", 라디오: "radio", 파일: "file", 이미지: "image", 테이블: "table", 리스트: "list", 폼: "form", }; // 한글을 영문으로 변환 let englishName = name; Object.entries(koreanToEnglish).forEach(([korean, english]) => { englishName = englishName.replace(new RegExp(korean, "g"), english); }); const cleanName = englishName .toLowerCase() .replace(/[^a-z0-9\s]/g, "") .replace(/\s+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); // 빈 문자열이거나 숫자로 시작하는 경우 기본값 설정 const finalName = cleanName || "component"; const validName = /^[0-9]/.test(finalName) ? `comp-${finalName}` : finalName; return type === "widget" ? validName : `${validName}-${type}`; }; // 폼 필드 변경 처리 const handleChange = (field: string, value: any) => { setFormData((prev) => { const newData = { ...prev }; if (field.includes(".")) { const [parent, child] = field.split("."); newData[parent as keyof ComponentFormData] = { ...(newData[parent as keyof ComponentFormData] as any), [child]: value, }; } else { (newData as any)[field] = value; } // 컴포넌트 이름이 변경되면 코드 자동 생성 if (field === "component_name" || field === "component_config.type") { const name = field === "component_name" ? value : newData.component_name; const type = field === "component_config.type" ? value : newData.component_config.type; if (name && mode === "create") { newData.component_code = generateComponentCode(name, type); // 자동 생성된 코드에 대해서도 중복 체크 활성화 setShouldCheckDuplicate(true); } } // 컴포넌트 코드가 직접 변경되면 중복 체크 활성화 if (field === "component_code" && mode === "create") { setShouldCheckDuplicate(true); } return newData; }); }; // 폼 제출 const handleSubmit = async () => { // 유효성 검사 if (!formData.component_code || !formData.component_name) { toast.error("컴포넌트 코드와 이름은 필수입니다."); return; } if (!formData.component_config.type) { toast.error("컴포넌트 타입을 선택해주세요."); return; } // 생성 모드에서 중복 체크 if (mode === "create" && duplicateCheck.data?.isDuplicate) { toast.error("이미 사용 중인 컴포넌트 코드입니다. 다른 코드를 사용해주세요."); return; } setIsSubmitting(true); try { await onSubmit(formData); toast.success(mode === "create" ? "컴포넌트가 생성되었습니다." : "컴포넌트가 수정되었습니다."); onClose(); } catch (error) { toast.error(mode === "create" ? "컴포넌트 생성에 실패했습니다." : "컴포넌트 수정에 실패했습니다."); } finally { setIsSubmitting(false); } }; // 폼 초기화 const handleReset = () => { if (mode === "edit" && initialData) { setFormData({ component_code: initialData.component_code || "", component_name: initialData.component_name || "", description: initialData.description || "", category: initialData.category || "other", component_config: initialData.component_config || { type: "widget" }, default_size: initialData.default_size || { width: 200, height: 40 }, icon_name: initialData.icon_name || "", is_active: initialData.is_active || "Y", sort_order: initialData.sort_order || 100, }); } else { setFormData({ component_code: "", component_name: "", description: "", category: "other", component_config: { type: "widget", }, default_size: { width: 200, height: 40, }, icon_name: "", is_active: "Y", sort_order: 100, }); } }; return ( {mode === "create" ? "새 컴포넌트 추가" : "컴포넌트 편집"} {mode === "create" ? "화면 설계에 사용할 새로운 컴포넌트를 추가합니다." : "선택한 컴포넌트의 정보를 수정합니다."}
{/* 기본 정보 */} 기본 정보
handleChange("component_name", e.target.value)} placeholder="예: 정보 알림" />
handleChange("component_code", e.target.value)} placeholder="예: alert-info" disabled={mode === "edit"} className={ mode === "create" && duplicateCheck.data?.isDuplicate ? "border-red-500 pr-10" : mode === "create" && duplicateCheck.data && !duplicateCheck.data.isDuplicate ? "border-green-500 pr-10" : "" } /> {mode === "create" && formData.component_code && duplicateCheck.data && (
{duplicateCheck.data.isDuplicate ? ( ) : ( )}
)}
{mode === "create" && formData.component_code && duplicateCheck.data && ( {duplicateCheck.data.isDuplicate ? "⚠️ 이미 사용 중인 컴포넌트 코드입니다." : "✅ 사용 가능한 컴포넌트 코드입니다."} )}