"use client"; import React, { useState, useEffect, useRef } 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 { Settings, Move, Type, Trash2, Copy, Group, Ungroup } from "lucide-react"; import { ComponentData, WebType, WidgetComponent, GroupComponent, DataTableComponent, TableInfo } from "@/types/screen"; import DataTableConfigPanel from "./DataTableConfigPanel"; interface PropertiesPanelProps { selectedComponent?: ComponentData; tables?: TableInfo[]; onUpdateProperty: (path: string, value: unknown) => void; onDeleteComponent: () => void; onCopyComponent: () => void; onGroupComponents?: () => void; onUngroupComponents?: () => void; canGroup?: boolean; canUngroup?: boolean; } const webTypeOptions: { value: WebType; label: string }[] = [ { value: "text", label: "텍스트" }, { value: "email", label: "이메일" }, { value: "tel", label: "전화번호" }, { value: "number", label: "숫자" }, { value: "decimal", label: "소수" }, { value: "date", label: "날짜" }, { value: "datetime", label: "날짜시간" }, { value: "select", label: "선택박스" }, { value: "dropdown", label: "드롭다운" }, { value: "textarea", label: "텍스트영역" }, { value: "boolean", label: "불린" }, { value: "checkbox", label: "체크박스" }, { value: "radio", label: "라디오" }, { value: "code", label: "코드" }, { value: "entity", label: "엔티티" }, { value: "file", label: "파일" }, { value: "button", label: "버튼" }, ]; export const PropertiesPanel: React.FC = ({ selectedComponent, tables = [], onUpdateProperty, onDeleteComponent, onCopyComponent, onGroupComponents, onUngroupComponents, canGroup = false, canUngroup = false, }) => { // 데이터테이블 설정 탭 상태를 여기서 관리 const [dataTableActiveTab, setDataTableActiveTab] = useState("basic"); // 최신 값들의 참조를 유지 const selectedComponentRef = useRef(selectedComponent); const onUpdatePropertyRef = useRef(onUpdateProperty); // 입력 필드들의 로컬 상태 (실시간 타이핑 반영용) const [localInputs, setLocalInputs] = useState({ placeholder: (selectedComponent?.type === "widget" ? (selectedComponent as WidgetComponent).placeholder : "") || "", title: (selectedComponent?.type === "group" ? (selectedComponent as GroupComponent).title : "") || "", positionX: selectedComponent?.position.x?.toString() || "0", positionY: selectedComponent?.position.y?.toString() || "0", positionZ: selectedComponent?.position.z?.toString() || "1", width: selectedComponent?.size.width?.toString() || "0", height: selectedComponent?.size.height?.toString() || "0", gridColumns: selectedComponent?.gridColumns?.toString() || "1", labelText: selectedComponent?.style?.labelText || selectedComponent?.label || "", labelFontSize: selectedComponent?.style?.labelFontSize || "12px", labelColor: selectedComponent?.style?.labelColor || "#374151", labelMarginBottom: selectedComponent?.style?.labelMarginBottom || "4px", required: (selectedComponent?.type === "widget" ? (selectedComponent as WidgetComponent).required : false) || false, readonly: (selectedComponent?.type === "widget" ? (selectedComponent as WidgetComponent).readonly : false) || false, labelDisplay: selectedComponent?.style?.labelDisplay !== false, }); useEffect(() => { selectedComponentRef.current = selectedComponent; onUpdatePropertyRef.current = onUpdateProperty; }); // 선택된 컴포넌트가 변경될 때 로컬 입력 상태 업데이트 useEffect(() => { if (selectedComponent) { const widget = selectedComponent.type === "widget" ? (selectedComponent as WidgetComponent) : null; const group = selectedComponent.type === "group" ? (selectedComponent as GroupComponent) : null; console.log("🔄 PropertiesPanel: 컴포넌트 변경 감지", { componentId: selectedComponent.id, componentType: selectedComponent.type, currentValues: { placeholder: widget?.placeholder, title: group?.title, positionX: selectedComponent.position.x, labelText: selectedComponent.style?.labelText || selectedComponent.label, }, }); setLocalInputs({ placeholder: widget?.placeholder || "", title: group?.title || "", positionX: selectedComponent.position.x?.toString() || "0", positionY: selectedComponent.position.y?.toString() || "0", positionZ: selectedComponent.position.z?.toString() || "1", width: selectedComponent.size.width?.toString() || "0", height: selectedComponent.size.height?.toString() || "0", gridColumns: selectedComponent.gridColumns?.toString() || "1", labelText: selectedComponent.style?.labelText || selectedComponent.label || "", labelFontSize: selectedComponent.style?.labelFontSize || "12px", labelColor: selectedComponent.style?.labelColor || "#374151", labelMarginBottom: selectedComponent.style?.labelMarginBottom || "4px", required: widget?.required || false, readonly: widget?.readonly || false, labelDisplay: selectedComponent.style?.labelDisplay !== false, }); } }, [ selectedComponent, selectedComponent?.position, selectedComponent?.size, selectedComponent?.style, selectedComponent?.label, ]); if (!selectedComponent) { return (

컴포넌트를 선택하세요

캔버스에서 컴포넌트를 클릭하면 속성을 편집할 수 있습니다.

); } // 데이터 테이블 컴포넌트인 경우 전용 패널 사용 if (selectedComponent.type === "datatable") { return (
{/* 헤더 */}
데이터 테이블 설정
{selectedComponent.type}
{/* 액션 버튼들 */}
{/* 데이터 테이블 설정 패널 */}
c.id))}-${JSON.stringify(selectedComponent.filters.map((f) => f.columnName))}`} component={selectedComponent as DataTableComponent} tables={tables} activeTab={dataTableActiveTab} onTabChange={setDataTableActiveTab} onUpdateComponent={(updates) => { console.log("🔄 DataTable 컴포넌트 업데이트:", updates); console.log("🔄 업데이트 항목들:", Object.keys(updates)); // 각 속성을 개별적으로 업데이트 Object.entries(updates).forEach(([key, value]) => { console.log(` - ${key}:`, value); if (key === "columns") { console.log(` 컬럼 개수: ${Array.isArray(value) ? value.length : 0}`); } if (key === "filters") { console.log(` 필터 개수: ${Array.isArray(value) ? value.length : 0}`); } onUpdateProperty(key, value); }); console.log("✅ DataTable 컴포넌트 업데이트 완료"); }} />
); } return (
{/* 헤더 */}

속성 편집

{selectedComponent.type}
{/* 액션 버튼들 */}
{canGroup && ( )} {canUngroup && ( )}
{/* 속성 편집 영역 */}
{/* 기본 정보 */}

기본 정보

{selectedComponent.type === "widget" && ( <>
{ const newValue = e.target.value; console.log("🔄 placeholder 변경:", newValue); setLocalInputs((prev) => ({ ...prev, placeholder: newValue })); onUpdateProperty("placeholder", newValue); }} placeholder="입력 힌트 텍스트" className="mt-1" />
{ setLocalInputs((prev) => ({ ...prev, required: !!checked })); onUpdateProperty("required", checked); }} />
{ setLocalInputs((prev) => ({ ...prev, readonly: !!checked })); onUpdateProperty("readonly", checked); }} />
)}
{/* 위치 및 크기 */}

위치 및 크기

{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, positionX: newValue })); onUpdateProperty("position", { ...selectedComponent.position, x: Number(newValue) }); }} className="mt-1" />
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, positionY: newValue })); onUpdateProperty("position", { ...selectedComponent.position, y: Number(newValue) }); }} className="mt-1" />
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, width: newValue })); onUpdateProperty("size", { ...selectedComponent.size, width: Number(newValue) }); }} className="mt-1" />
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, height: newValue })); onUpdateProperty("size", { ...selectedComponent.size, height: Number(newValue) }); }} className="mt-1" />
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, positionZ: newValue })); onUpdateProperty("position", { ...selectedComponent.position, z: Number(newValue) }); }} className="mt-1" placeholder="1" />
{ const newValue = e.target.value; const numValue = Number(newValue); if (numValue >= 1 && numValue <= 12) { setLocalInputs((prev) => ({ ...prev, gridColumns: newValue })); onUpdateProperty("gridColumns", numValue); } }} placeholder="1" className="mt-1" />
이 컴포넌트가 차지할 그리드 컬럼 수를 설정합니다 (기본: 1)
{/* 라벨 스타일 */}

라벨 설정

{/* 라벨 표시 토글 */}
{ console.log("🔄 라벨 표시 변경:", checked); setLocalInputs((prev) => ({ ...prev, labelDisplay: checked as boolean })); onUpdateProperty("style.labelDisplay", checked); }} />
{/* 라벨 텍스트 */}
{ const newValue = e.target.value; console.log("🔄 라벨 텍스트 변경:", newValue); setLocalInputs((prev) => ({ ...prev, labelText: newValue })); // 기본 라벨과 스타일 라벨을 모두 업데이트 onUpdateProperty("label", newValue); onUpdateProperty("style.labelText", newValue); }} placeholder="라벨 텍스트" className="mt-1" />
{/* 라벨 스타일 */}
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, labelFontSize: newValue })); onUpdateProperty("style.labelFontSize", newValue); }} placeholder="12px" className="mt-1" />
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, labelColor: newValue })); onUpdateProperty("style.labelColor", newValue); }} className="mt-1 h-8" />
{/* 라벨 여백 */}
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, labelMarginBottom: newValue })); onUpdateProperty("style.labelMarginBottom", newValue); }} placeholder="4px" className="mt-1" />
{selectedComponent.type === "group" && ( <> {/* 그룹 설정 */}

그룹 설정

{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, title: newValue })); onUpdateProperty("title", newValue); }} placeholder="그룹 제목" className="mt-1" />
)}
); }; export default PropertiesPanel;