"use client"; import React, { useState, useEffect } from "react"; 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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ChevronDown, Settings, Info, Database, Trash2, Copy, Palette, Monitor } 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 { AlertConfigPanel } from "../config-panels/AlertConfigPanel"; import { BadgeConfigPanel } from "../config-panels/BadgeConfigPanel"; import { DynamicComponentConfigPanel } from "@/lib/utils/getComponentConfigPanel"; import StyleEditor from "../StyleEditor"; import ResolutionPanel from "./ResolutionPanel"; 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; // 스타일 관련 onStyleChange?: (style: any) => void; // 해상도 관련 currentResolution?: { name: string; width: number; height: number }; onResolutionChange?: (resolution: { name: string; width: number; height: number }) => void; } export const UnifiedPropertiesPanel: React.FC = ({ selectedComponent, tables, onUpdateProperty, onDeleteComponent, onCopyComponent, currentTable, currentTableName, dragState, onStyleChange, currentResolution, onResolutionChange, }) => { const { webTypes } = useWebTypes({ active: "Y" }); 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": // 🔧 component.id만 key로 사용 (unmount 방지) 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}
{selectedComponent.id.slice(0, 8)}
{/* 라벨 + 최소 높이 (같은 행) */}
handleUpdate("label", e.target.value)} placeholder="라벨" className="h-6 text-[10px]" />
{ const value = parseInt(e.target.value) || 0; const roundedValue = Math.max(40, Math.round(value / 40) * 40); handleUpdate("size.height", roundedValue); }} step={40} placeholder="40" className="h-6 text-[10px]" />
{/* Placeholder (widget만) */} {selectedComponent.type === "widget" && (
handleUpdate("placeholder", e.target.value)} placeholder="입력 안내 텍스트" className="h-6 text-[10px]" />
)} {/* Title (group/area) */} {(selectedComponent.type === "group" || selectedComponent.type === "area") && (
handleUpdate("title", e.target.value)} placeholder="제목" className="h-6 text-[10px]" />
)} {/* Description (area만) */} {selectedComponent.type === "area" && (
handleUpdate("description", e.target.value)} placeholder="설명" className="h-6 text-[10px]" />
)} {/* 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); // 개별 속성별로 업데이트하여 다른 속성과의 충돌 방지 Object.entries(newConfig).forEach(([key, value]) => { handleUpdate(`componentConfig.${key}`, value); }); }} />
); } // 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 (

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

); }; return (
{/* 헤더 - 간소화 */}
{selectedComponent.type === "widget" && (
{(selectedComponent as WidgetComponent).label || selectedComponent.id}
)}
{/* 탭 컨텐츠 */} 편집 스타일 & 해상도 {/* 속성 탭 */}
{/* 기본 설정 */} {renderBasicTab()} {/* 상세 설정 통합 */} {renderDetailTab()}
{/* 스타일 & 해상도 탭 */}
{/* 해상도 설정 */} {currentResolution && onResolutionChange && (
)} {/* 스타일 설정 */} {selectedComponent ? (

컴포넌트 스타일

{ if (onStyleChange) { onStyleChange(style); } else { handleUpdate("style", style); } }} />
) : (
컴포넌트를 선택하여 스타일을 편집하세요
)}
); }; export default UnifiedPropertiesPanel;