"use client"; import React, { useState, useMemo } from "react"; import { Input } from "@/components/ui/input"; 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, Database, GripVertical } 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; // 이미 배치된 컬럼명 집합 // 테이블 선택 관련 props onTableSelect?: (tableName: string) => void; // 테이블 선택 콜백 showTableSelector?: boolean; // 테이블 선택 UI 표시 여부 (기본: 테이블 없으면 표시) } export function ComponentsPanel({ className, tables = [], searchTerm = "", onSearchChange, onTableDragStart, selectedTableName, placedColumns, onTableSelect, showTableSelector = true, }: ComponentsPanelProps) { const [searchQuery, setSearchQuery] = useState(""); // 레지스트리에서 모든 컴포넌트 조회 const allComponents = useMemo(() => { const components = ComponentRegistry.getAllComponents(); // v2-table-list가 자동 등록되므로 수동 추가 불필요 return components; }, []); // Unified 컴포넌트 정의 (새로운 통합 컴포넌트 시스템) // 입력 컴포넌트(unified-input, unified-select, unified-date)는 테이블 컬럼 드래그 시 자동 생성되므로 숨김 const unifiedComponents = useMemo( () => [ // unified-input: 테이블 컬럼 드래그 시 자동 생성되므로 숨김 처리 // unified-select: 테이블 컬럼 드래그 시 자동 생성되므로 숨김 처리 // unified-date: 테이블 컬럼 드래그 시 자동 생성되므로 숨김 처리 // unified-layout: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리 // unified-group: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리 // unified-list: table-list, card-display로 분리하여 숨김 처리 // unified-media 제거 - 테이블 컬럼의 image/file 입력 타입으로 사용 // unified-biz 제거 - 개별 컴포넌트(flow-widget, rack-structure, numbering-rule)로 직접 표시 // unified-hierarchy 제거 - 현재 미사용 { id: "v2-unified-repeater", name: "리피터 그리드", description: "행 단위로 데이터를 추가/수정/삭제", category: "data" as ComponentCategory, tags: ["repeater", "table", "modal", "button", "unified", "v2"], defaultSize: { width: 600, height: 300 }, }, ] as ComponentDefinition[], [], ); // 카테고리별 컴포넌트 그룹화 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 모드) // DataFlow 전용 (일반 화면에서 불필요) "mail-recipient-selector", // 현재 사용 안함 "repeater-field-group", // unified-repeater로 통합됨 "simple-repeater-table", // → unified-repeater (inline 모드) "modal-repeater-table", // → unified-repeater (modal 모드) // 특수 업무용 컴포넌트 (일반 화면에서 불필요) "tax-invoice-list", // 세금계산서 전용 "customer-item-mapping", // 고객-품목 매핑 전용 // card-display는 별도 컴포넌트로 유지 // unified-media로 통합됨 "image-display", // → unified-media (image) // 공통코드관리로 통합 예정 "category-manager", // → 공통코드관리 기능으로 통합 예정 // 분할 패널 정리 (split-panel-layout v1 유지) "split-panel-layout2", // → split-panel-layout로 통합 "screen-split-panel", // 화면 임베딩 방식은 사용하지 않음 // 미완성/미사용 컴포넌트 (기존 화면 호환성 유지, 새 추가만 막음) "accordion-basic", // 아코디언 컴포넌트 "conditional-container", // 조건부 컨테이너 "universal-form-modal", // 범용 폼 모달 // 통합 미디어 (테이블 컬럼 입력 타입으로 사용) "unified-media", // → 테이블 컬럼의 image/file 입력 타입으로 사용 // 플로우 위젯 숨김 처리 "flow-widget", // 선택 항목 상세입력 - 기존 컴포넌트 조합으로 대체 가능 "selected-items-detail-input", // 연관 데이터 버튼 - unified-repeater로 대체 가능 "related-data-buttons", // ===== V2로 대체된 기존 컴포넌트 (v2 버전만 사용) ===== "button-primary", // → v2-button-primary "split-panel-layout", // → v2-split-panel-layout "aggregation-widget", // → v2-aggregation-widget "card-display", // → v2-card-display "table-list", // → v2-table-list "text-display", // → v2-text-display "divider-line", // → v2-divider-line "numbering-rule", // → v2-numbering-rule "section-paper", // → v2-section-paper "section-card", // → v2-section-card "location-swap-selector", // → v2-location-swap-selector "rack-structure", // → v2-rack-structure "unified-repeater", // → v2-unified-repeater (아래 unifiedComponents에서 별도 처리) "repeat-container", // → v2-repeat-container "repeat-screen-modal", // → v2-repeat-screen-modal "pivot-grid", // → v2-pivot-grid "table-search-widget", // → v2-table-search-widget "tabs", // → v2-tabs ]; 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 getCategoryColor = (category: string) => { switch (category) { case "data": return "from-blue-500/10 to-blue-600/10 text-blue-600 group-hover:from-blue-500/20 group-hover:to-blue-600/20"; case "display": return "from-emerald-500/10 to-emerald-600/10 text-emerald-600 group-hover:from-emerald-500/20 group-hover:to-emerald-600/20"; case "input": return "from-violet-500/10 to-violet-600/10 text-violet-600 group-hover:from-violet-500/20 group-hover:to-violet-600/20"; case "layout": return "from-amber-500/10 to-amber-600/10 text-amber-600 group-hover:from-amber-500/20 group-hover:to-amber-600/20"; case "action": return "from-rose-500/10 to-rose-600/10 text-rose-600 group-hover:from-rose-500/20 group-hover:to-rose-600/20"; default: return "from-slate-500/10 to-slate-600/10 text-slate-600 group-hover:from-slate-500/20 group-hover:to-slate-600/20"; } }; // 컴포넌트 카드 렌더링 함수 (컴팩트 버전) 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/40 cursor-grab rounded-lg border px-3 py-2.5 transition-all duration-200 hover:shadow-sm active:scale-[0.98] active:cursor-grabbing" >
{getCategoryIcon(component.category)}
{component.name}
{component.category} | {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" />
{/* 테이블 / 컴포넌트 탭 */} 테이블 컴포넌트 {/* 테이블 컬럼 탭 */} {})} onDragStart={onTableDragStart || (() => {})} selectedTableName={selectedTableName} placedColumns={placedColumns} onTableSelect={onTableSelect} showTableSelector={showTableSelector} /> {/* 컴포넌트 탭 */} {(() => { const allFilteredComponents = [ ...getFilteredComponents("unified"), ...getFilteredComponents("action"), ...getFilteredComponents("display"), ...getFilteredComponents("data"), ...getFilteredComponents("layout"), ...getFilteredComponents("input"), ...getFilteredComponents("utility"), ]; return allFilteredComponents.length > 0 ? allFilteredComponents.map(renderComponentCard) : renderEmptyState(); })()} {/* 도움말 */}

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

); }