"use client"; import React, { useState, useEffect } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Database, Search, Plus, Trash2 } from "lucide-react"; import { WebTypeConfigPanelProps } from "@/lib/registry/types"; import { WidgetComponent, EntityTypeConfig } from "@/types/screen"; interface EntityField { name: string; label: string; type: string; visible: boolean; } export const EntityConfigPanel: React.FC = ({ component, onUpdateComponent, onUpdateProperty, }) => { const widget = component as WidgetComponent; const config = (widget.webTypeConfig as EntityTypeConfig) || {}; // 로컬 상태 const [localConfig, setLocalConfig] = useState({ entityType: config.entityType || "", displayFields: config.displayFields || [], searchFields: config.searchFields || [], valueField: config.valueField || "id", labelField: config.labelField || "name", multiple: config.multiple || false, searchable: config.searchable !== false, // 기본값 true placeholder: config.placeholder || "엔티티를 선택하세요", emptyMessage: config.emptyMessage || "검색 결과가 없습니다", pageSize: config.pageSize || 20, minSearchLength: config.minSearchLength || 1, defaultValue: config.defaultValue || "", required: config.required || false, readonly: config.readonly || false, apiEndpoint: config.apiEndpoint || "", filters: config.filters || {}, }); // 새 필드 추가용 상태 const [newFieldName, setNewFieldName] = useState(""); const [newFieldLabel, setNewFieldLabel] = useState(""); const [newFieldType, setNewFieldType] = useState("string"); // 컴포넌트 변경 시 로컬 상태 동기화 useEffect(() => { const currentConfig = (widget.webTypeConfig as EntityTypeConfig) || {}; setLocalConfig({ entityType: currentConfig.entityType || "", displayFields: currentConfig.displayFields || [], searchFields: currentConfig.searchFields || [], valueField: currentConfig.valueField || "id", labelField: currentConfig.labelField || "name", multiple: currentConfig.multiple || false, searchable: currentConfig.searchable !== false, placeholder: currentConfig.placeholder || "엔티티를 선택하세요", emptyMessage: currentConfig.emptyMessage || "검색 결과가 없습니다", pageSize: currentConfig.pageSize || 20, minSearchLength: currentConfig.minSearchLength || 1, defaultValue: currentConfig.defaultValue || "", required: currentConfig.required || false, readonly: currentConfig.readonly || false, apiEndpoint: currentConfig.apiEndpoint || "", filters: currentConfig.filters || {}, }); }, [widget.webTypeConfig]); // 설정 업데이트 핸들러 (즉시 부모에게 전달 - 드롭다운, 체크박스 등) const updateConfig = (field: keyof EntityTypeConfig, value: any) => { const newConfig = { ...localConfig, [field]: value }; setLocalConfig(newConfig); onUpdateProperty("webTypeConfig", newConfig); }; // 입력 필드용 업데이트 (로컬 상태만) const updateConfigLocal = (field: keyof EntityTypeConfig, value: any) => { setLocalConfig({ ...localConfig, [field]: value }); }; // 입력 완료 시 부모에게 전달 const handleInputBlur = () => { onUpdateProperty("webTypeConfig", localConfig); }; // 필드 추가 const addDisplayField = () => { if (!newFieldName.trim() || !newFieldLabel.trim()) return; const newField: EntityField = { name: newFieldName.trim(), label: newFieldLabel.trim(), type: newFieldType, visible: true, }; const newFields = [...localConfig.displayFields, newField]; updateConfig("displayFields", newFields); setNewFieldName(""); setNewFieldLabel(""); setNewFieldType("string"); }; // 필드 제거 const removeDisplayField = (index: number) => { const newFields = localConfig.displayFields.filter((_, i) => i !== index); updateConfig("displayFields", newFields); }; // 필드 업데이트 (입력 중) - 로컬 상태만 업데이트 const updateDisplayField = (index: number, field: keyof EntityField, value: any) => { const newFields = [...localConfig.displayFields]; newFields[index] = { ...newFields[index], [field]: value }; setLocalConfig({ ...localConfig, displayFields: newFields }); }; // 필드 업데이트 완료 (onBlur) - 부모에게 전달 const handleFieldBlur = () => { onUpdateProperty("webTypeConfig", localConfig); }; // 검색 필드 토글 const toggleSearchField = (fieldName: string) => { const currentSearchFields = localConfig.searchFields || []; const newSearchFields = currentSearchFields.includes(fieldName) ? currentSearchFields.filter((f) => f !== fieldName) : [...currentSearchFields, fieldName]; updateConfig("searchFields", newSearchFields); }; // 기본 엔티티 타입들 const commonEntityTypes = [ { value: "user", label: "사용자", fields: ["id", "name", "email", "department"] }, { value: "department", label: "부서", fields: ["id", "name", "code", "parentId"] }, { value: "product", label: "제품", fields: ["id", "name", "code", "category", "price"] }, { value: "customer", label: "고객", fields: ["id", "name", "company", "contact"] }, { value: "project", label: "프로젝트", fields: ["id", "name", "status", "manager", "startDate"] }, ]; // 기본 엔티티 타입 적용 const applyEntityType = (entityType: string) => { const entityConfig = commonEntityTypes.find((e) => e.value === entityType); if (!entityConfig) return; updateConfig("entityType", entityType); updateConfig("apiEndpoint", `/api/entities/${entityType}`); const defaultFields: EntityField[] = entityConfig.fields.map((field) => ({ name: field, label: field.charAt(0).toUpperCase() + field.slice(1), type: field.includes("Date") ? "date" : field.includes("price") || field.includes("Id") ? "number" : "string", visible: true, })); updateConfig("displayFields", defaultFields); updateConfig("searchFields", [entityConfig.fields[1] || "name"]); // 두 번째 필드를 기본 검색 필드로 }; // 필드 타입 옵션 const fieldTypes = [ { value: "string", label: "문자열" }, { value: "number", label: "숫자" }, { value: "date", label: "날짜" }, { value: "boolean", label: "불린" }, { value: "email", label: "이메일" }, { value: "url", label: "URL" }, ]; return ( 엔티티 설정 데이터베이스 엔티티 선택 필드의 설정을 관리합니다. {/* 기본 설정 */}

기본 설정

{/* UI 모드 선택 */}

{(localConfig as any).uiMode === "select" && "검색 가능한 드롭다운 형태로 표시됩니다."} {(localConfig as any).uiMode === "modal" && "모달 팝업에서 데이터를 검색하고 선택합니다."} {((localConfig as any).uiMode === "combo" || !(localConfig as any).uiMode) && "입력 필드와 검색 버튼이 함께 표시됩니다."} {(localConfig as any).uiMode === "autocomplete" && "입력하면서 자동완성 목록이 표시됩니다."}

updateConfigLocal("entityType", e.target.value)} onBlur={handleInputBlur} placeholder="user, product, department..." className="text-xs" />
{commonEntityTypes.map((entity) => ( ))}
updateConfigLocal("apiEndpoint", e.target.value)} onBlur={handleInputBlur} placeholder="/api/entities/user" className="text-xs" />
{/* 필드 매핑 */}

필드 매핑

updateConfigLocal("valueField", e.target.value)} onBlur={handleInputBlur} placeholder="id" className="text-xs" />
updateConfigLocal("labelField", e.target.value)} onBlur={handleInputBlur} placeholder="name" className="text-xs" />
{/* 표시 필드 관리 */}

표시 필드

{/* 새 필드 추가 */}
setNewFieldName(e.target.value)} placeholder="필드명" className="flex-1 text-xs" /> setNewFieldLabel(e.target.value)} placeholder="라벨" className="flex-1 text-xs" />
{/* 현재 필드 목록 */}
{localConfig.displayFields.map((field, index) => (
{ const newFields = [...localConfig.displayFields]; newFields[index] = { ...newFields[index], visible: checked }; const newConfig = { ...localConfig, displayFields: newFields }; setLocalConfig(newConfig); onUpdateProperty("webTypeConfig", newConfig); }} /> updateDisplayField(index, "name", e.target.value)} onBlur={handleFieldBlur} placeholder="필드명" className="flex-1 text-xs" /> updateDisplayField(index, "label", e.target.value)} onBlur={handleFieldBlur} placeholder="라벨" className="flex-1 text-xs" />
))}
{/* 검색 설정 */}

검색 설정

updateConfigLocal("placeholder", e.target.value)} onBlur={handleInputBlur} placeholder="엔티티를 선택하세요" className="text-xs" />
updateConfigLocal("emptyMessage", e.target.value)} onBlur={handleInputBlur} placeholder="검색 결과가 없습니다" className="text-xs" />
updateConfig("minSearchLength", parseInt(e.target.value))} min={0} max={10} className="text-xs" />
updateConfig("pageSize", parseInt(e.target.value))} min={5} max={100} className="text-xs" />

엔티티를 검색할 수 있습니다.

updateConfig("searchable", checked)} />

여러 엔티티를 선택할 수 있습니다.

updateConfig("multiple", checked)} />
{/* 필터 설정 */}

추가 필터