"use client"; import React, { useState, useMemo } from "react"; import { Plus, Layers, Search, Filter } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { useComponents } from "@/hooks/admin/useComponents"; interface ComponentsPanelProps { onDragStart: (e: React.DragEvent, component: ComponentItem) => void; } interface ComponentItem { id: string; name: string; description: string; category: string; componentType: string; componentConfig: any; icon: React.ReactNode; defaultSize: { width: number; height: number }; } // 컴포넌트 카테고리 정의 (실제 생성된 컴포넌트에 맞게) const COMPONENT_CATEGORIES = [ { id: "action", name: "액션", description: "사용자 동작을 처리하는 컴포넌트" }, { id: "layout", name: "레이아웃", description: "화면 구조를 제공하는 컴포넌트" }, { id: "data", name: "데이터", description: "데이터를 표시하는 컴포넌트" }, { id: "navigation", name: "네비게이션", description: "화면 이동을 도와주는 컴포넌트" }, { id: "feedback", name: "피드백", description: "사용자 피드백을 제공하는 컴포넌트" }, { id: "input", name: "입력", description: "사용자 입력을 받는 컴포넌트" }, { id: "other", name: "기타", description: "기타 컴포넌트" }, ]; export const ComponentsPanel: React.FC = ({ onDragStart }) => { const [searchTerm, setSearchTerm] = useState(""); const [selectedCategory, setSelectedCategory] = useState("all"); // 데이터베이스에서 컴포넌트 가져오기 const { data: componentsData, isLoading: loading, error, } = useComponents({ active: "Y", }); // 컴포넌트를 ComponentItem으로 변환 const componentItems = useMemo(() => { if (!componentsData?.components) return []; return componentsData.components.map((component) => ({ id: component.component_code, name: component.component_name, description: component.description || `${component.component_name} 컴포넌트`, category: component.category || "other", componentType: component.component_config?.type || component.component_code, componentConfig: component.component_config, icon: getComponentIcon(component.icon_name || component.component_config?.type), defaultSize: component.default_size || getDefaultSize(component.component_config?.type), })); }, [componentsData]); // 필터링된 컴포넌트 const filteredComponents = useMemo(() => { return componentItems.filter((component) => { const matchesSearch = component.name.toLowerCase().includes(searchTerm.toLowerCase()) || component.description.toLowerCase().includes(searchTerm.toLowerCase()); const matchesCategory = selectedCategory === "all" || component.category === selectedCategory; return matchesSearch && matchesCategory; }); }, [componentItems, searchTerm, selectedCategory]); // 카테고리별 그룹화 const groupedComponents = useMemo(() => { const groups: Record = {}; COMPONENT_CATEGORIES.forEach((category) => { groups[category.id] = filteredComponents.filter((component) => component.category === category.id); }); return groups; }, [filteredComponents]); if (loading) { return (

컴포넌트 로딩 중...

); } if (error) { return (

컴포넌트 로드 실패

{error.message}

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

컴포넌트

{filteredComponents.length}개

드래그하여 화면에 추가하세요

{/* 검색 및 필터 */}
{/* 검색 */}
setSearchTerm(e.target.value)} className="h-8 pl-9 text-xs" />
{/* 카테고리 필터 */}
{/* 컴포넌트 목록 */}
{selectedCategory === "all" ? ( // 카테고리별 그룹 표시
{COMPONENT_CATEGORIES.map((category) => { const categoryComponents = groupedComponents[category.id]; if (categoryComponents.length === 0) return null; return (

{category.name}

{categoryComponents.length}개

{category.description}

{categoryComponents.map((component) => ( ))}
); })}
) : ( // 선택된 카테고리만 표시
{filteredComponents.map((component) => ( ))}
)} {filteredComponents.length === 0 && (

검색 결과가 없습니다

다른 검색어를 시도해보세요

)}
); }; // 컴포넌트 카드 컴포넌트 const ComponentCard: React.FC<{ component: ComponentItem; onDragStart: (e: React.DragEvent, component: ComponentItem) => void; }> = ({ component, onDragStart }) => { return (
onDragStart(e, component)} className="group cursor-move rounded-lg border border-gray-200 bg-white p-3 shadow-sm transition-all hover:border-blue-300 hover:shadow-md" >
{component.icon}

{component.name}

{component.description}

{component.webType}
); }; // 웹타입별 아이콘 매핑 function getComponentIcon(webType: string): React.ReactNode { const iconMap: Record = { text: Aa, number: 123, date: 📅, select: , checkbox: , radio: , textarea: 📝, file: 📎, button: 🔘, email: 📧, tel: 📞, password: 🔒, code: <>, entity: 🔗, }; return iconMap[webType] || ; } // 웹타입별 기본 크기 function getDefaultSize(webType: string): { width: number; height: number } { const sizeMap: Record = { text: { width: 200, height: 36 }, number: { width: 150, height: 36 }, date: { width: 180, height: 36 }, select: { width: 200, height: 36 }, checkbox: { width: 150, height: 36 }, radio: { width: 200, height: 80 }, textarea: { width: 300, height: 100 }, file: { width: 300, height: 120 }, button: { width: 120, height: 36 }, email: { width: 250, height: 36 }, tel: { width: 180, height: 36 }, password: { width: 200, height: 36 }, code: { width: 200, height: 36 }, entity: { width: 200, height: 36 }, }; return sizeMap[webType] || { width: 200, height: 36 }; } export default ComponentsPanel;