"use client"; import React, { useState, useMemo } from "react"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ComponentRegistry } from "@/lib/registry/ComponentRegistry"; import { ComponentDefinition, ComponentCategory } from "@/types/component"; import { Search, Package, Grid, Layers, Palette, Zap, MousePointer, Edit3, BarChart3, Database, Wrench, Sparkles } from "lucide-react"; import { TableInfo, ColumnInfo } from "@/types/screen"; import TablesPanel from "./TablesPanel"; interface ComponentsPanelProps { className?: string; // 테이블 관련 props tables?: TableInfo[]; searchTerm?: string; onSearchChange?: (value: string) => void; onTableDragStart?: (e: React.DragEvent, table: TableInfo, column?: ColumnInfo) => void; selectedTableName?: string; placedColumns?: Set; // 이미 배치된 컬럼명 집합 } export function ComponentsPanel({ className, tables = [], searchTerm = "", onSearchChange, onTableDragStart, selectedTableName, placedColumns, }: ComponentsPanelProps) { const [searchQuery, setSearchQuery] = useState(""); // 레지스트리에서 모든 컴포넌트 조회 const allComponents = useMemo(() => { const components = ComponentRegistry.getAllComponents(); // 수동으로 table-list 컴포넌트 추가 (임시) const hasTableList = components.some((c) => c.id === "table-list"); if (!hasTableList) { components.push({ id: "table-list", name: "데이터 테이블 v2", description: "검색, 필터링, 페이징, CRUD 기능이 포함된 완전한 데이터 테이블 컴포넌트", category: "display", tags: ["table", "data", "crud"], defaultSize: { width: 1000, height: 680 }, } as ComponentDefinition); } return components; }, []); // Unified 컴포넌트 정의 (새로운 통합 컴포넌트 시스템) // 입력 컴포넌트(unified-input, unified-select, unified-date)는 테이블 컬럼 드래그 시 자동 생성되므로 숨김 const unifiedComponents: ComponentDefinition[] = useMemo(() => [ // unified-input: 테이블 컬럼 드래그 시 자동 생성되므로 숨김 처리 // unified-select: 테이블 컬럼 드래그 시 자동 생성되므로 숨김 처리 // unified-date: 테이블 컬럼 드래그 시 자동 생성되므로 숨김 처리 // unified-layout: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리 // unified-group: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리 { id: "unified-list", name: "통합 목록", description: "테이블, 카드, 칸반, 리스트 등 다양한 데이터 표시 방식 지원", category: "display" as ComponentCategory, tags: ["table", "list", "card", "kanban", "unified"], defaultSize: { width: 600, height: 400 }, }, { id: "unified-media", name: "통합 미디어", description: "이미지, 비디오, 오디오, 파일 업로드 등 미디어 컴포넌트", category: "display" as ComponentCategory, tags: ["image", "video", "audio", "file", "unified"], defaultSize: { width: 300, height: 200 }, }, { id: "unified-biz", name: "통합 비즈니스", description: "플로우 다이어그램, 랙 구조, 채번규칙 등 비즈니스 컴포넌트", category: "utility" as ComponentCategory, tags: ["flow", "rack", "numbering", "unified"], defaultSize: { width: 600, height: 400 }, }, { id: "unified-hierarchy", name: "통합 계층", description: "트리, 조직도, BOM, 연쇄 선택박스 등 계층 구조 컴포넌트", category: "data" as ComponentCategory, tags: ["tree", "org", "bom", "cascading", "unified"], defaultSize: { width: 400, height: 300 }, }, { id: "unified-repeater", name: "통합 반복 데이터", description: "반복 데이터 관리 (인라인/모달/버튼 모드)", category: "data" as ComponentCategory, tags: ["repeater", "table", "modal", "button", "unified"], defaultSize: { width: 600, height: 300 }, }, ], []); // 카테고리별 컴포넌트 그룹화 const componentsByCategory = useMemo(() => { // 숨길 컴포넌트 ID 목록 const hiddenComponents = [ // 기본 입력 컴포넌트 (테이블 컬럼 드래그 시 자동 생성) "text-input", "number-input", "date-input", "textarea-basic", // Unified 컴포넌트로 대체됨 "image-widget", // → UnifiedMedia (image) "file-upload", // → UnifiedMedia (file) "entity-search-input", // → UnifiedSelect (entity 모드) "autocomplete-search-input", // → UnifiedSelect (autocomplete 모드) // UnifiedBiz로 통합 예정 "rack-structure", // → UnifiedBiz (rack) // DataFlow 전용 (일반 화면에서 불필요) "mail-recipient-selector", // 현재 사용 안함 "repeater-field-group", ]; return { input: allComponents.filter( (c) => c.category === ComponentCategory.INPUT && !hiddenComponents.includes(c.id), ), action: allComponents.filter( (c) => c.category === ComponentCategory.ACTION && !hiddenComponents.includes(c.id), ), display: allComponents.filter( (c) => c.category === ComponentCategory.DISPLAY && !hiddenComponents.includes(c.id), ), data: allComponents.filter( (c) => c.category === ComponentCategory.DATA && !hiddenComponents.includes(c.id), ), layout: allComponents.filter( (c) => c.category === ComponentCategory.LAYOUT && !hiddenComponents.includes(c.id), ), utility: allComponents.filter( (c) => c.category === ComponentCategory.UTILITY && !hiddenComponents.includes(c.id), ), unified: unifiedComponents, }; }, [allComponents, unifiedComponents]); // 카테고리별 검색 필터링 const getFilteredComponents = (category: keyof typeof componentsByCategory) => { let components = componentsByCategory[category]; if (searchQuery) { const query = searchQuery.toLowerCase(); components = components.filter( (component: ComponentDefinition) => component.name.toLowerCase().includes(query) || component.description.toLowerCase().includes(query) || component.tags?.some((tag: string) => tag.toLowerCase().includes(query)), ); } return components; }; // 카테고리 아이콘 매핑 const getCategoryIcon = (category: ComponentCategory) => { switch (category) { case "display": return ; case "action": return ; case "data": return ; case "layout": return ; case "utility": return ; default: return ; } }; // 드래그 시작 핸들러 const handleDragStart = (e: React.DragEvent, component: ComponentDefinition) => { const dragData = { type: "component", component: component, }; e.dataTransfer.setData("application/json", JSON.stringify(dragData)); e.dataTransfer.effectAllowed = "copy"; }; // 컴포넌트 카드 렌더링 함수 const renderComponentCard = (component: ComponentDefinition) => (
{ handleDragStart(e, component); e.currentTarget.style.opacity = "0.6"; e.currentTarget.style.transform = "rotate(2deg) scale(0.98)"; }} onDragEnd={(e) => { e.currentTarget.style.opacity = "1"; e.currentTarget.style.transform = "none"; }} className="group bg-card hover:border-primary/50 cursor-grab rounded-lg border p-3 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md active:translate-y-0 active:scale-[0.98] active:cursor-grabbing" >
{getCategoryIcon(component.category)}

{component.name}

{component.description}

{component.defaultSize.width}×{component.defaultSize.height}
); // 빈 상태 렌더링 const renderEmptyState = () => (

컴포넌트를 찾을 수 없습니다

검색어를 조정해보세요

); return (
{/* 헤더 */}

컴포넌트

{allComponents.length}개 사용 가능

{/* 통합 검색 */}
{ const value = e.target.value; setSearchQuery(value); // 테이블 검색도 함께 업데이트 if (onSearchChange) { onSearchChange(value); } }} className="h-8 pl-8 text-xs" />
{/* 카테고리 탭 */} {/* 1행: Unified, 테이블, 입력, 데이터 */} Unified 테이블 입력 데이터 {/* 2행: 액션, 표시, 레이아웃, 유틸리티 */} 액션 표시 레이아웃 유틸 {/* Unified 컴포넌트 탭 */}

Unified 컴포넌트는 속성 기반으로 다양한 기능을 지원하는 새로운 컴포넌트입니다.

{getFilteredComponents("unified").length > 0 ? getFilteredComponents("unified").map(renderComponentCard) : renderEmptyState()}
{/* 테이블 탭 */} {tables.length > 0 && onTableDragStart ? ( {})} onDragStart={onTableDragStart} selectedTableName={selectedTableName} placedColumns={placedColumns} /> ) : (

테이블이 없습니다

)}
{/* 입력 컴포넌트 */} {getFilteredComponents("input").length > 0 ? getFilteredComponents("input").map(renderComponentCard) : renderEmptyState()} {/* 데이터 컴포넌트 */} {getFilteredComponents("data").length > 0 ? getFilteredComponents("data").map(renderComponentCard) : renderEmptyState()} {/* 액션 컴포넌트 */} {getFilteredComponents("action").length > 0 ? getFilteredComponents("action").map(renderComponentCard) : renderEmptyState()} {/* 표시 컴포넌트 */} {getFilteredComponents("display").length > 0 ? getFilteredComponents("display").map(renderComponentCard) : renderEmptyState()} {/* 레이아웃 컴포넌트 */} {getFilteredComponents("layout").length > 0 ? getFilteredComponents("layout").map(renderComponentCard) : renderEmptyState()} {/* 유틸리티 컴포넌트 */} {getFilteredComponents("utility").length > 0 ? getFilteredComponents("utility").map(renderComponentCard) : renderEmptyState()}
{/* 도움말 */}

컴포넌트를 드래그하여 화면에 추가하세요

); }