"use client"; import React, { useState, useEffect } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { ChevronDown, Settings, Info, Database, Trash2, Copy } from "lucide-react"; import { ComponentData, WebType, WidgetComponent, GroupComponent, DataTableComponent, TableInfo, LayoutComponent, FileComponent, AreaComponent, } from "@/types/screen"; import { ColumnSpanPreset, COLUMN_SPAN_PRESETS } from "@/lib/constants/columnSpans"; // 컬럼 스팬 숫자 배열 (1~12) const COLUMN_NUMBERS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; import { cn } from "@/lib/utils"; import DataTableConfigPanel from "./DataTableConfigPanel"; import { WebTypeConfigPanel } from "./WebTypeConfigPanel"; import { FileComponentConfigPanel } from "./FileComponentConfigPanel"; import { useWebTypes } from "@/hooks/admin/useWebTypes"; import { isFileComponent } from "@/lib/utils/componentTypeUtils"; import { BaseInputType, BASE_INPUT_TYPE_OPTIONS, getBaseInputType, getDefaultDetailType, getDetailTypes, DetailTypeOption, } from "@/types/input-type-mapping"; // 새로운 컴포넌트 설정 패널들 import { ButtonConfigPanel } from "../config-panels/ButtonConfigPanel"; import { CardConfigPanel } from "../config-panels/CardConfigPanel"; import { DashboardConfigPanel } from "../config-panels/DashboardConfigPanel"; import { StatsCardConfigPanel } from "../config-panels/StatsCardConfigPanel"; import { ProgressBarConfigPanel } from "../config-panels/ProgressBarConfigPanel"; import { ChartConfigPanel } from "../config-panels/ChartConfigPanel"; import { ResponsiveConfigPanel } from "./ResponsiveConfigPanel"; import { AlertConfigPanel } from "../config-panels/AlertConfigPanel"; import { BadgeConfigPanel } from "../config-panels/BadgeConfigPanel"; import { DynamicComponentConfigPanel } from "@/lib/utils/getComponentConfigPanel"; interface UnifiedPropertiesPanelProps { selectedComponent?: ComponentData; tables: TableInfo[]; onUpdateProperty: (componentId: string, path: string, value: any) => void; onDeleteComponent?: (componentId: string) => void; onCopyComponent?: (componentId: string) => void; currentTable?: TableInfo; currentTableName?: string; dragState?: any; } export const UnifiedPropertiesPanel: React.FC = ({ selectedComponent, tables, onUpdateProperty, onDeleteComponent, onCopyComponent, currentTable, currentTableName, dragState, }) => { const { webTypes } = useWebTypes({ active: "Y" }); const [activeTab, setActiveTab] = useState("basic"); const [localComponentDetailType, setLocalComponentDetailType] = useState(""); // 새로운 컴포넌트 시스템의 webType 동기화 useEffect(() => { if (selectedComponent?.type === "component") { const webType = selectedComponent.componentConfig?.webType; if (webType) { setLocalComponentDetailType(webType); } } }, [selectedComponent?.type, selectedComponent?.componentConfig?.webType, selectedComponent?.id]); // 컴포넌트가 선택되지 않았을 때 if (!selectedComponent) { return (

컴포넌트를 선택하여

속성을 편집하세요

); } const handleUpdate = (path: string, value: any) => { onUpdateProperty(selectedComponent.id, path, value); }; // 드래그 중일 때 실시간 위치 표시 const currentPosition = dragState?.isDragging && dragState?.draggedComponent?.id === selectedComponent.id ? dragState.currentPosition : selectedComponent.position; // 컴포넌트별 설정 패널 렌더링 함수 (DetailSettingsPanel의 로직) const renderComponentConfigPanel = () => { if (!selectedComponent) return null; const componentType = selectedComponent.componentConfig?.type || selectedComponent.type; const handleUpdateProperty = (path: string, value: any) => { onUpdateProperty(selectedComponent.id, path, value); }; switch (componentType) { case "button": case "button-primary": case "button-secondary": return ; case "card": return ; case "dashboard": return ; case "stats": case "stats-card": return ; case "progress": case "progress-bar": return ; case "chart": case "chart-basic": return ; case "alert": case "alert-info": return ; case "badge": case "badge-status": return ; default: return null; } }; // 기본 정보 탭 const renderBasicTab = () => { const widget = selectedComponent as WidgetComponent; const group = selectedComponent as GroupComponent; const area = selectedComponent as AreaComponent; return (
{/* 컴포넌트 정보 */}
컴포넌트 정보
{selectedComponent.type}
ID: {selectedComponent.id}
{widget.widgetType &&
위젯: {widget.widgetType}
}
{/* 라벨 */}
handleUpdate("label", e.target.value)} placeholder="컴포넌트 라벨" />
{/* Placeholder (widget만) */} {selectedComponent.type === "widget" && (
handleUpdate("placeholder", e.target.value)} placeholder="입력 안내 텍스트" />
)} {/* Title (group/area) */} {(selectedComponent.type === "group" || selectedComponent.type === "area") && (
handleUpdate("title", e.target.value)} placeholder="제목" />
)} {/* Description (area만) */} {selectedComponent.type === "area" && (
handleUpdate("description", e.target.value)} placeholder="설명" />
)} {/* 크기 */}
{ const value = parseInt(e.target.value) || 0; // 40 단위로 반올림 const roundedValue = Math.max(40, Math.round(value / 40) * 40); handleUpdate("size.height", roundedValue); }} step={40} placeholder="40 단위로 입력" />

40 단위로 자동 조정됩니다

{/* 컬럼 스팬 */} {widget.columnSpan !== undefined && (
)} {/* Grid Columns */} {(selectedComponent as any).gridColumns !== undefined && (
)} {/* 위치 */}
handleUpdate("position.z", parseInt(e.target.value) || 1)} />
{/* 라벨 스타일 */} 라벨 스타일
handleUpdate("style.labelText", e.target.value)} />
handleUpdate("style.labelFontSize", e.target.value)} />
handleUpdate("style.labelColor", e.target.value)} />
handleUpdate("style.labelMarginBottom", e.target.value)} />
handleUpdate("style.labelDisplay", checked)} />
{/* 옵션 */}
handleUpdate("visible", checked)} />
handleUpdate("disabled", checked)} />
{widget.required !== undefined && (
handleUpdate("required", checked)} />
)} {widget.readonly !== undefined && (
handleUpdate("readonly", checked)} />
)}
{/* 액션 버튼 */}
{onCopyComponent && ( )} {onDeleteComponent && ( )}
); }; // 상세 설정 탭 (DetailSettingsPanel의 전체 로직 통합) const renderDetailTab = () => { // 1. DataTable 컴포넌트 if (selectedComponent.type === "datatable") { return ( { Object.entries(updates).forEach(([key, value]) => { handleUpdate(key, value); }); }} /> ); } // 3. 파일 컴포넌트 if (isFileComponent(selectedComponent)) { return ( ); } // 4. 새로운 컴포넌트 시스템 (button, card 등) const componentType = selectedComponent.componentConfig?.type || selectedComponent.type; const hasNewConfigPanel = componentType && [ "button", "button-primary", "button-secondary", "card", "dashboard", "stats", "stats-card", "progress", "progress-bar", "chart", "chart-basic", "alert", "alert-info", "badge", "badge-status", ].includes(componentType); if (hasNewConfigPanel) { const configPanel = renderComponentConfigPanel(); if (configPanel) { return
{configPanel}
; } } // 5. 새로운 컴포넌트 시스템 (type: "component") if (selectedComponent.type === "component") { const componentId = (selectedComponent as any).componentType || selectedComponent.componentConfig?.type; const webType = selectedComponent.componentConfig?.webType; // 테이블 패널에서 드래그한 컴포넌트인지 확인 const isFromTablePanel = !!(selectedComponent.tableName && selectedComponent.columnName); if (!componentId) { return (

컴포넌트 ID가 설정되지 않았습니다

); } // 현재 웹타입의 기본 입력 타입 추출 const currentBaseInputType = webType ? getBaseInputType(webType as any) : null; // 선택 가능한 세부 타입 목록 const availableDetailTypes = currentBaseInputType ? getDetailTypes(currentBaseInputType) : []; // 세부 타입 변경 핸들러 const handleDetailTypeChange = (newDetailType: string) => { setLocalComponentDetailType(newDetailType); handleUpdate("componentConfig.webType", newDetailType); }; return (
{/* 세부 타입 선택 - 테이블 패널에서 드래그한 컴포넌트만 표시 */} {isFromTablePanel && webType && availableDetailTypes.length > 1 && (
)} {/* DynamicComponentConfigPanel */} { console.log("🔄 DynamicComponentConfigPanel onChange:", newConfig); // 전체 componentConfig를 업데이트 handleUpdate("componentConfig", newConfig); }} />
); } // 6. Widget 컴포넌트 if (selectedComponent.type === "widget") { const widget = selectedComponent as WidgetComponent; // Widget에 webType이 있는 경우 if (widget.webType) { return (
{/* WebType 선택 */}
); } // 새로운 컴포넌트 시스템 (widgetType이 button, card 등) if ( widget.widgetType && ["button", "card", "dashboard", "stats-card", "progress-bar", "chart", "alert", "badge"].includes( widget.widgetType, ) ) { return ( { console.log("🔄 DynamicComponentConfigPanel onChange (widget):", newConfig); // 전체 componentConfig를 업데이트 handleUpdate("componentConfig", newConfig); }} /> ); } } // 기본 메시지 return (

이 컴포넌트는 추가 설정이 없습니다

); }; // 데이터 바인딩 탭 const renderDataTab = () => { if (selectedComponent.type !== "widget") { return (

이 컴포넌트는 데이터 바인딩을 지원하지 않습니다

); } const widget = selectedComponent as WidgetComponent; return (
데이터 바인딩
{/* 테이블 컬럼 */}
handleUpdate("columnName", e.target.value)} placeholder="컬럼명 입력" />
{/* 기본값 */}
handleUpdate("defaultValue", e.target.value)} placeholder="기본값 입력" />
); }; return (
{/* 헤더 */}

속성 편집

{selectedComponent.type}
{selectedComponent.type === "widget" && (
{(selectedComponent as WidgetComponent).label || selectedComponent.id}
)}
{/* 탭 컨텐츠 */}
기본 상세 데이터 반응형
{renderBasicTab()} {renderDetailTab()} {renderDataTab()} { onUpdateProperty(selectedComponent.id, "responsiveConfig", config); }} />
); }; export default UnifiedPropertiesPanel;