2025-09-03 11:32:09 +09:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
|
|
import React from "react";
|
|
|
|
|
|
import { Settings } from "lucide-react";
|
2025-09-04 14:23:35 +09:00
|
|
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
2025-09-09 14:29:04 +09:00
|
|
|
|
import { useWebTypes } from "@/hooks/admin/useWebTypes";
|
2025-09-09 15:42:04 +09:00
|
|
|
|
import { getConfigPanelComponent } from "@/lib/utils/getConfigPanelComponent";
|
|
|
|
|
|
import { ComponentData, WidgetComponent, FileComponent, WebTypeConfig, TableInfo } from "@/types/screen";
|
2025-09-04 11:33:52 +09:00
|
|
|
|
import { ButtonConfigPanel } from "./ButtonConfigPanel";
|
2025-09-05 21:52:19 +09:00
|
|
|
|
import { FileComponentConfigPanel } from "./FileComponentConfigPanel";
|
2025-09-03 11:32:09 +09:00
|
|
|
|
|
|
|
|
|
|
interface DetailSettingsPanelProps {
|
|
|
|
|
|
selectedComponent?: ComponentData;
|
|
|
|
|
|
onUpdateProperty: (componentId: string, path: string, value: any) => void;
|
2025-09-06 00:16:27 +09:00
|
|
|
|
currentTable?: TableInfo; // 현재 화면의 테이블 정보
|
|
|
|
|
|
currentTableName?: string; // 현재 화면의 테이블명
|
2025-09-03 11:32:09 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-06 00:16:27 +09:00
|
|
|
|
export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
|
|
|
|
|
|
selectedComponent,
|
|
|
|
|
|
onUpdateProperty,
|
|
|
|
|
|
currentTable,
|
|
|
|
|
|
currentTableName,
|
|
|
|
|
|
}) => {
|
2025-09-09 14:29:04 +09:00
|
|
|
|
// 데이터베이스에서 입력 가능한 웹타입들을 동적으로 가져오기
|
|
|
|
|
|
const { webTypes } = useWebTypes({ active: "Y" });
|
2025-09-04 15:20:26 +09:00
|
|
|
|
|
2025-09-09 15:42:04 +09:00
|
|
|
|
console.log(`🔍 DetailSettingsPanel webTypes 로드됨:`, webTypes?.length, "개");
|
|
|
|
|
|
console.log(`🔍 webTypes:`, webTypes);
|
|
|
|
|
|
console.log(`🔍 DetailSettingsPanel selectedComponent:`, selectedComponent);
|
|
|
|
|
|
console.log(`🔍 DetailSettingsPanel selectedComponent.widgetType:`, selectedComponent?.widgetType);
|
|
|
|
|
|
const inputableWebTypes = webTypes.map((wt) => wt.web_type);
|
2025-09-03 11:32:09 +09:00
|
|
|
|
|
2025-09-09 15:42:04 +09:00
|
|
|
|
// 웹타입별 상세 설정 렌더링 함수 - useCallback 제거하여 항상 최신 widget 사용
|
|
|
|
|
|
const renderWebTypeConfig = (widget: WidgetComponent) => {
|
|
|
|
|
|
const currentConfig = widget.webTypeConfig || {};
|
|
|
|
|
|
|
|
|
|
|
|
console.log("🎨 DetailSettingsPanel renderWebTypeConfig 호출:", {
|
|
|
|
|
|
componentId: widget.id,
|
|
|
|
|
|
widgetType: widget.widgetType,
|
|
|
|
|
|
currentConfig,
|
|
|
|
|
|
configExists: !!currentConfig,
|
|
|
|
|
|
configKeys: Object.keys(currentConfig),
|
|
|
|
|
|
configStringified: JSON.stringify(currentConfig),
|
|
|
|
|
|
widgetWebTypeConfig: widget.webTypeConfig,
|
|
|
|
|
|
widgetWebTypeConfigExists: !!widget.webTypeConfig,
|
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log("🎨 selectedComponent 전체:", selectedComponent);
|
|
|
|
|
|
|
|
|
|
|
|
const handleConfigChange = (newConfig: WebTypeConfig) => {
|
|
|
|
|
|
console.log("🔧 WebTypeConfig 업데이트:", {
|
2025-09-03 11:32:09 +09:00
|
|
|
|
widgetType: widget.widgetType,
|
2025-09-09 15:42:04 +09:00
|
|
|
|
oldConfig: currentConfig,
|
|
|
|
|
|
newConfig,
|
|
|
|
|
|
componentId: widget.id,
|
|
|
|
|
|
isEqual: JSON.stringify(currentConfig) === JSON.stringify(newConfig),
|
2025-09-03 11:32:09 +09:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-09 15:42:04 +09:00
|
|
|
|
// 강제 새 객체 생성으로 React 변경 감지 보장
|
|
|
|
|
|
const freshConfig = { ...newConfig };
|
|
|
|
|
|
onUpdateProperty(widget.id, "webTypeConfig", freshConfig);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 1순위: DB에서 지정된 설정 패널 사용
|
|
|
|
|
|
const dbWebType = webTypes.find((wt) => wt.web_type === widget.widgetType);
|
|
|
|
|
|
console.log(`🎨 웹타입 "${widget.widgetType}" DB 조회 결과:`, dbWebType);
|
|
|
|
|
|
|
|
|
|
|
|
if (dbWebType?.config_panel) {
|
|
|
|
|
|
console.log(`🎨 웹타입 "${widget.widgetType}" → DB 지정 설정 패널 "${dbWebType.config_panel}" 사용`);
|
|
|
|
|
|
const ConfigPanelComponent = getConfigPanelComponent(dbWebType.config_panel);
|
|
|
|
|
|
console.log(`🎨 getConfigPanelComponent 결과:`, ConfigPanelComponent);
|
|
|
|
|
|
|
|
|
|
|
|
if (ConfigPanelComponent) {
|
|
|
|
|
|
console.log(`🎨 ✅ ConfigPanelComponent 렌더링 시작`);
|
|
|
|
|
|
return <ConfigPanelComponent config={currentConfig} onConfigChange={handleConfigChange} />;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log(`🎨 ❌ ConfigPanelComponent가 null - 기본 설정 표시`);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="py-8 text-center text-gray-500">
|
|
|
|
|
|
⚙️ 기본 설정
|
|
|
|
|
|
<br />
|
|
|
|
|
|
웹타입 "{widget.widgetType}"은 추가 설정이 없습니다.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
2025-09-03 11:32:09 +09:00
|
|
|
|
}
|
2025-09-09 15:42:04 +09:00
|
|
|
|
} else {
|
|
|
|
|
|
console.log(`🎨 config_panel이 없음 - 기본 설정 표시`);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="py-8 text-center text-gray-500">
|
|
|
|
|
|
⚙️ 기본 설정
|
|
|
|
|
|
<br />
|
|
|
|
|
|
웹타입 "{widget.widgetType}"은 추가 설정이 없습니다.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-09-03 11:32:09 +09:00
|
|
|
|
|
|
|
|
|
|
if (!selectedComponent) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex h-full flex-col items-center justify-center p-6 text-center">
|
|
|
|
|
|
<Settings className="mb-4 h-12 w-12 text-gray-400" />
|
|
|
|
|
|
<h3 className="mb-2 text-lg font-medium text-gray-900">컴포넌트를 선택하세요</h3>
|
|
|
|
|
|
<p className="text-sm text-gray-500">위젯 컴포넌트를 선택하면 상세 설정을 편집할 수 있습니다.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-09 17:42:23 +09:00
|
|
|
|
if (selectedComponent.type !== "widget" && selectedComponent.type !== "file" && selectedComponent.type !== "button") {
|
2025-09-03 11:32:09 +09:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex h-full flex-col items-center justify-center p-6 text-center">
|
|
|
|
|
|
<Settings className="mb-4 h-12 w-12 text-gray-400" />
|
2025-09-05 21:52:19 +09:00
|
|
|
|
<h3 className="mb-2 text-lg font-medium text-gray-900">설정할 수 없는 컴포넌트입니다</h3>
|
2025-09-03 11:32:09 +09:00
|
|
|
|
<p className="text-sm text-gray-500">
|
2025-09-09 17:42:23 +09:00
|
|
|
|
상세 설정은 위젯, 파일, 버튼 컴포넌트에서만 사용할 수 있습니다.
|
2025-09-03 11:32:09 +09:00
|
|
|
|
<br />
|
|
|
|
|
|
현재 선택된 컴포넌트: {selectedComponent.type}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 21:52:19 +09:00
|
|
|
|
// 파일 컴포넌트인 경우 FileComponentConfigPanel 렌더링
|
|
|
|
|
|
if (selectedComponent.type === "file") {
|
|
|
|
|
|
const fileComponent = selectedComponent as FileComponent;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex h-full flex-col">
|
|
|
|
|
|
{/* 헤더 */}
|
|
|
|
|
|
<div className="border-b border-gray-200 p-4">
|
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
|
<Settings className="h-4 w-4 text-gray-600" />
|
|
|
|
|
|
<h3 className="font-medium text-gray-900">파일 컴포넌트 설정</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-2 flex items-center space-x-2">
|
|
|
|
|
|
<span className="text-sm text-gray-600">타입:</span>
|
|
|
|
|
|
<span className="rounded bg-purple-100 px-2 py-1 text-xs font-medium text-purple-800">파일 업로드</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-1 text-xs text-gray-500">문서 타입: {fileComponent.fileConfig.docTypeName}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 파일 컴포넌트 설정 영역 */}
|
|
|
|
|
|
<div className="flex-1 overflow-y-auto p-4">
|
2025-09-06 00:16:27 +09:00
|
|
|
|
<FileComponentConfigPanel
|
|
|
|
|
|
component={fileComponent}
|
|
|
|
|
|
onUpdateProperty={onUpdateProperty}
|
|
|
|
|
|
currentTable={currentTable}
|
|
|
|
|
|
currentTableName={currentTableName}
|
|
|
|
|
|
/>
|
2025-09-05 21:52:19 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-09 17:42:23 +09:00
|
|
|
|
// 버튼 컴포넌트인 경우 ButtonConfigPanel 렌더링
|
|
|
|
|
|
if (selectedComponent.type === "button") {
|
|
|
|
|
|
const buttonWidget = selectedComponent as WidgetComponent;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex h-full flex-col">
|
|
|
|
|
|
{/* 헤더 */}
|
|
|
|
|
|
<div className="border-b border-gray-200 p-4">
|
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
|
<Settings className="h-4 w-4 text-gray-600" />
|
|
|
|
|
|
<h3 className="font-medium text-gray-900">버튼 상세 설정</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-2 flex items-center space-x-2">
|
|
|
|
|
|
<span className="text-sm text-gray-600">타입:</span>
|
|
|
|
|
|
<span className="rounded bg-green-100 px-2 py-1 text-xs font-medium text-green-800">버튼</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-1 text-xs text-gray-500">라벨: {buttonWidget.label || "버튼"}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 버튼 설정 영역 */}
|
|
|
|
|
|
<div className="flex-1 overflow-y-auto p-4">
|
|
|
|
|
|
<ButtonConfigPanel
|
|
|
|
|
|
component={buttonWidget}
|
|
|
|
|
|
onUpdateComponent={(updates) => {
|
|
|
|
|
|
Object.entries(updates).forEach(([key, value]) => {
|
|
|
|
|
|
onUpdateProperty(buttonWidget.id, key, value);
|
|
|
|
|
|
});
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 11:32:09 +09:00
|
|
|
|
const widget = selectedComponent as WidgetComponent;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex h-full flex-col">
|
|
|
|
|
|
{/* 헤더 */}
|
|
|
|
|
|
<div className="border-b border-gray-200 p-4">
|
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
|
<Settings className="h-4 w-4 text-gray-600" />
|
|
|
|
|
|
<h3 className="font-medium text-gray-900">상세 설정</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-2 flex items-center space-x-2">
|
|
|
|
|
|
<span className="text-sm text-gray-600">웹타입:</span>
|
|
|
|
|
|
<span className="rounded bg-blue-100 px-2 py-1 text-xs font-medium text-blue-800">{widget.widgetType}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-1 text-xs text-gray-500">컬럼: {widget.columnName}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 상세 설정 영역 */}
|
|
|
|
|
|
<div className="flex-1 overflow-y-auto p-4">{renderWebTypeConfig(widget)}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default DetailSettingsPanel;
|